diff --git a/BUILD.gn b/BUILD.gn index 5035661b..3ddf2d6a 100644 --- a/BUILD.gn +++ b/BUILD.gn
@@ -581,6 +581,7 @@ if (is_linux) { deps += [ + "//components/safe_browsing/content/resources/real_time_url_checks_allowlist:make_real_time_url_allowlist_protobuf_for_gcs", "//skia:filter_fuzz_stub", "//skia:image_operations_bench", "//ui/snapshot:snapshot_unittests",
diff --git a/DEPS b/DEPS index c8f82f6..19f6c858 100644 --- a/DEPS +++ b/DEPS
@@ -309,11 +309,11 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Skia # and whatever else without interference from each other. - 'skia_revision': 'b2b28690e53f1ef3800684da325a4e887cd1c2a8', + 'skia_revision': 'ac8e8aa24d3f181282154de83003380aa7700f3c', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. - 'v8_revision': '7f18b53ba6dc3b556a997eca385c56490a4d13df', + 'v8_revision': '9c21f2111e58e1c9028fb8a2d1e55b124c52b4c3', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling ANGLE # and whatever else without interference from each other. @@ -321,7 +321,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling SwiftShader # and whatever else without interference from each other. - 'swiftshader_revision': '794b0cfce1d828d187637e6d932bae484fbe0976', + 'swiftshader_revision': 'efd5e79e9ca377c898cc09a3fd5abb32af83bd2f', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling PDFium # and whatever else without interference from each other. @@ -329,7 +329,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling BoringSSL # and whatever else without interference from each other. - 'boringssl_revision': 'eae76e0715de794f4fe0a189fe8c8146cbc9990c', + 'boringssl_revision': 'bc81f389c45e0192251c4b29622cfd734ae7baf2', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Fuchsia sdk # and whatever else without interference from each other. @@ -377,7 +377,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling CrossBench # and whatever else without interference from each other. - 'crossbench_revision': '5cedfa8fd09b3fc8aa4d0396d2794d08708998f1', + 'crossbench_revision': '0dc6211158fa20bde2b712e87fe292b610e75afa', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling CrossBench # and whatever else without interference from each other. @@ -397,7 +397,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling devtools-frontend # and whatever else without interference from each other. - 'devtools_frontend_revision': '7b5a5c02f8c5013a12f57f8142ceeee464084381', + 'devtools_frontend_revision': '975b3f788309aad670821b7228a4419c6aa28a4c', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libprotobuf-mutator # and whatever else without interference from each other. @@ -421,7 +421,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. - 'dawn_revision': '571a08a06de340c8b41d96b07043aa42a11e32ac', + 'dawn_revision': '49b39c02c939dfffd9efdac1630e358bc006d702', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. @@ -529,7 +529,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling llvm-libc # and whatever else without interference from each other. - 'compiler_rt_revision': 'bc66085be3ed8f3c2a3cb9a938cbc57edb86934d', + 'compiler_rt_revision': '3a23fdd90573a881f5077eed539502702e467357', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling clusterfuzz-data # and whatever else without interference from each other. @@ -1201,7 +1201,7 @@ 'packages': [ { 'package': 'chromium/chrome/android/orderfiles/arm64', - 'version': 'Wk-osRxJQoLozKD9tyxTiSa7c5tmk3YvG-V1fn6OZNcC', + 'version': 'dOZRElEBFk3xjr8iaeEQcbsXXgFE_3nygty36be2yBEC', }, ], 'condition': 'checkout_android and non_git_source', @@ -1212,7 +1212,7 @@ 'packages': [ { 'package': 'chromium/android_webview/tools/orderfiles/arm', - 'version': 'CDAzd5FKKEpbFOb-JbKwy9rFDUSjCDUJYojjBoDRE7kC', + 'version': 'wLE7Xf7pEna3zCkq5mrnoKVm91ZxeU_XonEwoRf4q6EC', }, ], 'condition': 'checkout_android and non_git_source', @@ -1223,7 +1223,7 @@ 'packages': [ { 'package': 'chromium/android_webview/tools/orderfiles/arm64', - 'version': 'VcxzvCFCZw1Z3J3OCVHadfv1WmzjPXNgNcCKKXrAK1QC', + 'version': 'bewkF8j9Gh1WLuddq6AhVnUot_8q_GtyMNyEnHEqudEC', }, ], 'condition': 'checkout_android and non_git_source', @@ -1619,7 +1619,7 @@ 'src/clank': { 'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' + - '9239a72a8bfc1673b2683f630e943092bb6686fb', + '5c6055aa5052256bc67ff1a033d2852d0b205282', 'condition': 'checkout_android and checkout_src_internal', }, @@ -2023,7 +2023,7 @@ # Tools used when building Chrome for Chrome OS. This affects both the Simple # Chrome workflow, as well as the chromeos-chrome ebuild. 'src/third_party/chromite': { - 'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '8048c5f57f9d7a21da839b452f4b864a5ca5bc83', + 'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'eee11b00b6754f040c86aee8d731d8e9d9e53bbf', 'condition': 'checkout_chromeos', }, @@ -2415,7 +2415,7 @@ # The library for IPP protocol (Chrome OS). 'src/third_party/libipp/libipp': { - 'url': Var('chromium_git') + '/chromiumos/platform2/libipp.git' + '@' + '2209bb84a8e122dab7c02fe66cc61a7b42873d7f', + 'url': Var('chromium_git') + '/chromiumos/platform2/libipp.git' + '@' + '4be5f77f672a3a9f1bbf3c935fb0ea8b3f86ce61', 'condition': 'checkout_linux', }, @@ -2457,7 +2457,7 @@ Var('chromium_git') + '/webm/libwebm.git' + '@' + 'f2a982d748b80586ae53b89a2e6ebbc305848b8c', 'src/third_party/libwebp/src': - Var('chromium_git') + '/webm/libwebp.git' + '@' + 'b0e8039062eedbcb20ebb1bad62bfeaee2b94ec6', + Var('chromium_git') + '/webm/libwebp.git' + '@' + 'c00d83f6642e7838a12bb03bca94237f03cc2e00', 'src/third_party/libyuv': Var('chromium_git') + '/libyuv/libyuv.git' + '@' + 'f5c7d8a44daa9bf3340cd37358caeed0b189ad6c', @@ -2605,7 +2605,7 @@ Var('pdfium_git') + '/pdfium.git' + '@' + Var('pdfium_revision'), 'src/third_party/perfetto': - Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + 'da58fbdf0f14a29a3ccd4f304ccb83ef4940185d', + Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + '6a960b04f6e20ace0f0e084be42a660f7ff6490a', 'src/base/tracing/test/data': { 'bucket': 'perfetto', @@ -2976,7 +2976,7 @@ Var('chromium_git') + '/webpagereplay.git' + '@' + Var('webpagereplay_revision'), 'src/third_party/webrtc': - Var('webrtc_git') + '/src.git' + '@' + '95593099246e559f60e889332f18d19541edb012', + Var('webrtc_git') + '/src.git' + '@' + 'e311af088e81d4f545696d1162dbbc02cd146f2e', # Wuffs' canonical repository is at github.com/google/wuffs, but we use # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file. @@ -3120,7 +3120,7 @@ 'packages': [ { 'package': 'chromeos_internal/apps/help_app/app', - 'version': 'Hgx3LpC5Qq3NBst1XwELpoat5RklWrpOb4Tz8f1OfwgC', + 'version': 'IHvwzGWQk7sk7iMvBbpOcP0ZUcev_YjGmbUj35Z3NY8C', }, ], 'condition': 'checkout_chromeos and checkout_src_internal', @@ -3131,7 +3131,7 @@ 'packages': [ { 'package': 'chromeos_internal/apps/media_app/app', - 'version': 'sHh0VnyWCU_U-ptYWVFrgy2zrVsY4qfyniZB-B6ZUdsC', + 'version': 'rB2j7F8oDmBy7lHupIKCubqlu2NWLBSm4DH4PQUjhl8C', }, ], 'condition': 'checkout_chromeos and checkout_src_internal', @@ -3662,7 +3662,7 @@ 'src/components/autofill/core/browser/form_parsing/internal_resources': { 'url': Var('chrome_git') + '/chrome/components/autofill_regex_patterns.git' + '@' + - '317b38a3a7f50da4a2d28f3e46be947fd4349afb', + '8563faf0edafd32896f5bd9fedc58c31df574213', 'condition': 'checkout_src_internal', }, @@ -3757,7 +3757,7 @@ 'src/ios_internal': { 'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' + - '71d59afa95dde3450fd6b7ae73cf8478a52ea226', + '0b03de5e10bb21ce7ce99f72042c16a172139d41', 'condition': 'checkout_ios and checkout_src_internal', },
diff --git a/PRESUBMIT.py b/PRESUBMIT.py index b473a35..e2706e0 100644 --- a/PRESUBMIT.py +++ b/PRESUBMIT.py
@@ -694,6 +694,10 @@ ), True, [ + # Only used to implement a test HTTP server: + # https://crbug.com/438422635#comment8 + '^third_party/crashpad/crashpad/third_party/' + r'cpp-httplib/cpp-httplib/httplib\.h$', # Abseil's benchmarks never linked into chrome. 'third_party/abseil-cpp/.*_benchmark.cc', ],
diff --git a/WATCHLISTS b/WATCHLISTS index ed5e2ed..6f97ccb3 100644 --- a/WATCHLISTS +++ b/WATCHLISTS
@@ -1364,6 +1364,9 @@ 'chromeos/crosapi/mojom/.*holding_space|'\ 'tools/metrics/histograms/metadata/holding_space' }, + 'hsts': { + 'filepath': 'net/http/transport_security_state.*' + }, 'i18n': { 'filepath': 'base/i18n/|base/string|l10n|icu|'\ 'locale_settings|encoding', @@ -3015,6 +3018,7 @@ 'headless': ['headless-reviews@chromium.org'], 'heap_mojo': ['chikamune+watch@chromium.org'], 'holding_space': ['tote-eng+reviews@google.com'], + 'hsts': ['jdeblasio+watch@chromium.org'], 'i18n': ['jshin+watch@chromium.org'], 'incident_reporting': ['grt+watch@chromium.org'], 'incognito': ['roagarwal+watch@chromium.org'],
diff --git a/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt b/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt index bcf3149..a9907ee1 100644 --- a/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt +++ b/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt
@@ -7842,6 +7842,7 @@ interface RtcReceivedPacket attribute @@toStringTag getter data + getter receiveTime method constructor interface RtcTransport attribute @@toStringTag
diff --git a/base/allocator/partition_alloc_features.cc b/base/allocator/partition_alloc_features.cc index 2a01f8f4..999b766 100644 --- a/base/allocator/partition_alloc_features.cc +++ b/base/allocator/partition_alloc_features.cc
@@ -9,20 +9,15 @@ #include "base/allocator/partition_alloc_features.h" -#include "base/allocator/miracle_parameter.h" #include "base/base_export.h" #include "base/feature_list.h" #include "base/features.h" -#include "base/metrics/field_trial_params.h" -#include "base/time/time.h" #include "build/build_config.h" #include "build/chromecast_buildflags.h" #include "partition_alloc/buildflags.h" -#include "partition_alloc/partition_alloc_base/time/time.h" #include "partition_alloc/partition_alloc_constants.h" #include "partition_alloc/partition_root.h" #include "partition_alloc/shim/allocator_shim_dispatch_to_noop_on_free.h" -#include "partition_alloc/thread_cache.h" namespace base::features { @@ -379,51 +374,6 @@ return Microseconds(time_delta.InMicroseconds()); } -BASE_FEATURE(kEnableConfigurableThreadCachePurgeInterval, - FEATURE_DISABLED_BY_DEFAULT); - -MIRACLE_PARAMETER_FOR_TIME_DELTA( - GetThreadCacheMinPurgeIntervalValue, - kEnableConfigurableThreadCachePurgeInterval, - "ThreadCacheMinPurgeInterval", - FromPartitionAllocTimeDelta(partition_alloc::kMinPurgeInterval)) - -MIRACLE_PARAMETER_FOR_TIME_DELTA( - GetThreadCacheMaxPurgeIntervalValue, - kEnableConfigurableThreadCachePurgeInterval, - "ThreadCacheMaxPurgeInterval", - FromPartitionAllocTimeDelta(partition_alloc::kMaxPurgeInterval)) - -MIRACLE_PARAMETER_FOR_TIME_DELTA( - GetThreadCacheDefaultPurgeIntervalValue, - kEnableConfigurableThreadCachePurgeInterval, - "ThreadCacheDefaultPurgeInterval", - FromPartitionAllocTimeDelta(partition_alloc::kDefaultPurgeInterval)) - -const partition_alloc::internal::base::TimeDelta -GetThreadCacheMinPurgeInterval() { - return ToPartitionAllocTimeDelta(GetThreadCacheMinPurgeIntervalValue()); -} - -const partition_alloc::internal::base::TimeDelta -GetThreadCacheMaxPurgeInterval() { - return ToPartitionAllocTimeDelta(GetThreadCacheMaxPurgeIntervalValue()); -} - -const partition_alloc::internal::base::TimeDelta -GetThreadCacheDefaultPurgeInterval() { - return ToPartitionAllocTimeDelta(GetThreadCacheDefaultPurgeIntervalValue()); -} - -BASE_FEATURE(kEnableConfigurableThreadCacheMinCachedMemoryForPurging, - FEATURE_DISABLED_BY_DEFAULT); - -MIRACLE_PARAMETER_FOR_INT( - GetThreadCacheMinCachedMemoryForPurgingBytes, - kEnableConfigurableThreadCacheMinCachedMemoryForPurging, - "ThreadCacheMinCachedMemoryForPurgingBytes", - partition_alloc::kMinCachedMemoryForPurgingBytes) - // An apparent quarantine leak in the buffer partition unacceptably // bloats memory when MiraclePtr is enabled in the renderer process. // We believe we have found and patched the leak, but out of an
diff --git a/base/allocator/partition_alloc_features.h b/base/allocator/partition_alloc_features.h index 844d5427..64caa9e9 100644 --- a/base/allocator/partition_alloc_features.h +++ b/base/allocator/partition_alloc_features.h
@@ -16,10 +16,8 @@ #include "base/compiler_specific.h" #include "base/feature_list.h" #include "base/metrics/field_trial_params.h" -#include "base/time/time.h" #include "build/build_config.h" #include "partition_alloc/buildflags.h" -#include "partition_alloc/partition_alloc_base/time/time.h" #include "partition_alloc/partition_root.h" namespace base::features { @@ -200,18 +198,6 @@ kPartialLowEndModeExcludePartitionAllocSupport); #endif -BASE_EXPORT BASE_DECLARE_FEATURE(kEnableConfigurableThreadCachePurgeInterval); -extern const partition_alloc::internal::base::TimeDelta -GetThreadCacheMinPurgeInterval(); -extern const partition_alloc::internal::base::TimeDelta -GetThreadCacheMaxPurgeInterval(); -extern const partition_alloc::internal::base::TimeDelta -GetThreadCacheDefaultPurgeInterval(); - -BASE_EXPORT BASE_DECLARE_FEATURE( - kEnableConfigurableThreadCacheMinCachedMemoryForPurging); -BASE_EXPORT int GetThreadCacheMinCachedMemoryForPurgingBytes(); - BASE_EXPORT BASE_DECLARE_FEATURE(kPartitionAllocDisableBRPInBufferPartition); // When set, partitions use a larger ring buffer and free memory less
diff --git a/base/allocator/partition_alloc_support.cc b/base/allocator/partition_alloc_support.cc index 09ec480..85e7fa4 100644 --- a/base/allocator/partition_alloc_support.cc +++ b/base/allocator/partition_alloc_support.cc
@@ -1284,12 +1284,6 @@ // initialized later. DCHECK(process_type != switches::kZygoteProcess); - partition_alloc::ThreadCacheRegistry::Instance().SetPurgingConfiguration( - base::features::GetThreadCacheMinPurgeInterval(), - base::features::GetThreadCacheMaxPurgeInterval(), - base::features::GetThreadCacheDefaultPurgeInterval(), - size_t(base::features::GetThreadCacheMinCachedMemoryForPurgingBytes())); - base::allocator::StartThreadCachePeriodicPurge(); #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS)
diff --git a/base/allocator/partition_allocator/src/partition_alloc/thread_cache.cc b/base/allocator/partition_allocator/src/partition_alloc/thread_cache.cc index e0a5292..2a27567 100644 --- a/base/allocator/partition_allocator/src/partition_alloc/thread_cache.cc +++ b/base/allocator/partition_allocator/src/partition_alloc/thread_cache.cc
@@ -248,28 +248,12 @@ } } -void ThreadCacheRegistry::SetPurgingConfiguration( - const internal::base::TimeDelta min_purge_interval, - const internal::base::TimeDelta max_purge_interval, - const internal::base::TimeDelta default_purge_interval, - size_t min_cached_memory_for_purging_bytes) { - PA_CHECK(min_purge_interval <= default_purge_interval); - PA_CHECK(default_purge_interval <= max_purge_interval); - min_purge_interval_ = min_purge_interval; - max_purge_interval_ = max_purge_interval; - default_purge_interval_ = default_purge_interval; - min_cached_memory_for_purging_bytes_ = min_cached_memory_for_purging_bytes; - is_purging_configured_ = true; -} - void ThreadCacheRegistry::RunPeriodicPurge() { if (!periodic_purge_is_initialized_) { ThreadCache::EnsureThreadSpecificDataInitialized(); periodic_purge_is_initialized_ = true; } - PA_CHECK(is_purging_configured_); - // Summing across all threads can be slow, but is necessary. Otherwise we rely // on the assumption that the current thread is a good proxy for overall // allocation activity. This is not the case for all process types. @@ -304,15 +288,15 @@ // scheduled purge with a small enough interval. This is the case for instance // of a renderer moving to foreground. To mitigate that, if cached memory // jumps is very large, make a greater leap to faster purging. - if (cached_memory_approx > 10 * min_cached_memory_for_purging_bytes_) { + if (cached_memory_approx > 10 * kMinCachedMemoryForPurgingBytes) { periodic_purge_next_interval_ = - std::min(default_purge_interval_, periodic_purge_next_interval_ / 2); - } else if (cached_memory_approx > 2 * min_cached_memory_for_purging_bytes_) { + std::min(kDefaultPurgeInterval, periodic_purge_next_interval_ / 2); + } else if (cached_memory_approx > 2 * kMinCachedMemoryForPurgingBytes) { periodic_purge_next_interval_ = - std::max(min_purge_interval_, periodic_purge_next_interval_ / 2); - } else if (cached_memory_approx < min_cached_memory_for_purging_bytes_) { + std::max(kMinPurgeInterval, periodic_purge_next_interval_ / 2); + } else if (cached_memory_approx < kMinCachedMemoryForPurgingBytes) { periodic_purge_next_interval_ = - std::min(max_purge_interval_, periodic_purge_next_interval_ * 2); + std::min(kMaxPurgeInterval, periodic_purge_next_interval_ * 2); } // Make sure that the next interval is in the right bounds. Even though the @@ -324,7 +308,7 @@ // background threads, but only ask them to purge their own cache at the next // allocation). periodic_purge_next_interval_ = std::clamp( - periodic_purge_next_interval_, min_purge_interval_, max_purge_interval_); + periodic_purge_next_interval_, kMinPurgeInterval, kMaxPurgeInterval); PurgeAll(); } @@ -335,7 +319,7 @@ } void ThreadCacheRegistry::ResetForTesting() { - periodic_purge_next_interval_ = default_purge_interval_; + periodic_purge_next_interval_ = kDefaultPurgeInterval; } // static
diff --git a/base/allocator/partition_allocator/src/partition_alloc/thread_cache.h b/base/allocator/partition_allocator/src/partition_alloc/thread_cache.h index 67c9c9a..86ae60b 100644 --- a/base/allocator/partition_allocator/src/partition_alloc/thread_cache.h +++ b/base/allocator/partition_allocator/src/partition_alloc/thread_cache.h
@@ -84,14 +84,6 @@ } // namespace internal -constexpr internal::base::TimeDelta kMinPurgeInterval = - internal::base::Seconds(1); -constexpr internal::base::TimeDelta kMaxPurgeInterval = - internal::base::Minutes(1); -constexpr internal::base::TimeDelta kDefaultPurgeInterval = - 2 * kMinPurgeInterval; -constexpr size_t kMinCachedMemoryForPurgingBytes = 500 * 1024; - // Global registry of all ThreadCache instances. // // This class cannot allocate in the (Un)registerThreadCache() functions, as @@ -138,26 +130,6 @@ void SetThreadCacheMultiplier(float multiplier); void SetLargestActiveBucketIndex(uint16_t largest_active_bucket_index); - // Controls the thread cache purging configuration. - void SetPurgingConfiguration( - const internal::base::TimeDelta min_purge_interval, - const internal::base::TimeDelta max_purge_interval, - const internal::base::TimeDelta default_purge_interval, - size_t min_cached_memory_for_purging_bytes); - internal::base::TimeDelta min_purge_interval() const { - return min_purge_interval_; - } - internal::base::TimeDelta max_purge_interval() const { - return max_purge_interval_; - } - internal::base::TimeDelta default_purge_interval() const { - return default_purge_interval_; - } - size_t min_cached_memory_for_purging_bytes() const { - return min_cached_memory_for_purging_bytes_; - } - bool is_purging_configured() const { return is_purging_configured_; } - static internal::Lock& GetLock() { return Instance().lock_; } // Purges all thread caches *now*. This is completely thread-unsafe, and // should only be called in a post-fork() handler. @@ -165,6 +137,14 @@ void ResetForTesting(); + static constexpr internal::base::TimeDelta kMinPurgeInterval = + internal::base::Seconds(1); + static constexpr internal::base::TimeDelta kMaxPurgeInterval = + internal::base::Minutes(1); + static constexpr internal::base::TimeDelta kDefaultPurgeInterval = + 2 * kMinPurgeInterval; + static constexpr size_t kMinCachedMemoryForPurgingBytes = 500 * 1024; + private: friend class tools::ThreadCacheInspector; friend class tools::HeapDumper; @@ -173,12 +153,7 @@ internal::Lock lock_; ThreadCache* list_head_ PA_GUARDED_BY(GetLock()) = nullptr; bool periodic_purge_is_initialized_ = false; - internal::base::TimeDelta min_purge_interval_; - internal::base::TimeDelta max_purge_interval_; - internal::base::TimeDelta default_purge_interval_; - size_t min_cached_memory_for_purging_bytes_ = 0u; internal::base::TimeDelta periodic_purge_next_interval_; - bool is_purging_configured_ = false; uint16_t largest_active_bucket_index_ = BucketIndexLookup::GetIndexForNeutralBuckets(
diff --git a/base/allocator/partition_allocator/src/partition_alloc/thread_cache_unittest.cc b/base/allocator/partition_allocator/src/partition_alloc/thread_cache_unittest.cc index 2d64a521..4d63ce56 100644 --- a/base/allocator/partition_allocator/src/partition_alloc/thread_cache_unittest.cc +++ b/base/allocator/partition_allocator/src/partition_alloc/thread_cache_unittest.cc
@@ -132,9 +132,6 @@ ThreadCacheRegistry::Instance().SetThreadCacheMultiplier( ThreadCache::kDefaultMultiplier); - ThreadCacheRegistry::Instance().SetPurgingConfiguration( - kMinPurgeInterval, kMaxPurgeInterval, kDefaultPurgeInterval, - kMinCachedMemoryForPurgingBytes); ThreadCache::SetLargestCachedSize(ThreadCache::kLargeSizeThreshold); // Make sure that enough slot spans have been touched, otherwise cache fill @@ -811,41 +808,43 @@ registry.GetPeriodicPurgeNextIntervalInMicroseconds()); }; - EXPECT_EQ(NextInterval(), registry.default_purge_interval()); + EXPECT_EQ(NextInterval(), ThreadCacheRegistry::kDefaultPurgeInterval); // Small amount of memory, the period gets longer. auto* tcache = ThreadCache::Get(); ASSERT_LT(tcache->CachedMemory(), - registry.min_cached_memory_for_purging_bytes()); + ThreadCacheRegistry::kMinCachedMemoryForPurgingBytes); registry.RunPeriodicPurge(); - EXPECT_EQ(NextInterval(), 2 * registry.default_purge_interval()); + EXPECT_EQ(NextInterval(), 2 * ThreadCacheRegistry::kDefaultPurgeInterval); registry.RunPeriodicPurge(); - EXPECT_EQ(NextInterval(), 4 * registry.default_purge_interval()); + EXPECT_EQ(NextInterval(), 4 * ThreadCacheRegistry::kDefaultPurgeInterval); // Check that the purge interval is clamped at the maximum value. - while (NextInterval() < registry.max_purge_interval()) { + while (NextInterval() < ThreadCacheRegistry::kMaxPurgeInterval) { registry.RunPeriodicPurge(); } registry.RunPeriodicPurge(); // Not enough memory to decrease the interval. - FillThreadCacheWithMemory(registry.min_cached_memory_for_purging_bytes() + 1); + FillThreadCacheWithMemory( + ThreadCacheRegistry::kMinCachedMemoryForPurgingBytes + 1); registry.RunPeriodicPurge(); - EXPECT_EQ(NextInterval(), registry.max_purge_interval()); + EXPECT_EQ(NextInterval(), ThreadCacheRegistry::kMaxPurgeInterval); - FillThreadCacheWithMemory(2 * registry.min_cached_memory_for_purging_bytes() + - 1); + FillThreadCacheWithMemory( + 2 * ThreadCacheRegistry::kMinCachedMemoryForPurgingBytes + 1); registry.RunPeriodicPurge(); - EXPECT_EQ(NextInterval(), registry.max_purge_interval() / 2); + EXPECT_EQ(NextInterval(), ThreadCacheRegistry::kMaxPurgeInterval / 2); // Enough memory, interval doesn't change. - FillThreadCacheWithMemory(registry.min_cached_memory_for_purging_bytes()); + FillThreadCacheWithMemory( + ThreadCacheRegistry::kMinCachedMemoryForPurgingBytes); registry.RunPeriodicPurge(); - EXPECT_EQ(NextInterval(), registry.max_purge_interval() / 2); + EXPECT_EQ(NextInterval(), ThreadCacheRegistry::kMaxPurgeInterval / 2); // No cached memory, increase the interval. registry.RunPeriodicPurge(); - EXPECT_EQ(NextInterval(), registry.max_purge_interval()); + EXPECT_EQ(NextInterval(), ThreadCacheRegistry::kMaxPurgeInterval); // Cannot test the very large size with only one thread, this is tested below // in the multiple threads test. @@ -887,10 +886,9 @@ bucket_distribution_(bucket_distribution) {} void ThreadMain() override { - FillThreadCacheWithMemory(root_, - 5 * ThreadCacheRegistry::Instance() - .min_cached_memory_for_purging_bytes(), - bucket_distribution_); + FillThreadCacheWithMemory( + root_, 5 * ThreadCacheRegistry::kMinCachedMemoryForPurgingBytes, + bucket_distribution_); allocations_done_.fetch_add(1, std::memory_order_release); // This thread needs to be alive when the next periodic purge task runs. @@ -915,27 +913,28 @@ return internal::base::Microseconds( registry.GetPeriodicPurgeNextIntervalInMicroseconds()); }; - EXPECT_EQ(NextInterval(), registry.default_purge_interval()); + EXPECT_EQ(NextInterval(), ThreadCacheRegistry::kDefaultPurgeInterval); // Small amount of memory, the period gets longer. auto* tcache = ThreadCache::Get(); ASSERT_LT(tcache->CachedMemory(), - registry.min_cached_memory_for_purging_bytes()); + ThreadCacheRegistry::kMinCachedMemoryForPurgingBytes); registry.RunPeriodicPurge(); - EXPECT_EQ(NextInterval(), 2 * registry.default_purge_interval()); + EXPECT_EQ(NextInterval(), 2 * ThreadCacheRegistry::kDefaultPurgeInterval); registry.RunPeriodicPurge(); - EXPECT_EQ(NextInterval(), 4 * registry.default_purge_interval()); + EXPECT_EQ(NextInterval(), 4 * ThreadCacheRegistry::kDefaultPurgeInterval); // Check that the purge interval is clamped at the maximum value. - while (NextInterval() < registry.max_purge_interval()) { + while (NextInterval() < ThreadCacheRegistry::kMaxPurgeInterval) { registry.RunPeriodicPurge(); } registry.RunPeriodicPurge(); // Not enough memory on this thread to decrease the interval. - FillThreadCacheWithMemory(registry.min_cached_memory_for_purging_bytes() / 2); + FillThreadCacheWithMemory( + ThreadCacheRegistry::kMinCachedMemoryForPurgingBytes / 2); registry.RunPeriodicPurge(); - EXPECT_EQ(NextInterval(), registry.max_purge_interval()); + EXPECT_EQ(NextInterval(), ThreadCacheRegistry::kMaxPurgeInterval); std::atomic<int> allocations_done{0}; std::atomic<bool> can_finish{false}; @@ -955,7 +954,7 @@ // Many allocations on the other thread. registry.RunPeriodicPurge(); - EXPECT_EQ(NextInterval(), registry.default_purge_interval()); + EXPECT_EQ(NextInterval(), ThreadCacheRegistry::kDefaultPurgeInterval); can_finish.store(true, std::memory_order_release); internal::base::PlatformThreadForTesting::Join(thread_handle);
diff --git a/build/android/pylib/local/device/local_device_environment.py b/build/android/pylib/local/device/local_device_environment.py index 94f0c7b..ef3d556 100644 --- a/build/android/pylib/local/device/local_device_environment.py +++ b/build/android/pylib/local/device/local_device_environment.py
@@ -18,6 +18,7 @@ from devil.android import device_utils from devil.android import logcat_monitor from devil.android.sdk import adb_wrapper +from devil.android.sdk import version_codes from devil.utils import file_utils from devil.utils import parallelizer from pylib import constants @@ -212,6 +213,15 @@ self._logcat_monitors.append(monitor) monitor.Start() + # There is a change in soft keyboard behavior since Android 16. + # See https://crbug.com/443782461 for more details. + if d.build_version_sdk >= version_codes.BAKLAVA: + with d.GboardPreferences() as gboard_prefs: + # Disable the stylus. + gboard_prefs.SetBoolean('enable_scribe', False) + # Always show the soft keyboards. + gboard_prefs.SetBoolean('pk_always_show_vk', True) + self.parallel_devices.pMap(prepare_device) @property
diff --git a/build/android/pylib/local/emulator/avd.py b/build/android/pylib/local/emulator/avd.py index 98ae991..a268d844 100644 --- a/build/android/pylib/local/emulator/avd.py +++ b/build/android/pylib/local/emulator/avd.py
@@ -1233,6 +1233,7 @@ if enable_network: _EnableNetwork(self.device) + except base_error.BaseError as e: self.UploadCrashreport() raise AvdStartException(str(e)) from e @@ -1354,6 +1355,16 @@ device.RunShellCommand( ['settings', 'put', 'global', 'hide_error_dialogs', '1']) + # There is a change in soft keyboard behavior since Android 16. + # See https://crbug.com/443782461 for more details. + if device.build_version_sdk >= version_codes.BAKLAVA: + logging.info('Update Gboard preferences.') + with device.GboardPreferences() as gboard_prefs: + # Disable the stylus. + gboard_prefs.SetBoolean('enable_scribe', False) + # Always show the soft keyboards. + gboard_prefs.SetBoolean('pk_always_show_vk', True) + def _EnableNetwork(device): logging.info('Enable the network on the emulator.')
diff --git a/build/config/rust.gni b/build/config/rust.gni index 55bd7b78..6b646a5 100644 --- a/build/config/rust.gni +++ b/build/config/rust.gni
@@ -68,12 +68,9 @@ # # One important consideration is whether the linker uses the same LLVM # version as `rustc` (i.e. if it can understand the LLVM-IR from the - # compilation artifacts produced by `rustc`). In LaCrOS and ash builds this - # may not be true - see b/299483903. - # - # TODO(crbug.com/40281834): Re-enable ThinLTO for Rust on LaCrOS - # TODO(b/300937673): Re-enable ThinLTO for Rust on ash-chrome - toolchain_supports_rust_thin_lto = !is_chromeos + # compilation artifacts produced by `rustc`). For more context please see + # older bugs like b/299483903, https://crbug.com/40281834, or b/300937673. + toolchain_supports_rust_thin_lto = true # Any extra std rlibs in your Rust toolchain, relative to the standard # Rust toolchain. Typically used with 'rust_sysroot_absolute'
diff --git a/build/rust/gni_impl/rust_target.gni b/build/rust/gni_impl/rust_target.gni index f109442..08ab3f1 100644 --- a/build/rust/gni_impl/rust_target.gni +++ b/build/rust/gni_impl/rust_target.gni
@@ -144,6 +144,12 @@ _rustflags += [ "--cfg=feature=\"${i}\"" ] } } + + # Default to the 2021 edition of the Rust language, but allow the `invoker` + # to override this. See also doc comments for `edition` at the top of + # `//build/rust/rust_static_library.gni`. + # + # TODO(https://crbug.com/450891119): Change the default to "2024". _edition = "2021" if (defined(invoker.edition)) { _edition = invoker.edition
diff --git a/build/rust/rust_executable.gni b/build/rust/rust_executable.gni index 029a865..668321f5 100644 --- a/build/rust/rust_executable.gni +++ b/build/rust/rust_executable.gni
@@ -11,7 +11,8 @@ # # edition (optional) # Edition of the Rust language to be used. -# Options are "2015", "2018" and "2021". Defaults to "2021". +# Options are "2015", "2018", "2021", and "2024". Defaults to "2021". +# TODO(https://crbug.com/450891119): Change the default to "2024". # # test_deps (optional) # List of GN targets on which this crate's tests depend, in addition
diff --git a/build/rust/rust_static_library.gni b/build/rust/rust_static_library.gni index 3a3845a..07d69d6af 100644 --- a/build/rust/rust_static_library.gni +++ b/build/rust/rust_static_library.gni
@@ -25,7 +25,8 @@ # # edition (optional) # Edition of the Rust language to be used. -# Options are "2015", "2018" and "2021". Defaults to "2021". +# Options are "2015", "2018", "2021", and "2024". Defaults to "2021". +# TODO(https://crbug.com/450891119): Change the default to "2024". # # allow_unsafe (optional) # Set to true to allow unsafe code in this target. Defaults to false. @@ -196,12 +197,14 @@ _crate_name = string_replace(_crate_name, "/", "_s") _crate_name = string_replace(_crate_name, ":", "_c") _crate_name = string_replace(_crate_name, "-", "_d") + # The string replacements below are introduced to decrease the number # of characters in the crate name. This is particularly important while # building Chromium with lld_emit_indexes_and_imports=True, since it # will potentially need to generate huge filenames, and the build will # fail in some filesystems. - _crate_name = string_replace(_crate_name, "third_uparty_srust_s", "_tpr") + _crate_name = + string_replace(_crate_name, "third_uparty_srust_s", "_tpr") } _rlib_path = "${_output_dir}/lib${_crate_name}.rlib"
diff --git a/build/rust/rust_unit_test.gni b/build/rust/rust_unit_test.gni index c6bc5ae..b6436b1 100644 --- a/build/rust/rust_unit_test.gni +++ b/build/rust/rust_unit_test.gni
@@ -59,6 +59,8 @@ } } _configs = invoker.configs + + # TODO(https://crbug.com/450891119): Change the default to "2024". _edition = "2021" if (defined(invoker.edition)) { _edition = invoker.edition
diff --git a/build/rust/tests/bindgen_cpp_test_with_cpp_linkage/lib.rs b/build/rust/tests/bindgen_cpp_test_with_cpp_linkage/lib.rs index 25d80df..bdf4fb5 100644 --- a/build/rust/tests/bindgen_cpp_test_with_cpp_linkage/lib.rs +++ b/build/rust/tests/bindgen_cpp_test_with_cpp_linkage/lib.rs
@@ -6,7 +6,7 @@ "//build/rust/tests/bindgen_cpp_test_with_cpp_linkage:cpp_lib_bindgen"; } -#[no_mangle] +#[unsafe(no_mangle)] pub fn rust_main() { let from_cpp = unsafe { cpp_lib_bindgen::functions::normal_fn(cpp_lib_bindgen::functions::kNumber) };
diff --git a/build/rust/tests/test_rust_calling_cpp/rust_calling_cpp_rlib.rs b/build/rust/tests/test_rust_calling_cpp/rust_calling_cpp_rlib.rs index 54557e4..8fe0cddc 100644 --- a/build/rust/tests/test_rust_calling_cpp/rust_calling_cpp_rlib.rs +++ b/build/rust/tests/test_rust_calling_cpp/rust_calling_cpp_rlib.rs
@@ -15,7 +15,7 @@ } } -#[no_mangle] +#[unsafe(no_mangle)] pub fn rust_calling_cpp() { assert_eq!(ffi::mul_by_2_in_cpp_library(3), 6); }
diff --git a/build/rust/tests/test_rust_metadata/lib.rs b/build/rust/tests/test_rust_metadata/lib.rs index bfe2e1b2..46452297 100644 --- a/build/rust/tests/test_rust_metadata/lib.rs +++ b/build/rust/tests/test_rust_metadata/lib.rs
@@ -19,7 +19,7 @@ pub use foo_dependency::say_foo_directly; pub use transitive_dep::say_something; -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn print_foo_bar() { println!("{}", foo_dependency::say_foo()); println!("{}", transitive_dep::say_something());
diff --git a/chrome/VERSION b/chrome/VERSION index 23ef7956..949925b 100644 --- a/chrome/VERSION +++ b/chrome/VERSION
@@ -1,4 +1,4 @@ MAJOR=143 MINOR=0 -BUILD=7470 +BUILD=7471 PATCH=0
diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd index f1219c88..13e0068 100644 --- a/chrome/app/chromium_strings.grd +++ b/chrome/app/chromium_strings.grd
@@ -2900,6 +2900,24 @@ Sign in faster to sites using saved passwords for Chromium on iOS </message> </if> + + <!--Default Search Engine Reset Notification--> + <message name="IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_TITLE" + desc="Title of the notification shown when Chromium resets the default search engine (DSE) due to preference tampering."> + Your settings were changed by an unknown app + </message> + <message name="IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_BODY" + desc="Body text of the notification shown when Chromium resets the default search engine, explaining that a change from outside Chromium was detected."> + Your default search engine was changed from outside of Chromium. To protect you, Chromium reset it. + </message> + <message name="IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_GOT_IT_BUTTON" + desc="Label for the 'Got it' button in the default search engine reset notification, which dismisses the UI."> + Got it + </message> + <message name="IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_LEARN_MORE_BUTTON" + desc="Label for the 'Learn more' button in the default search engine reset notification, which opens a help center article."> + Learn more + </message> </messages> </release> </grit>
diff --git a/chrome/app/chromium_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_BODY.png.sha1 b/chrome/app/chromium_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_BODY.png.sha1 new file mode 100644 index 0000000..978aff5 --- /dev/null +++ b/chrome/app/chromium_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_BODY.png.sha1
@@ -0,0 +1 @@ +95a29cb440a42aad7053625f95a7d97c1f7d4eb8 \ No newline at end of file
diff --git a/chrome/app/chromium_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_GOT_IT_BUTTON.png.sha1 b/chrome/app/chromium_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_GOT_IT_BUTTON.png.sha1 new file mode 100644 index 0000000..978aff5 --- /dev/null +++ b/chrome/app/chromium_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_GOT_IT_BUTTON.png.sha1
@@ -0,0 +1 @@ +95a29cb440a42aad7053625f95a7d97c1f7d4eb8 \ No newline at end of file
diff --git a/chrome/app/chromium_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_LEARN_MORE_BUTTON.png.sha1 b/chrome/app/chromium_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_LEARN_MORE_BUTTON.png.sha1 new file mode 100644 index 0000000..978aff5 --- /dev/null +++ b/chrome/app/chromium_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_LEARN_MORE_BUTTON.png.sha1
@@ -0,0 +1 @@ +95a29cb440a42aad7053625f95a7d97c1f7d4eb8 \ No newline at end of file
diff --git a/chrome/app/chromium_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_TITLE.png.sha1 b/chrome/app/chromium_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_TITLE.png.sha1 new file mode 100644 index 0000000..978aff5 --- /dev/null +++ b/chrome/app/chromium_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_TITLE.png.sha1
@@ -0,0 +1 @@ +95a29cb440a42aad7053625f95a7d97c1f7d4eb8 \ No newline at end of file
diff --git a/chrome/app/google_chrome_strings.grd b/chrome/app/google_chrome_strings.grd index 71ea85e..521d2dd 100644 --- a/chrome/app/google_chrome_strings.grd +++ b/chrome/app/google_chrome_strings.grd
@@ -2922,6 +2922,24 @@ Sign in faster to sites using saved passwords for Chrome on iOS </message> </if> + + <!--Default Search Engine Reset Notification--> + <message name="IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_TITLE" + desc="Title of the notification shown when Chrome resets the default search engine (DSE) due to preference tampering."> + Your settings were changed by an unknown app + </message> + <message name="IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_BODY" + desc="Body text of the notification shown when Chrome resets the default search engine, explaining that a change from outside Chrome was detected."> + Your default search engine was changed from outside of Chrome. To protect you, Chrome reset it. + </message> + <message name="IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_GOT_IT_BUTTON" + desc="Label for the 'Got it' button in the default search engine reset notification, which dismisses the UI."> + Got it + </message> + <message name="IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_LEARN_MORE_BUTTON" + desc="Label for the 'Learn more' button in the default search engine reset notification, which opens a help center article."> + Learn more + </message> </messages> </release> </grit>
diff --git a/chrome/app/google_chrome_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_BODY.png.sha1 b/chrome/app/google_chrome_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_BODY.png.sha1 new file mode 100644 index 0000000..b6be94a1 --- /dev/null +++ b/chrome/app/google_chrome_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_BODY.png.sha1
@@ -0,0 +1 @@ +5a1355f16674517d2e3f43f20e430df901bfd23d \ No newline at end of file
diff --git a/chrome/app/google_chrome_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_GOT_IT_BUTTON.png.sha1 b/chrome/app/google_chrome_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_GOT_IT_BUTTON.png.sha1 new file mode 100644 index 0000000..b6be94a1 --- /dev/null +++ b/chrome/app/google_chrome_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_GOT_IT_BUTTON.png.sha1
@@ -0,0 +1 @@ +5a1355f16674517d2e3f43f20e430df901bfd23d \ No newline at end of file
diff --git a/chrome/app/google_chrome_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_LEARN_MORE_BUTTON.png.sha1 b/chrome/app/google_chrome_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_LEARN_MORE_BUTTON.png.sha1 new file mode 100644 index 0000000..b6be94a1 --- /dev/null +++ b/chrome/app/google_chrome_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_LEARN_MORE_BUTTON.png.sha1
@@ -0,0 +1 @@ +5a1355f16674517d2e3f43f20e430df901bfd23d \ No newline at end of file
diff --git a/chrome/app/google_chrome_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_TITLE.png.sha1 b/chrome/app/google_chrome_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_TITLE.png.sha1 new file mode 100644 index 0000000..b6be94a1 --- /dev/null +++ b/chrome/app/google_chrome_strings_grd/IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_TITLE.png.sha1
@@ -0,0 +1 @@ +5a1355f16674517d2e3f43f20e430df901bfd23d \ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index e86a07e..d39a9de 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn
@@ -281,8 +281,6 @@ "chrome_content_browser_client_receiver_bindings.cc", "chrome_resource_bundle_helper.cc", "chrome_resource_bundle_helper.h", - "client_hints/client_hints_factory.cc", - "client_hints/client_hints_factory.h", "collaboration/collaboration_service_factory.cc", "collaboration/collaboration_service_factory.h", "collaboration/comments/comments_service_factory.cc", @@ -1890,6 +1888,7 @@ "//chrome/browser/btm", "//chrome/browser/btm:impl", "//chrome/browser/chooser_controller", + "//chrome/browser/client_hints", "//chrome/browser/commerce", "//chrome/browser/commerce:impl", "//chrome/browser/companion/text_finder",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index d69162c..87a9c7d 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc
@@ -3882,17 +3882,16 @@ #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || // BUILDFLAG(IS_CHROMEOS) -#if BUILDFLAG(IS_ANDROID) - -// UnoPhase2FollowUp flags. -const char kFastFollowFeatures[] = "UnoPhase2FollowUp"; - +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) const FeatureEntry::Choice kReplaceSyncPromosWithSignInPromosChoices[] = { {"Default", "", ""}, - {"Follow-ups disabled", "disable-features", kFastFollowFeatures}, - {"Follow-ups enabled", "enable-features", kFastFollowFeatures}, + {"Disabled", switches::kDisableFeatures, + "ReplaceSyncPromosWithSignInPromos"}, + {"Enabled", switches::kEnableFeatures, "ReplaceSyncPromosWithSignInPromos"}, + {"Enabled with follow-ups", switches::kEnableFeatures, + "ReplaceSyncPromosWithSignInPromos,UnoPhase2FollowUp"}, }; -#endif // BUILDFLAG(IS_ANDROID) +#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) #if !BUILDFLAG(IS_ANDROID) const FeatureEntry::FeatureParam kLinkPreviewTriggerTypeAltClick[] = { @@ -11090,15 +11089,8 @@ {"replace-sync-promos-with-sign-in-promos-desktop", flag_descriptions::kReplaceSyncPromosWithSignInPromosName, flag_descriptions::kReplaceSyncPromosWithSignInPromosDescription, - kOsDesktop, - FEATURE_VALUE_TYPE(syncer::kReplaceSyncPromosWithSignInPromos)}, -#elif BUILDFLAG(IS_ANDROID) - {"replace-sync-promos-with-sign-in-promos", - flag_descriptions::kReplaceSyncPromosWithSignInPromosName, - flag_descriptions::kReplaceSyncPromosWithSignInPromosDescription, - kOsAndroid, MULTI_VALUE_TYPE(kReplaceSyncPromosWithSignInPromosChoices)}, -#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || - // BUILDFLAG(IS_ANDROID) + kOsDesktop, MULTI_VALUE_TYPE(kReplaceSyncPromosWithSignInPromosChoices)}, +#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) {"pwm-show-suggestions-on-autofocus", flag_descriptions::kPasswordManagerShowSuggestionsOnAutofocusName, @@ -11406,6 +11398,10 @@ flag_descriptions::kDevToolsPrivacyUIDescription, kOsAll, FEATURE_VALUE_TYPE(features::kDevToolsPrivacyUI)}, + {"devtools-greendev-ui", flag_descriptions::kDevToolsGreenDevUiName, + flag_descriptions::kDevToolsGreenDevUiDescription, kOsAll, + FEATURE_VALUE_TYPE(features::kDevToolsGreenDevUi)}, + {"devtools-individual-request-throttling", flag_descriptions::kDevToolsIndividualRequestThrottlingName, flag_descriptions::kDevToolsIndividualRequestThrottlingDescription, kOsAll,
diff --git a/chrome/browser/actor/ui/actor_overlay_browsertest.cc b/chrome/browser/actor/ui/actor_overlay_browsertest.cc index fd0c00e..a730f47 100644 --- a/chrome/browser/actor/ui/actor_overlay_browsertest.cc +++ b/chrome/browser/actor/ui/actor_overlay_browsertest.cc
@@ -26,10 +26,8 @@ #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/ui_test_utils.h" #include "components/tabs/public/tab_interface.h" -#include "content/public/browser/browser_accessibility_state.h" #include "content/public/browser/web_contents.h" #include "content/public/test/browser_test.h" -#include "ui/accessibility/accessibility_switches.h" namespace actor::ui { namespace { @@ -403,49 +401,6 @@ ->ShouldIgnoreInputEventsForTesting()); } -IN_PROC_BROWSER_TEST_F(ActorOverlayTest, - UnderlyingContentsIgnoredForAccessibility) { - Profile* const profile = browser()->profile(); - ActorUiStateManagerInterface* state_manager = - ActorKeyedService::Get(profile)->GetActorUiStateManager(); - ASSERT_NE(state_manager, nullptr); - tabs::TabHandle tab_handle = - browser()->tab_strip_model()->GetActiveTab()->GetHandle(); - content::WebContents* web_contents = - browser()->tab_strip_model()->GetActiveWebContents(); - - const ::ui::AXMode original_mode = web_contents->GetAccessibilityMode(); - - // Activate the overlay. - TestFuture<ActionResultPtr> result; - state_manager->OnUiEvent(StartingToActOnTab(tab_handle, TaskId(1)), - result.GetCallback()); - ExpectOkResult(result); - ASSERT_TRUE( - base::test::RunUntil([&]() { return IsActorOverlayVisible(browser()); })); - - // The --force-renderer-accessibility switch prevents the AXMode from being - // changed. Verify the mode is unchanged if the flag is present, otherwise - // verify it was set to ::ui::AXMode::kNone when the ActorOverlayWebView is - // visible. - const bool is_renderer_forced_a11y = - base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kForceRendererAccessibility); - if (is_renderer_forced_a11y) { - EXPECT_EQ(web_contents->GetAccessibilityMode(), original_mode); - } else { - EXPECT_EQ(web_contents->GetAccessibilityMode(), ::ui::AXMode::kNone); - } - - // Deactivate the overlay. - state_manager->OnUiEvent(StoppedActingOnTab(tab_handle)); - ASSERT_TRUE(base::test::RunUntil( - [&]() { return !IsActorOverlayVisible(browser()); })); - - // The page should be restored to the accessibility tree. - EXPECT_EQ(web_contents->GetAccessibilityMode(), original_mode); -} - class ActorOverlayDisabledTest : public InProcessBrowserTest { public: void SetUp() override {
diff --git a/chrome/browser/actor/ui/actor_overlay_web_view.cc b/chrome/browser/actor/ui/actor_overlay_web_view.cc index a14a9d32..489f19d 100644 --- a/chrome/browser/actor/ui/actor_overlay_web_view.cc +++ b/chrome/browser/actor/ui/actor_overlay_web_view.cc
@@ -9,9 +9,7 @@ #include "chrome/browser/ui/webui/webui_embedding_context.h" #include "chrome/common/chrome_features.h" #include "components/tabs/public/tab_interface.h" -#include "content/public/browser/browser_accessibility_state.h" #include "content/public/browser/render_widget_host_view.h" -#include "content/public/browser/scoped_accessibility_mode.h" #include "content/public/browser/web_contents.h" #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/views/controls/webview/web_contents_set_background_color.h" @@ -37,10 +35,6 @@ scoped_ignore_input_events_ = tab->GetContents()->IgnoreInputEvents(std::nullopt); } - // Prevent assistive technologies from interacting with the underlying page. - scoped_ax_mode_ = content::BrowserAccessibilityState::GetInstance() - ->CreateScopedModeForWebContents(tab->GetContents(), - ui::AXMode::kNone); // Set the tab interface webui::SetTabInterface(web_contents(), tab); @@ -61,8 +55,6 @@ // Re-enable mouse and keyboard events to the underlying web contents by // resetting the ScopedIgnoreInputEvents object. scoped_ignore_input_events_.reset(); - // Allow assistive technologies to interact with the underlying page. - scoped_ax_mode_.reset(); web_contents()->WasHidden(); SetWebContents(nullptr); }
diff --git a/chrome/browser/actor/ui/actor_overlay_web_view.h b/chrome/browser/actor/ui/actor_overlay_web_view.h index 20995dec..eebcdf3 100644 --- a/chrome/browser/actor/ui/actor_overlay_web_view.h +++ b/chrome/browser/actor/ui/actor_overlay_web_view.h
@@ -13,10 +13,6 @@ #include "ui/base/metadata/metadata_header_macros.h" #include "ui/views/controls/webview/webview.h" -namespace content { -class ScopedAccessibilityMode; -} // namespace content - namespace tabs { class TabInterface; } // namespace tabs @@ -46,8 +42,6 @@ // Manages the lifetime of the WebContents input event ignoring state. std::optional<content::WebContents::ScopedIgnoreInputEvents> scoped_ignore_input_events_; - // Manages the lifetime of the underlying WebContents's accessibility mode. - std::unique_ptr<content::ScopedAccessibilityMode> scoped_ax_mode_; raw_ptr<BrowserWindowInterface> browser_;
diff --git a/chrome/browser/android/quick_delete/quick_delete_bridge.cc b/chrome/browser/android/quick_delete/quick_delete_bridge.cc index c4375fa..2190123 100644 --- a/chrome/browser/android/quick_delete/quick_delete_bridge.cc +++ b/chrome/browser/android/quick_delete/quick_delete_bridge.cc
@@ -5,13 +5,16 @@ #include "chrome/browser/android/quick_delete/quick_delete_bridge.h" #include "base/android/jni_string.h" +#include "base/strings/utf_string_conversions.h" #include "chrome/browser/history/history_service_factory.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/browser/sync/sync_service_factory.h" #include "chrome/browser/ui/hats/hats_service.h" #include "chrome/browser/ui/hats/hats_service_factory.h" #include "chrome/browser/ui/hats/survey_config.h" #include "chrome/grit/generated_resources.h" #include "components/browsing_data/core/browsing_data_utils.h" +#include "components/browsing_data/core/counters/history_counter.h" #include "components/history/core/browser/history_service.h" #include "components/history/core/browser/history_types.h" #include "components/keyed_service/core/service_access_type.h" @@ -22,7 +25,7 @@ #include "chrome/browser/quick_delete/jni_headers/QuickDeleteBridge_jni.h" using base::android::AttachCurrentThread; -using base::android::ConvertUTF8ToJavaString; +using base::android::ConvertUTF16ToJavaString; using base::android::JavaParamRef; using base::android::JavaRef; using base::android::ScopedJavaGlobalRef; @@ -30,26 +33,49 @@ namespace { struct QuickDeleteDomainResult { - std::string last_visited_domain; - size_t domain_count; + std::u16string last_visited_domain; + int domain_count; }; QuickDeleteDomainResult GetLastVisitedDomainAndUniqueDomainCountFromResult( - const history::DomainsVisitedResult& result) { - if (result.all_visited_domains.empty()) { - return {"", 0}; + const browsing_data::HistoryCounter::HistoryResult* result) { + browsing_data::BrowsingDataCounter::ResultInt unique_domains_count = + result->unique_domains_result(); + std::u16string last_visited_domain = + base::UTF8ToUTF16(result->last_visited_domain()); + + // Subtract one from the unique_domains_count since one of the domains will be + // shown as the last_visited_domain. + if (unique_domains_count > 0) { + CHECK(!last_visited_domain.empty()); + unique_domains_count--; } - return {result.all_visited_domains.front(), - result.all_visited_domains.size()}; + return {last_visited_domain, static_cast<int>(unique_domains_count)}; } } // namespace -QuickDeleteBridge::QuickDeleteBridge(Profile* profile) { +QuickDeleteBridge::QuickDeleteBridge( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& obj, + Profile* profile) + : jobject_(obj) { profile_ = profile; - history_service_ = HistoryServiceFactory::GetForProfile( - profile_, ServiceAccessType::EXPLICIT_ACCESS); + history_counter_ = std::make_unique<browsing_data::HistoryCounter>( + HistoryServiceFactory::GetForProfile(profile_, + ServiceAccessType::EXPLICIT_ACCESS), + browsing_data::HistoryCounter::GetUpdatedWebHistoryServiceCallback(), + SyncServiceFactory::GetForProfile(profile_)); + + base::Time begin_time = + CalculateBeginDeleteTime(browsing_data::TimePeriod::LAST_15_MINUTES); + + history_counter_->InitWithoutPeriodPref( + profile_->GetPrefs(), browsing_data::ClearBrowsingDataTab::ADVANCED, + begin_time, + base::BindRepeating(&QuickDeleteBridge::OnHistoryCounterResult, + weak_ptr_factory_.GetWeakPtr())); } QuickDeleteBridge::~QuickDeleteBridge() = default; @@ -58,42 +84,37 @@ delete this; } -void QuickDeleteBridge::GetLastVisitedDomainAndUniqueDomainCount( - JNIEnv* env, - const jint time_period, - const JavaParamRef<jobject>& j_callback) { +void QuickDeleteBridge::RestartCounterForTimePeriod(JNIEnv* env, + const jint time_period) { browsing_data::TimePeriod period = static_cast<browsing_data::TimePeriod>(time_period); - base::Time begin_time = CalculateBeginDeleteTime(period); - base::Time end_time = CalculateEndDeleteTime(period); - history_service_->GetUniqueDomainsVisited( - begin_time, end_time, history::VisitQuery404sPolicy::kInclude404s, - base::BindOnce(&QuickDeleteBridge:: - OnGetLastVisitedDomainAndUniqueDomainCountComplete, - weak_ptr_factory_.GetWeakPtr(), - ScopedJavaGlobalRef<jobject>(j_callback)), - &task_tracker_); + history_counter_->SetBeginTime(begin_time); } -// TODO(crbug.com/40255099) use rvalue reference to pass the result and define -// copy ctor and copy assignment in history::DomainsVisitedResult. -void QuickDeleteBridge::OnGetLastVisitedDomainAndUniqueDomainCountComplete( - const JavaRef<jobject>& j_callback, - history::DomainsVisitedResult result) { +void QuickDeleteBridge::OnHistoryCounterResult( + std::unique_ptr<browsing_data::BrowsingDataCounter::Result> result) { JNIEnv* env = AttachCurrentThread(); + if (!result->Finished()) { + return; + } + QuickDeleteDomainResult quickDeleteResult = - GetLastVisitedDomainAndUniqueDomainCountFromResult(std::move(result)); + GetLastVisitedDomainAndUniqueDomainCountFromResult( + static_cast<const browsing_data::HistoryCounter::HistoryResult*>( + result.get())); Java_QuickDeleteBridge_onLastVisitedDomainAndUniqueDomainCountReady( - env, j_callback, - ConvertUTF8ToJavaString(env, quickDeleteResult.last_visited_domain), + env, jobject_, + ConvertUTF16ToJavaString(env, quickDeleteResult.last_visited_domain), quickDeleteResult.domain_count); } -static jlong JNI_QuickDeleteBridge_Init(JNIEnv* env, - Profile* profile) { - QuickDeleteBridge* bridge = new QuickDeleteBridge(profile); +static jlong JNI_QuickDeleteBridge_Init( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& obj, + Profile* profile) { + QuickDeleteBridge* bridge = new QuickDeleteBridge(env, obj, profile); return reinterpret_cast<intptr_t>(bridge); }
diff --git a/chrome/browser/android/quick_delete/quick_delete_bridge.h b/chrome/browser/android/quick_delete/quick_delete_bridge.h index 33a6704b..41427de 100644 --- a/chrome/browser/android/quick_delete/quick_delete_bridge.h +++ b/chrome/browser/android/quick_delete/quick_delete_bridge.h
@@ -6,16 +6,17 @@ #define CHROME_BROWSER_ANDROID_QUICK_DELETE_QUICK_DELETE_BRIDGE_H_ #include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/task/cancelable_task_tracker.h" +#include "components/browsing_data/core/counters/browsing_data_counter.h" class Profile; -namespace history { -class HistoryService; -struct DomainsVisitedResult; -} // namespace history +namespace browsing_data { +class HistoryCounter; +} // namespace browsing_data using base::android::JavaParamRef; using base::android::JavaRef; @@ -24,7 +25,9 @@ // Quick Delete UI. class QuickDeleteBridge { public: - explicit QuickDeleteBridge(Profile* profile); + explicit QuickDeleteBridge(JNIEnv* env, + const base::android::JavaParamRef<jobject>& obj, + Profile* profile); QuickDeleteBridge(const QuickDeleteBridge&) = delete; QuickDeleteBridge& operator=(const QuickDeleteBridge&) = delete; @@ -32,28 +35,24 @@ void Destroy(JNIEnv* env); - // Gets the most recently visited synced domain and count of unique domains - // visited on all devices within the time period. - void GetLastVisitedDomainAndUniqueDomainCount( - JNIEnv* env, - const jint time_period, - const JavaParamRef<jobject>& j_callback); + void RestartCounterForTimePeriod(JNIEnv* env, const jint time_period); private: + base::android::ScopedJavaGlobalRef<jobject> jobject_; + raw_ptr<Profile> profile_; - raw_ptr<history::HistoryService> history_service_; + std::unique_ptr<browsing_data::HistoryCounter> history_counter_; // Tracker for requests to the history service. base::CancelableTaskTracker task_tracker_; base::WeakPtrFactory<QuickDeleteBridge> weak_ptr_factory_{this}; - // Called when the unique domains visited query is completed and informs - // the java side that the result is ready. - void OnGetLastVisitedDomainAndUniqueDomainCountComplete( - const JavaRef<jobject>& j_callback, - history::DomainsVisitedResult result); + // Called when the history counter result is completed and informs the java + // side that the result is ready. + void OnHistoryCounterResult( + std::unique_ptr<browsing_data::BrowsingDataCounter::Result> result); }; #endif // CHROME_BROWSER_ANDROID_QUICK_DELETE_QUICK_DELETE_BRIDGE_H_
diff --git a/chrome/browser/android/quick_delete/quick_delete_bridge_unittest.cc b/chrome/browser/android/quick_delete/quick_delete_bridge_unittest.cc deleted file mode 100644 index 687bd4b..0000000 --- a/chrome/browser/android/quick_delete/quick_delete_bridge_unittest.cc +++ /dev/null
@@ -1,103 +0,0 @@ -// Copyright 2023 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/android/quick_delete/quick_delete_bridge.h" - -#include "base/memory/raw_ptr.h" -#include "base/test/test_mock_time_task_runner.h" -#include "base/time/time_override.h" -#include "chrome/browser/history/history_service_factory.h" -#include "chrome/test/base/testing_profile.h" -#include "components/browsing_data/core/browsing_data_utils.h" -#include "components/history/core/browser/history_service.h" -#include "components/history/core/browser/history_types.h" -#include "content/public/test/browser_task_environment.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -using ::testing::_; - -namespace history { - -class MockHistoryService : public history::HistoryService { - public: - MockHistoryService() = default; - - MOCK_METHOD(void, - GetUniqueDomainsVisited, - (const base::Time begin_time, - const base::Time end_time, - history::VisitQuery404sPolicy policy_for_404_visits, - GetUniqueDomainsVisitedCallback callback, - base::CancelableTaskTracker* tracker), - (override)); -}; - -} // namespace history - -std::unique_ptr<KeyedService> buildHistoryServiceMock( - content::BrowserContext* context) { - return std::make_unique<::testing::NiceMock<history::MockHistoryService>>(); -} - -class QuickDeleteBridgeTest : public testing::Test { - public: - QuickDeleteBridgeTest() : env_(base::android::AttachCurrentThread()) {} - - ~QuickDeleteBridgeTest() override = default; - - void SetUp() override { - mock_history_service_ = static_cast<history::MockHistoryService*>( - HistoryServiceFactory::GetInstance()->SetTestingFactoryAndUse( - &profile_, base::BindRepeating(&buildHistoryServiceMock))); - - bridge_ = std::make_unique<QuickDeleteBridge>(&profile_); - } - - QuickDeleteBridge* bridge() { return bridge_.get(); } - - JNIEnv* env() { return env_; } - - history::MockHistoryService* history_service() { - return mock_history_service_; - } - - static base::Time OverrideTimeNow() { return override_time_now_; } - - static void SetOverrideTimeNow(base::Time override_now) { - override_time_now_ = override_now; - } - - private: - content::BrowserTaskEnvironment task_environment_; - raw_ptr<JNIEnv> env_; - raw_ptr<history::MockHistoryService> mock_history_service_; - std::unique_ptr<QuickDeleteBridge> bridge_; - TestingProfile profile_; - static base::Time override_time_now_; -}; - -base::Time QuickDeleteBridgeTest::override_time_now_; - -TEST_F(QuickDeleteBridgeTest, GetLastVisitedDomainAndUniqueDomainCount) { - SetOverrideTimeNow(base::Time::Now()); - - base::subtle::ScopedTimeClockOverrides time_override( - &QuickDeleteBridgeTest::OverrideTimeNow, - /*time_ticks_override=*/nullptr, - /*thread_ticks_override=*/nullptr); - - const base::Time expected_begin_time = OverrideTimeNow() - base::Minutes(15); - const base::Time expected_end_time = base::Time::Max(); - - EXPECT_CALL(*history_service(), - GetUniqueDomainsVisited( - expected_begin_time, expected_end_time, - history::VisitQuery404sPolicy::kInclude404s, _, _)) - .Times(1); - - bridge()->GetLastVisitedDomainAndUniqueDomainCount( - env(), static_cast<jint>(browsing_data::TimePeriod::LAST_15_MINUTES), - JavaParamRef<jobject>(nullptr)); -}
diff --git a/chrome/browser/ash/accessibility/magnification_controller_browsertest.cc b/chrome/browser/ash/accessibility/magnification_controller_browsertest.cc index 2a16aee9..215f16d 100644 --- a/chrome/browser/ash/accessibility/magnification_controller_browsertest.cc +++ b/chrome/browser/ash/accessibility/magnification_controller_browsertest.cc
@@ -54,8 +54,7 @@ } // namespace class FullscreenMagnifierControllerTest - : public AccessibilityFeatureBrowserTest, - public ::testing::WithParamInterface<ManifestVersion> { + : public AccessibilityFeatureBrowserTest { public: FullscreenMagnifierControllerTest() = default; FullscreenMagnifierControllerTest(const FullscreenMagnifierControllerTest&) = @@ -68,17 +67,8 @@ void SetUpCommandLine(base::CommandLine* command_line) override { // Make screens sufficiently wide to host 2 browsers side by side. command_line->AppendSwitchASCII("ash-host-window-bounds", "1200x800"); - - std::vector<base::test::FeatureRef> enabled_features; - std::vector<base::test::FeatureRef> disabled_features; - if (GetParam() == ManifestVersion::kTwo) { - disabled_features.push_back( - ::features::kAccessibilityManifestV3AccessibilityCommon); - } else if (GetParam() == ManifestVersion::kThree) { - enabled_features.push_back( - ::features::kAccessibilityManifestV3AccessibilityCommon); - } - scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features); + scoped_feature_list_.InitWithFeatureStates( + {{::features::kAccessibilityManifestV3AccessibilityCommon, true}}); AccessibilityFeatureBrowserTest::SetUpCommandLine(command_line); } @@ -105,14 +95,7 @@ base::test::ScopedFeatureList scoped_feature_list_; }; -INSTANTIATE_TEST_SUITE_P(ManifestV2, - FullscreenMagnifierControllerTest, - ::testing::Values(ManifestVersion::kTwo)); -INSTANTIATE_TEST_SUITE_P(ManifestV3, - FullscreenMagnifierControllerTest, - ::testing::Values(ManifestVersion::kThree)); - -IN_PROC_BROWSER_TEST_P(FullscreenMagnifierControllerTest, +IN_PROC_BROWSER_TEST_F(FullscreenMagnifierControllerTest, FollowFocusOnWebButton) { helper()->LoadMagnifier(GetProfile()); @@ -139,7 +122,7 @@ EXPECT_TRUE(GetViewPort().Contains(button_bounds)); } -IN_PROC_BROWSER_TEST_P(FullscreenMagnifierControllerTest, +IN_PROC_BROWSER_TEST_F(FullscreenMagnifierControllerTest, AnimatesToFollowKeyboardFocus) { helper()->LoadMagnifier(GetProfile()); @@ -169,7 +152,7 @@ } while (!GetViewPort().Contains(button_bounds)); } -IN_PROC_BROWSER_TEST_P(FullscreenMagnifierControllerTest, +IN_PROC_BROWSER_TEST_F(FullscreenMagnifierControllerTest, MovesContinuouslyWithMouse) { GetProfile()->GetPrefs()->SetInteger( prefs::kAccessibilityScreenMagnifierMouseFollowingMode, @@ -195,7 +178,7 @@ } } -IN_PROC_BROWSER_TEST_P(FullscreenMagnifierControllerTest, +IN_PROC_BROWSER_TEST_F(FullscreenMagnifierControllerTest, MovesWithMouseAtEdge) { GetProfile()->GetPrefs()->SetInteger( prefs::kAccessibilityScreenMagnifierMouseFollowingMode, @@ -226,7 +209,7 @@ EXPECT_GT(GetViewPort().CenterPoint().y(), initial_center.y()); } -IN_PROC_BROWSER_TEST_P(FullscreenMagnifierControllerTest, +IN_PROC_BROWSER_TEST_F(FullscreenMagnifierControllerTest, ChangeZoomWithAccelerator) { helper()->LoadMagnifier(GetProfile()); @@ -243,7 +226,7 @@ EXPECT_GT(scale, GetFullscreenMagnifierController()->GetScale()); } -IN_PROC_BROWSER_TEST_P(FullscreenMagnifierControllerTest, ChangeZoomWithPrefs) { +IN_PROC_BROWSER_TEST_F(FullscreenMagnifierControllerTest, ChangeZoomWithPrefs) { helper()->LoadMagnifier(GetProfile()); // Change the bounds pref.
diff --git a/chrome/browser/ash/accessibility/select_to_speak_browsertest.cc b/chrome/browser/ash/accessibility/select_to_speak_browsertest.cc index eb5a044..fb84212 100644 --- a/chrome/browser/ash/accessibility/select_to_speak_browsertest.cc +++ b/chrome/browser/ash/accessibility/select_to_speak_browsertest.cc
@@ -62,9 +62,7 @@ "Accessibility.CrosSelectToSpeak.SpeechDuration"; } // namespace -class SelectToSpeakTest - : public AccessibilityFeatureBrowserTest, - public ::testing::WithParamInterface<ManifestVersion> { +class SelectToSpeakTest : public AccessibilityFeatureBrowserTest { public: SelectToSpeakTest(const SelectToSpeakTest&) = delete; SelectToSpeakTest& operator=(const SelectToSpeakTest&) = delete; @@ -107,16 +105,8 @@ ~SelectToSpeakTest() override = default; void SetUpCommandLine(base::CommandLine* command_line) override { - std::vector<base::test::FeatureRef> enabled_features; - std::vector<base::test::FeatureRef> disabled_features; - if (GetParam() == ManifestVersion::kTwo) { - disabled_features.push_back( - ::features::kAccessibilityManifestV3SelectToSpeak); - } else if (GetParam() == ManifestVersion::kThree) { - enabled_features.push_back( - ::features::kAccessibilityManifestV3SelectToSpeak); - } - scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features); + scoped_feature_list_.InitWithFeatureStates( + {{::features::kAccessibilityManifestV3SelectToSpeak, true}}); InProcessBrowserTest::SetUpCommandLine(command_line); } @@ -276,23 +266,7 @@ } }; -INSTANTIATE_TEST_SUITE_P(ManifestV2, - SelectToSpeakTest, - ::testing::Values(ManifestVersion::kTwo)); - -INSTANTIATE_TEST_SUITE_P(ManifestV3, - SelectToSpeakTest, - ::testing::Values(ManifestVersion::kThree)); - -INSTANTIATE_TEST_SUITE_P(ManifestV2, - SelectToSpeakTestWithVoiceSwitching, - ::testing::Values(ManifestVersion::kTwo)); - -INSTANTIATE_TEST_SUITE_P(ManifestV3, - SelectToSpeakTestWithVoiceSwitching, - ::testing::Values(ManifestVersion::kThree)); - -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, SpeakStatusTray) { +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, SpeakStatusTray) { sts_test_utils::TurnOnSelectToSpeakForTest( AccessibilityManager::Get()->profile()); gfx::Rect tray_bounds = Shell::Get() @@ -312,7 +286,7 @@ sm_.Replay(); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, ActivatesWithTapOnSelectToSpeakTray) { +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, ActivatesWithTapOnSelectToSpeakTray) { LoadURLAndSelectToSpeak( "data:text/html;charset=utf-8,<p>This is some text</p>"); @@ -336,7 +310,7 @@ sm_.Replay(); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, WorksWithTouchSelection) { +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, WorksWithTouchSelection) { LoadURLAndSelectToSpeak( "data:text/html;charset=utf-8,<p>This is some text</p>"); @@ -360,7 +334,7 @@ sm_.Replay(); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, WorksWithTouchSelectionOnNonPrimaryMonitor) { // Don't observe error messages. // An error message is observed consistently on MSAN, see crbug.com/1201212, @@ -410,7 +384,7 @@ sm_.Replay(); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, SelectToSpeakTrayNotSpoken) { +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, SelectToSpeakTrayNotSpoken) { sts_test_utils::TurnOnSelectToSpeakForTest( AccessibilityManager::Get()->profile()); @@ -432,7 +406,7 @@ sm_.Replay(); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, SmoothlyReadsAcrossInlineUrl) { +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, SmoothlyReadsAcrossInlineUrl) { // Make sure an inline URL is read smoothly. ActivateSelectToSpeakInWindowBounds( "data:text/html;charset=utf-8,<p>This is some text <a href=\"\">with a" @@ -444,7 +418,7 @@ sm_.Replay(); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, SetsWordHighlights) { +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, SetsWordHighlights) { AccessibilityFocusRingControllerImpl* controller = Shell::Get()->accessibility_focus_ring_controller(); EXPECT_FALSE(controller->highlight_layer_for_testing()); @@ -466,7 +440,7 @@ EXPECT_EQ(SkColorSetRGB(94, 155, 255), highlight_layer->color_for_test()); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, SmoothlyReadsAcrossMultipleLines) { +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, SmoothlyReadsAcrossMultipleLines) { // Sentences spanning multiple lines. ActivateSelectToSpeakInWindowBounds( "data:text/html;charset=utf-8,<div style=\"width:100px\">This" @@ -480,7 +454,7 @@ sm_.Replay(); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, SmoothlyReadsAcrossFormattedText) { +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, SmoothlyReadsAcrossFormattedText) { // Bold or formatted text ActivateSelectToSpeakInWindowBounds( "data:text/html;charset=utf-8,<p>This is some text <b>with a node" @@ -493,7 +467,7 @@ sm_.Replay(); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, ReadsStaticTextWithoutInlineTextChildren) { // Bold or formatted text ActivateSelectToSpeakInWindowBounds( @@ -503,7 +477,7 @@ sm_.Replay(); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, BreaksAtParagraphBounds) { +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, BreaksAtParagraphBounds) { ActivateSelectToSpeakInWindowBounds( "data:text/html;charset=utf-8,<div><p>First paragraph</p>" "<p>Second paragraph</p></div>"); @@ -514,7 +488,7 @@ sm_.Replay(); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, LanguageBoundsIgnoredByDefault) { +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, LanguageBoundsIgnoredByDefault) { // Splitting at language bounds is behind a feature flag, test the default // behaviour doesn't introduce a regression. ActivateSelectToSpeakInWindowBounds( @@ -526,7 +500,7 @@ sm_.Replay(); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTestWithVoiceSwitching, +IN_PROC_BROWSER_TEST_F(SelectToSpeakTestWithVoiceSwitching, BreaksAtLanguageBounds) { ActivateSelectToSpeakInWindowBounds( "data:text/html;charset=utf-8,<div>" @@ -542,7 +516,7 @@ sm_.Replay(); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, DoesNotCrashWithMousewheelEvent) { +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, DoesNotCrashWithMousewheelEvent) { std::string url = "data:text/html;charset=utf-8,<p>This is some text</p>"; LoadURLAndSelectToSpeak(url); gfx::Rect bounds = GetWebContentsBounds(url); @@ -563,7 +537,7 @@ sm_.Replay(); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, FocusRingMovesWithMouse) { +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, FocusRingMovesWithMouse) { std::string url = "data:text/html;charset=utf-8," "<p>This is some text</p>"; @@ -640,7 +614,7 @@ EXPECT_EQ(focus_rings.size(), 0u); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, SelectToSpeakSelectionPansFullscreenMagnifier) { FullscreenMagnifierController* fullscreen_magnifier_controller = Shell::Get()->fullscreen_magnifier_controller(); @@ -686,7 +660,7 @@ EXPECT_GT(final_window_position.y(), initial_window_position.y()); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, FullscreenMagnifierFollowsTextBoundsWhenPrefOn) { sm_.send_word_events_and_wait_to_finish(true); Profile* profile = AccessibilityManager::Get()->profile(); @@ -716,8 +690,6 @@ 8.0); // Wait for Fullscreen magnifier to initialize. - extensions::ExtensionHostTestHelper host_helper( - profile, extension_misc::kAccessibilityCommonExtensionId); extensions::ExtensionRegistryTestHelper observer( extension_misc::kAccessibilityCommonExtensionId, profile); profile->GetPrefs()->SetBoolean(prefs::kAccessibilityScreenMagnifierEnabled, @@ -728,11 +700,7 @@ MagnifierAnimationWaiter waiter(fullscreen_magnifier_controller); waiter.Wait(); - if (observer.WaitForManifestVersion() == 3) { - observer.WaitForServiceWorkerStart(); - } else { - host_helper.WaitForHostCompletedFirstLoad(); - } + observer.WaitForServiceWorkerStart(); FullscreenMagnifierTestHelper::WaitForMagnifierJSReady(profile); gfx::Rect initial_viewport = @@ -818,7 +786,7 @@ // TODO(b/259363112): Add a test that Select to Speak follows focus for nodes // with no inline text boxes. -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, ContinuesReadingDuringResize) { +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, ContinuesReadingDuringResize) { ActivateSelectToSpeakInWindowBounds( "data:text/html;charset=utf-8,<p>First paragraph</p>" "<div id='resize' style='width:300px; font-size: 1em'>" @@ -837,7 +805,7 @@ sm_.Replay(); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, WorksWithStickyKeys) { +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, WorksWithStickyKeys) { AccessibilityManager::Get()->EnableStickyKeys(true); std::string url = "data:text/html;charset=utf-8,<p>This is some text</p>"; @@ -864,7 +832,7 @@ sm_.Replay(); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, SelectToSpeakDoesNotDismissTrayBubble) { sts_test_utils::TurnOnSelectToSpeakForTest( AccessibilityManager::Get()->profile()); @@ -886,7 +854,7 @@ } // TODO(anastasi): Test that metrics record duration here. -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, ReadsSelectedTextWithSearchS) { +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, ReadsSelectedTextWithSearchS) { std::string text = "This is some selected text"; LoadURLAndSelectToSpeak(base::StringPrintf( "data:text/html;charset=utf-8,<p>Not me!</p><p>%s</p><p>Nor me!</p>", @@ -906,7 +874,7 @@ sm_.Replay(); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, ReadsSelectedTextFromContextMenuClick) { std::string text = "This is some selected text"; LoadURLAndSelectToSpeak(base::StringPrintf( @@ -942,7 +910,7 @@ sm_.Replay(); } -IN_PROC_BROWSER_TEST_P(SelectToSpeakTest, +IN_PROC_BROWSER_TEST_F(SelectToSpeakTest, ReadsSelectedTextWithContextMenuNotification) { std::string text = "Pick me! Read me!"; LoadURLAndSelectToSpeak(base::StringPrintf(
diff --git a/chrome/browser/ash/accessibility/select_to_speak_test_utils.cc b/chrome/browser/ash/accessibility/select_to_speak_test_utils.cc index 073cc46..123365b 100644 --- a/chrome/browser/ash/accessibility/select_to_speak_test_utils.cc +++ b/chrome/browser/ash/accessibility/select_to_speak_test_utils.cc
@@ -28,20 +28,12 @@ // dialog does not block. profile->GetPrefs()->SetBoolean( prefs::kAccessibilitySelectToSpeakEnhancedVoicesDialogShown, true); - - // Watch events from an MV2 extension which runs in a background page. - extensions::ExtensionHostTestHelper host_helper( - profile, extension_misc::kSelectToSpeakExtensionId); // Watch events from an MV3 extension which runs in a service worker. extensions::ExtensionRegistryTestHelper observer( extension_misc::kSelectToSpeakExtensionId, profile); AccessibilityManager::Get()->SetSelectToSpeakEnabled(true); - if (observer.WaitForManifestVersion() == 3) { - observer.WaitForServiceWorkerStart(); - } else { - host_helper.WaitForHostCompletedFirstLoad(); - } + observer.WaitForServiceWorkerStart(); base::ScopedAllowBlockingForTesting allow_blocking;
diff --git a/chrome/browser/ash/policy/remote_commands/crd/crd_remote_command_utils.cc b/chrome/browser/ash/policy/remote_commands/crd/crd_remote_command_utils.cc index 06f660c7..26e0065 100644 --- a/chrome/browser/ash/policy/remote_commands/crd/crd_remote_command_utils.cc +++ b/chrome/browser/ash/policy/remote_commands/crd/crd_remote_command_utils.cc
@@ -133,6 +133,12 @@ return base::TimeTicks::Now() - last_activity; } +bool IsDeviceIdleSinceReboot() { + base::TimeTicks last_activity = + CHECK_DEREF(ui::UserActivityDetector::Get()).last_activity_time(); + return last_activity.is_null(); +} + UserSessionType GetCurrentUserSessionType() { const auto& user_manager = CHECK_DEREF(user_manager::UserManager::Get());
diff --git a/chrome/browser/ash/policy/remote_commands/crd/crd_remote_command_utils.h b/chrome/browser/ash/policy/remote_commands/crd/crd_remote_command_utils.h index fb47c4fc..9a082a2f 100644 --- a/chrome/browser/ash/policy/remote_commands/crd/crd_remote_command_utils.h +++ b/chrome/browser/ash/policy/remote_commands/crd/crd_remote_command_utils.h
@@ -39,6 +39,9 @@ // reboot. base::TimeDelta GetDeviceIdleTime(); +// Returns true if the device has been idle since the last reboot. +bool IsDeviceIdleSinceReboot(); + // Returns the type of the currently active user session. UserSessionType GetCurrentUserSessionType();
diff --git a/chrome/browser/ash/policy/remote_commands/crd/device_command_start_crd_session_job.cc b/chrome/browser/ash/policy/remote_commands/crd/device_command_start_crd_session_job.cc index 4ab01f9..c407066 100644 --- a/chrome/browser/ash/policy/remote_commands/crd/device_command_start_crd_session_job.cc +++ b/chrome/browser/ash/policy/remote_commands/crd/device_command_start_crd_session_job.cc
@@ -505,8 +505,20 @@ return false; } - return is_in_managed_environment && ShouldShowConfirmationDialog() && - GetDeviceIdleTime() <= kAutoApproveDeviceIdlenessCutoff; + if (!is_in_managed_environment || !ShouldShowConfirmationDialog()) { + return false; + } + + // This enables shared unattended Chrome Remote Desktop (CRD) sessions to + // auto-launched managed guest sessions. Specifically, this is for scenarios + // where there is an active managed guest session, and the device has been + // idle since the last reboot. + if (GetCurrentUserSessionType() == UserSessionType::MANAGED_GUEST_SESSION && + IsDeviceIdleSinceReboot()) { + return true; + } + + return GetDeviceIdleTime() <= kAutoApproveDeviceIdlenessCutoff; } ErrorCallback DeviceCommandStartCrdSessionJob::GetErrorCallback() {
diff --git a/chrome/browser/ash/policy/remote_commands/crd/device_command_start_crd_session_job_unittest.cc b/chrome/browser/ash/policy/remote_commands/crd/device_command_start_crd_session_job_unittest.cc index c96dbcc..4437b66 100644 --- a/chrome/browser/ash/policy/remote_commands/crd/device_command_start_crd_session_job_unittest.cc +++ b/chrome/browser/ash/policy/remote_commands/crd/device_command_start_crd_session_job_unittest.cc
@@ -275,6 +275,10 @@ StartSessionOfType(TestSessionType::kAffiliatedUserSession); } + void LogInAsManagedGuestSessionUser() { + StartSessionOfType(TestSessionType::kManagedGuestSession); + } + void SetDeviceIdleTime(int idle_time_in_sec) { user_activity_detector_->set_last_activity_time_for_test( base::TimeTicks::Now() - base::Seconds(idle_time_in_sec)); @@ -886,6 +890,35 @@ } TEST_F(DeviceCommandStartCrdSessionJobTest, + ShouldSetAutoAcceptTimeOutIfMgsIsIdleSinceBoot) { + AddActiveManagedNetwork(); + base::TimeTicks never; + ASSERT_TRUE(never.is_null()); + SetLastDeviceActivityTime(never); + + LogInAsManagedGuestSessionUser(); + Result result = RunJobAndWaitForResult(); + + EXPECT_SUCCESS(result); + EXPECT_EQ(delegate().session_parameters().connection_auto_accept_timeout, + kAutoApproveConnectionTimeout); +} + +TEST_F(DeviceCommandStartCrdSessionJobTest, + ShouldNotSetAutoAcceptTimeOutIfMgsIsConnectedToUnmanagedNetwork) { + base::TimeTicks never; + ASSERT_TRUE(never.is_null()); + SetLastDeviceActivityTime(never); + + LogInAsManagedGuestSessionUser(); + Result result = RunJobAndWaitForResult(); + + EXPECT_SUCCESS(result); + EXPECT_EQ(delegate().session_parameters().connection_auto_accept_timeout, + std::nullopt); +} + +TEST_F(DeviceCommandStartCrdSessionJobTest, ShouldNotSetConnectionAutoApproveTimeoutIfDisabledByFeatureFlag) { DisableFeature(kAutoApproveEnterpriseSharedSessions); AddActiveManagedNetwork();
diff --git a/chrome/browser/ash/printing/cups_printers_manager.cc b/chrome/browser/ash/printing/cups_printers_manager.cc index 73a2246..d55ca7f 100644 --- a/chrome/browser/ash/printing/cups_printers_manager.cc +++ b/chrome/browser/ash/printing/cups_printers_manager.cc
@@ -577,8 +577,7 @@ // UNREACHABLE if the printer is disconnected. if (printer->IsUsbProtocol()) { CupsPrinterStatus printer_status(printer_id); - // TODO(b/443704245): Also make status work for enterprise USB printers. - if (FindDetectedPrinter(printer_id)) { + if (UsbPrinterIsConnected(printer_id)) { printer_status.AddStatusReason( CupsPrinterStatus::CupsPrinterStatusReason::Reason::kNoError, CupsPrinterStatus::CupsPrinterStatusReason::Severity:: @@ -825,6 +824,19 @@ return result; } + const PrinterDetector::DetectedPrinter* FindUsbDetectedPrinter( + std::optional<Printer::UsbDeviceId> usb_device_id) const { + if (!usb_device_id.has_value()) { + return nullptr; + } + for (const auto& detected : usb_detections_) { + if (detected.printer.usb_device_id() == usb_device_id) { + return &detected; + } + } + return nullptr; + } + bool AnyEnterprisePrinterMatches( std::optional<Printer::UsbDeviceId> usb_device_id) const { return FindUsbEnterprisePrinters(usb_device_id).size() > 0; @@ -870,19 +882,41 @@ } } - // Look through all sources for the detected printer with the given id. - // Return a pointer to the printer on found, null if no entry is found. - const PrinterDetector::DetectedPrinter* FindDetectedPrinter( + struct UsbPrinterPpdInfo { + chromeos::Printer::PpdReference ppd_reference; + chromeos::PrinterSearchData ppd_search_data; + }; + // Get PPD information for the given USB printer (detected or enterprise). + // Returns `std::nullopt` if there's no such connected USB printer. + std::optional<UsbPrinterPpdInfo> GetUsbPrinterPpdInfo( const std::string& id) const { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_); - for (const auto* printer_list : {&usb_detections_, &zeroconf_detections_}) { - for (const auto& detected : *printer_list) { - if (detected.printer.id() == id) { - return &detected; - } + // First look through regular usb detected printers. + for (const auto& detected : usb_detections_) { + if (detected.printer.id() == id) { + return UsbPrinterPpdInfo(detected.printer.ppd_reference(), + detected.ppd_search_data); } } - return nullptr; + // Now look through enterprise usb printers. + if (!base::FeatureList::IsEnabled(features::kManagedUsbPrinters)) { + return std::nullopt; + } + if (std::optional<Printer> enterprise = + printers_.Get(chromeos::PrinterClass::kEnterprise, id)) { + // Need to find the corresponding detected usb printer and get the + // `ppd_search_data` from it. + if (const PrinterDetector::DetectedPrinter* detected = + FindUsbDetectedPrinter(enterprise->usb_device_id())) { + return UsbPrinterPpdInfo(enterprise->ppd_reference(), + detected->ppd_search_data); + } + } + return std::nullopt; + } + + bool UsbPrinterIsConnected(const std::string& id) { + return GetUsbPrinterPpdInfo(id).has_value(); } void MaybeRecordInstallation( @@ -895,38 +929,32 @@ return; } + // For recording purposes, this is an automatic install if the ppd reference + // generated at detection time is the one we actually used -- i.e. the user + // didn't have to change anything to obtain a ppd that worked. + PrinterEventTracker::SetupMode mode; + if (is_automatic_installation) { + mode = PrinterEventTracker::kAutomatic; + } else { + mode = PrinterEventTracker::kUser; + } + // For compatibility with the previous implementation, record USB printers // separately from other IPP printers. Eventually we may want to shift // this to be split by autodetected/not autodetected instead of USB/other // IPP. if (printer.IsUsbProtocol()) { // Get the associated detection record if one exists. - const auto* detected = FindDetectedPrinter(printer.id()); - // We should have the full DetectedPrinter. We can't log the printer if - // we don't have it. - if (!detected) { + std::optional<UsbPrinterPpdInfo> ppd_info = + GetUsbPrinterPpdInfo(printer.id()); + if (!ppd_info.has_value()) { LOG(WARNING) << "Failed to find USB printer " << printer.id() << " for installation event logging"; return; } - // For recording purposes, this is an automatic install if the ppd - // reference generated at detection time is the is the one we actually - // used -- i.e. the user didn't have to change anything to obtain a ppd - // that worked. - PrinterEventTracker::SetupMode mode; - if (is_automatic_installation) { - mode = PrinterEventTracker::kAutomatic; - } else { - mode = PrinterEventTracker::kUser; - } - event_tracker_->RecordUsbPrinterInstalled(*detected, mode); + event_tracker_->RecordUsbPrinterInstalled( + ppd_info->ppd_reference, ppd_info->ppd_search_data, mode); } else { - PrinterEventTracker::SetupMode mode; - if (is_automatic_installation) { - mode = PrinterEventTracker::kAutomatic; - } else { - mode = PrinterEventTracker::kUser; - } event_tracker_->RecordIppPrinterInstalled(printer, mode, ipp_printer_info); } @@ -1074,13 +1102,14 @@ void RecordSetupAbandoned(const Printer& printer) override { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_); if (printer.IsUsbProtocol()) { - const auto* detected = FindDetectedPrinter(printer.id()); - if (!detected) { + std::optional<UsbPrinterPpdInfo> ppd_info = + GetUsbPrinterPpdInfo(printer.id()); + if (!ppd_info.has_value()) { LOG(WARNING) << "Failed to find USB printer " << printer.id() << " for abandoned event logging"; return; } - event_tracker_->RecordUsbSetupAbandoned(*detected); + event_tracker_->RecordUsbSetupAbandoned(ppd_info->ppd_search_data); } else { event_tracker_->RecordSetupAbandoned(printer); }
diff --git a/chrome/browser/ash/printing/cups_printers_manager_unittest.cc b/chrome/browser/ash/printing/cups_printers_manager_unittest.cc index 92be5e0..503fb54 100644 --- a/chrome/browser/ash/printing/cups_printers_manager_unittest.cc +++ b/chrome/browser/ash/printing/cups_printers_manager_unittest.cc
@@ -53,6 +53,7 @@ #include "testing/gtest/include/gtest/gtest.h" #include "third_party/cros_system_api/dbus/dlcservice/dbus-constants.h" #include "third_party/cros_system_api/dbus/shill/dbus-constants.h" +#include "third_party/metrics_proto/printer_event.pb.h" namespace ash { namespace { @@ -63,6 +64,9 @@ using ::chromeos::PrinterClass; using ::chromeos::PrinterSearchData; using DetectedPrinter = PrinterDetector::DetectedPrinter; +using ::chromeos::CupsPrinterStatus; +using CupsPrinterStatusReason = + ::chromeos::CupsPrinterStatus::CupsPrinterStatusReason; constexpr base::TimeDelta kMetricsDelayTimerInterval = base::Seconds(60); @@ -438,6 +442,8 @@ std::move(enterprise_printers_provider), &event_tracker_, &pref_service_); manager_->AddObserver(this); + + event_tracker_.set_logging(true); } ~CupsPrintersManagerTest() override { @@ -487,6 +493,24 @@ return print_server; } + void ExpectPrinterStatusReason( + const std::string& printer_id, + CupsPrinterStatusReason::Reason expected_reason) { + manager_->FetchPrinterStatus( + printer_id, + base::BindLambdaForTesting([&](const CupsPrinterStatus& status) { + ASSERT_EQ(status.GetStatusReasons().size(), 1u); + EXPECT_EQ(status.GetStatusReasons().begin()->GetReason(), + expected_reason); + })); + } + + std::vector<metrics::PrinterEventProto> GetLoggedEvents() { + std::vector<metrics::PrinterEventProto> events; + event_tracker_.FlushPrinterEvents(&events); + return events; + } + protected: // Everything from PrintServersProvider must be called on Chrome_UIThread // Note: MainThreadType::IO is strictly about requesting a specific @@ -1881,5 +1905,290 @@ EXPECT_EQ("usb://1234/5678?serial", enterprise->uri().GetNormalized()); } +TEST_F(CupsPrintersManagerTest, Status_DetectedUsbPrinter) { + // Add a detected USB printer. + DetectedPrinter detected_usb = CreateDetectedUsbPrinter( + "DetectedUsb", "usb://1234/5678?serial=ABC", 0x1234, 0x5678); + usb_detector_->AddDetections({detected_usb}); + task_environment_.RunUntilIdle(); + + // It should be connected and have NoError status. + ExpectPrinterStatusReason("DetectedUsb", + CupsPrinterStatusReason::Reason::kNoError); + + // Remove the detection + usb_detector_->RemoveDetections({"DetectedUsb"}); + task_environment_.RunUntilIdle(); + + // Now it should be unreachable. + ExpectPrinterStatusReason( + "DetectedUsb", CupsPrinterStatusReason::Reason::kPrinterUnreachable); +} + +TEST_F(CupsPrintersManagerTest, Status_EnterpriseUsbPrinter) { + feature_list_.InitAndEnableFeature(features::kManagedUsbPrinters); + + // Enterprise printer. + Printer enterprise_printer = + CreateEnterpriseUsbPrinter("EnterpriseUsb", 0xAAAA, 0xBBBB); + enterprise_printers_provider_->AddEnterprisePrinters({enterprise_printer}); + task_environment_.RunUntilIdle(); + + // Initially, the enterprise printer is not detected via USB. + ExpectPrinterStatusReason( + "EnterpriseUsb", CupsPrinterStatusReason::Reason::kPrinterUnreachable); + + // Detected USB printer matching the enterprise one. + DetectedPrinter detected_usb = CreateDetectedUsbPrinter( + "MatchedUsb", "usb://AAAA/BBBB?serial=XYZ", 0xAAAA, 0xBBBB); + usb_detector_->AddDetections({detected_usb}); + task_environment_.RunUntilIdle(); + + // Now the enterprise printer should be considered connected. + ExpectPrinterStatusReason("EnterpriseUsb", + CupsPrinterStatusReason::Reason::kNoError); + + // Remove the USB detection. + usb_detector_->RemoveDetections({"MatchedUsb"}); + task_environment_.RunUntilIdle(); + + // Should be unreachable again. + ExpectPrinterStatusReason( + "EnterpriseUsb", CupsPrinterStatusReason::Reason::kPrinterUnreachable); +} + +TEST_F(CupsPrintersManagerTest, Status_EnterpriseUsbPrinter_FeatureDisabled) { + feature_list_.InitAndDisableFeature(features::kManagedUsbPrinters); + + // Enterprise printer. + Printer enterprise_printer = + CreateEnterpriseUsbPrinter("EnterpriseUsb", 0xCCCC, 0xDDDD); + enterprise_printers_provider_->AddEnterprisePrinters({enterprise_printer}); + task_environment_.RunUntilIdle(); + + // Detected USB printer matching the enterprise VID/PID. + DetectedPrinter detected_usb = CreateDetectedUsbPrinter( + "MatchedUsb", "usb://CCCC/DDDD?serial=123", 0xCCCC, 0xDDDD); + usb_detector_->AddDetections({detected_usb}); + task_environment_.RunUntilIdle(); + + // The enterprise printer is unreachable since the feature is disabled. + ExpectPrinterStatusReason( + "EnterpriseUsb", CupsPrinterStatusReason::Reason::kPrinterUnreachable); +} + +TEST_F(CupsPrintersManagerTest, Status_MixUsbPrinters) { + feature_list_.InitAndEnableFeature(features::kManagedUsbPrinters); + + // Regular Detected USB that is also saved. + DetectedPrinter detected_usb = CreateDetectedUsbPrinter( + "DetectedUsb", "usb://1111/2222?serial=A", 0x1111, 0x2222); + usb_detector_->AddDetections({detected_usb}); + manager_->SavePrinter(detected_usb.printer); + + // Enterprise USB - Connected. + Printer enterprise_conn = + CreateEnterpriseUsbPrinter("EnterpriseConn", 0x3333, 0x4444); + enterprise_printers_provider_->AddEnterprisePrinters({enterprise_conn}); + DetectedPrinter detected_enterprise = CreateDetectedUsbPrinter( + "MatchedEnterprise", "usb://3333/4444?serial=B", 0x3333, 0x4444); + usb_detector_->AddDetections({detected_enterprise}); + + // Enterprise USB - Disconnected. + Printer enterprise_disconn = + CreateEnterpriseUsbPrinter("EnterpriseDisconn", 0x5555, 0x6666); + enterprise_printers_provider_->AddEnterprisePrinters({enterprise_disconn}); + + task_environment_.RunUntilIdle(); + + ExpectPrinterStatusReason("DetectedUsb", + CupsPrinterStatusReason::Reason::kNoError); + ExpectPrinterStatusReason("EnterpriseConn", + CupsPrinterStatusReason::Reason::kNoError); + ExpectPrinterStatusReason( + "EnterpriseDisconn", + CupsPrinterStatusReason::Reason::kPrinterUnreachable); +} + +DetectedPrinter CreateDetectedUsbPrinterWithPpdData(const std::string& id, + const std::string& uri, + int vendor_id, + int product_id) { + DetectedPrinter ret = + CreateDetectedUsbPrinter(id, uri, vendor_id, product_id); + + // Populate ppd_search data. + ret.ppd_search_data.usb_vendor_id = vendor_id; + ret.ppd_search_data.usb_product_id = product_id; + ret.ppd_search_data.usb_manufacturer = "ACME"; + ret.ppd_search_data.usb_model = "Anvil"; + + // Populate ppd_reference. + ret.printer.mutable_ppd_reference()->effective_make_and_model = + "Invisible paint"; + return ret; +} + +TEST_F(CupsPrintersManagerTest, Metrics_RecordInstallRegularUsb) { + auto detected_printer = CreateDetectedUsbPrinterWithPpdData( + "RegUsb", "usb://1111/2222?serial=A", 0x1111, 0x2222); + usb_detector_->AddDetections({detected_printer}); + task_environment_.RunUntilIdle(); + + // This will trigger the call to MaybeRecordInstallation in + // OnPrinterSetupResult + base::RunLoop run_loop; + manager_->SetUpPrinter(detected_printer.printer, + /*is_automatic_installation=*/true, + CallQuitOnRunLoop(&run_loop)); + run_loop.Run(); + task_environment_.RunUntilIdle(); + + auto events = GetLoggedEvents(); + ASSERT_THAT(events.size(), 1); + const auto& event = events[0]; + EXPECT_EQ(event.event_type(), metrics::PrinterEventProto::SETUP_AUTOMATIC); + EXPECT_EQ(event.usb_vendor_id(), 0x1111); + EXPECT_EQ(event.usb_model_id(), 0x2222); + EXPECT_EQ(event.usb_printer_manufacturer(), "ACME"); + EXPECT_EQ(event.usb_printer_model(), "Anvil"); + EXPECT_EQ(event.ppd_identifier(), "Invisible paint"); +} + +TEST_F(CupsPrintersManagerTest, Metrics_RecordInstallEnterpriseUsb) { + feature_list_.InitAndEnableFeature(features::kManagedUsbPrinters); + + // Enterprise printer. + Printer enterprise_printer = + CreateEnterpriseUsbPrinter("EntUsb", 0x3333, 0x4444); + enterprise_printer.mutable_ppd_reference()->effective_make_and_model = + "Jet-Propelled Unicycle"; + enterprise_printers_provider_->AddEnterprisePrinters({enterprise_printer}); + task_environment_.RunUntilIdle(); + + // Matching detected USB printer. + auto detected_printer = CreateDetectedUsbPrinterWithPpdData( + "MatchUsb", "usb://3333/4444?serial=B", 0x3333, 0x4444); + usb_detector_->AddDetections({detected_printer}); + task_environment_.RunUntilIdle(); + + // Setup the *enterprise* printer. MaybeRecordInstallation should find the + // matching detected printer for PPD info. + base::RunLoop run_loop; + manager_->SetUpPrinter(enterprise_printer, + /*is_automatic_installation=*/false, + CallQuitOnRunLoop(&run_loop)); + run_loop.Run(); + task_environment_.RunUntilIdle(); + + auto events = GetLoggedEvents(); + ASSERT_THAT(events.size(), 1); + const auto& event = events[0]; + EXPECT_EQ(event.event_type(), metrics::PrinterEventProto::SETUP_MANUAL); + // The usb_* information should come from the detected printer, while the + // ppd_* information should come from the enterprise printer itself. + EXPECT_EQ(event.usb_vendor_id(), 0x3333); + EXPECT_EQ(event.usb_model_id(), 0x4444); + EXPECT_EQ(event.usb_printer_manufacturer(), "ACME"); + EXPECT_EQ(event.usb_printer_model(), "Anvil"); + EXPECT_EQ(event.ppd_identifier(), "Jet-Propelled Unicycle"); +} + +// Test metrics when a regular USB printer setup is abandoned. +TEST_F(CupsPrintersManagerTest, Metrics_RecordAbandonRegularUsb) { + auto detected_printer = CreateDetectedUsbPrinterWithPpdData( + "RegUsb", "usb://5555/6666?serial=C", 0x5555, 0x6666); + usb_detector_->AddDetections({detected_printer}); + task_environment_.RunUntilIdle(); + + manager_->RecordSetupAbandoned(detected_printer.printer); + task_environment_.RunUntilIdle(); + + auto events = GetLoggedEvents(); + ASSERT_THAT(events.size(), 1); + const auto& event = events[0]; + EXPECT_EQ(event.event_type(), metrics::PrinterEventProto::SETUP_ABANDONED); + EXPECT_EQ(event.usb_vendor_id(), 0x5555); + EXPECT_EQ(event.usb_model_id(), 0x6666); + EXPECT_EQ(event.usb_printer_manufacturer(), "ACME"); + EXPECT_EQ(event.usb_printer_model(), "Anvil"); +} + +// Test metrics when an enterprise USB printer setup is abandoned. +TEST_F(CupsPrintersManagerTest, Metrics_RecordAbandonEnterpriseUsb) { + feature_list_.InitAndEnableFeature(features::kManagedUsbPrinters); + + // Enterprise printer. + Printer enterprise_printer = + CreateEnterpriseUsbPrinter("EntUsb", 0x7777, 0x8888); + enterprise_printers_provider_->AddEnterprisePrinters({enterprise_printer}); + task_environment_.RunUntilIdle(); + + // Matching detected USB printer. + auto detected_printer = CreateDetectedUsbPrinterWithPpdData( + "MatchUsb", "usb://7777/8888?serial=D", 0x7777, 0x8888); + usb_detector_->AddDetections({detected_printer}); + task_environment_.RunUntilIdle(); + + manager_->RecordSetupAbandoned(enterprise_printer); + task_environment_.RunUntilIdle(); + + auto events = GetLoggedEvents(); + ASSERT_THAT(events.size(), 1); + const auto& event = events[0]; + EXPECT_EQ(event.event_type(), metrics::PrinterEventProto::SETUP_ABANDONED); + // The ppd information should come from the detected printer. + EXPECT_EQ(event.usb_vendor_id(), 0x7777); + EXPECT_EQ(event.usb_model_id(), 0x8888); + EXPECT_EQ(event.usb_printer_manufacturer(), "ACME"); + EXPECT_EQ(event.usb_printer_model(), "Anvil"); +} + +TEST_F(CupsPrintersManagerTest, + Metrics_NoRecordInstallEnterpriseUsb_FeatureDisabled) { + feature_list_.InitAndDisableFeature(features::kManagedUsbPrinters); + + Printer enterprise_printer = + CreateEnterpriseUsbPrinter("EntUsb", 0x9999, 0xAAAA); + enterprise_printer.set_make_and_model("Enterprise Make"); + enterprise_printers_provider_->AddEnterprisePrinters({enterprise_printer}); + task_environment_.RunUntilIdle(); + + auto detected_printer = CreateDetectedUsbPrinterWithPpdData( + "MatchUsb", "usb://9999/AAAA?serial=E", 0x9999, 0xAAAA); + usb_detector_->AddDetections({detected_printer}); + task_environment_.RunUntilIdle(); + + base::RunLoop run_loop; + manager_->SetUpPrinter(enterprise_printer, + /*is_automatic_installation=*/true, + CallQuitOnRunLoop(&run_loop)); + run_loop.Run(); + task_environment_.RunUntilIdle(); + + EXPECT_EQ(GetLoggedEvents().size(), 0u); +} + +TEST_F(CupsPrintersManagerTest, + Metrics_NoRecordAbandonEnterpriseUsb_FeatureDisabled) { + feature_list_.InitAndDisableFeature(features::kManagedUsbPrinters); + + Printer enterprise_printer = + CreateEnterpriseUsbPrinter("EntUsb", 0xBBBB, 0xCCCC); + enterprise_printer.set_make_and_model("Enterprise Make Abandon"); + enterprise_printers_provider_->AddEnterprisePrinters({enterprise_printer}); + task_environment_.RunUntilIdle(); + + auto detected_printer = CreateDetectedUsbPrinterWithPpdData( + "MatchUsb", "usb://BBBB/CCCC?serial=F", 0xBBBB, 0xCCCC); + usb_detector_->AddDetections({detected_printer}); + task_environment_.RunUntilIdle(); + + manager_->RecordSetupAbandoned(enterprise_printer); + task_environment_.RunUntilIdle(); + + EXPECT_EQ(GetLoggedEvents().size(), 0u); +} + } // namespace } // namespace ash
diff --git a/chrome/browser/ash/printing/printer_event_tracker.cc b/chrome/browser/ash/printing/printer_event_tracker.cc index b827ebc8..e1a3e317 100644 --- a/chrome/browser/ash/printing/printer_event_tracker.cc +++ b/chrome/browser/ash/printing/printer_event_tracker.cc
@@ -4,6 +4,7 @@ #include "chrome/browser/ash/printing/printer_event_tracker.h" +#include "chromeos/printing/ppd_provider.h" #include "chromeos/printing/printer_configuration.h" namespace ash { @@ -148,12 +149,11 @@ // Add information to |event| specific to |usb_printer|. void SetUsbInfo(metrics::PrinterEventProto* event, - const PrinterDetector::DetectedPrinter& detected) { - event->set_usb_vendor_id(detected.ppd_search_data.usb_vendor_id); - event->set_usb_model_id(detected.ppd_search_data.usb_product_id); - event->set_usb_printer_manufacturer( - detected.ppd_search_data.usb_manufacturer); - event->set_usb_printer_model(detected.ppd_search_data.usb_model); + const chromeos::PrinterSearchData& ppd_search_data) { + event->set_usb_vendor_id(ppd_search_data.usb_vendor_id); + event->set_usb_model_id(ppd_search_data.usb_product_id); + event->set_usb_printer_manufacturer(ppd_search_data.usb_manufacturer); + event->set_usb_printer_model(ppd_search_data.usb_model); } // Add information to the |event| that only network printers have. @@ -211,7 +211,8 @@ } void PrinterEventTracker::RecordUsbPrinterInstalled( - const PrinterDetector::DetectedPrinter& detected, + const chromeos::Printer::PpdReference& ppd_reference, + const chromeos::PrinterSearchData& ppd_search_data, SetupMode mode) { base::AutoLock l(lock_); if (!logging_) { @@ -220,8 +221,8 @@ metrics::PrinterEventProto event; SetEventType(&event, mode); - SetPpdInfo(&event, detected.printer.ppd_reference()); - SetUsbInfo(&event, detected); + SetPpdInfo(&event, ppd_reference); + SetUsbInfo(&event, ppd_search_data); events_.push_back(event); } @@ -245,7 +246,7 @@ } void PrinterEventTracker::RecordUsbSetupAbandoned( - const PrinterDetector::DetectedPrinter& detected) { + const chromeos::PrinterSearchData& ppd_search_data) { base::AutoLock l(lock_); if (!logging_) { return; @@ -253,7 +254,7 @@ metrics::PrinterEventProto event; event.set_event_type(metrics::PrinterEventProto::SETUP_ABANDONED); - SetUsbInfo(&event, detected); + SetUsbInfo(&event, ppd_search_data); events_.push_back(event); }
diff --git a/chrome/browser/ash/printing/printer_event_tracker.h b/chrome/browser/ash/printing/printer_event_tracker.h index 82c93c85..8c30ab6 100644 --- a/chrome/browser/ash/printing/printer_event_tracker.h +++ b/chrome/browser/ash/printing/printer_event_tracker.h
@@ -8,12 +8,13 @@ #include <vector> #include "base/synchronization/lock.h" -#include "chrome/browser/ash/printing/printer_detector.h" +#include "chromeos/printing/printer_configuration.h" #include "components/keyed_service/core/keyed_service.h" #include "third_party/metrics_proto/printer_event.pb.h" namespace chromeos { class Printer; +struct PrinterSearchData; } // namespace chromeos namespace ash { @@ -44,7 +45,8 @@ // Store a succesful USB printer installation. |mode| indicates if // the PPD was selected automatically or chosen by the user. void RecordUsbPrinterInstalled( - const PrinterDetector::DetectedPrinter& printer, + const chromeos::Printer::PpdReference& ppd_reference, + const chromeos::PrinterSearchData& ppd_search_data, SetupMode mode); // Store a succesful network printer installation. |mode| indicates if @@ -58,7 +60,8 @@ void RecordSetupAbandoned(const chromeos::Printer& printer); // Record an abandoned setup for a USB printer. - void RecordUsbSetupAbandoned(const PrinterDetector::DetectedPrinter& printer); + void RecordUsbSetupAbandoned( + const chromeos::PrinterSearchData& ppd_search_data); // Store a printer removal. void RecordPrinterRemoved(const chromeos::Printer& printer);
diff --git a/chrome/browser/ash/printing/printer_event_tracker_unittest.cc b/chrome/browser/ash/printing/printer_event_tracker_unittest.cc index ce0fe09..e0f3644 100644 --- a/chrome/browser/ash/printing/printer_event_tracker_unittest.cc +++ b/chrome/browser/ash/printing/printer_event_tracker_unittest.cc
@@ -5,6 +5,7 @@ #include "chrome/browser/ash/printing/printer_event_tracker.h" #include "base/time/time.h" +#include "chromeos/printing/ppd_provider.h" #include "chromeos/printing/printer_configuration.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -279,15 +280,15 @@ TEST_F(PrinterEventTrackerTest, InstalledUsbPrinter) { tracker_.set_logging(true); - PrinterDetector::DetectedPrinter usb_printer; - usb_printer.ppd_search_data.usb_vendor_id = kVendorId; - usb_printer.ppd_search_data.usb_product_id = kProductId; - usb_printer.ppd_search_data.usb_manufacturer = kUsbManufacturer; - usb_printer.ppd_search_data.usb_model = kUsbModel; - usb_printer.printer.mutable_ppd_reference()->effective_make_and_model = - kEffectiveMakeAndModel; + chromeos::Printer::PpdReference ppd_reference; + ppd_reference.effective_make_and_model = kEffectiveMakeAndModel; + chromeos::PrinterSearchData ppd_search_data; + ppd_search_data.usb_vendor_id = kVendorId, + ppd_search_data.usb_product_id = kProductId, + ppd_search_data.usb_manufacturer = kUsbManufacturer, + ppd_search_data.usb_model = kUsbModel, - tracker_.RecordUsbPrinterInstalled(usb_printer, + tracker_.RecordUsbPrinterInstalled(ppd_reference, ppd_search_data, PrinterEventTracker::SetupMode::kUser); auto events = GetEvents(); @@ -336,13 +337,13 @@ TEST_F(PrinterEventTrackerTest, AbandonedUsbPrinter) { tracker_.set_logging(true); - PrinterDetector::DetectedPrinter usb_printer; - usb_printer.ppd_search_data.usb_vendor_id = kVendorId; - usb_printer.ppd_search_data.usb_product_id = kProductId; - usb_printer.ppd_search_data.usb_manufacturer = kUsbManufacturer; - usb_printer.ppd_search_data.usb_model = kUsbModel; + chromeos::PrinterSearchData ppd_search_data; + ppd_search_data.usb_vendor_id = kVendorId; + ppd_search_data.usb_product_id = kProductId; + ppd_search_data.usb_manufacturer = kUsbManufacturer; + ppd_search_data.usb_model = kUsbModel; - tracker_.RecordUsbSetupAbandoned(usb_printer); + tracker_.RecordUsbSetupAbandoned(ppd_search_data); auto events = GetEvents(); ASSERT_FALSE(events.empty());
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc index 895a32a..96f2fa9 100644 --- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc +++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
@@ -3604,15 +3604,11 @@ const GURL kOrigin2("http://host2.com:1"); { // Test REMOVE_HISTORY. - EXPECT_FALSE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - kOrigin1, permission)); + EXPECT_FALSE(history->RecordTemporaryGrant(kOrigin1, permission)); for (int i = 0; i < kHeuristicGrantThreshold; ++i) { - history->RecordTemporaryGrantAndSetAutoGrantIfNecessary(kOrigin2, - permission); + history->RecordTemporaryGrant(kOrigin2, permission); } - EXPECT_TRUE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - kOrigin2, permission)); BlockUntilOriginDataRemoved(AnHourAgo(), base::Time::Max(), constants::DATA_TYPE_SITE_USAGE_DATA, @@ -3638,15 +3634,12 @@ } { // Test REMOVE_SITE_DATA. - EXPECT_FALSE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - kOrigin1, permission)); + EXPECT_FALSE(history->RecordTemporaryGrant(kOrigin1, permission)); for (int i = 0; i < kHeuristicGrantThreshold; ++i) { - history->RecordTemporaryGrantAndSetAutoGrantIfNecessary(kOrigin2, - permission); + history->RecordTemporaryGrant(kOrigin2, permission); } - EXPECT_TRUE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - kOrigin2, permission)); + EXPECT_TRUE(history->RecordTemporaryGrant(kOrigin2, permission)); BlockUntilOriginDataRemoved(AnHourAgo(), base::Time::Max(), constants::DATA_TYPE_SITE_USAGE_DATA, std::move(filter_builder_2));
diff --git a/chrome/browser/client_hints/BUILD.gn b/chrome/browser/client_hints/BUILD.gn new file mode 100644 index 0000000..32c73e7 --- /dev/null +++ b/chrome/browser/client_hints/BUILD.gn
@@ -0,0 +1,81 @@ +# Copyright 2025 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +source_set("client_hints") { + sources = [ + "client_hints_factory.cc", + "client_hints_factory.h", + ] + public_deps = [ + "//base", + "//chrome/browser/profiles:profile", + "//content/public/browser", + ] + + deps = [ + "//chrome/browser:browser_process", + "//chrome/browser/content_settings:content_settings_factory", + "//components/client_hints/browser", + "//components/embedder_support:user_agent", + ] +} + +if (!is_android) { + source_set("browser_tests") { + testonly = true + defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ] + sources = [ "client_hints_browsertest.cc" ] + deps = [ + "//base", + "//base:base_static", + "//base/test:test_support", + "//chrome/browser", + "//chrome/browser:browser_process", + "//chrome/browser/content_settings:content_settings_factory", + "//chrome/browser/policy:test_support", + "//chrome/browser/profiles:profile", + "//chrome/browser/ui", + "//chrome/test:test_support", + "//chrome/test:test_support_ui", + "//components/client_hints/common", + "//components/content_settings/browser", + "//components/content_settings/core/browser", + "//components/content_settings/core/browser:cookie_settings", + "//components/content_settings/core/common", + "//components/embedder_support", + "//components/embedder_support:user_agent", + "//components/metrics:content", + "//components/network_session_configurator/common", + "//components/page_load_metrics/browser:test_support", + "//components/policy:generated", + "//components/policy:policy_code_generate", + "//components/policy/core/common", + "//components/policy/core/common:common_constants", + "//components/prefs", + "//components/ukm:test_support", + "//content/public/browser", + "//content/public/common", + "//content/test:browsertest_support", + "//content/test:test_support", + "//net", + "//net:net_features", + "//net:test_support", + "//services/metrics/public/cpp:gen_ukm_builders", + "//services/metrics/public/cpp:ukm_builders", + "//services/network/public/cpp", + "//services/network/public/cpp:flags_and_switches", + "//services/network/public/cpp:web_platform", + "//services/network/public/mojom:url_loader_base_shared", + "//services/network/public/mojom:url_loader_base_shared_cpp_sources", + "//testing/gmock", + "//third_party/blink/public/common:headers", + "//third_party/re2", + "//url", + ] + + # TODO(crbug.com/40031409): Fix code that adds exit-time destructors and + # enable the diagnostic by removing this line. + configs += [ "//build/config/compiler:no_exit_time_destructors" ] + } +}
diff --git a/chrome/browser/devtools/devtools_ui_bindings.cc b/chrome/browser/devtools/devtools_ui_bindings.cc index 88d1ab6..b36ae823 100644 --- a/chrome/browser/devtools/devtools_ui_bindings.cc +++ b/chrome/browser/devtools/devtools_ui_bindings.cc
@@ -1896,6 +1896,11 @@ response_dict.Set("devToolsEnableOriginBoundCookies", std::move(origin_bound_cookies_dict)); + if (base::FeatureList::IsEnabled(features::kDevToolsGreenDevUi)) { + response_dict.Set("devToolsGreenDevUi", + base::Value::Dict().Set("enabled", true)); + } + if (base::FeatureList::IsEnabled( ::features::kDevToolsAnimationStylesInStylesTab)) { base::Value::Dict devtools_animation_styles_in_styles_tab_dict;
diff --git a/chrome/browser/devtools/features.cc b/chrome/browser/devtools/features.cc index c1f9dca9..372ca2c 100644 --- a/chrome/browser/devtools/features.cc +++ b/chrome/browser/devtools/features.cc
@@ -150,6 +150,9 @@ // Whether DevTools shows 'Debug with AI' and new badges. BASE_FEATURE(kDevToolsAiDebugWithAi, base::FEATURE_DISABLED_BY_DEFAULT); +// Turns on the GreenDev experimental UI. +BASE_FEATURE(kDevToolsGreenDevUi, base::FEATURE_DISABLED_BY_DEFAULT); + // Whether the global AI entrypoint is enabled. BASE_FEATURE(kDevToolsGlobalAiButton, base::FEATURE_ENABLED_BY_DEFAULT); // Whether the promotion animation is enabled.
diff --git a/chrome/browser/devtools/features.h b/chrome/browser/devtools/features.h index 81cd54a..fe82bc98 100644 --- a/chrome/browser/devtools/features.h +++ b/chrome/browser/devtools/features.h
@@ -97,6 +97,8 @@ BASE_DECLARE_FEATURE(kDevToolsAiSubmenuPrompts); BASE_DECLARE_FEATURE(kDevToolsAiDebugWithAi); +BASE_DECLARE_FEATURE(kDevToolsGreenDevUi); + BASE_DECLARE_FEATURE(kDevToolsGlobalAiButton); extern const base::FeatureParam<bool> kDevToolsGlobalAiButtonPromotionEnabled;
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc index cae67ea9..352c0563 100644 --- a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc +++ b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
@@ -1073,10 +1073,10 @@ } //////////////////////////////////////////////////////////////////////////////// -// AutofillPrivateGetAllEntityTypesFunction +// AutofillPrivateGetWritableEntityTypesFunction ExtensionFunction::ResponseAction -AutofillPrivateGetAllEntityTypesFunction::Run() { +AutofillPrivateGetWritableEntityTypesFunction::Run() { const auto all_types = autofill::DenseSet<EntityType>::all(); std::vector<autofill_private::EntityType> result; result.reserve(all_types.size()); @@ -1085,6 +1085,9 @@ autofill_client()->GetVariationConfigCountryCode())) { continue; } + if (entity_type.read_only()) { + continue; + } autofill_private::EntityType& api_type = result.emplace_back(); api_type.type_name = base::to_underlying(entity_type.name()); api_type.type_name_as_string = @@ -1097,7 +1100,7 @@ autofill_ai_util::GetDeleteEntityTypeStringForI18n(entity_type); } return RespondNow(ArgumentList( - autofill_private::GetAllEntityTypes::Results::Create(result))); + autofill_private::GetWritableEntityTypes::Results::Create(result))); } ////////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_private_api.h b/chrome/browser/extensions/api/autofill_private/autofill_private_api.h index c340eaf..3841f97 100644 --- a/chrome/browser/extensions/api/autofill_private/autofill_private_api.h +++ b/chrome/browser/extensions/api/autofill_private/autofill_private_api.h
@@ -514,19 +514,19 @@ ResponseAction Run() override; }; -class AutofillPrivateGetAllEntityTypesFunction +class AutofillPrivateGetWritableEntityTypesFunction : public AutofillPrivateExtensionFunction { public: - AutofillPrivateGetAllEntityTypesFunction() = default; - AutofillPrivateGetAllEntityTypesFunction( - const AutofillPrivateGetAllEntityTypesFunction&) = delete; - AutofillPrivateGetAllEntityTypesFunction& operator=( - const AutofillPrivateGetAllEntityTypesFunction&) = delete; - DECLARE_EXTENSION_FUNCTION("autofillPrivate.getAllEntityTypes", - AUTOFILLPRIVATE_GETALLENTITYTYPES) + AutofillPrivateGetWritableEntityTypesFunction() = default; + AutofillPrivateGetWritableEntityTypesFunction( + const AutofillPrivateGetWritableEntityTypesFunction&) = delete; + AutofillPrivateGetWritableEntityTypesFunction& operator=( + const AutofillPrivateGetWritableEntityTypesFunction&) = delete; + DECLARE_EXTENSION_FUNCTION("autofillPrivate.getWritableEntityTypes", + AUTOFILLPRIVATE_GETWRITABLEENTITYTYPES) protected: - ~AutofillPrivateGetAllEntityTypesFunction() override = default; + ~AutofillPrivateGetWritableEntityTypesFunction() override = default; // ExtensionFunction overrides. ResponseAction Run() override;
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_private_api_unittest.cc b/chrome/browser/extensions/api/autofill_private/autofill_private_api_unittest.cc index 5d0a8fb..21b9859 100644 --- a/chrome/browser/extensions/api/autofill_private/autofill_private_api_unittest.cc +++ b/chrome/browser/extensions/api/autofill_private/autofill_private_api_unittest.cc
@@ -187,7 +187,9 @@ public: AutofillPrivateApiUnitTest() { feature_list_.InitWithFeatures( - /*enabled_features=*/{autofill::features::kAutofillAiWithDataSchema}, + /*enabled_features=*/{autofill::features::kAutofillAiWithDataSchema, + autofill::features:: + kAutofillAiWalletFlightReservation}, /*disabled_features=*/ {autofill::features::kAutofillAiIgnoreLocale}); } @@ -403,7 +405,7 @@ ASSERT_TRUE(RunAutofillSubtest("loadEmptyEntityInstancesList")); ASSERT_TRUE(RunAutofillSubtest("testExpectedLabelsAreGenerated")); // Test that retrieving general entity type information works. - ASSERT_TRUE(RunAutofillSubtest("getAllEntityTypes")); + ASSERT_TRUE(RunAutofillSubtest("getWritableEntityTypes")); ASSERT_TRUE(RunAutofillSubtest("getAllAttributeTypesForEntityTypeName")); } @@ -464,4 +466,10 @@ EXPECT_TRUE(RunAutofillSubtest("verifyUserOptedOutOfAutofillAi")); } +IN_PROC_BROWSER_TEST_F(AutofillPrivateApiUnitTest, + GetAllWritableEntityTypes_DoesNotIncludeReadOnlyTypes) { + ASSERT_TRUE(RunAutofillSubtest( + "verifyWritableEntityTypesDoesNotIncludeReadOnlyTypes")); +} + } // namespace
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json index acb4fe78..75de816 100644 --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json
@@ -2341,6 +2341,11 @@ "expiry_milestone": 140 }, { + "name": "devtools-greendev-ui", + "owners": [ "alinavarkki@chromium.org", "finnur@chromium.org", "jacktfranklin@chromium.org" ], + "expiry_milestone": 250 + }, + { "name": "devtools-individual-request-throttling", "owners": [ "pfaffe@chromium.org" ], "expiry_milestone": 145 @@ -8806,14 +8811,9 @@ "expiry_milestone": 145 }, { - "name": "replace-sync-promos-with-sign-in-promos", - "owners": [ "myuu@google.com", "bsazonov@chromium.org", "chrome-signin-team@google.com" ], - "expiry_milestone": 137 - }, - { "name": "replace-sync-promos-with-sign-in-promos-desktop", "owners": [ "amelies@google.com", "chrome-signin-team@google.com" ], - "expiry_milestone": 145 + "expiry_milestone": 148 }, { "name": "report-notification-content-detection-data",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc index 8862ba9..ed550a6 100644 --- a/chrome/browser/flag_descriptions.cc +++ b/chrome/browser/flag_descriptions.cc
@@ -511,6 +511,10 @@ const char kDevToolsPrivacyUIDescription[] = "Enables the Privacy UI in the current 'Security' panel in DevTools."; +const char kDevToolsGreenDevUiName[] = "DevTools GreenDev UI"; +const char kDevToolsGreenDevUiDescription[] = + "Enables the experimental GreenDev UI in DevTools."; + #if !BUILDFLAG(IS_ANDROID) const char kDevToolsProjectSettingsName[] = "DevTools Project Settings"; const char kDevToolsProjectSettingsDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h index 09f9740..8c4a7fa 100644 --- a/chrome/browser/flag_descriptions.h +++ b/chrome/browser/flag_descriptions.h
@@ -331,6 +331,9 @@ extern const char kDevToolsPrivacyUIName[]; extern const char kDevToolsPrivacyUIDescription[]; +extern const char kDevToolsGreenDevUiName[]; +extern const char kDevToolsGreenDevUiDescription[]; + #if !BUILDFLAG(IS_ANDROID) extern const char kDevToolsProjectSettingsName[]; extern const char kDevToolsProjectSettingsDescription[];
diff --git a/chrome/browser/glic/host/context/glic_active_pinned_focused_tab_manager.cc b/chrome/browser/glic/host/context/glic_active_pinned_focused_tab_manager.cc index c6724aea..5865c51 100644 --- a/chrome/browser/glic/host/context/glic_active_pinned_focused_tab_manager.cc +++ b/chrome/browser/glic/host/context/glic_active_pinned_focused_tab_manager.cc
@@ -43,7 +43,6 @@ base::CallbackListSubscription GlicActivePinnedFocusedTabManager::AddFocusedTabDataChangedCallback( FocusedTabDataChangedCallback callback) { - // TODO(b:444463509): Implement focused tab data changed tracking. return focused_tab_data_changed_callback_list_.Add(std::move(callback)); } @@ -76,6 +75,18 @@ void GlicActivePinnedFocusedTabManager::OnActiveTabChanged( tabs::TabInterface* active_tab) { + // TODO(b:444463509): consider handling TabChangedAt() events. + active_tab_data_observer_ = std::make_unique<TabDataObserver>( + active_tab ? active_tab->GetContents() : nullptr, + base::BindRepeating( + &GlicActivePinnedFocusedTabManager::OnActiveTabDataChanged, + base::Unretained(this))); + + UpdateFocusedTab(); +} + +void GlicActivePinnedFocusedTabManager::OnActiveTabDataChanged( + TabDataChange change) { UpdateFocusedTab(); } @@ -90,7 +101,13 @@ } void GlicActivePinnedFocusedTabManager::UpdateFocusedTab() { - NotifyFocusedTabChanged(GetFocusedTabData()); + FocusedTabData focused_tab_data = GetFocusedTabData(); + NotifyFocusedTabChanged(focused_tab_data); + NotifyFocusedTabDataChanged( + CreateTabData(focused_tab_data.focus() + ? focused_tab_data.focus()->GetContents() + : nullptr) + .get()); } void GlicActivePinnedFocusedTabManager::NotifyFocusedTabChanged( @@ -98,4 +115,9 @@ focused_tab_changed_callback_list_.Notify(focused_tab); } +void GlicActivePinnedFocusedTabManager::NotifyFocusedTabDataChanged( + const glic::mojom::TabData* focused_tab_data) { + focused_tab_data_changed_callback_list_.Notify(focused_tab_data); +} + } // namespace glic
diff --git a/chrome/browser/glic/host/context/glic_active_pinned_focused_tab_manager.h b/chrome/browser/glic/host/context/glic_active_pinned_focused_tab_manager.h index c5e8ebd..2d71f4e 100644 --- a/chrome/browser/glic/host/context/glic_active_pinned_focused_tab_manager.h +++ b/chrome/browser/glic/host/context/glic_active_pinned_focused_tab_manager.h
@@ -7,6 +7,7 @@ #include "chrome/browser/glic/host/context/glic_focused_tab_manager_interface.h" #include "chrome/browser/glic/host/context/glic_sharing_utils.h" +#include "chrome/browser/glic/host/context/glic_tab_data.h" namespace glic { @@ -55,12 +56,19 @@ // Callback for tab pinning status changes. void OnTabPinningStatusChanged(tabs::TabInterface* tab, bool status); + // Callback for tab data changes to active tab. + void OnActiveTabDataChanged(TabDataChange change); + // Updates the currently focused tab and notifies subscribers when changed. void UpdateFocusedTab(); // Notifies subscribers of a change to the focused tab. void NotifyFocusedTabChanged(const FocusedTabData& focused_tab); + // Notifies subscribers of a change to the focused tab data. + void NotifyFocusedTabDataChanged( + const glic::mojom::TabData* focused_tab_data); + // Source of truth for pinned tabs. raw_ptr<GlicSharingManager> sharing_manager_; @@ -81,6 +89,9 @@ base::RepeatingCallbackList<void(const glic::mojom::TabData*)> focused_tab_data_changed_callback_list_; + // `TabDataObserver` for the active tab (if one exists). + std::unique_ptr<TabDataObserver> active_tab_data_observer_; + raw_ptr<Profile> profile_; };
diff --git a/chrome/browser/glic/host/glic_api_browsertest.cc b/chrome/browser/glic/host/glic_api_browsertest.cc index 60141cc..ced0132f 100644 --- a/chrome/browser/glic/host/glic_api_browsertest.cc +++ b/chrome/browser/glic/host/glic_api_browsertest.cc
@@ -202,6 +202,11 @@ NonInteractiveGlicApiTest::SetUpCommandLine(command_line); } + GURL page_url() { + return InProcessBrowserTest::embedded_test_server()->GetURL( + "/glic/browser_tests/test.html"); + } + protected: base::test::ScopedFeatureList features_; logging::ScopedVmoduleSwitches vmodule_switches_; @@ -229,11 +234,6 @@ GlicInstrumentMode::kHostAndContents)); } - GURL page_url() { - return InProcessBrowserTest::embedded_test_server()->GetURL( - "/glic/browser_tests/test.html"); - } - std::string GetDocumentIdForTab(ui::ElementIdentifier tab_id) { ui::TrackedElement* const element = ui::ElementTracker::GetElementTracker()->GetElementInAnyContext(tab_id); @@ -823,6 +823,60 @@ ExecuteJsTest(); } +IN_PROC_BROWSER_TEST_P(GlicApiTestWithOneTab, testGetPanelStateAttachedHidden) { + if (!GetParam().multi_instance) { + GTEST_SKIP() << "Attached only supported with multi-instance."; + } + ExecuteJsTest(); + + // Open and select a second tab. This should result in panel state hidden. + ASSERT_TRUE(AddTabAtIndex(1, GURL("about:blank"), ui::PAGE_TRANSITION_TYPED)); + browser()->tab_strip_model()->ActivateTabAt(1); + ContinueJsTest(); + + // Open the first tab again, it should send the attached state. + browser()->tab_strip_model()->ActivateTabAt(0); + ContinueJsTest(); +} + +IN_PROC_BROWSER_TEST_P(GlicApiTestWithOneTab, testDetachPanel) { + if (!GetParam().multi_instance) { + GTEST_SKIP() << "Attached only supported with multi-instance."; + } + ExecuteJsTest(); +} + +IN_PROC_BROWSER_TEST_P(GlicApiTest, testMultiplePanelsDetachedAndFloating) { + if (!GetParam().multi_instance) { + GTEST_SKIP() << "Attached only supported with multi-instance."; + } + // Open two tabs, select the first, open glic. + RunTestSequence(InstrumentTab(kFirstTab), + NavigateWebContents(kFirstTab, page_url())); + + ASSERT_TRUE(AddTabAtIndex(1, GURL("about:blank"), ui::PAGE_TRANSITION_TYPED)); + browser()->tab_strip_model()->ActivateTabAt(0); + ASSERT_EQ(0, browser()->tab_strip_model()->active_index()); + RunTestSequence(OpenGlicWindow(GlicWindowMode::kDetached, + GlicInstrumentMode::kHostAndContents)); + + // Execute test on the first tab instance. + ExecuteJsTest({.params = base::Value("first")}); + + // Select the second tab, open glic, and execute the test on the second + // instance. + SetGlicInstanceTabIndex(1); + browser()->tab_strip_model()->ActivateTabAt(1); + RunTestSequence(InstrumentTab(kSecondTab), + OpenGlicWindow(GlicWindowMode::kDetached, + GlicInstrumentMode::kHostAndContents)); + ExecuteJsTest({.params = base::Value("second")}); + + // Continue on the first tab. + SetGlicInstanceTabIndex(0); + ContinueJsTest(); +} + IN_PROC_BROWSER_TEST_P(GlicApiTestWithOneTab, testClosePanel) { ExecuteJsTest(); RunTestSequence(WaitForHide(kGlicViewElementId));
diff --git a/chrome/browser/glic/service/glic_instance_coordinator_impl.cc b/chrome/browser/glic/service/glic_instance_coordinator_impl.cc index cb80b87..b7d4190 100644 --- a/chrome/browser/glic/service/glic_instance_coordinator_impl.cc +++ b/chrome/browser/glic/service/glic_instance_coordinator_impl.cc
@@ -304,22 +304,6 @@ NOTIMPLEMENTED(); } -std::unique_ptr<views::View> -GlicInstanceCoordinatorImpl::CreateViewForSidePanel(tabs::TabInterface& tab) { - // This method is only used by single instance (when the GlicMultiTab feature - // is disabled). This implementation is never called. - NOTIMPLEMENTED(); - return std::make_unique<views::View>(); -} - -void GlicInstanceCoordinatorImpl::SidePanelShown( - BrowserWindowInterface* browser) { - // This is a related to the legacy side panel behavior that - // GlicWindowController implements, and will not be called when - // kGlicMultiInstance is enabled. - NOTREACHED(); -} - base::CallbackListSubscription GlicInstanceCoordinatorImpl::RegisterLastActiveInstanceChangedCallback( LastActiveInstanceChangedCallback callback) {
diff --git a/chrome/browser/glic/service/glic_instance_coordinator_impl.h b/chrome/browser/glic/service/glic_instance_coordinator_impl.h index d992b98..6661f15 100644 --- a/chrome/browser/glic/service/glic_instance_coordinator_impl.h +++ b/chrome/browser/glic/service/glic_instance_coordinator_impl.h
@@ -106,9 +106,6 @@ gfx::Rect GetInitialBounds(Browser* browser) override; void ShowDetachedForTesting() override; void SetPreviousPositionForTesting(gfx::Point position) override; - std::unique_ptr<views::View> CreateViewForSidePanel( - tabs::TabInterface& tab) override; - void SidePanelShown(BrowserWindowInterface* browser) override; base::CallbackListSubscription RegisterLastActiveInstanceChangedCallback( LastActiveInstanceChangedCallback callback) override;
diff --git a/chrome/browser/glic/service/glic_instance_impl.cc b/chrome/browser/glic/service/glic_instance_impl.cc index 65012d89..4060b4cb9e 100644 --- a/chrome/browser/glic/service/glic_instance_impl.cc +++ b/chrome/browser/glic/service/glic_instance_impl.cc
@@ -601,15 +601,10 @@ return new_entry; } -void GlicInstanceImpl::WillCloseFor(tabs::TabInterface* tab) { - auto key = tab ? CreateSidePanelEmbedderKey(tab) : FloatingEmbedderKey{}; +void GlicInstanceImpl::WillCloseFor(EmbedderKey key) { MaybeDeactivateEmbedderAndCloseHostUi(key); } -void GlicInstanceImpl::Attach(tabs::TabInterface* tab) { - Show(SidePanelShowOptions(*tab)); -} - void GlicInstanceImpl::WebUiStateChanged(mojom::WebUiState state) { if (state == mojom::WebUiState::kReady) { if (auto* embedder = GetActiveEmbedder()) {
diff --git a/chrome/browser/glic/service/glic_instance_impl.h b/chrome/browser/glic/service/glic_instance_impl.h index abf3c3a..35a4c8d 100644 --- a/chrome/browser/glic/service/glic_instance_impl.h +++ b/chrome/browser/glic/service/glic_instance_impl.h
@@ -84,7 +84,7 @@ gfx::Size GetPanelSize() override; // These methods should only be called by the GlicInstanceCoordinator. - void Show(const ShowOptions& options); + void Show(const ShowOptions& options) override; void Close(EmbedderKey key); void Toggle(const ShowOptions& options, bool prevent_close); @@ -141,8 +141,7 @@ const ShowOptions& options, glic::mojom::ConversationInfoPtr info, mojom::WebClientHandler::SwitchConversationCallback callback) override; - void WillCloseFor(tabs::TabInterface* tab) override; - void Attach(tabs::TabInterface* tab) override; + void WillCloseFor(EmbedderKey key) override; void NotifyPanelStateChanged() override; // Opens the floating UI for this instance void Detach(tabs::TabInterface* tab) override;
diff --git a/chrome/browser/glic/service/glic_ui_embedder.h b/chrome/browser/glic/service/glic_ui_embedder.h index b9c5d617..5652009 100644 --- a/chrome/browser/glic/service/glic_ui_embedder.h +++ b/chrome/browser/glic/service/glic_ui_embedder.h
@@ -27,9 +27,9 @@ const ShowOptions& options, glic::mojom::ConversationInfoPtr info, mojom::WebClientHandler::SwitchConversationCallback callback) = 0; - virtual void WillCloseFor(tabs::TabInterface* tab) = 0; + virtual void WillCloseFor(EmbedderKey key) = 0; virtual Host& host() = 0; - virtual void Attach(tabs::TabInterface* tab) = 0; + virtual void Show(const ShowOptions& options) = 0; virtual void Detach(tabs::TabInterface* tab) = 0; // Called after the value of GetPanelState() changes. virtual void NotifyPanelStateChanged() = 0;
diff --git a/chrome/browser/glic/test_support/glic_api_test.h b/chrome/browser/glic/test_support/glic_api_test.h index a7286de0..1d9120f 100644 --- a/chrome/browser/glic/test_support/glic_api_test.h +++ b/chrome/browser/glic/test_support/glic_api_test.h
@@ -14,6 +14,7 @@ #include "chrome/browser/glic/test_support/interactive_glic_test.h" #include "chrome/browser/glic/test_support/non_interactive_glic_test.h" #include "chrome/test/interaction/interactive_browser_test.h" +#include "content/public/browser/global_routing_id.h" #include "content/public/test/browser_test_utils.h" #include "net/dns/mock_host_resolver.h" #include "testing/gtest/include/gtest/gtest.h" @@ -176,7 +177,7 @@ } void TearDownOnMainThread() override { - if (next_step_required_) { + if (!next_step_required_.empty()) { FAIL() << "Test not finished: call ContinueJsTest()"; } NonInteractiveGlicTest::TearDownOnMainThread(); @@ -204,7 +205,7 @@ ASSERT_TRUE(glic_guest_frame); std::string param_json = base::WriteJson(options.params).value_or(""); ProcessTestResult( - options, + glic_guest_frame->GetGlobalId(), options, content::EvalJs( glic_guest_frame, base::StrCat( @@ -217,13 +218,13 @@ // Continues test execution if `advanceToNextStep()` was used to return // control to C++. void ContinueJsTest(ExecuteTestOptions options = {}) { - ASSERT_TRUE(next_step_required_); content::RenderFrameHost* glic_guest_frame = T::FindGlicGuestMainFrame(); - next_step_required_ = false; ASSERT_TRUE(glic_guest_frame); + ASSERT_TRUE(next_step_required_.contains(glic_guest_frame->GetGlobalId())); + next_step_required_.erase(glic_guest_frame->GetGlobalId()); std::string param_json = base::WriteJson(options.params).value_or(""); ProcessTestResult( - options, + glic_guest_frame->GetGlobalId(), options, content::EvalJs(glic_guest_frame, base::StrCat({"continueApiTest(", param_json, ")"}))); } @@ -288,7 +289,8 @@ return result; } - void ProcessTestResult(const ExecuteTestOptions& options, + void ProcessTestResult(content::GlobalRenderFrameHostId frame_id, + const ExecuteTestOptions& options, const content::EvalJsResult& result) { if (options.expect_guest_frame_destroyed) { ASSERT_THAT(result, content::EvalJsResult::ErrorIs( @@ -303,7 +305,7 @@ if (id && id->is_string() && id->GetString() == "next-step") { step_data_ = dict.Find("payload")->Clone(); } - next_step_required_ = true; + next_step_required_.insert(frame_id); return; } if (!options.should_fail) { @@ -370,7 +372,7 @@ } std::vector<net::test_server::HttpRequest> embedded_test_server_requests_; - bool next_step_required_ = false; + std::set<content::GlobalRenderFrameHostId> next_step_required_; std::optional<base::Value> step_data_; base::test::ScopedFeatureList features_; };
diff --git a/chrome/browser/glic/test_support/interactive_glic_test.h b/chrome/browser/glic/test_support/interactive_glic_test.h index 1272a68..610643d4 100644 --- a/chrome/browser/glic/test_support/interactive_glic_test.h +++ b/chrome/browser/glic/test_support/interactive_glic_test.h
@@ -596,7 +596,8 @@ // make this behavior more obvious somehow. return glic_service()->GetInstanceForTab( - browser()->tab_strip_model()->GetTabAtIndex(0)); + browser()->tab_strip_model()->GetTabAtIndex( + glic_instance_tab_index_)); } return glic_service()->GetInstanceForActiveTab(browser()); } @@ -717,6 +718,11 @@ // instead of always operating on the instance in tab 0. void TrackGlicInstanceById(InstanceId id) { tracked_instance_id_ = id; } + // Sets the tab index to use when fetching the glic instance. Tests can use + // this to swap between interacting with multiple instances. This affects + // many functions in this fixture. + void SetGlicInstanceTabIndex(int index) { glic_instance_tab_index_ = index; } + private: // Because of limitations in the template system, calls to base class methods // that are guaranteed by the `requires` clause must still be scoped. These @@ -725,6 +731,9 @@ using Test = InProcessBrowserTest; std::optional<InstanceId> tracked_instance_id_; + // Which tab index to use when getting the glic instance. This affects many + // functions in this fixture. + int glic_instance_tab_index_ = 0; base::WeakPtr<Browser> active_browser_; glic::GlicTestEnvironment glic_test_environment_; net::test_server::EmbeddedTestServerHandle test_server_handle_;
diff --git a/chrome/browser/glic/widget/glic_floating_ui.cc b/chrome/browser/glic/widget/glic_floating_ui.cc index ed988bf..3bf6ea5 100644 --- a/chrome/browser/glic/widget/glic_floating_ui.cc +++ b/chrome/browser/glic/widget/glic_floating_ui.cc
@@ -135,7 +135,7 @@ void GlicFloatingUi::Close() { glic_window_animator_.reset(); glic_widget_.reset(); - delegate_->WillCloseFor(/*tab=*/nullptr); + delegate_->WillCloseFor(FloatingEmbedderKey{}); } void GlicFloatingUi::ClosePanel() {
diff --git a/chrome/browser/glic/widget/glic_inactive_side_panel_ui.cc b/chrome/browser/glic/widget/glic_inactive_side_panel_ui.cc index f2d55164..ab768c9b 100644 --- a/chrome/browser/glic/widget/glic_inactive_side_panel_ui.cc +++ b/chrome/browser/glic/widget/glic_inactive_side_panel_ui.cc
@@ -73,7 +73,7 @@ // tab. void GlicInactiveSidePanelUi::OnViewFocused(views::View* observed_view) { if (tab_) { - delegate_->Attach(tab_.get()); + delegate_->Show(SidePanelShowOptions(*tab_)); } }
diff --git a/chrome/browser/glic/widget/glic_side_panel_ui.cc b/chrome/browser/glic/widget/glic_side_panel_ui.cc index 0baa5f4..20e7ce4 100644 --- a/chrome/browser/glic/widget/glic_side_panel_ui.cc +++ b/chrome/browser/glic/widget/glic_side_panel_ui.cc
@@ -87,8 +87,7 @@ } void GlicSidePanelUi::Attach() { - // The Side Panel Ui is already attached. - NOTREACHED(); + // The Side Panel Ui is already attached, do nothing. } void GlicSidePanelUi::Detach() {
diff --git a/chrome/browser/glic/widget/glic_window_controller.h b/chrome/browser/glic/widget/glic_window_controller.h index 680db04..4329b92 100644 --- a/chrome/browser/glic/widget/glic_window_controller.h +++ b/chrome/browser/glic/widget/glic_window_controller.h
@@ -160,10 +160,6 @@ virtual void ShowDetachedForTesting() = 0; virtual void SetPreviousPositionForTesting(gfx::Point position) = 0; - virtual std::unique_ptr<views::View> CreateViewForSidePanel( - tabs::TabInterface& tab) = 0; - - virtual void SidePanelShown(BrowserWindowInterface* browser) = 0; // TODO: Move to GlicInstanceCoordinator. using LastActiveInstanceChangedCallback = @@ -195,6 +191,10 @@ // Returns whether or not the glic web contents are loaded (this can also be // true if `IsActive()` (i.e., if the contents are loaded in the glic window). virtual bool IsWarmed() const = 0; + + virtual void SidePanelShown(BrowserWindowInterface* browser) = 0; + virtual std::unique_ptr<views::View> CreateViewForSidePanel( + tabs::TabInterface& tab) = 0; }; } // namespace glic
diff --git a/chrome/browser/icon_loader_win.cc b/chrome/browser/icon_loader_win.cc index 47bc2e3..649c5cf 100644 --- a/chrome/browser/icon_loader_win.cc +++ b/chrome/browser/icon_loader_win.cc
@@ -24,8 +24,8 @@ #include "content/public/browser/browser_thread.h" #include "ui/display/win/dpi.h" #include "ui/gfx/geometry/size.h" -#include "ui/gfx/icon_util.h" #include "ui/gfx/image/image_skia.h" +#include "ui/gfx/win/icon_util.h" namespace { // Helper class to manage lifetime of icon reader service.
diff --git a/chrome/browser/media/webrtc/window_icon_util_win.cc b/chrome/browser/media/webrtc/window_icon_util_win.cc index 2dc34ea55..23fdcd0 100644 --- a/chrome/browser/media/webrtc/window_icon_util_win.cc +++ b/chrome/browser/media/webrtc/window_icon_util_win.cc
@@ -7,7 +7,7 @@ #include <windows.h> #include "third_party/skia/include/core/SkBitmap.h" -#include "ui/gfx/icon_util.h" +#include "ui/gfx/win/icon_util.h" gfx::ImageSkia GetWindowIcon(content::DesktopMediaID id) { DCHECK(id.type == content::DesktopMediaID::TYPE_WINDOW);
diff --git a/chrome/browser/policy/policy_network_browsertest.cc b/chrome/browser/policy/policy_network_browsertest.cc index e74b4cb..5437c20 100644 --- a/chrome/browser/policy/policy_network_browsertest.cc +++ b/chrome/browser/policy/policy_network_browsertest.cc
@@ -252,7 +252,7 @@ IN_PROC_BROWSER_TEST_F(SSLPolicyTest, PreferSlowKexAlgorithmsPolicy) { net::SSLServerConfig ssl_config; - ssl_config.curves_for_testing = {NID_MLKEM1024}; + ssl_config.curves_for_testing = {NID_ML_KEM_1024}; ASSERT_TRUE(StartTestServer(ssl_config)); // Should fail to load a page from the test server because, by default, we @@ -293,7 +293,7 @@ IN_PROC_BROWSER_TEST_F(SSLPolicyTest, PostQuantumDisabledOverridesPreferSlowKexAlgorithms) { net::SSLServerConfig ssl_config; - ssl_config.curves_for_testing = {NID_MLKEM1024}; + ssl_config.curves_for_testing = {NID_ML_KEM_1024}; ASSERT_TRUE(StartTestServer(ssl_config)); PolicyMap policies;
diff --git a/chrome/browser/prefs/BUILD.gn b/chrome/browser/prefs/BUILD.gn index 552c779..78f41ce 100644 --- a/chrome/browser/prefs/BUILD.gn +++ b/chrome/browser/prefs/BUILD.gn
@@ -98,7 +98,7 @@ "//components/browsing_data/core", "//components/certificate_transparency", "//components/collaboration/public:prefs", - "//components/commerce/core:pref_names", + "//components/commerce/core:prefs", "//components/content_settings/core/browser", "//components/custom_handlers", "//components/domain_reliability:prefs",
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc index 20aabc66..9e3a8412 100644 --- a/chrome/browser/prefs/browser_prefs.cc +++ b/chrome/browser/prefs/browser_prefs.cc
@@ -107,7 +107,7 @@ #include "components/browsing_data/core/pref_names.h" #include "components/certificate_transparency/pref_names.h" #include "components/collaboration/public/pref_names.h" -#include "components/commerce/core/pref_names.h" +#include "components/commerce/core/prefs.h" #include "components/content_settings/core/browser/host_content_settings_map.h" #include "components/content_settings/core/common/pref_names.h" #include "components/custom_handlers/protocol_handler_registry.h" @@ -2050,7 +2050,7 @@ chrome_browser_net::NetErrorTabHelper::RegisterProfilePrefs(registry); chrome_prefs::RegisterProfilePrefs(registry); collaboration::prefs::RegisterProfilePrefs(registry); - commerce::RegisterPrefs(registry); + commerce::RegisterProfilePrefs(registry); cross_device::RegisterProfilePrefs(registry); enterprise::RegisterIdentifiersProfilePrefs(registry); enterprise_connectors::RegisterProfilePrefs(registry); @@ -2487,6 +2487,8 @@ registry->RegisterBooleanPref( ntp_tiles::prefs::kTabResumptionHomeModuleEnabled, true); + + registry->RegisterBooleanPref(ntp_tiles::prefs::kTipsHomeModuleEnabled, true); } void RegisterUserProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
diff --git a/chrome/browser/profiles/BUILD.gn b/chrome/browser/profiles/BUILD.gn index 476b24fb..a01cdcad 100644 --- a/chrome/browser/profiles/BUILD.gn +++ b/chrome/browser/profiles/BUILD.gn
@@ -243,6 +243,7 @@ "//chrome/browser/bluetooth", "//chrome/browser/breadcrumbs", "//chrome/browser/btm", + "//chrome/browser/client_hints", "//chrome/browser/commerce", "//chrome/browser/content_settings:content_settings_factory", "//chrome/browser/contextual_tasks",
diff --git a/chrome/browser/profiles/profile_avatar_icon_util.cc b/chrome/browser/profiles/profile_avatar_icon_util.cc index 742ad491..d25d86a 100644 --- a/chrome/browser/profiles/profile_avatar_icon_util.cc +++ b/chrome/browser/profiles/profile_avatar_icon_util.cc
@@ -70,7 +70,7 @@ #include "base/win/windows_version.h" #include "chrome/browser/profiles/profile_attributes_entry.h" #include "chrome/grit/chrome_unscaled_resources.h" // nogncheck crbug.com/1125897 -#include "ui/gfx/icon_util.h" // For Iconutil::kLargeIconSize. +#include "ui/gfx/win/icon_util.h" // For Iconutil::kLargeIconSize. #endif #if BUILDFLAG(IS_MAC)
diff --git a/chrome/browser/profiles/profile_shortcut_manager_win.cc b/chrome/browser/profiles/profile_shortcut_manager_win.cc index dccd337..f7f60c4 100644 --- a/chrome/browser/profiles/profile_shortcut_manager_win.cc +++ b/chrome/browser/profiles/profile_shortcut_manager_win.cc
@@ -53,9 +53,9 @@ #include "third_party/skia/include/core/SkRRect.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" -#include "ui/gfx/icon_util.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_family.h" +#include "ui/gfx/win/icon_util.h" using content::BrowserThread;
diff --git a/chrome/browser/quick_delete/android/java/src/org/chromium/chrome/browser/quick_delete/QuickDeleteBridge.java b/chrome/browser/quick_delete/android/java/src/org/chromium/chrome/browser/quick_delete/QuickDeleteBridge.java index eb32cfd..2281fc3 100644 --- a/chrome/browser/quick_delete/android/java/src/org/chromium/chrome/browser/quick_delete/QuickDeleteBridge.java +++ b/chrome/browser/quick_delete/android/java/src/org/chromium/chrome/browser/quick_delete/QuickDeleteBridge.java
@@ -13,11 +13,18 @@ import org.chromium.chrome.browser.browsing_data.TimePeriod; import org.chromium.chrome.browser.profiles.Profile; -/** The JNI bridge for Quick Delete on Android to fetch browsing history data. */ +/** + * The JNI bridge for Quick Delete on Android to fetch browsing history data. + * TODO(crbug.com/406230204): Update bridge name to more accurately reflect it's purpose, which is + * to fetch History count. + */ @NullMarked class QuickDeleteBridge { private long mNativeQuickDeleteBridge; + /** Called when the lastVisitedDomain and domainCount are fetched. */ + private final DomainVisitsCallback mCallback; + /** Interface for a class that is fetching visited domains information. */ public interface DomainVisitsCallback { /** @@ -36,9 +43,11 @@ * Creates a {@link QuickDeleteBridge} for accessing browsing history data for the current user. * * @param profile {@link Profile} The profile for which to fetch the browsing history. + * @param callback The callback to call with the last visited domain and domain count. */ - public QuickDeleteBridge(Profile profile) { - mNativeQuickDeleteBridge = QuickDeleteBridgeJni.get().init(profile); + public QuickDeleteBridge(Profile profile, DomainVisitsCallback callback) { + mNativeQuickDeleteBridge = QuickDeleteBridgeJni.get().init(this, profile); + mCallback = callback; } /** Destroys this instance so no further calls can be executed. */ @@ -50,32 +59,27 @@ } /** - * Gets the synced last visited domain and unique domain count on all devices within the time - * period. + * Restarts the HistoryCounter with the selected time period. * * @param timePeriod The time period to fetch the results for. - * @param callback The callback to call with the last visited domain and domain count. */ - public void getLastVisitedDomainAndUniqueDomainCount( - @TimePeriod int timePeriod, DomainVisitsCallback callback) { + public void restartCounterForTimePeriod(@TimePeriod int timePeriod) { QuickDeleteBridgeJni.get() - .getLastVisitedDomainAndUniqueDomainCount( - mNativeQuickDeleteBridge, timePeriod, callback); + .restartCounterForTimePeriod(mNativeQuickDeleteBridge, timePeriod); } @CalledByNative - private static void onLastVisitedDomainAndUniqueDomainCountReady( - DomainVisitsCallback callback, String lastVisitedDomain, int domainCount) { - callback.onLastVisitedDomainAndUniqueDomainCountReady(lastVisitedDomain, domainCount); + private void onLastVisitedDomainAndUniqueDomainCountReady( + String lastVisitedDomain, int domainCount) { + mCallback.onLastVisitedDomainAndUniqueDomainCountReady(lastVisitedDomain, domainCount); } @NativeMethods interface Natives { - long init(@JniType("Profile*") @Nullable Profile profile); + long init(QuickDeleteBridge self, @JniType("Profile*") @Nullable Profile profile); void destroy(long nativeQuickDeleteBridge); - void getLastVisitedDomainAndUniqueDomainCount( - long nativeQuickDeleteBridge, int timePeriod, DomainVisitsCallback callback); + void restartCounterForTimePeriod(long nativeQuickDeleteBridge, int timePeriod); } }
diff --git a/chrome/browser/quick_delete/android/java/src/org/chromium/chrome/browser/quick_delete/QuickDeleteController.java b/chrome/browser/quick_delete/android/java/src/org/chromium/chrome/browser/quick_delete/QuickDeleteController.java index 01cd7a3..5137552 100644 --- a/chrome/browser/quick_delete/android/java/src/org/chromium/chrome/browser/quick_delete/QuickDeleteController.java +++ b/chrome/browser/quick_delete/android/java/src/org/chromium/chrome/browser/quick_delete/QuickDeleteController.java
@@ -52,7 +52,6 @@ private final LayoutManager mLayoutManager; private final Profile mProfile; private final TabModel mTabModel; - private final QuickDeleteBridge mQuickDeleteBridge; private final QuickDeleteMediator mQuickDeleteMediator; private final PropertyModel mPropertyModel; private final PropertyModelChangeProcessor mPropertyModelChangeProcessor; @@ -100,7 +99,6 @@ mDeleteArchivedTabsFilter = null; } mProfile = assumeNonNull(tabModelSelector.getCurrentModel().getProfile()); - mQuickDeleteBridge = new QuickDeleteBridge(mProfile); // MVC setup. View quickDeleteView = @@ -119,7 +117,6 @@ new QuickDeleteMediator( mPropertyModel, mProfile, - mQuickDeleteBridge, mDeleteRegularTabsFilter, mDeleteArchivedTabsFilter); @@ -135,7 +132,7 @@ void destroy() { mPropertyModelChangeProcessor.destroy(); - mQuickDeleteBridge.destroy(); + mQuickDeleteMediator.destroy(); } /**
diff --git a/chrome/browser/quick_delete/android/java/src/org/chromium/chrome/browser/quick_delete/QuickDeleteMediator.java b/chrome/browser/quick_delete/android/java/src/org/chromium/chrome/browser/quick_delete/QuickDeleteMediator.java index 70458890..85613fc 100644 --- a/chrome/browser/quick_delete/android/java/src/org/chromium/chrome/browser/quick_delete/QuickDeleteMediator.java +++ b/chrome/browser/quick_delete/android/java/src/org/chromium/chrome/browser/quick_delete/QuickDeleteMediator.java
@@ -28,20 +28,19 @@ /** * @param propertyModel {@link PropertyModel} associated with the quick delete {@link View}. * @param profile {@link Profile} to check if the user is signed-in or syncing. - * @param quickDeleteBridge {@link QuickDeleteBridge} used to fetch the recent visited domain - * and site data. - * @param quickDeleteTabsFilter {@link QuickDeleteTabsFilter} used to fetch the tabs to be - * closed data. + * @param quickDeleteRegularTabsFilter {@link QuickDeleteTabsFilter} used to fetch the regular + * tabs to be closed. + * @param quickDeleteArchivedTabsFilter {@link QuickDeleteTabsFilter} used to fetch the archived + * tabs to be closed. */ QuickDeleteMediator( PropertyModel propertyModel, Profile profile, - QuickDeleteBridge quickDeleteBridge, QuickDeleteTabsFilter quickDeleteRegularTabsFilter, @Nullable QuickDeleteTabsFilter quickDeleteArchivedTabsFilter) { mPropertyModel = propertyModel; mProfile = profile; - mQuickDeleteBridge = quickDeleteBridge; + mQuickDeleteBridge = new QuickDeleteBridge(mProfile, this); mQuickDeleteRegularTabsFilter = quickDeleteRegularTabsFilter; mQuickDeleteArchivedTabsFilter = quickDeleteArchivedTabsFilter; } @@ -74,7 +73,7 @@ mPropertyModel.set(QuickDeleteProperties.IS_SYNCING_HISTORY, false); mPropertyModel.set(QuickDeleteProperties.IS_DOMAIN_VISITED_DATA_PENDING, true); // This is an async call which would update the browsing history row. - mQuickDeleteBridge.getLastVisitedDomainAndUniqueDomainCount(timePeriod, this); + mQuickDeleteBridge.restartCounterForTimePeriod(timePeriod); } /** @@ -104,4 +103,8 @@ } return count; } + + public void destroy() { + mQuickDeleteBridge.destroy(); + } }
diff --git a/chrome/browser/quick_delete/android/javatests/src/org/chromium/chrome/browser/quick_delete/QuickDeleteBridgeTest.java b/chrome/browser/quick_delete/android/javatests/src/org/chromium/chrome/browser/quick_delete/QuickDeleteBridgeTest.java index 3f5ae3c..a924be6 100644 --- a/chrome/browser/quick_delete/android/javatests/src/org/chromium/chrome/browser/quick_delete/QuickDeleteBridgeTest.java +++ b/chrome/browser/quick_delete/android/javatests/src/org/chromium/chrome/browser/quick_delete/QuickDeleteBridgeTest.java
@@ -51,6 +51,7 @@ private WebPageStation mPage; private QuickDeleteBridge mQuickDeleteBridge; + private DomainVisitsCallback mDomainVisitsCallback; private static class DomainVisitsCallback implements QuickDeleteBridge.DomainVisitsCallback { private final CallbackHelper mCallbackHelper = new CallbackHelper(); @@ -69,11 +70,12 @@ @Before public void setUp() throws ExecutionException { mPage = mActivityTestRule.startOnBlankPage(); + mDomainVisitsCallback = new DomainVisitsCallback(); ThreadUtils.runOnUiThreadBlocking( () -> { Profile profile = mActivityTestRule.getActivity().getCurrentTabModel().getProfile(); - mQuickDeleteBridge = new QuickDeleteBridge(profile); + mQuickDeleteBridge = new QuickDeleteBridge(profile, mDomainVisitsCallback); }); } @@ -100,35 +102,28 @@ @Test @MediumTest - public void testLastVisitedDomainAndUniqueDomains_WhenNoVisits() throws TimeoutException { - DomainVisitsCallback callback = new DomainVisitsCallback(); + public void testRestartCounterForTimePeriod_WhenNoVisits() throws TimeoutException { ThreadUtils.runOnUiThreadBlocking( - () -> - mQuickDeleteBridge.getLastVisitedDomainAndUniqueDomainCount( - TimePeriod.LAST_15_MINUTES, callback)); + () -> mQuickDeleteBridge.restartCounterForTimePeriod(TimePeriod.LAST_15_MINUTES)); - callback.mCallbackHelper.waitForCallback(0); + mDomainVisitsCallback.mCallbackHelper.waitForCallback(0); - assertEquals("", callback.mLastVisitedDomain); - assertEquals(0, callback.mDomainCount); + assertEquals("", mDomainVisitsCallback.mLastVisitedDomain); + assertEquals(0, mDomainVisitsCallback.mDomainCount); } @Test @MediumTest @Restriction(Restriction.RESTRICTION_TYPE_INTERNET) - public void testLastVisitedDomainAndUniqueDomains_WhenVisitsExistInRange() - throws TimeoutException { + public void testRestartCounterForTimePeriod_WhenVisitsExistInRange() throws TimeoutException { visitUrls(); - DomainVisitsCallback callback = new DomainVisitsCallback(); ThreadUtils.runOnUiThreadBlocking( - () -> - mQuickDeleteBridge.getLastVisitedDomainAndUniqueDomainCount( - TimePeriod.LAST_15_MINUTES, callback)); + () -> mQuickDeleteBridge.restartCounterForTimePeriod(TimePeriod.LAST_15_MINUTES)); - callback.mCallbackHelper.waitForCallback(0); + mDomainVisitsCallback.mCallbackHelper.waitForCallback(0); - assertEquals("google.com", callback.mLastVisitedDomain); - assertEquals(2, callback.mDomainCount); + assertEquals("google.com", mDomainVisitsCallback.mLastVisitedDomain); + assertEquals(2, mDomainVisitsCallback.mDomainCount); } }
diff --git a/chrome/browser/quick_delete/android/junit/src/org/chromium/chrome/browser/quick_delete/QuickDeleteMediatorTest.java b/chrome/browser/quick_delete/android/junit/src/org/chromium/chrome/browser/quick_delete/QuickDeleteMediatorTest.java index d04803a..5ac8972 100644 --- a/chrome/browser/quick_delete/android/junit/src/org/chromium/chrome/browser/quick_delete/QuickDeleteMediatorTest.java +++ b/chrome/browser/quick_delete/android/junit/src/org/chromium/chrome/browser/quick_delete/QuickDeleteMediatorTest.java
@@ -7,8 +7,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -49,7 +49,7 @@ @Mock private IdentityServicesProvider mIdentityServicesProviderMock; @Mock private Profile mProfileMock; - @Mock private QuickDeleteBridge mQuickDeleteBridgeMock; + @Mock private QuickDeleteBridge.Natives mQuickDeleteBridgeNatives; @Mock private QuickDeleteTabsFilter mQuickDeleteTabsFilterMock; @Mock private QuickDeleteTabsFilter mQuickDeleteArchivedTabsFilterMock; @Mock private List<Tab> mTabsListMock; @@ -63,6 +63,7 @@ when(mIdentityServicesProviderMock.getIdentityManager(mProfileMock)) .thenReturn(mIdentityManagerMock); IdentityServicesProvider.setInstanceForTests(mIdentityServicesProviderMock); + QuickDeleteBridgeJni.setInstanceForTesting(mQuickDeleteBridgeNatives); mPropertyModel = new PropertyModel.Builder(QuickDeleteProperties.ALL_KEYS).build(); } @@ -85,11 +86,7 @@ mQuickDeleteMediator = new QuickDeleteMediator( - mPropertyModel, - mProfileMock, - mQuickDeleteBridgeMock, - mQuickDeleteTabsFilterMock, - null); + mPropertyModel, mProfileMock, mQuickDeleteTabsFilterMock, null); mQuickDeleteMediator.onTimePeriodChanged(TimePeriod.LAST_15_MINUTES); assertTrue(mPropertyModel.get(QuickDeleteProperties.IS_SIGNED_IN)); @@ -98,8 +95,8 @@ TimePeriod.LAST_15_MINUTES, mPropertyModel.get(QuickDeleteProperties.TIME_PERIOD)); assertTrue(mPropertyModel.get(QuickDeleteProperties.IS_DOMAIN_VISITED_DATA_PENDING)); assertFalse(mPropertyModel.get(QuickDeleteProperties.IS_SYNCING_HISTORY)); - verify(mQuickDeleteBridgeMock) - .getLastVisitedDomainAndUniqueDomainCount(eq(TimePeriod.LAST_15_MINUTES), any()); + verify(mQuickDeleteBridgeNatives) + .restartCounterForTimePeriod(anyLong(), eq(TimePeriod.LAST_15_MINUTES)); } @Test @@ -124,7 +121,6 @@ new QuickDeleteMediator( mPropertyModel, mProfileMock, - mQuickDeleteBridgeMock, mQuickDeleteTabsFilterMock, mQuickDeleteArchivedTabsFilterMock); mQuickDeleteMediator.onTimePeriodChanged(TimePeriod.LAST_15_MINUTES); @@ -135,7 +131,7 @@ TimePeriod.LAST_15_MINUTES, mPropertyModel.get(QuickDeleteProperties.TIME_PERIOD)); assertTrue(mPropertyModel.get(QuickDeleteProperties.IS_DOMAIN_VISITED_DATA_PENDING)); assertFalse(mPropertyModel.get(QuickDeleteProperties.IS_SYNCING_HISTORY)); - verify(mQuickDeleteBridgeMock) - .getLastVisitedDomainAndUniqueDomainCount(eq(TimePeriod.LAST_15_MINUTES), any()); + verify(mQuickDeleteBridgeNatives) + .restartCounterForTimePeriod(anyLong(), eq(TimePeriod.LAST_15_MINUTES)); } }
diff --git a/chrome/browser/resources/chromeos/accessibility/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/BUILD.gn index 50c28c2f..1330021 100644 --- a/chrome/browser/resources/chromeos/accessibility/BUILD.gn +++ b/chrome/browser/resources/chromeos/accessibility/BUILD.gn
@@ -97,7 +97,6 @@ "chromevox/mv3:browser_tests", "common:browser_tests", "enhanced_network_tts/mv3:browser_tests", - "select_to_speak/mv2:browser_tests", "select_to_speak/mv3:browser_tests", "switch_access/mv2:browser_tests", "switch_access/mv3:browser_tests",
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/mv2/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/accessibility_common/mv2/BUILD.gn index b81404bb..f00edb3 100644 --- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/mv2/BUILD.gn +++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/mv2/BUILD.gn
@@ -262,7 +262,6 @@ "dictation/macros/dictation_macros_test.js", "dictation/parse/dictation_parse_test.js", "dictation/parse/dictation_pumpkin_parse_test.js", - "magnifier/magnifier_test.js", ] gen_include_files = [
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/mv2/magnifier/magnifier_test.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/mv2/magnifier/magnifier_test.js deleted file mode 100644 index 39be491d..0000000 --- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/mv2/magnifier/magnifier_test.js +++ /dev/null
@@ -1,510 +0,0 @@ -// Copyright 2020 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -GEN_INCLUDE(['../../../common/testing/e2e_test_base.js']); -GEN_INCLUDE(['../../../common/testing/mock_accessibility_private.js']); - -/** - * Magnifier feature using accessibility common extension browser tests. - */ -MagnifierMV2E2ETest = class extends E2ETestBase { - constructor() { - super(); - window.RoleType = chrome.automation.RoleType; - } - - async getNextMagnifierBounds() { - return new Promise((resolve) => { - const listener = (magnifierBounds) => { - chrome.accessibilityPrivate.onMagnifierBoundsChanged.removeListener( - listener); - resolve(magnifierBounds); - }; - chrome.accessibilityPrivate.onMagnifierBoundsChanged.addListener( - listener); - }); - } - - /** @override */ - testGenCppIncludes() { - super.testGenCppIncludes(); - GEN(` -#include "chrome/browser/ash/accessibility/magnification_manager.h" -#include "ui/accessibility/accessibility_features.h" - `); - } - - /** @override */ - testGenPreamble() { - super.testGenPreamble(); - GEN(` - base::OnceClosure load_cb = - base::BindOnce(&ash::MagnificationManager::SetMagnifierEnabled, - base::Unretained(ash::MagnificationManager::Get()), - true); - `); - super.testGenPreambleCommon('kAccessibilityCommonExtensionId'); - } - - /** @override */ - get featureList() { - return { - enabled: [ - 'features::kAccessibilityMagnifierFollowsChromeVox', - ], - disabled: ['features::kAccessibilityManifestV3AccessibilityCommon'] - }; - } -}; - -// Flaky: http://crbug.com/1171635 -AX_TEST_F( - 'MagnifierMV2E2ETest', 'DISABLED_MovesScreenMagnifierToFocusedElement', - async function() { - const site = ` - <button id="apple">Apple</button><br /> - <button id="banana" style="margin-top: 400px">Banana</button> - `; - const root = await this.runWithLoadedTree(site); - const magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); - - const apple = root.find({attributes: {name: 'Apple'}}); - const banana = root.find({attributes: {name: 'Banana'}}); - - // Focus and move magnifier to apple. - apple.focus(); - - // Verify magnifier bounds contains apple, but not banana. - let bounds = await this.getNextMagnifierBounds(); - assertTrue(RectUtil.contains(bounds, apple.location)); - assertFalse(RectUtil.contains(bounds, banana.location)); - - // Focus and move magnifier to banana. - banana.focus(); - - // Verify magnifier bounds contains banana, but not apple. - bounds = await this.getNextMagnifierBounds(); - assertFalse(RectUtil.contains(bounds, apple.location)); - assertTrue(RectUtil.contains(bounds, banana.location)); - }); - -// Disabled - flaky: https://crbug.com/1145612 -AX_TEST_F( - 'MagnifierMV2E2ETest', 'DISABLED_MovesDockedMagnifierToActiveDescendant', - async function() { - const site = ` - <div role="group" id="parent" aria-activedescendant="apple"> - <div id="apple" role="treeitem">Apple</div> - <div id="banana" role="treeitem">Banana</div> - </div> - <script> - const parent = document.getElementById('parent'); - parent.addEventListener('click', function() { - parent.setAttribute('aria-activedescendant', 'banana'); - }); - </script> - `; - const root = await this.runWithLoadedTree(site); - // Enable docked magnifier. - await new Promise((resolve) => { - chrome.accessibilityFeatures.dockedMagnifier.set( - {value: true}, resolve); - }); - - // Validate magnifier wants to move to root. - const rootLocation = await getNextMagnifierLocation(); - assertTrue(RectUtil.equal(rootLocation, root.location)); - - // Click parent to change active descendant from apple to banana. - const parent = root.find({role: RoleType.GROUP}); - parent.doDefault(); - - // Register and wait for rect from magnifier. - const rect = await getNextMagnifierLocation(); - - // Validate rect from magnifier is rect of banana. - const bananaNode = root.find({ - role: RoleType.TREE_ITEM, - attributes: {name: 'Banana'}, - }); - assertTrue(RectUtil.equal(rect, bananaNode.location)); - }); - -// Flaky: http://crbug.com/1171750 -AX_TEST_F( - 'MagnifierMV2E2ETest', 'DISABLED_MovesScreenMagnifierToActiveDescendant', - async function() { - const site = ` - <span tabindex="1">Top</span> - <div id="group" role="group" style="width: 200px" - aria-activedescendant="apple"> - <div id="apple" role="treeitem">Apple</div> - <div id="banana" role="treeitem" style="margin-top: 400px">Banana</div> - </div> - <script> - const group = document.getElementById('group'); - group.addEventListener('click', function() { - group.setAttribute('aria-activedescendant', 'banana'); - }); - </script> - `; - const root = await this.runWithLoadedTree(site); - const magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); - - const top = root.find({attributes: {name: 'Top'}}); - const banana = root.find({attributes: {name: 'Banana'}}); - const group = root.find({role: RoleType.GROUP}); - - // Focus and move magnifier to top. - top.focus(); - - // Verify magnifier bounds don't contain banana. - let bounds = await this.getNextMagnifierBounds(); - assertFalse(RectUtil.contains(bounds, banana.location)); - - // Click group to change active descendant to banana. - group.doDefault(); - - // Verify magnifier bounds contain banana. - bounds = await this.getNextMagnifierBounds(); - assertTrue(RectUtil.contains(bounds, banana.location)); - }); - -TEST_F( - 'MagnifierMV2E2ETest', 'MovesFullscreenMagnifierSelectionEvent', - function() { - this.runWithLoadedDesktop(async function(desktop) { - const magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); - - const moveMenuSelectionAssertBounds = async (targetBounds) => { - // Send arrow up key. - chrome.accessibilityPrivate.sendSyntheticKeyEvent({ - type: - chrome.accessibilityPrivate.SyntheticKeyboardEventType.KEYDOWN, - keyCode: KeyCode.UP, - }); - - // Verify new magnifier bounds include |targetBounds|. - await new Promise((resolve) => { - const boundsChangedListener = (newBounds) => { - if (RectUtil.contains(newBounds, targetBounds)) { - chrome.accessibilityPrivate.onMagnifierBoundsChanged - .removeListener(boundsChangedListener); - resolve(); - } - }; - chrome.accessibilityPrivate.onMagnifierBoundsChanged.addListener( - boundsChangedListener); - }); - }; - - // Trigger Chrome menu. - chrome.accessibilityPrivate.sendSyntheticKeyEvent({ - type: chrome.accessibilityPrivate.SyntheticKeyboardEventType.KEYDOWN, - keyCode: KeyCode.E, - modifiers: {alt: true}, - }); - - // Wait for Chrome menu to open. - await new Promise( - (resolve) => desktop.addEventListener( - chrome.automation.EventType.MENU_START, resolve, false)); - - // Move menu selection to end of menu, and await new magnifier bounds. - await moveMenuSelectionAssertBounds({ - left: 650, - top: 450, - width: 0, - height: 0, - }); - }); - }); - -AX_TEST_F('MagnifierMV2E2ETest', 'IgnoresRootNodeFocus', async function() { - await this.runWithLoadedTree(''); - - const magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); - - chrome.accessibilityPrivate.onMagnifierBoundsChanged.addListener( - (newBounds) => { - throw new Error( - 'Magnifier did not ignore focus change on document load - ' + - 'moved to following location: ' + JSON.stringify(newBounds)); - }); - - // Wait seven seconds to verify magnifier successfully ignored focus on root - // node. - await new Promise((resolve) => setTimeout(resolve, 7000)); -}); - -// TODO(crbug.com/1295685): Test is flaky. -AX_TEST_F( - 'MagnifierMV2E2ETest', 'DISABLED_MagnifierCenterOnPoint', async function() { - await this.runWithLoadedTree(''); - const magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); - - const movePointAssertBounds = async (targetPoint, targetBounds) => { - // Repeatedly center magnifier on |targetPoint|. - const id = setInterval(() => { - chrome.accessibilityPrivate.magnifierCenterOnPoint(targetPoint); - }, 500); - - // Verify new magnifier bounds include |targetBounds|. - await new Promise((resolve) => { - const boundsChangedListener = (newBounds) => { - if (RectUtil.contains(newBounds, targetBounds)) { - chrome.accessibilityPrivate.onMagnifierBoundsChanged - .removeListener(boundsChangedListener); - clearInterval(id); - resolve(); - } - }; - chrome.accessibilityPrivate.onMagnifierBoundsChanged.addListener( - boundsChangedListener); - }); - }; - - // Move magnifier to upper left of screen. - await movePointAssertBounds( - {x: 100, y: 100}, {left: 100, top: 100, width: 0, height: 0}); - - // Move magnifier to lower right of screen. - await movePointAssertBounds( - {x: 650, y: 450}, {left: 650, top: 450, width: 0, height: 0}); - }); - -AX_TEST_F('MagnifierMV2E2ETest', 'OnCaretBoundsChanged', async function() { - const site = ` - <input type="text" id="input" style="width: 1000px"> - <button id="button">Type words</button> - <script> - const input = document.getElementById('input'); - const button = document.getElementById('button'); - button.addEventListener('click', function() { - input.focus(); - input.value += 'The quick brown fox jumps over the lazy dog.'; - }); - </script> - `; - const root = await this.runWithLoadedTree(site); - const magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); - const button = root.find({attributes: {name: 'Type words'}}); - const input = root.find({role: RoleType.TEXT_FIELD}); - input.doDefault(); - - const typeWordsAssertBounds = async (targetBounds) => { - // Type words in the input field to move the text caret forward. - const id = setInterval(() => { - button.doDefault(); - }, 500); - - // Verify new magnifier bounds include |targetBounds|. - await new Promise((resolve) => { - const boundsChangedListener = (newBounds) => { - if (RectUtil.contains(newBounds, targetBounds)) { - chrome.accessibilityPrivate.onMagnifierBoundsChanged.removeListener( - boundsChangedListener); - clearInterval(id); - resolve(); - } - }; - chrome.accessibilityPrivate.onMagnifierBoundsChanged.addListener( - boundsChangedListener); - }); - }; - - // Type words to move text cursor forward, verify magnifier contains caret. - await typeWordsAssertBounds({left: 400, top: 100, width: 0, height: 0}); - - // Additional words to move caret forward, make sure magnifier follows. - await typeWordsAssertBounds({left: 800, top: 100, width: 0, height: 0}); - - // Even more words to move caret forward, make sure magnifier follows. - await typeWordsAssertBounds({left: 1200, top: 100, width: 0, height: 0}); -}); - -TEST_F('MagnifierMV2E2ETest', 'ScreenMagnifierFocusFollowingPref', function() { - this.newCallback(async () => { - // Disable focus following for full screen magnifier, and verify prefs and - // state. - await this.setPref(Magnifier.Prefs.SCREEN_MAGNIFIER_FOCUS_FOLLOWING, false); - magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); - assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN); - assertFalse(magnifier.shouldFollowFocus()); - - // Enable focus following for full screen magnifier, and verify prefs and - // state. - await this.setPref(Magnifier.Prefs.SCREEN_MAGNIFIER_FOCUS_FOLLOWING, true); - magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); - assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN); - assertTrue(magnifier.shouldFollowFocus()); - })(); -}); - -TEST_F( - 'MagnifierMV2E2ETest', 'ScreenMagnifierSelectToSpeakFollowingPref', - function() { - this.newCallback(async () => { - // Disable select to speak following for full screen magnifier, and - // verify prefs and state. - await this.setPref( - Magnifier.Prefs.SCREEN_MAGNIFIER_SELECT_TO_SPEAK_FOCUS_FOLLOWING, - false); - magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); - assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN); - assertFalse(magnifier.shouldFollowStsFocus()); - - // Enable select to speak following for full screen magnifier, and - // verify prefs and state. - await this.setPref( - Magnifier.Prefs.SCREEN_MAGNIFIER_SELECT_TO_SPEAK_FOCUS_FOLLOWING, - true); - magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); - assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN); - assertTrue(magnifier.shouldFollowStsFocus()); - })(); - }); - -TEST_F( - 'MagnifierMV2E2ETest', 'FullscreenMagnifierDoesNotFollowStsWhenPrefOff', - function() { - this.newCallback(async () => { - // Disable select to speak following for full screen magnifier, and - // verify prefs and state. - await this.setPref( - Magnifier.Prefs.SCREEN_MAGNIFIER_SELECT_TO_SPEAK_FOCUS_FOLLOWING, - false); - magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); - assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN); - assertFalse(magnifier.shouldFollowStsFocus()); - - let count = 0; - chrome.accessibilityPrivate.moveMagnifierToRect = () => (count += 1); - assertEquals(count, 0); - magnifier.onSelectToSpeakFocusChanged_({ - left: 2, - top: 4, - width: 5, - height: 7, - }); - assertEquals(count, 0); - })(); - }); - -TEST_F( - 'MagnifierMV2E2ETest', 'FullscreenMagnifierFollowsStsWhenPrefOn', - function() { - this.newCallback(async () => { - // Disable select to speak following for full screen magnifier, and - // verify prefs and state. - await this.setPref( - Magnifier.Prefs.SCREEN_MAGNIFIER_SELECT_TO_SPEAK_FOCUS_FOLLOWING, - true); - magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); - assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN); - assertTrue(magnifier.shouldFollowStsFocus()); - - let count = 0; - chrome.accessibilityPrivate.moveMagnifierToRect = () => (count += 1); - magnifier.lastMouseMovedTime_ = undefined; - assertEquals(count, 0); - magnifier.onSelectToSpeakFocusChanged_({ - left: 2, - top: 4, - width: 5, - height: 7, - }); - assertEquals(count, 1); - })(); - }); - -// TODO(crbug.com/417125183): Test is flaky. -TEST_F( - 'MagnifierMV2E2ETest', 'DISABLED_ScreenMagnifierChromeVoxFollowingPref', - function() { - this.newCallback(async () => { - // Disable ChromeVox following for full screen magnifier, and - // verify prefs and state. - await this.setPref( - Magnifier.Prefs.SCREEN_MAGNIFIER_CHROMEVOX_FOCUS_FOLLOWING, false); - magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); - assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN); - assertFalse(magnifier.shouldFollowChromeVoxFocus()); - - // Enable ChromeVox following for full screen magnifier, and - // verify prefs and state. - await this.setPref( - Magnifier.Prefs.SCREEN_MAGNIFIER_CHROMEVOX_FOCUS_FOLLOWING, true); - magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); - assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN); - assertTrue(magnifier.shouldFollowChromeVoxFocus()); - })(); - }); - -TEST_F( - 'MagnifierMV2E2ETest', 'ScreenMagnifierChromeVoxDoesNotFollowWhenPrefOff', - function() { - this.newCallback(async () => { - // Disable ChromeVox following for full screen magnifier, and - // verify prefs and state. - await this.setPref( - Magnifier.Prefs.SCREEN_MAGNIFIER_CHROMEVOX_FOCUS_FOLLOWING, false); - magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); - assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN); - assertFalse(magnifier.shouldFollowChromeVoxFocus()); - - let count = 0; - chrome.accessibilityPrivate.moveMagnifierToRect = () => (count += 1); - assertEquals(count, 0); - magnifier.onChromeVoxFocusChanged_({ - left: 2, - top: 4, - width: 5, - height: 7, - }); - assertEquals(count, 0); - })(); - }); - -TEST_F( - 'MagnifierMV2E2ETest', 'ScreenMagnifierChromeVoxFollowsWhenPrefOn', - function() { - this.newCallback(async () => { - // Disable ChromeVox following for full screen magnifier, and - // verify prefs and state. - await this.setPref( - Magnifier.Prefs.SCREEN_MAGNIFIER_CHROMEVOX_FOCUS_FOLLOWING, true); - magnifier = accessibilityCommon.getMagnifierForTest(); - magnifier.setIsInitializingForTest(false); - assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN); - assertTrue(magnifier.shouldFollowChromeVoxFocus()); - - let count = 0; - chrome.accessibilityPrivate.moveMagnifierToRect = () => (count += 1); - magnifier.lastMouseMovedTime_ = undefined; - assertEquals(count, 0); - magnifier.onChromeVoxFocusChanged_({ - left: 2, - top: 4, - width: 5, - height: 7, - }); - assertEquals(count, 1); - })(); - });
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/BUILD.gn index 395e01a..a6bfb45 100644 --- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/BUILD.gn +++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/BUILD.gn
@@ -7,8 +7,6 @@ import( "//chrome/browser/resources/chromeos/accessibility/tools/run_jsbundler.gni") import("//chrome/common/features.gni") -import("//chrome/test/base/ash/js2gtest.gni") -import("//testing/test.gni") import("//tools/typescript/ts_library.gni") import("//ui/webui/resources/tools/bundle_js.gni") import("//ui/webui/resources/tools/minify_js.gni") @@ -127,68 +125,3 @@ sources = ts_modules outputs = [ "$ts_build_staging_dir/{{source_target_relative}}" ] } - -source_set("browser_tests") { - testonly = true - assert(enable_extensions) - - deps = [ ":select_to_speak_extjs_tests" ] - - # TODO(jamescook): Figure out which of these are really necessary. - data = [ - "$root_out_dir/chrome_100_percent.pak", - "$root_out_dir/chrome_200_percent.pak", - "$root_out_dir/locales/en-US.pak", - "$root_out_dir/resources.pak", - "$root_out_dir/resources/chromeos/accessibility/select_to_speak/", - "$root_out_dir/test_data/chrome/browser/resources/chromeos/accessibility/select_to_speak/", - - # The test uses data from the original location, not the copied one. - "//chrome/browser/resources/chromeos/accessibility/common/", - "//chrome/browser/resources/chromeos/accessibility/select_to_speak/", - ] - data += js2gtest_js_libraries -} - -js2gtest("select_to_speak_extjs_tests") { - test_type = "extension" - sources = [ - # These are end-to-end tests. - "select_to_speak_context_menu_tests.js", - "select_to_speak_enhanced_voices_test.js", - "select_to_speak_keystroke_selection_test.js", - "select_to_speak_mouse_selection_test.js", - "select_to_speak_navigation_control_test.js", - "select_to_speak_prefs_test.js", - ] - - # These are unit tests run under an extension environment to get ES6 module support. - sources += [ - "select_to_speak_unittest.js", - "tts_manager_unittest.js", - "ui_manager_unittest.js", - ] - gen_include_files = [ - "../../common/testing/accessibility_test_base.js", - "../../common/testing/callback_helper.js", - "../../common/testing/e2e_test_base.js", - "../../common/testing/fake_chrome_event.js", - "../../common/testing/fake_settings_private.js", - "../../common/testing/mock_storage.js", - "../../common/testing/mock_tts.js", - "select_to_speak_e2e_test_base.js", - "pipe.jpg", - ] - - # The test base classes generate C++ code with these deps. - deps = [ - "//ash", - "//ash/keyboard/ui", - "//base", - "//chrome/browser/ash/accessibility", - "//chrome/browser/ash/crosapi", - "//chrome/browser/ash/system_web_apps", - "//chrome/common", - ] - defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ] -}
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_context_menu_tests.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_context_menu_tests.js deleted file mode 100644 index 9022e41b..0000000 --- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_context_menu_tests.js +++ /dev/null
@@ -1,56 +0,0 @@ -// Copyright 2022 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -GEN_INCLUDE(['select_to_speak_e2e_test_base.js']); -GEN_INCLUDE(['../../common/testing/mock_tts.js']); - -/** - * Browser tests for select-to-speak's feature to speak text - * using the context menu. - */ -SelectToSpeakMV2ContextMenuTest = class extends SelectToSpeakE2ETest { - constructor() { - super(); - this.mockTts = new MockTts(); - chrome.tts = this.mockTts; - } - - /** @override */ - async setUpDeferred() { - await super.setUpDeferred(); - selectToSpeak.prefsManager_.enhancedVoicesDialogShown_ = true; - } -}; - -AX_TEST_F( - 'SelectToSpeakMV2ContextMenuTest', 'SimpleSpeakingTest', async function() { - const root = await this.runWithLoadedTree('<p>This is some text</p>'); - const textNode = this.findTextNode(root, 'This is some text'); - chrome.automation.setDocumentSelection({ - anchorObject: textNode, - anchorOffset: 0, - focusObject: textNode, - focusOffset: 17, - }); - const menuName = chrome.i18n.getMessage( - 'select_to_speak_listen_context_menu_option_text'); - const listener = (change) => { - // On Lacros, the context menu might be changed before we can click on - // it, whereas on Ash we can usually click on it first. Easiest to - // look for just node changed rather than created. - if (change.type === chrome.automation.TreeChangeType.NODE_CHANGED && - change.target.name === menuName) { - change.target.doDefault(); - } - }; - this.mockTts.setOnSpeechCallbacks([this.newCallback((utterance) => { - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace(utterance, 'This is some text'); - this.mockTts.finishPendingUtterance(); - chrome.automation.removeTreeChangeObserver(listener); - })]); - chrome.automation.addTreeChangeObserver('allTreeChanges', listener); - textNode.showContextMenu(); - });
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_enhanced_voices_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_enhanced_voices_test.js deleted file mode 100644 index 8f5acb05..0000000 --- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_enhanced_voices_test.js +++ /dev/null
@@ -1,156 +0,0 @@ -// Copyright 2021 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -GEN_INCLUDE(['select_to_speak_e2e_test_base.js']); -GEN_INCLUDE(['../../common/testing/mock_tts.js']); - -SelectToSpeakMV2EnhancedNetworkTtsVoicesTest = - class extends SelectToSpeakE2ETest { - constructor() { - super(); - this.mockTts = new MockTts(); - chrome.tts = this.mockTts; - this.confirmationDialogShowCount_ = 0; - this.confirmationDialogResponse_ = true; - - chrome.accessibilityPrivate.showConfirmationDialog = - (title, description, cancelName, callback) => { - this.confirmationDialogShowCount_ += 1; - callback(this.confirmationDialogResponse_); - }; - - chrome.i18n = { - getMessage(msgid) { - return msgid; - }, - }; - } - - // Sets the policy to allow or disallow the network voices. - // Waits for the setting to propagate. - async setEnhancedNetworkVoicesPolicy(allowed) { - chrome.settingsPrivate.setPref( - PrefsManager.ENHANCED_VOICES_POLICY_KEY, allowed); - await new Promise( - resolve => selectToSpeak.prefsManager_ - .updateSettingsPrefsCallbackForTest_ = () => { - if (selectToSpeak.prefsManager_.enhancedNetworkVoicesAllowed_ === - allowed) { - selectToSpeak.prefsManager_.updateSettingsPrefsCallbackForTest_ = - null; - resolve(); - } - }); - } -}; - -AX_TEST_F( - 'SelectToSpeakMV2EnhancedNetworkTtsVoicesTest', - 'EnablesVoicesIfConfirmedInDialog', async function() { - this.confirmationDialogResponse_ = true; - - const root = await this.runWithLoadedTree('<p>This is some text</p>'); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - this.mockTts.setOnSpeechCallbacks([this.newCallback(function(utterance) { - // Speech starts asynchronously. - assertEquals(this.confirmationDialogShowCount_, 1); - assertTrue(selectToSpeak.prefsManager_.enhancedVoicesDialogShown()); - assertTrue(selectToSpeak.prefsManager_.enhancedNetworkVoicesEnabled()); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'This is some text'); - })]); - const textNode = this.findTextNode(root, 'This is some text'); - const event = { - screenX: textNode.location.left + 1, - screenY: textNode.location.top + 1, - }; - this.triggerReadMouseSelectedText(event, event); - }); - -AX_TEST_F( - 'SelectToSpeakMV2EnhancedNetworkTtsVoicesTest', - 'DisablesVoicesIfCanceledInDialog', async function() { - this.confirmationDialogResponse_ = false; - - const root = await this.runWithLoadedTree('<p>This is some text</p>'); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - this.mockTts.setOnSpeechCallbacks([this.newCallback(function(utterance) { - // Speech starts asynchronously. - assertEquals(this.confirmationDialogShowCount_, 1); - assertTrue(selectToSpeak.prefsManager_.enhancedVoicesDialogShown()); - assertFalse(selectToSpeak.prefsManager_.enhancedNetworkVoicesEnabled()); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'This is some text'); - })]); - const textNode = this.findTextNode(root, 'This is some text'); - const event = { - screenX: textNode.location.left + 1, - screenY: textNode.location.top + 1, - }; - this.triggerReadMouseSelectedText(event, event); - }); - -AX_TEST_F( - 'SelectToSpeakMV2EnhancedNetworkTtsVoicesTest', - 'DisablesVoicesIfDisallowedByPolicy', async function() { - this.confirmationDialogResponse_ = true; - - const root = await this.runWithLoadedTree('<p>This is some text</p>'); - this.mockTts.setOnSpeechCallbacks([this.newCallback(async function( - utterance) { - // Network voices are enabled initially because of the - // confirmation. - assertEquals(this.confirmationDialogShowCount_, 1); - assertTrue(selectToSpeak.prefsManager_.enhancedVoicesDialogShown()); - assertTrue(selectToSpeak.prefsManager_.enhancedNetworkVoicesEnabled()); - - // Sets the policy to disallow network voices. - await this.setEnhancedNetworkVoicesPolicy(/* allowed= */ false); - assertFalse(selectToSpeak.prefsManager_.enhancedNetworkVoicesEnabled()); - })]); - const textNode = this.findTextNode(root, 'This is some text'); - const event = { - screenX: textNode.location.left + 1, - screenY: textNode.location.top + 1, - }; - this.triggerReadMouseSelectedText(event, event); - }); - -AX_TEST_F( - 'SelectToSpeakMV2EnhancedNetworkTtsVoicesTest', - 'DisablesDialogIfDisallowedByPolicy', async function() { - await this.setEnhancedNetworkVoicesPolicy(/* allowed= */ false); - - // For some reason after setting enhanced network voices pref - // we often lose mockTts on Select to Speak. Ensure it's set. - chrome.tts = this.mockTts; - - const root = await this.runWithLoadedTree('<p>This is some text</p>'); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - this.mockTts.setOnSpeechCallbacks([this.newCallback(function(utterance) { - // Dialog was not shown. - assertEquals(this.confirmationDialogShowCount_, 0); - assertFalse(selectToSpeak.prefsManager_.enhancedVoicesDialogShown()); - - // Speech proceeds without enhanced voices. - assertFalse(selectToSpeak.prefsManager_.enhancedNetworkVoicesEnabled()); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'This is some text'); - })]); - const textNode = this.findTextNode(root, 'This is some text'); - const event = { - screenX: textNode.location.left + 1, - screenY: textNode.location.top + 1, - }; - this.triggerReadMouseSelectedText(event, event); - });
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_keystroke_selection_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_keystroke_selection_test.js deleted file mode 100644 index e7775ff..0000000 --- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_keystroke_selection_test.js +++ /dev/null
@@ -1,839 +0,0 @@ -// Copyright 2018 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -GEN_INCLUDE(['select_to_speak_e2e_test_base.js']); -GEN_INCLUDE(['../../common/testing/mock_tts.js']); - -/** - * Browser tests for select-to-speak's feature to speak text - * at the press of a keystroke. - */ -SelectToSpeakMV2KeystrokeSelectionTest = class extends SelectToSpeakE2ETest { - constructor() { - super(); - this.mockTts = new MockTts(); - chrome.tts = this.mockTts; - } - - /** @override */ - async setUpDeferred() { - await super.setUpDeferred(); - - await new Promise(resolve => { - chrome.settingsPrivate.setPref( - PrefsManager.ENHANCED_VOICES_DIALOG_SHOWN_KEY, true, - '' /* unused, see crbug.com/866161 */, () => resolve()); - }); - - if (!selectToSpeak.prefsManager_.enhancedVoicesDialogShown()) { - // TODO(b/267705784): This shouldn't happen, but sometimes the - // setPref call above does not cause PrefsManager.updateSettingsPrefs_ to - // be called (test: listen to updateSettingsPrefsCallbackForTest_, never - // called). - selectToSpeak.prefsManager_.enhancedVoicesDialogShown_ = true; - } - } - - /** - * Function to load a simple webpage, select some of the single text - * node, and trigger Select-to-Speak to read that partial node. Tests - * that the selected region creates tts output that matches the expected - * output. - * @param {string} text The text to load on the simple webpage. - * @param {number} anchorOffset The offset into the text node where - * focus starts. - * @param {number} focusOffset The offset into the text node where - * focus ends. - * @param {string} expected The expected string that will be read, ignoring - * extra whitespace, after this selection is triggered. - */ - async testSimpleTextAtKeystroke(text, anchorOffset, focusOffset, expected) { - await this.testReadTextAtKeystroke( - '<p>' + text + '</p>', async function(root) { - // Set the document selection. This will fire the changed event - // above, allowing us to do the keystroke and test that speech - // occurred properly. - const textNode = this.findTextNode(root, text); - chrome.automation.setDocumentSelection({ - anchorObject: textNode, - anchorOffset, - focusObject: textNode, - focusOffset, - }); - }, expected); - } - - /** - * Function to load given html using a data url, have the caller set a - * selection on that page, and then trigger select-to-speak to read - * the selected text. Tests that the tts output matches the expected - * output. - * @param {string} contents The web contents to load - * @param {function(AutomationNode)} setFocusCallback Callback - * to take the root node and set the selection appropriately. Once - * selection is set, the test will listen for the focus set event and - * trigger select-to-speak, comparing the resulting tts output to what - * was expected. - * will trigger select-to-speak to speak any selected text - * @param {string} expected The expected string that will be read, ignoring - * extra whitespace, after this selection is triggered. - */ - async testReadTextAtKeystroke(contents, setFocusCallback, expected) { - setFocusCallback = this.newCallback(setFocusCallback); - const root = await this.runWithLoadedTree(contents); - // Set the selection. - setFocusCallback(root); - // Wait for Automation to update. - await this.waitForEvent( - root, 'documentSelectionChanged', /*capture=*/ false); - // Speak selected text. - await this.triggerReadSelectedText(root); - await this.waitForSpeech(); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], expected); - } - - generateHtmlWithSelection(selectionCode, bodyHtml) { - return '<script type="text/javascript">' + - 'function doSelection() {' + - 'let selection = window.getSelection();' + - 'let range = document.createRange();' + - 'selection.removeAllRanges();' + selectionCode + - 'selection.addRange(range);}' + - '</script>' + - '<body onload="doSelection()">' + bodyHtml + '</body>'; - } - - /** - * Function to set the value property and the text selection properties of - * the given node using a text value, a start index, and an end index. It - * keeps trying to set and wait for textSelStart and textSelEnd until these - * text selection properties are set with the given indices, respectively. - * @param {AutomationNode} node The automation node to be set. - * @param {string} text The text to be set to the node's value property. - * @param {number} startIndex The index in the text field where focus starts. - * @param {number} endIndex The index in the text field where focus ends. - */ - async setValueAndTextSelection(node, value, startIndex, endIndex) { - node.setValue(value); - await this.waitForEvent(node, 'valueChanged'); - - while (node.textSelStart !== startIndex || node.textSelEnd !== endIndex) { - node.setSelection(startIndex, endIndex); - await this.waitForEvent(node, 'textSelectionChanged'); - } - } -}; - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'SpeaksTextAtKeystrokeFullText', - async function() { - await this.testSimpleTextAtKeystroke( - 'This is some text', 0, 17, 'This is some text'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', - 'SpeaksTextAtKeystrokePartialText', async function() { - await this.testSimpleTextAtKeystroke( - 'This is some text', 0, 12, 'This is some'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'SpeaksTextAtKeystrokeSingleWord', - async function() { - await this.testSimpleTextAtKeystroke('This is some text', 8, 12, 'some'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', - 'SpeaksTextAtKeystrokePartialWord', async function() { - await this.testSimpleTextAtKeystroke('This is some text', 8, 10, 'so'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'SpeaksAcrossNodesAtKeystroke', - async function() { - await this.testReadTextAtKeystroke( - '<p>This is some <b>bold</b> text</p><p>Second paragraph</p>', - function(root) { - const firstNode = this.findTextNode(root, 'This is some '); - const lastNode = this.findTextNode(root, ' text'); - chrome.automation.setDocumentSelection({ - anchorObject: firstNode, - anchorOffset: 0, - focusObject: lastNode, - focusOffset: 5, - }); - }, - 'This is some bold text'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', - 'SpeaksAcrossNodesSelectedBackwardsAtKeystroke', async function() { - await this.testReadTextAtKeystroke( - '<p>This is some <b>bold</b> text</p><p>Second paragraph</p>', - function(root) { - // Set the document selection backwards in page order. - const lastNode = this.findTextNode(root, 'This is some '); - const firstNode = this.findTextNode(root, ' text'); - chrome.automation.setDocumentSelection({ - anchorObject: firstNode, - anchorOffset: 5, - focusObject: lastNode, - focusOffset: 0, - }); - }, - 'This is some bold text'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'SpeakTextSurroundedByBrs', - async function() { - // If you load this html and double-click on "Selected text", this is the - // document selection that occurs -- into the second <br/> element. - - let setFocusCallback = function(root) { - const firstNode = this.findTextNode(root, 'Selected text'); - const lastNode = root.findAll({role: 'lineBreak'})[1]; - chrome.automation.setDocumentSelection({ - anchorObject: firstNode, - anchorOffset: 0, - focusObject: lastNode, - focusOffset: 1, - }); - }; - setFocusCallback = this.newCallback(setFocusCallback); - const root = - await this.runWithLoadedTree('<br/><p>Selected text</p><br/>'); - // Add an event listener that will start the user interaction - // of the test once the selection is completed. - root.addEventListener( - 'documentSelectionChanged', this.newCallback(async function(event) { - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Selected text'); - - this.mockTts.finishPendingUtterance(); - if (this.mockTts.pendingUtterances().length === 1) { - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], ''); - } - }), - false); - setFocusCallback(root); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', - 'StartsReadingAtFirstNodeWithText', async function() { - await this.testReadTextAtKeystroke( - '<div id="empty"></div><div><p>This is some <b>bold</b> text</p></div>', - function(root) { - const firstNode = - this.findTextNode(root, 'This is some ').root.children[0]; - const lastNode = this.findTextNode(root, ' text'); - chrome.automation.setDocumentSelection({ - anchorObject: firstNode, - anchorOffset: 0, - focusObject: lastNode, - focusOffset: 5, - }); - }, - 'This is some bold text'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', - 'IgnoresTextMarkedNotUserSelectable', async function() { - await this.testReadTextAtKeystroke( - '<div><p>This is some <span style="user-select:none">unselectable</span> text</p></div>', - function(root) { - const firstNode = - this.findTextNode(root, 'This is some ').root.children[0]; - const lastNode = this.findTextNode(root, ' text'); - chrome.automation.setDocumentSelection({ - anchorObject: firstNode, - anchorOffset: 0, - focusObject: lastNode, - focusOffset: 5, - }); - }, - 'This is some text'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', - 'HandlesSingleImageCorrectlyWithAutomation', async function() { - await this.testReadTextAtKeystroke( - '<img src="pipe.jpg" alt="one"/>', function(root) { - const container = root.findAll({role: 'genericContainer'})[0]; - chrome.automation.setDocumentSelection({ - anchorObject: container, - anchorOffset: 0, - focusObject: container, - focusOffset: 1, - }); - }, 'one'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', - 'HandlesMultipleImagesCorrectlyWithAutomation', async function() { - await this.testReadTextAtKeystroke( - '<img src="pipe.jpg" alt="one"/>' + - '<img src="pipe.jpg" alt="two"/><img src="pipe.jpg" alt="three"/>', - function(root) { - const container = root.findAll({role: 'genericContainer'})[0]; - chrome.automation.setDocumentSelection({ - anchorObject: container, - anchorOffset: 1, - focusObject: container, - focusOffset: 2, - }); - }, - 'two'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', - 'HandlesMultipleImagesCorrectlyWithJS1', async function() { - // Using JS to do the selection instead of Automation, so that we can - // ensure this is stable against changes in chrome.automation. - const selectionCode = - 'let body = document.getElementsByTagName("body")[0];' + - 'range.setStart(body, 1);' + - 'range.setEnd(body, 2);'; - const root = await this.runWithLoadedTree(this.generateHtmlWithSelection( - selectionCode, - '<img id="one" src="pipe.jpg" alt="one"/>' + - '<img id="two" src="pipe.jpg" alt="two"/>' + - '<img id="three" src="pipe.jpg" alt="three"/>')); - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'two'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', - 'HandlesMultipleImagesCorrectlyWithJS2', async function() { - const selectionCode = - 'let body = document.getElementsByTagName("body")[0];' + - 'range.setStart(body, 1);' + - 'range.setEnd(body, 3);'; - const root = await this.runWithLoadedTree(this.generateHtmlWithSelection( - selectionCode, - '<img id="one" src="pipe.jpg" alt="one"/>' + - '<img id="two" src="pipe.jpg" alt="two"/>' + - '<img id="three" src="pipe.jpg" alt="three"/>')); - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'two three'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'TextFieldFullySelected', - async function() { - const selectionCode = 'let p = document.getElementsByTagName("p")[0];' + - 'let body = document.getElementsByTagName("body")[0];' + - 'range.setStart(p, 0);' + - 'range.setEnd(body, 2);'; - const root = await this.runWithLoadedTree(this.generateHtmlWithSelection( - selectionCode, - '<p>paragraph</p>' + - '<input type="text" value="text field">')); - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'paragraph'); - - this.mockTts.finishPendingUtterance(); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'text field'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'TwoTextFieldsFullySelected', - async function() { - const selectionCode = - 'let body = document.getElementsByTagName("body")[0];' + - 'range.setStart(body, 0);' + - 'range.setEnd(body, 2);'; - const root = await this.runWithLoadedTree(this.generateHtmlWithSelection( - selectionCode, - '<input type="text" value="one"></input>' + - '<textarea cols="5">two three</textarea>')); - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'one'); - - this.mockTts.finishPendingUtterance(); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'two three'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'TextInputPartiallySelected', - async function() { - const html = '<script type="text/javascript">' + - 'function doSelection() {' + - 'let input = document.getElementById("input");' + - 'input.focus();' + - 'input.setSelectionRange(5, 10);' + - '}' + - '</script>' + - '<body onload="doSelection()">' + - '<input id="input" type="text" value="text field"></input>' + - '</body>'; - const root = await this.runWithLoadedTree(html); - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'field'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'TextAreaPartiallySelected', - async function() { - const html = '<script type="text/javascript">' + - 'function doSelection() {' + - 'let input = document.getElementById("input");' + - 'input.focus();' + - 'input.setSelectionRange(6, 17);' + - '}' + - '</script>' + - '<body onload="doSelection()">' + - '<textarea id="input" type="text" cols="10">first line second line</textarea>' + - '</body>'; - const root = await this.runWithLoadedTree(html); - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'line second'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'HandlesTextWithBr', - async function() { - const selectionCode = - 'let body = document.getElementsByTagName("body")[0];' + - 'range.setStart(body, 0);' + - 'range.setEnd(body, 3);'; - const root = await this.runWithLoadedTree(this.generateHtmlWithSelection( - selectionCode, 'Test<br/><br/>Unread')); - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Test'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'HandlesTextWithBrComplex', - async function() { - const selectionCode = 'let p = document.getElementsByTagName("p")[0];' + - 'let body = document.getElementsByTagName("body")[0];' + - 'range.setStart(p, 0);' + - 'range.setEnd(body, 2);'; - const root = await this.runWithLoadedTree(this.generateHtmlWithSelection( - selectionCode, '<p>Some text</p><br/><br/>Unread')); - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Some text'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'HandlesTextWithBrAfterText1', - async function() { - // A bug was that if the selection was on the rootWebArea, paragraphs were - // not counted correctly. The more divs and paragraphs before the - // selection, the further off it got. - const selectionCode = 'let p = document.getElementsByTagName("p")[0];' + - 'let body = document.getElementsByTagName("body")[0];' + - 'range.setStart(p, 1);' + - 'range.setEnd(body, 2);'; - const root = await this.runWithLoadedTree(this.generateHtmlWithSelection( - selectionCode, '<p>Unread</p><p>Some text</p><br/>Unread')); - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Some text'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'HandlesTextWithBrAfterText2', - async function() { - // A bug was that if the selection was on the rootWebArea, paragraphs were - // not counted correctly. The more divs and paragraphs before the - // selection, the further off it got. - const selectionCode = 'let p = document.getElementsByTagName("p")[0];' + - 'let body = document.getElementsByTagName("body")[0];' + - 'range.setStart(p, 1);' + - 'range.setEnd(body, 3);'; - const root = await this.runWithLoadedTree(this.generateHtmlWithSelection( - selectionCode, '<p>Unread</p><p>Some text</p><br/>Unread')); - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - assertTrue(this.mockTts.pendingUtterances().length > 0); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Some text'); - - this.mockTts.finishPendingUtterance(); - if (this.mockTts.pendingUtterances().length > 0) { - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], ''); - } - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'HandlesTextAreaAndBrs', - async function() { - const selectionCode = - 'let body = document.getElementsByTagName("body")[0];' + - 'range.setStart(body, 1);' + - 'range.setEnd(body, 4);'; - const root = await this.runWithLoadedTree(this.generateHtmlWithSelection( - selectionCode, - '<br/><br/><textarea>Some text</textarea><br/><br/>Unread')); - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Some text'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'textFieldWithComboBoxSimple', - async function() { - const selectionCode = - 'let body = document.getElementsByTagName("body")[0];' + - 'range.setStart(body, 0);' + - 'range.setEnd(body, 1);'; - const root = await this.runWithLoadedTree(this.generateHtmlWithSelection( - selectionCode, - '<input list="list" value="one"></label><datalist id="list">' + - '<option value="one"></datalist>')); - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'one'); - }); -// TODO(katie): It doesn't seem possible to programatically specify a range that -// selects only part of the text in a combo box. - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', - 'ContentEditableInternallySelected', async function() { - const html = '<script type="text/javascript">' + - 'function doSelection() {' + - 'let input = document.getElementById("input");' + - 'input.focus();' + - 'let selection = window.getSelection();' + - 'let range = document.createRange();' + - 'selection.removeAllRanges();' + - 'let p1 = document.getElementsByTagName("p")[0];' + - 'let p2 = document.getElementsByTagName("p")[1];' + - 'range.setStart(p1.firstChild, 1);' + - 'range.setEnd(p2.firstChild, 3);' + - 'selection.addRange(range);' + - '}' + - '</script>' + - '<body onload="doSelection()">' + - '<div id="input" contenteditable><p>a b c</p><p>d e f</p></div>' + - '</body>'; - const root = await this.runWithLoadedTree(html); - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'b c'); - - this.mockTts.finishPendingUtterance(); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'd e'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', - 'ContentEditableExternallySelected', async function() { - const selectionCode = - 'let body = document.getElementsByTagName("body")[0];' + - 'range.setStart(body, 1);' + - 'range.setEnd(body, 2);'; - const root = await this.runWithLoadedTree(this.generateHtmlWithSelection( - selectionCode, - 'Unread <div id="input" contenteditable><p>a b c</p><p>d e f</p>' + - '</div> Unread')); - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'a b c'); - - this.mockTts.finishPendingUtterance(); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'd e f'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'ReordersSvgSingleLine', - async function() { - const selectionCode = - 'let body = document.getElementsByTagName("body")[0];' + - 'range.setStart(body, 0);' + - 'range.setEnd(body, 1);'; - const root = await this.runWithLoadedTree(this.generateHtmlWithSelection( - selectionCode, - '<svg viewBox="0 0 240 80" xmlns="http://www.w3.org/2000/svg">' + - ' <text x="65" y="55">Grumpy!</text>' + - ' <text x="20" y="35">My</text>' + - ' <text x="40" y="35">cat</text>' + - ' <text x="55" y="55">is</text>' + - '</svg>')); - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'My cat is Grumpy!'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'ReordersSvgWithGroups', - async function() { - const selectionCode = - 'let body = document.getElementsByTagName("body")[0];' + - 'range.setStart(body, 0);' + - 'range.setEnd(body, 1);'; - const root = await this.runWithLoadedTree(this.generateHtmlWithSelection( - selectionCode, - '<svg viewBox="0 0 240 80" xmlns="http://www.w3.org/2000/svg">' + - ' <g>' + - ' <text x="65" y="0">Column 2, Text 1</text>' + - ' <text x="65" y="50">Column 2, Text 2</text>' + - ' </g>' + - ' <g>' + - ' <text x="0" y="50">Column 1, Text 2</text>' + - ' <text x="0" y="0">Column 1, Text 1</text>' + - ' </g>' + - '</svg>')); - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Column 1, Text 1'); - - this.mockTts.finishPendingUtterance(); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Column 1, Text 2'); - - this.mockTts.finishPendingUtterance(); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Column 2, Text 1'); - - this.mockTts.finishPendingUtterance(); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Column 2, Text 2'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', - 'NonReorderedSvgPreservesSelectionStartEnd', async function() { - const selectionCode = 'const t1 = document.getElementById("t1");' + - 'const t2 = document.getElementById("t2");' + - 'range.setStart(t1.childNodes[0], 3);' + - 'range.setEnd(t2.childNodes[0], 2);'; - const root = await this.runWithLoadedTree(this.generateHtmlWithSelection( - selectionCode, - '<svg viewBox="0 0 240 80" xmlns="http://www.w3.org/2000/svg">' + - ' <text id="t1" x="0" y="55">My cat</text>' + - ' <text id="t2" x="100" y="55">is Grumpy!</text>' + - '</svg>')); - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'cat is'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', - 'ReorderedSvgIgnoresSelectionStartEnd', async function() { - const selectionCode = 'const t1 = document.getElementById("t1");' + - 'const t2 = document.getElementById("t2");' + - 'range.setStart(t1.childNodes[0], 3);' + - 'range.setEnd(t2.childNodes[0], 2);'; - const root = await this.runWithLoadedTree(this.generateHtmlWithSelection( - selectionCode, - '<svg viewBox="0 0 240 80" xmlns="http://www.w3.org/2000/svg">' + - ' <text id="t1" x="100" y="55">is Grumpy!</text>' + - ' <text id="t2" x="0" y="55">My cat</text>' + - '</svg>')); - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'My cat is Grumpy!'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'OmniboxFullySelected', - async function() { - let omnibox; - await this.runWithLoadedDesktop(desktop => { - omnibox = desktop.find({attributes: {className: 'OmniboxViewViews'}}); - }); - - await this.setValueAndTextSelection( - omnibox, 'Hello, Chromium a11y', 0, 20); - assertEquals('Hello, Chromium a11y', omnibox.value); - assertEquals(0, omnibox.textSelStart); - assertEquals(20, omnibox.textSelEnd); - - await this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Hello, Chromium a11y'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', - 'OmniboxPartiallySelectedFromStart', async function() { - let omnibox; - await this.runWithLoadedDesktop(desktop => { - omnibox = desktop.find({attributes: {className: 'OmniboxViewViews'}}); - }); - - await this.setValueAndTextSelection( - omnibox, 'Hello, Chromium a11y', 0, 5); - assertEquals('Hello, Chromium a11y', omnibox.value); - assertEquals(0, omnibox.textSelStart); - assertEquals(5, omnibox.textSelEnd); - - await this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Hello'); - }); - - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'OmniboxPartiallySelectedToEnd', - async function() { - let omnibox; - const root = await this.runWithLoadedDesktop(desktop => { - omnibox = desktop.find({attributes: {className: 'OmniboxViewViews'}}); - }); - - await this.setValueAndTextSelection( - omnibox, 'Hello, Chromium a11y', 7, 20); - assertEquals('Hello, Chromium a11y', omnibox.value); - assertEquals(7, omnibox.textSelStart); - assertEquals(20, omnibox.textSelEnd); - - await this.triggerReadSelectedText(root); - assertTrue(this.mockTts.currentlySpeaking()); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Chromium a11y'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'OmniboxPartiallySelectedInMid', - async function() { - let omnibox; - await this.runWithLoadedDesktop(desktop => { - omnibox = desktop.find({attributes: {className: 'OmniboxViewViews'}}); - }); - - await this.setValueAndTextSelection( - omnibox, 'Hello, Chromium a11y', 7, 15); - assertEquals('Hello, Chromium a11y', omnibox.value); - assertEquals(7, omnibox.textSelStart); - assertEquals(15, omnibox.textSelEnd); - - await this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Chromium'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'OmniboxNoneSelected', - async function() { - let omnibox; - await this.runWithLoadedDesktop(desktop => { - omnibox = desktop.find({attributes: {className: 'OmniboxViewViews'}}); - }); - - await this.setValueAndTextSelection( - omnibox, 'Hello, Chromium a11y', 0, 0); - assertEquals('Hello, Chromium a11y', omnibox.value); - assertEquals(0, omnibox.textSelStart); - assertEquals(0, omnibox.textSelEnd); - - await this.triggerReadSelectedText(); - assertEquals(false, this.mockTts.currentlySpeaking()); - }); - -AX_TEST_F( - 'SelectToSpeakMV2KeystrokeSelectionTest', 'SearchUpBeforeS', - async function() { - // SelectToSpeakE2ETest.triggerReadSelectedText releases the 'S' key - // before the 'SEARCH' key. - // This test releases 'SEARCH' before 'S' to ensure that speech is still - // started. - const setFocusCallback = this.newCallback(async function(root) { - // Set the document selection. This will fire the changed event - // above, allowing us to do the keystroke and test that speech - // occurred properly. - const textNode = this.findTextNode(root, 'This is some text'); - chrome.automation.setDocumentSelection({ - anchorObject: textNode, - anchorOffset: 0, - focusObject: textNode, - focusOffset: 12, - }); - }); - - const root = await this.runWithLoadedTree('<p>This is some text</p>'); - // Set the selection. - setFocusCallback(root); - // Wait for Automation to update. - await this.waitForEvent( - root, 'documentSelectionChanged', /*capture=*/ false); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - - // Speak selected text lifting the 'search' key before the 's' key. - selectToSpeak.sendMockSelectToSpeakKeysPressedChanged( - [SelectToSpeakConstants.SEARCH_KEY_CODE]); - selectToSpeak.sendMockSelectToSpeakKeysPressedChanged([ - SelectToSpeakConstants.SEARCH_KEY_CODE, - SelectToSpeakConstants.READ_SELECTION_KEY_CODE, - ]); - assertTrue(selectToSpeak.inputHandler_.isSelectionKeyDown_); - - // Release the SEARCH_KEY_CODE. - selectToSpeak.sendMockSelectToSpeakKeysPressedChanged( - [SelectToSpeakConstants.READ_SELECTION_KEY_CODE]); - selectToSpeak.sendMockSelectToSpeakKeysPressedChanged([]); - - await this.waitForSpeech(); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'This is some'); - });
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_mouse_selection_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_mouse_selection_test.js deleted file mode 100644 index 33d9ea7..0000000 --- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_mouse_selection_test.js +++ /dev/null
@@ -1,357 +0,0 @@ -// Copyright 2018 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -GEN_INCLUDE(['select_to_speak_e2e_test_base.js']); -GEN_INCLUDE(['../../common/testing/mock_tts.js']); - -/** - * Browser tests for select-to-speak's feature to speak text - * by holding down a key and clicking or dragging with the mouse. - */ -SelectToSpeakMV2MouseSelectionTest = class extends SelectToSpeakE2ETest { - constructor() { - super(); - this.mockTts = new MockTts(); - chrome.tts = this.mockTts; - } - - /** @override */ - async setUpDeferred() { - await super.setUpDeferred(); - window.EventType = chrome.automation.EventType; - window.SelectToSpeakState = chrome.accessibilityPrivate.SelectToSpeakState; - - await new Promise(resolve => { - chrome.settingsPrivate.setPref( - PrefsManager.ENHANCED_VOICES_DIALOG_SHOWN_KEY, true, - '' /* unused, see crbug.com/866161 */, () => resolve()); - }); - if (!selectToSpeak.prefsManager_.enhancedVoicesDialogShown()) { - // TODO(b/267705784): This shouldn't happen, but sometimes the - // setPref call above does not cause PrefsManager.updateSettingsPrefs_ to - // be called (test: listen to updateSettingsPrefsCallbackForTest_, never - // called). - selectToSpeak.prefsManager_.enhancedVoicesDialogShown_ = true; - } - } - - tapTrayButton(desktop, callback) { - const button = desktop.find({ - attributes: {className: SELECT_TO_SPEAK_TRAY_CLASS_NAME}, - }); - - callback = this.newCallback(callback); - selectToSpeak.onStateChangeRequestedCallbackForTest_ = - this.newCallback(() => { - selectToSpeak.onStateChangeRequestedCallbackForTest_ = null; - callback(); - }); - button.doDefault(); - } -}; - -AX_TEST_F( - 'SelectToSpeakMV2MouseSelectionTest', 'SpeaksNodeWhenClicked', - async function() { - const root = await this.runWithLoadedTree( - 'data:text/html;charset=utf-8,' + - '<p>This is some text</p>'); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - this.mockTts.setOnSpeechCallbacks([this.newCallback(function(utterance) { - // Speech starts asynchronously. - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'This is some text'); - })]); - const textNode = this.findTextNode(root, 'This is some text'); - const event = { - screenX: textNode.location.left + 1, - screenY: textNode.location.top + 1, - }; - this.triggerReadMouseSelectedText(event, event); - }); - -AX_TEST_F( - 'SelectToSpeakMV2MouseSelectionTest', 'SpeaksMultipleNodesWhenDragged', - async function() { - const root = await this.runWithLoadedTree( - 'data:text/html;charset=utf-8,' + - '<p>This is some text</p><p>This is some more text</p>'); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - this.mockTts.setOnSpeechCallbacks([ - this.newCallback(function(utterance) { - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace(utterance, 'This is some text'); - this.mockTts.finishPendingUtterance(); - }), - this.newCallback(function(utterance) { - this.assertEqualsCollapseWhitespace( - utterance, 'This is some more text'); - }), - ]); - const firstNode = this.findTextNode(root, 'This is some text'); - const downEvent = { - screenX: firstNode.location.left + 1, - screenY: firstNode.location.top + 1, - }; - const lastNode = this.findTextNode(root, 'This is some more text'); - const upEvent = { - screenX: lastNode.location.left + lastNode.location.width, - screenY: lastNode.location.top + lastNode.location.height, - }; - this.triggerReadMouseSelectedText(downEvent, upEvent); - }); - -AX_TEST_F( - 'SelectToSpeakMV2MouseSelectionTest', 'SpeaksAcrossNodesInAParagraph', - async function() { - const root = await this.runWithLoadedTree( - 'data:text/html;charset=utf-8,' + - '<p style="width:200px">This is some text in a paragraph that ' + - 'wraps. <i>Italic text</i></p>'); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - this.mockTts.setOnSpeechCallbacks([this.newCallback(function(utterance) { - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - utterance, - 'This is some text in a paragraph that wraps. ' + - 'Italic text'); - })]); - const firstNode = this.findTextNode( - root, 'This is some text in a paragraph that wraps. '); - const downEvent = { - screenX: firstNode.location.left + 1, - screenY: firstNode.location.top + 1, - }; - const lastNode = this.findTextNode(root, 'Italic text'); - const upEvent = { - screenX: lastNode.location.left + lastNode.location.width, - screenY: lastNode.location.top + lastNode.location.height, - }; - this.triggerReadMouseSelectedText(downEvent, upEvent); - }); - -AX_TEST_F( - 'SelectToSpeakMV2MouseSelectionTest', 'SpeaksNodeAfterTrayTapAndMouseClick', - async function() { - const root = await this.runWithLoadedTree( - 'data:text/html;charset=utf-8,' + - '<p>This is some text</p>'); - assertFalse(this.mockTts.currentlySpeaking()); - this.mockTts.setOnSpeechCallbacks([this.newCallback(function(utterance) { - // Speech starts asynchronously. - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'This is some text'); - })]); - - const textNode = this.findTextNode(root, 'This is some text'); - const mouseX = textNode.location.left + 1; - const mouseY = textNode.location.top + 1; - // A state change request should shift us into 'selecting' state - // from 'inactive'. - const desktop = root.parent.root; - this.tapTrayButton(desktop, () => { - selectToSpeak.fireMockMouseEvent( - chrome.accessibilityPrivate.SyntheticMouseEventType.PRESS, mouseX, - mouseY); - selectToSpeak.fireMockMouseEvent( - chrome.accessibilityPrivate.SyntheticMouseEventType.RELEASE, mouseX, - mouseY); - }); - }); - -AX_TEST_F( - 'SelectToSpeakMV2MouseSelectionTest', 'CancelsSelectionModeWithStateChange', - async function() { - const root = await this.runWithLoadedTree( - 'data:text/html;charset=utf-8,' + - '<p>This is some text</p>'); - const textNode = this.findTextNode(root, 'This is some text'); - // A state change request should shift us into 'selecting' state - // from 'inactive'. - const desktop = root.parent.root; - this.tapTrayButton(desktop, () => { - selectToSpeak.fireMockMouseEvent( - chrome.accessibilityPrivate.SyntheticMouseEventType.PRESS, - textNode.location.left + 1, textNode.location.top + 1); - assertEquals(SelectToSpeakState.SELECTING, selectToSpeak.state_); - - // Another state change puts us back in 'inactive'. - this.tapTrayButton(desktop, () => { - assertEquals(SelectToSpeakState.INACTIVE, selectToSpeak.state_); - }); - }); - }); - -AX_TEST_F( - 'SelectToSpeakMV2MouseSelectionTest', 'CancelsSpeechWithTrayTap', - async function() { - const root = await this.runWithLoadedTree( - 'data:text/html;charset=utf-8,' + - '<p>This is some text</p>'); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - this.mockTts.setOnSpeechCallbacks([this.newCallback(function(utterance) { - // Speech starts asynchronously. - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'This is some text'); - - // Cancel speech and make sure state resets to INACTIVE. - const desktop = root.parent.root; - this.tapTrayButton(desktop, () => { - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - assertEquals(SelectToSpeakState.INACTIVE, selectToSpeak.state_); - }); - })]); - const textNode = this.findTextNode(root, 'This is some text'); - const event = { - screenX: textNode.location.left + 1, - screenY: textNode.location.top + 1, - }; - this.triggerReadMouseSelectedText(event, event); - }); - -// TODO(crbug.com/40748296) Re-enable test -TEST_F( - 'SelectToSpeakMV2MouseSelectionTest', - 'DISABLED_DoesNotSpeakOnlyTheTrayButton', function() { - // The tray button itself should not be spoken when clicked in selection - // mode per UI review (but if more elements are being verbalized than just - // the STS tray button, it may be spoken). This is similar to how the - // stylus may act as a laser pointer unless it taps on the stylus options - // button, which always opens on a tap regardless of the stylus behavior - // selected. - this.runWithLoadedDesktop(desktop => { - this.tapTrayButton(desktop, () => { - assertEquals(selectToSpeak.state_, SelectToSpeakState.SELECTING); - const button = desktop.find({ - attributes: {className: SELECT_TO_SPEAK_TRAY_CLASS_NAME}, - }); - - // Use the same automation callbacks as Select-to-Speak to make - // sure we actually don't start speech after the hittest and focus - // callbacks are used to check which nodes should be spoken. - desktop.addEventListener( - EventType.MOUSE_RELEASED, this.newCallback(evt => { - chrome.automation.getFocus(this.newCallback(node => { - assertEquals( - selectToSpeak.state_, SelectToSpeakState.INACTIVE); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - })); - }), - true); - - const mouseX = button.location.left + 1; - const mouseY = button.location.top + 1; - selectToSpeak.fireMockMouseEvent( - chrome.accessibilityPrivate.SyntheticMouseEventType.PRESS, mouseX, - mouseY); - selectToSpeak.fireMockMouseEvent( - chrome.accessibilityPrivate.SyntheticMouseEventType.RELEASE, - mouseX, mouseY); - }); - }); - }); - -AX_TEST_F( - 'SelectToSpeakMV2MouseSelectionTest', 'VoiceSwitching', async function() { - selectToSpeak.shouldUseVoiceSwitching_ = () => true; - const root = await this.runWithLoadedTree( - 'data:text/html;charset=utf-8,<div>' + - '<span lang="fr-FR">The first paragraph</span>' + - '<span lang="en-US">The second paragraph</span></div>'); - - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - this.mockTts.setOnSpeechCallbacks([ - this.newCallback(function(utterance) { - const options = this.mockTts.getOptions(); - assertEquals('fr-FR', options.lang); - assertEquals(undefined, options.voiceName); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'The first paragraph'); - this.mockTts.finishPendingUtterance(); - }), - this.newCallback(function(utterance) { - const options = this.mockTts.getOptions(); - assertEquals('en-US', options.lang); - assertEquals(undefined, options.voiceName); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'The second paragraph'); - }), - ]); - - const firstNode = this.findTextNode(root, 'The first paragraph'); - assertNotNullNorUndefined(firstNode); - const downEvent = { - screenX: firstNode.location.left + 1, - screenY: firstNode.location.top + 1, - }; - const lastNode = this.findTextNode(root, 'The second paragraph'); - assertNotNullNorUndefined(lastNode); - const upEvent = { - screenX: lastNode.location.left + lastNode.location.width, - screenY: lastNode.location.top + lastNode.location.height, - }; - this.triggerReadMouseSelectedText(downEvent, upEvent); - }); - -AX_TEST_F('SelectToSpeakMV2MouseSelectionTest', 'SystemUI', async function() { - this.runWithLoadedDesktop(async desktop => { - // Select STS tray and system tray to ensure STS tray is spoken. - // We can test against the STS tray text because we own it, the - // rest of the system tray may change. - const systemTray = desktop.find({ - attributes: {className: 'UnifiedSystemTray'}, - }); - const stsTray = desktop.find({ - attributes: {className: SELECT_TO_SPEAK_TRAY_CLASS_NAME}, - }); - const start = { - screenX: stsTray.location.left + 1, - screenY: stsTray.location.top + 1, - }; - const end = { - screenX: systemTray.location.left + systemTray.location.width - 1, - screenY: systemTray.location.top + 10, - }; - this.mockTts.setOnSpeechCallbacks([this.newCallback(function(utterance) { - assertTrue(this.mockTts.currentlySpeaking()); - // Sometimes we get "Select-to-speak button" and sometimes - // "Select-to-speak" and sometimes "Highlight text on your screen". Any - // are acceptable. - const trimmedUtterance = - utterance.replace(/button/, '').toLowerCase().trim(); - assertTrue(['select-to-speak', 'highlight text on your screen'].includes( - trimmedUtterance)); - })]); - - focusRingsCallback = this.newCallback((focusRings) => { - // Check focus rings are reasonably sized. - assertTrue(focusRings[0].rects[0].width < 200); - assertTrue(focusRings[0].rects[0].height < 100); - }); - // Override focus rings method for this test. - chrome.accessibilityPrivate.setFocusRings = rings => { - if (focusRingsCallback && rings.length > 0 && rings[0].rects.length > 0) { - focusRingsCallback(rings); - focusRingsCallback = null; - } - }; - - this.triggerReadMouseSelectedText(start, end); - }); -});
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_navigation_control_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_navigation_control_test.js deleted file mode 100644 index 7b0d5f0..0000000 --- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_navigation_control_test.js +++ /dev/null
@@ -1,1055 +0,0 @@ -// Copyright 2020 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -GEN_INCLUDE(['select_to_speak_e2e_test_base.js']); -GEN_INCLUDE(['../../common/testing/mock_tts.js']); - -/** - * Browser tests for select-to-speak's navigation control features. - */ -SelectToSpeakMV2NavigationControlTest = class extends SelectToSpeakE2ETest { - constructor() { - super(); - this.mockTts = new MockTts(); - chrome.tts = this.mockTts; - - // Save original updateSelectToSpeakPanel function so we can override it in - // tests, then later restore the original implementation. - this.updateSelectToSpeakPanel = - chrome.accessibilityPrivate.updateSelectToSpeakPanel; - } - - /** @override */ - async setUpDeferred() { - await super.setUpDeferred(); - window.EventType = chrome.automation.EventType; - window.RoleType = chrome.automation.RoleType; - window.SelectToSpeakState = chrome.accessibilityPrivate.SelectToSpeakState; - chrome.accessibilityPrivate.updateSelectToSpeakPanel = - this.updateSelectToSpeakPanel; - - await new Promise(resolve => { - chrome.settingsPrivate.setPref( - PrefsManager.ENHANCED_VOICES_DIALOG_SHOWN_KEY, true, - '' /* unused, see crbug.com/866161 */, () => resolve()); - }); - if (!selectToSpeak.prefsManager_.enhancedVoicesDialogShown()) { - // TODO(b/267705784): This shouldn't happen, but sometimes the - // setPref call above does not cause PrefsManager.updateSettingsPrefs_ to - // be called (test: listen to updateSettingsPrefsCallbackForTest_, never - // called). - selectToSpeak.prefsManager_.enhancedVoicesDialogShown_ = true; - } - } - - generateHtmlWithSelectedElement(elementId, bodyHtml) { - return ` - <script type="text/javascript"> - function doSelection() { - let selection = window.getSelection(); - let range = document.createRange(); - selection.removeAllRanges(); - let node = document.getElementById("${elementId}"); - range.selectNodeContents(node); - selection.addRange(range); - } - </script> - <body onload="doSelection()">${bodyHtml}</body>`; - } - - isNodeWithinPanel(node) { - const windowParent = - AutomationUtil.getFirstAncestorWithRole(node, RoleType.WINDOW); - return windowParent.className === 'TrayBubbleView' && - windowParent.children.length === 1 && - windowParent.children[0].className === 'SelectToSpeakMenuView'; - } - - waitForPanelFocus(root, callback) { - callback = this.newCallback(callback); - const focusCallback = () => { - chrome.automation.getFocus(node => { - if (!this.isNodeWithinPanel(node)) { - return; - } - root.removeEventListener(EventType.FOCUS, focusCallback); - callback(node); - }); - }; - root.addEventListener(EventType.FOCUS, focusCallback); - } -}; - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'NavigatesToNextParagraph', - async function() { - const bodyHtml = ` - <p id="p1">Paragraph 1</p> - <p id="p2">Paragraph 2</p>' - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml)); - this.triggerReadSelectedText(); - - // Speaks first paragraph - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph 1'); - - // TODO(joelriley@google.com): Figure out a better way to trigger - // the actual floating panel button rather than calling private - // method directly. - selectToSpeak.onNextParagraphRequested(); - - // Speaks second paragraph - this.waitOneEventLoop(() => { - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph 2'); - }); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'NavigatesToPreviousParagraph', - async function() { - const bodyHtml = ` - <p id="p1">Paragraph 1</p> - <p id="p2">Paragraph 2</p>' - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p2', bodyHtml)); - this.triggerReadSelectedText(); - - // Speaks first paragraph - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph 2'); - - // TODO(joelriley@google.com): Figure out a better way to trigger - // the actual floating panel button rather than calling private - // method directly. - selectToSpeak.onPreviousParagraphRequested(); - - // Speaks second paragraph - this.waitOneEventLoop(() => { - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph 1'); - }); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'ReadsParagraphOnClick', - async function() { - const bodyHtml = ` - <p id="p1">Sentence <span>one</span>. Sentence two.</p> - <p id="p2">Paragraph <span>two</span></p>' - `; - const root = await this.runWithLoadedTree(bodyHtml); - this.mockTts.setOnSpeechCallbacks([ - this.newCallback(utterance => { - // Speech for first click. - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'Sentence one . Sentence two.'); - - this.mockTts.setOnSpeechCallbacks([this.newCallback(utterance => { - // Speech for second click. - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph two'); - })]); - - // Click on node in second paragraph. - const textNode2 = this.findTextNode(root, 'two'); - const mouseEvent2 = { - screenX: textNode2.location.left + 1, - screenY: textNode2.location.top + 1, - }; - this.triggerReadMouseSelectedText(mouseEvent2, mouseEvent2); - }), - ]); - - // Click on node in first paragraph. - const textNode1 = this.findTextNode(root, 'one'); - const event1 = { - screenX: textNode1.location.left + 1, - screenY: textNode1.location.top + 1, - }; - this.triggerReadMouseSelectedText(event1, event1); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'PauseResumeWithinTheSentence', - async function() { - const bodyHtml = ` - <p id="p1">First sentence. Second sentence. Third sentence.</p>' - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml)); - this.triggerReadSelectedText(); - - // Speaks until the second word of the second sentence. - this.mockTts.speakUntilCharIndex(23); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'First sentence. Second sentence. Third sentence.'); - - // Hitting pause will stop the current TTS. - selectToSpeak.onPauseRequested(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - - // Hitting resume will start from the remaining content of the - // second sentence. - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'sentence. Third sentence.'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', - 'PauseResumeAtTheBeginningOfSentence', async function() { - const bodyHtml = ` - <p id="p1">First sentence. Second sentence. Third sentence.</p>' - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml)); - this.triggerReadSelectedText(); - - // Speaks until the third sentence. - this.mockTts.speakUntilCharIndex(33); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'First sentence. Second sentence. Third sentence.'); - - // Hitting pause will stop the current TTS. - selectToSpeak.onPauseRequested(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - - // Hitting resume will start from the beginning of the third - // sentence. - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Third sentence.'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', - 'PauseResumeAtTheBeginningOfParagraph', async function() { - const bodyHtml = ` - <p id="p1">first sentence.</p>' - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml)); - this.triggerReadSelectedText(); - - // Speaks until the second word. - this.mockTts.speakUntilCharIndex(6); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'first sentence.'); - - // Hitting pause will stop the current TTS. - selectToSpeak.onPauseRequested(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - - // Hitting resume will start from the remaining content of the - // paragraph. - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'sentence.'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', - 'PauseResumeInTheMiddleOfMultiParagraphs', async function() { - const bodyHtml = ` - <span id='s1'> - <p>Paragraph one.</p> - <p>Paragraph two.</p> - <p>Paragraph three.</p> - </span>' - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('s1', bodyHtml)); - this.triggerReadSelectedText(); - - // Speaks until the second word. - this.mockTts.speakUntilCharIndex(10); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph one.'); - - // Hitting pause will stop the current TTS. - selectToSpeak.onPauseRequested(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - - // Hitting resume will start from the remaining content of the - // paragraph. - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'one.'); - - // Keep reading will finish all the content. - this.mockTts.finishPendingUtterance(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph two.'); - this.mockTts.finishPendingUtterance(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph three.'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', - 'PauseResumeAfterParagraphNavigation', async function() { - const bodyHtml = ` - <span id='s1'> - <p>Paragraph one.</p> - <p>Paragraph two.</p> - <p>Paragraph three.</p> - </span>' - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('s1', bodyHtml)); - this.triggerReadSelectedText(); - - // Navigates to the next paragraph and speaks until the second word. - await selectToSpeak.onNextParagraphRequested(); - this.mockTts.speakUntilCharIndex(10); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph two.'); - - // Hitting pause and resume will start reading the remaining content - // in the second paragraph. - selectToSpeak.onPauseRequested(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'two.'); - - // Should not keep reading beyond the second paragraph. - this.mockTts.finishPendingUtterance(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', - 'PauseResumeAfterSentenceNavigation', async function() { - const bodyHtml = ` - <span id='s1'> - <p>Sentence one. Sentence two.</p> - <p>Paragraph two.</p> - </span>' - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('s1', bodyHtml)); - this.triggerReadSelectedText(); - // Navigates to the next sentence and speaks until the last word - // (i.e., "two") in the first pargraph. - await selectToSpeak.onNextSentenceRequested(); - this.mockTts.speakUntilCharIndex(23); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Sentence two.'); - - // Hitting pause and resume will start reading the remaining content - // in the first paragraph. - selectToSpeak.onPauseRequested(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'two.'); - - // Should not keep reading beyond the first paragraph. - this.mockTts.finishPendingUtterance(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', - 'PauseResumeAtTheEndOfNodeGroupItem', async function() { - const bodyHtml = ` - <p id="p1">Sentence <span>one</span>. Sentence two.</p> - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml)); - this.triggerReadSelectedText(); - - // Finishes the second word. - this.mockTts.speakUntilCharIndex(13); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Sentence one . Sentence two.'); - - // Hitting pause will stop the current TTS. - selectToSpeak.onPauseRequested(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - - // Hitting resume will start from the remaining content of the - // paragraph. - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], '. Sentence two.'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', - 'PauseResumeFromKeystrokeSelection', async function() { - const bodyHtml = - '<p>This is some <b>bold</b> text</p><p>Second paragraph</p>'; - const setFocusCallback = this.newCallback(root => { - const firstNode = this.findTextNode(root, 'This is some '); - const lastNode = this.findTextNode(root, 'Second paragraph'); - // Sets the selection from "is some" to "Second". - chrome.automation.setDocumentSelection({ - anchorObject: firstNode, - anchorOffset: 5, - focusObject: lastNode, - focusOffset: 6, - }); - }); - const root = await this.runWithLoadedTree(bodyHtml); - root.addEventListener( - 'documentSelectionChanged', this.newCallback(function(event) { - this.triggerReadSelectedText(); - - // Speaks the first word 'is', the char index will count from the - // beginning of the node (i.e., from "This"). - this.mockTts.speakUntilCharIndex(8); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'is some bold text'); - - // Hitting pause will stop the current TTS. - selectToSpeak.onPauseRequested(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - - // Hitting resume will start from the remaining content of the - // paragraph. - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'some bold text'); - - // Keep reading will finish all the content. - this.mockTts.finishPendingUtterance(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Second'); - }), - false); - setFocusCallback(root); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'NextSentence', async function() { - const bodyHtml = ` - <p id="p1">This is the first. This is the second.</p>' - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml)); - this.triggerReadSelectedText(); - - // Speaks the first word. - this.mockTts.speakUntilCharIndex(5); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'This is the first. This is the second.'); - - // Hitting next sentence will start another TTS. - await selectToSpeak.onNextSentenceRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'This is the second.'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'NextSentenceWithinParagraph', - async function() { - const bodyHtml = ` - <p id="p1">Sent 1. <span id="s1">Sent 2.</span> Sent 3. Sent 4.</p> - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('s1', bodyHtml)); - this.triggerReadSelectedText(); - - // Speaks the first word. - this.mockTts.speakUntilCharIndex(5); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Sent 2.'); - - // Hitting next sentence will start from the next sentence. - selectToSpeak.onNextSentenceRequested(); - this.waitOneEventLoop(() => { - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Sent 3. Sent 4.'); - }); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'NextSentenceAcrossParagraph', - async function() { - const bodyHtml = ` - <p id="p1">Sent 1.</p> - <p id="p2">Sent 2. Sent 3.</p>' - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml)); - this.triggerReadSelectedText(); - - // Speaks the first word. - this.mockTts.speakUntilCharIndex(5); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Sent 1.'); - - // Hitting next sentence will star from the next paragraph as there - // is no more sentence in the current paragraph. - selectToSpeak.onNextSentenceRequested(); - this.waitOneEventLoop(() => { - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Sent 2. Sent 3.'); - }); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'PrevSentence', async function() { - const bodyHtml = ` - <p id="p1">First sentence. Second sentence. Third sentence.</p>' - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml)); - this.triggerReadSelectedText(); - - // Speaks util the start of the second sentence. - this.mockTts.speakUntilCharIndex(33); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'First sentence. Second sentence. Third sentence.'); - - // Hitting prev sentence will start another TTS. - await selectToSpeak.onPreviousSentenceRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'Second sentence. Third sentence.'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'PrevSentenceFromMiddleOfSentence', - async function() { - const bodyHtml = ` - <p id="p1">First sentence. Second sentence. Third sentence.</p>' - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml)); - this.triggerReadSelectedText(); - - // Speaks util the start of "sentence" in "Second sentence". - this.mockTts.speakUntilCharIndex(23); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'First sentence. Second sentence. Third sentence.'); - - // Hitting prev sentence will start another TTS. - await selectToSpeak.onPreviousSentenceRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'First sentence. Second sentence. Third sentence.'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'PrevSentenceWithinParagraph', - async function() { - const bodyHtml = ` - <p id="p1">Sent 0. Sent 1. <span id="s1">Sent 2.</span> Sent 3.</p> - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('s1', bodyHtml)); - this.triggerReadSelectedText(); - - // Supposing we are at the start of the sentence. - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Sent 2.'); - - // Hitting previous sentence will start from the previous sentence. - selectToSpeak.onPreviousSentenceRequested(); - this.waitOneEventLoop(() => { - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Sent 1. Sent 2. Sent 3.'); - }); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'PrevSentenceAcrossParagraph', - async function() { - const bodyHtml = ` - <p id="p1">Sent 1. Sent 2.</p> - <p id="p2">Sent 3.</p>' - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p2', bodyHtml)); - this.triggerReadSelectedText(); - - // We are at the start of the sentence. - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Sent 3.'); - - // Hitting previous sentence will start from the last sentence in - // the previous paragraph as there is no more sentence in the - // current paragraph. - selectToSpeak.onPreviousSentenceRequested(); - this.waitOneEventLoop(() => { - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Sent 2.'); - }); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'ChangeSpeedWhilePlaying', - async function() { - chrome.settingsPrivate.setPref('settings.tts.speech_rate', 1.2); - const bodyHtml = ` - <p id="p1">Paragraph 1</p>' - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml)); - this.triggerReadSelectedText(); - - // Speaks the first word. - this.mockTts.speakUntilCharIndex(10); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph 1'); - assertEquals(this.mockTts.getOptions().rate, 1.2); - - // Changing speed will resume with the remaining content of the - // current sentence. - selectToSpeak.onChangeSpeedRequested(1.5); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - - // Wait an event loop so all pending promises are resolved prior to - // asserting that TTS resumed with the proper rate. - setTimeout( - this.newCallback(() => { - // Should resume TTS with the remaining content with adjusted - // rate. - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.getOptions().rate, 1.8); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], '1'); - }), - 0); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'RetainsSpeedChange', - async function() { - chrome.settingsPrivate.setPref('settings.tts.speech_rate', 1.0); - const bodyHtml = ` - <p id="p1">Paragraph 1</p>' - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml)); - this.triggerReadSelectedText(); - - // Changing speed then exit. - selectToSpeak.onChangeSpeedRequested(1.5); - selectToSpeak.onExitRequested(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - - // Next TTS session should remember previous rate. - this.triggerReadSelectedText(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.getOptions().rate, 1.5); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'ChangeSpeedWhilePaused', - async function() { - chrome.settingsPrivate.setPref('settings.tts.speech_rate', 1.2); - const bodyHtml = ` - <p id="p1">Paragraph 1</p>' - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml)); - this.triggerReadSelectedText(); - - // Speaks the first word. - this.mockTts.speakUntilCharIndex(10); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph 1'); - assertEquals(this.mockTts.getOptions().rate, 1.2); - - // User-intiated pause. - selectToSpeak.onPauseRequested(); - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - - // Changing speed will remain paused. - selectToSpeak.onChangeSpeedRequested(1.5); - - // Wait an event loop so all pending promises are resolved prior to - // asserting that TTS remains paused. - setTimeout(this.newCallback(() => { - assertFalse(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - }, 0)); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'ResumeAtTheEndOfParagraph', - async function() { - const bodyHtml = ` - <p id="p1">Paragraph 1</p> - <p id="p2">Paragraph 2</p> - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml)); - this.triggerReadSelectedText(); - - // Finishes the current utterance. - this.mockTts.finishPendingUtterance(); - - // Hitting resume will start the next paragraph. - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'Paragraph 2'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'ResumeAtTheEndOfUserSelection', - async function() { - const bodyHtml = ` - <p id="p1">Sentence <span id="s1">one</span>. Sentence two.</p> - <p id="p2">Paragraph 2</p> - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('s1', bodyHtml)); - this.triggerReadSelectedText(); - - // Finishes the current utterance. - this.mockTts.finishPendingUtterance(); - - // Hitting resume will start the remaining content. - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], '. Sentence two.'); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'ResumeFromSelectionEndingInSpace', - async function() { - const bodyHtml = '<p>This is some text with space.</p>'; - const setFocusCallback = this.newCallback(root => { - const node = this.findTextNode(root, 'This is some text with space.'); - // Sets the selection to "This ". - chrome.automation.setDocumentSelection({ - anchorObject: node, - anchorOffset: 0, - focusObject: node, - focusOffset: 5, - }); - }); - const root = await this.runWithLoadedTree(bodyHtml); - root.addEventListener( - 'documentSelectionChanged', this.newCallback(event => { - this.triggerReadSelectedText(); - - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'This'); - - // Finishes the current utterance. - this.mockTts.finishPendingUtterance(); - - // Hitting resume will start from the remaining content of the - // paragraph. - selectToSpeak.onResumeRequested(); - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], - 'is some text with space.'); - }), - false); - setFocusCallback(root); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'ResizeWhilePlaying', - async function() { - const longLine = - 'Second paragraph is longer than 300 pixels and will wrap when' + - 'resized'; - const bodyHtml = ` - <script type="text/javascript"> - function doResize() { - document.getElementById('resize').style.width = '100px'; - } - </script> - <div id="content"> - <p>First paragraph</p> - <p id='resize' style='width:300px; font-size: 1em'> - ${longLine} - </p> - </div> - <button onclick="doResize()">Resize</button> - `; - const root = await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('content', bodyHtml)); - this.triggerReadSelectedText(); - - // Speaks the first paragraph. - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], 'First paragraph'); - - const resizeButton = - root.find({role: 'button', attributes: {name: 'Resize'}}); - - // Wait for click event, at which point the automation tree should - // be updated from the resize. - resizeButton.addEventListener(EventType.CLICKED, this.newCallback(() => { - // Trigger next node group by completing first TTS request. - this.mockTts.finishPendingUtterance(); - - // Should still read second paragraph, even though some nodes - // were invalided from the resize. - assertTrue(this.mockTts.currentlySpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 1); - this.assertEqualsCollapseWhitespace( - this.mockTts.pendingUtterances()[0], longLine); - })); - - // Perform resize. - resizeButton.doDefault(); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', - 'RemainsActiveAfterCompletingUtterance', async function() { - const bodyHtml = '<p id="p1">Paragraph 1</p>'; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml)); - // Simulate starting and completing TTS. - this.triggerReadSelectedText(); - this.mockTts.finishPendingUtterance(); - - // Should remain in speaking state. - assertEquals(selectToSpeak.state_, SelectToSpeakState.SPEAKING); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', - 'AutoDismissesIfNavigationControlsDisabled', async function() { - const bodyHtml = '<p id="p1">Paragraph 1</p>'; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml)); - - // Disable navigation controls. - selectToSpeak.prefsManager_.navigationControlsEnabled_ = false; - - // Simulate starting and completing TTS. - this.triggerReadSelectedText(); - this.mockTts.finishPendingUtterance(); - - // Should auto-dismiss. - assertEquals(selectToSpeak.state_, SelectToSpeakState.INACTIVE); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'NavigatesToNextParagraphQuickly', - async function() { - const bodyHtml = ` - <p id="p1">Paragraph 1</p> - <p id="p2">Paragraph 2</p>' - `; - await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml)); - // Have mock TTS engine wait to send events so we can simulate a - // delayed 'start' event. - this.mockTts.setWaitToSendEvents(true); - this.triggerReadSelectedText(); - const speakOptions = this.mockTts.getOptions(); - - // Navigate to next paragraph before speech begins. - selectToSpeak.onNextParagraphRequested(); - - this.waitOneEventLoop(() => { - // Manually triggered delayed events. - this.mockTts.sendPendingEvents(); - - // Should remain in speaking state. - assertEquals(selectToSpeak.state_, SelectToSpeakState.SPEAKING); - }); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'SetsInitialFocusToPanel', - async function() { - const bodyHtml = '<p id="p1">Sample text</p>'; - const root = await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml)); - const desktop = root.parent.root; - - // Wait for button in STS panel to be focused. - // Test will fail if panel is never focused. - this.waitForPanelFocus(desktop, () => {}); - - // Trigger STS, which will initially set focus to the panel. - this.triggerReadSelectedText(); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', - 'KeyboardShortcutKeepsFocusInPanel', async function() { - const bodyHtml = '<p id="p1">Sample text</p>'; - const root = await this.runWithLoadedTree( - this.generateHtmlWithSelectedElement('p1', bodyHtml)); - const desktop = root.parent.root; - - // Wait for button within STS panel is focused. - this.waitForPanelFocus(desktop, () => { - // Remove text selection. - const textNode = this.findTextNode(root, 'Sample text'); - chrome.automation.setDocumentSelection({ - anchorObject: textNode, - anchorOffset: 0, - focusObject: textNode, - focusOffset: 0, - }); - - // Perform Search key + S, which should restore focus to - // panel. - selectToSpeak.sendMockSelectToSpeakKeysPressedChanged( - [SelectToSpeakConstants.SEARCH_KEY_CODE]); - selectToSpeak.sendMockSelectToSpeakKeysPressedChanged([ - SelectToSpeakConstants.SEARCH_KEY_CODE, - SelectToSpeakConstants.READ_SELECTION_KEY_CODE, - ]); - selectToSpeak.sendMockSelectToSpeakKeysPressedChanged( - [SelectToSpeakConstants.SEARCH_KEY_CODE]); - selectToSpeak.sendMockSelectToSpeakKeysPressedChanged([]); - - // Verify focus is still on button within panel. - chrome.automation.getFocus(this.newCallback(focusedNode => { - assertEquals(focusedNode.role, RoleType.TOGGLE_BUTTON); - assertTrue(this.isNodeWithinPanel(focusedNode)); - })); - }); - - // Trigger STS, which will initially set focus to the panel. - this.triggerReadSelectedText(); - }); - -AX_TEST_F( - 'SelectToSpeakMV2NavigationControlTest', 'SelectingWindowDoesNotShowPanel', - async function() { - const bodyHtml = ` - <title>Test</title> - <div style="position: absolute; top: 300px;"> - Hello - </div> - `; - const root = await this.runWithLoadedTree(bodyHtml); - // Expect call to updateSelectToSpeakPanel to set panel to be hidden. - chrome.accessibilityPrivate.updateSelectToSpeakPanel = - this.newCallback(visible => { - assertFalse(visible); - }); - - // Trigger mouse selection on a part of the page where no text nodes - // exist, should select entire page. - const mouseEvent = { - screenX: root.location.left + 1, - screenY: root.location.top + 1, - }; - this.triggerReadMouseSelectedText(mouseEvent, mouseEvent); - });
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_prefs_test.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_prefs_test.js deleted file mode 100644 index 061fca9e..0000000 --- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_prefs_test.js +++ /dev/null
@@ -1,285 +0,0 @@ -// Copyright 2018 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -GEN_INCLUDE(['select_to_speak_e2e_test_base.js']); -GEN_INCLUDE(['../../common/testing/mock_storage.js']); - -/** - * Browser tests for Select-to-speak's preferences and prefs migration. - */ -SelectToSpeakMV2PrefsTest = class extends SelectToSpeakE2ETest { - /** @override */ - constructor() { - super(); - this.mockStorage_ = MockStorage; - chrome.storage = this.mockStorage_; - - chrome.i18n = { - getMessage(msgid) { - return msgid; - }, - }; - } - - /** @override */ - async setUpDeferred() { - await super.setUpDeferred(); - await this.resetStorage(); - } - - async resetStorage() { - this.mockStorage_.clear(); - await selectToSpeak.prefsManager_.initPreferences(); - } - - // This must be done before setting STS rate and pitch for tests to work - // properly. - setGlobalRateAndPitch(rate, pitch) { - chrome.settingsPrivate.setPref('settings.tts.speech_rate', rate); - chrome.settingsPrivate.setPref('settings.tts.speech_pitch', pitch); - } - - setStsRateAndPitch(rate, pitch) { - this.mockStorage_.sync.set({rate}); - this.mockStorage_.sync.set({pitch}); - } - - setStoragePrefs(prefs) { - this.mockStorage_.sync.set(prefs); - - for (const [pref, value] of Object.keys(prefs)) { - if (value === undefined) { - this.mockStorage_.sync.remove(pref); - } - } - } - - ensurePrefsRemovedAndGlobalSetTo(rate, pitch) { - const onPrefsRemovedFromStorage = this.newCallback(() => { - // Once prefs are removed from storage, make sure the global prefs are - // updated to the appropriate values. - chrome.settingsPrivate.getPref( - 'settings.tts.speech_rate', this.newCallback(pref => { - assertEquals(rate, pref.value); - })); - chrome.settingsPrivate.getPref( - 'settings.tts.speech_pitch', this.newCallback(pref => { - assertEquals(pitch, pref.value); - })); - }); - this.mockStorage_.onChanged.addListener(prefs => { - // checks that rate and pitch are removed. - if (prefs !== undefined && !('rate' in prefs) && !('pitch' in prefs)) { - onPrefsRemovedFromStorage(); - } - }); - } - - async getStoragePrefs(prefNames) { - // Filter list from this.mockStorage_.sync.get, which returns all prefs. - const allPrefs = - await new Promise(resolve => this.mockStorage_.sync.get([], resolve)); - const prefs = {}; - for (const prefName of prefNames) { - if (allPrefs.hasOwnProperty(prefName)) { - prefs[prefName] = allPrefs[prefName]; - } - } - return prefs; - } - - async getSettingsPrefs(prefNames) { - const prefs = {}; - for (const prefName of prefNames) { - const {value} = await new Promise( - resolve => chrome.settingsPrivate.getPref(prefName, resolve)); - prefs[prefName] = value; - } - return prefs; - } - - async setStoragePrefsAndMigrate(prefs) { - // Clear out Storage Prefs and Listeners. - this.mockStorage_.clear(); - - // Load Storage Prefs. - this.mockStorage_.sync.set(prefs); - - // Reinitialize and Migrate Prefs. - await selectToSpeak.prefsManager_.initPreferences(); - } - - async ensureStoragePrefsRemoved() { - const prefs = await this.getStoragePrefs( - SelectToSpeakMV2PrefsTest.STORAGE_PREF_NAMES); - assertEqualsJSON(prefs, {}, 'Storage Prefs still present.'); - } - - async ensureSettingsPrefsIncludes(expectedPrefs) { - const actualPrefs = await this.getSettingsPrefs(Object.keys(expectedPrefs)); - assertEqualsJSON( - expectedPrefs, actualPrefs, - 'Settings Prefs don\'t match expected results.'); - } -}; - -SelectToSpeakMV2PrefsTest.STORAGE_PREF_NAMES = [ - 'backgroundShading', - 'enhancedNetworkVoices', - 'enhancedVoiceName', - 'enhancedVoicesDialogShown', - 'highlightColor', - 'navigationControls', - 'voice', - 'voiceSwitching', - 'wordHighlight', -]; - -// TODO(katie): Test no alert -- this is hard because it happens last. -TEST_F( - 'SelectToSpeakMV2PrefsTest', 'RemovesPrefsWithNoAlertIfAllDefault', - function() { - this.setGlobalRateAndPitch(1.0, 1.0); - this.setStsRateAndPitch(1.0, 1.0); - this.mockStorage_.callOnChangedListeners(); - - this.ensurePrefsRemovedAndGlobalSetTo(1.0, 1.0); - }); - -// TODO(katie): Test no alert -- this is hard because it happens last. -TEST_F( - 'SelectToSpeakMV2PrefsTest', 'RemovesPrefsWithNoAlertIfAllEqual', - function() { - this.setGlobalRateAndPitch(1.5, 1.8); - this.setStsRateAndPitch(1.5, 1.8); - this.mockStorage_.callOnChangedListeners(); - - this.ensurePrefsRemovedAndGlobalSetTo(1.5, 1.8); - }); - -TEST_F( - 'SelectToSpeakMV2PrefsTest', 'SavesNonDefaultStsPrefsToGlobal', function() { - this.setGlobalRateAndPitch(1.0, 1.0); - this.setStsRateAndPitch(2.0, 2.5); - this.mockStorage_.callOnChangedListeners(); - - this.ensurePrefsRemovedAndGlobalSetTo(2.0, 2.5); - }); - -TEST_F( - 'SelectToSpeakMV2PrefsTest', - 'DoesNotSaveNonDefaultStsPrefsToGlobalIfGlobalChanged', function() { - this.setGlobalRateAndPitch(1.0, 1.5); - this.setStsRateAndPitch(1.0, 2.5); - this.mockStorage_.callOnChangedListeners(); - - this.ensurePrefsRemovedAndGlobalSetTo(1.0, 1.5); - }); - -TEST_F( - 'SelectToSpeakMV2PrefsTest', 'DoesNotSaveStsPrefsToGlobalIfGlobalChanged', - function() { - this.setGlobalRateAndPitch(2.0, 1.0); - this.setStsRateAndPitch(1.0, 1.0); - this.mockStorage_.callOnChangedListeners(); - - this.ensurePrefsRemovedAndGlobalSetTo(2.0, 1.0); - }); - -// Clears all storage prefs, runs the migration in prefs_manager, verifies that -// prefs are set to their default values, and verifies that there are still no -// storage prefs. This mimics the state of a fresh user profile. -AX_TEST_F( - 'SelectToSpeakMV2PrefsTest', - 'DefaultSettingsPrefsSetAfterNoStoragePrefsSet', async function() { - // Set no storage prefs. - await this.setStoragePrefsAndMigrate({}); - - // Check settings prefs match expected defaults. - await this.ensureSettingsPrefsIncludes({ - [PrefsManager.BACKGROUND_SHADING_KEY]: false, - [PrefsManager.ENHANCED_NETWORK_VOICES_KEY]: false, - [PrefsManager.ENHANCED_VOICE_NAME_KEY]: 'default-wavenet', - [PrefsManager.ENHANCED_VOICES_DIALOG_SHOWN_KEY]: false, - [PrefsManager.HIGHLIGHT_COLOR_KEY]: '#5e9bff', - [PrefsManager.NAVIGATION_CONTROLS_KEY]: true, - [PrefsManager.VOICE_NAME_KEY]: 'select_to_speak_system_voice', - [PrefsManager.VOICE_SWITCHING_KEY]: false, - [PrefsManager.WORD_HIGHLIGHT_KEY]: true, - }); - - // Ensure all storage prefs deleted. - await this.ensureStoragePrefsRemoved(); - }); - -// Sets some storage prefs, runs the migration in prefs_manager, verifies that -// the prefs that were set are migrated, verifies the rest are set according to -// their default values, and verifies that storage prefs are removed. -AX_TEST_F( - 'SelectToSpeakMV2PrefsTest', - 'PrefsMigratedToSettingsAndDefaultsSetAfterSomeStoragePrefsSet', - async function() { - // Set some storage prefs. - await this.setStoragePrefsAndMigrate({ - backgroundShading: true, - enhancedNetworkVoices: true, - enhancedVoiceName: 'enhanced_cool_voice', - enhancedVoicesDialogShown: true, - navigationControls: false, - voiceSwitching: true, - }); - - // Check set storage prefs migrated to settings prefs and other settings - // prefs match expected defaults. - await this.ensureSettingsPrefsIncludes({ - [PrefsManager.BACKGROUND_SHADING_KEY]: true, - [PrefsManager.ENHANCED_NETWORK_VOICES_KEY]: true, - [PrefsManager.ENHANCED_VOICE_NAME_KEY]: 'enhanced_cool_voice', - [PrefsManager.ENHANCED_VOICES_DIALOG_SHOWN_KEY]: true, - [PrefsManager.HIGHLIGHT_COLOR_KEY]: '#5e9bff', - [PrefsManager.NAVIGATION_CONTROLS_KEY]: false, - [PrefsManager.VOICE_NAME_KEY]: 'select_to_speak_system_voice', - [PrefsManager.VOICE_SWITCHING_KEY]: true, - [PrefsManager.WORD_HIGHLIGHT_KEY]: true, - }); - - // Ensure all storage prefs deleted. - await this.ensureStoragePrefsRemoved(); - }); - -// Sets all storage prefs, runs the migration in prefs_manager, verifies all -// prefs migrated to settings prefs, and verifies that storage prefs are -// removed. -AX_TEST_F( - 'SelectToSpeakMV2PrefsTest', - 'AllPrefsMigratedToSettingsAfterAllStoragePrefsSet', async function() { - // Set all storage prefs. - await this.setStoragePrefsAndMigrate({ - backgroundShading: true, - enhancedNetworkVoices: true, - enhancedVoiceName: 'enhanced_cool_voice', - enhancedVoicesDialogShown: true, - highlightColor: '#123456', - navigationControls: false, - voice: 'cool_voice', - voiceSwitching: true, - wordHighlight: false, - }); - - // Check all storage prefs migrated to settings prefs. - await this.ensureSettingsPrefsIncludes({ - [PrefsManager.BACKGROUND_SHADING_KEY]: true, - [PrefsManager.ENHANCED_NETWORK_VOICES_KEY]: true, - [PrefsManager.ENHANCED_VOICE_NAME_KEY]: 'enhanced_cool_voice', - [PrefsManager.ENHANCED_VOICES_DIALOG_SHOWN_KEY]: true, - [PrefsManager.HIGHLIGHT_COLOR_KEY]: '#123456', - [PrefsManager.NAVIGATION_CONTROLS_KEY]: false, - [PrefsManager.VOICE_NAME_KEY]: 'cool_voice', - [PrefsManager.VOICE_SWITCHING_KEY]: true, - [PrefsManager.WORD_HIGHLIGHT_KEY]: false, - }); - - // Ensure all storage prefs deleted. - await this.ensureStoragePrefsRemoved(); - });
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_unittest.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_unittest.js deleted file mode 100644 index 2a2bfe0..0000000 --- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/select_to_speak_unittest.js +++ /dev/null
@@ -1,37 +0,0 @@ -// Copyright 2016 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -GEN_INCLUDE(['select_to_speak_e2e_test_base.js']); - -/** - * Test fixture for select_to_speak.js. - */ -SelectToSpeakMV2UnitTest = class extends SelectToSpeakE2ETest {}; - -AX_TEST_F('SelectToSpeakMV2UnitTest', 'getGSuiteAppRoot', function() { - const root = {url: 'https://docs.google.com/presentation/p/cats_r_awesome'}; - const div1 = {root}; - const frame1 = {url: 'about:blank', parent: div1}; - const div2 = {root: frame1}; - const frame2 = {url: 'about:blank', parent: div2}; - const focus = {root: frame2}; - assertEquals(getGSuiteAppRoot(focus), root); - assertEquals(getGSuiteAppRoot(div2), root); - - // Sandbox URLs should still work. - root.url = 'https://docs.sandbox.google.com/spreadsheets/s/spreadsheet'; - assertEquals(getGSuiteAppRoot(focus), root); - - // GSuite app embedded in something else - const parent = {url: 'https://www.has_embedded_doc.com'}; - const div3 = {root: parent}; - root.parent = div3; - assertEquals(getGSuiteAppRoot(focus), root); - assertEquals(getGSuiteAppRoot(div2), root); - - // Not in GSuite app - root.url = 'https://www.not_a_doc.com'; - assertEquals(getGSuiteAppRoot(focus), null); - assertEquals(getGSuiteAppRoot(div2), null); -});
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/tts_manager_unittest.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/tts_manager_unittest.js deleted file mode 100644 index 7ab35f9..0000000 --- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/tts_manager_unittest.js +++ /dev/null
@@ -1,161 +0,0 @@ -// Copyright 2021 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -GEN_INCLUDE(['select_to_speak_e2e_test_base.js']); -GEN_INCLUDE(['../../common/testing/mock_tts.js']); - -/** Mock TTS client. */ -class MockTtsClient { - constructor() { - this.receivedEvent; - } - - getTtsOptions() { - const options = /** @type {!chrome.tts.TtsOptions} */ ({}); - options.onEvent = event => this.receivedEvent = event; - return options; - } -} - -/** - * Test fixture for tts_manager.js. - */ -SelectToSpeakMV2TtsManagerUnitTest = class extends SelectToSpeakE2ETest { - constructor() { - super(); - this.mockTts = new MockTts(); - chrome.tts = this.mockTts; - } - - /** @override */ - async setUpDeferred() { - await super.setUpDeferred(); - - this.mockTtsClient = new MockTtsClient(); - this.ttsManager = new TtsManager(); - } -}; - -AX_TEST_F('SelectToSpeakMV2TtsManagerUnitTest', 'SpeakUtterance', function() { - this.ttsManager.speak( - ' text with space ', this.mockTtsClient.getTtsOptions()); - const receivedEvent = this.mockTtsClient.receivedEvent; - assertEquals(receivedEvent.type, chrome.tts.EventType.START); - assertEquals(receivedEvent.charIndex, 1); - assertTrue(this.ttsManager.isSpeaking()); -}); - -AX_TEST_F('SelectToSpeakMV2TtsManagerUnitTest', 'StopUtterance', function() { - this.ttsManager.speak( - ' text with space ', this.mockTtsClient.getTtsOptions()); - this.ttsManager.stop(); - const receivedEvent = this.mockTtsClient.receivedEvent; - assertEquals(receivedEvent.type, chrome.tts.EventType.INTERRUPTED); - assertFalse(this.ttsManager.isSpeaking()); -}); - -AX_TEST_F('SelectToSpeakMV2TtsManagerUnitTest', 'FinishUtterance', function() { - this.ttsManager.speak( - ' text with space ', this.mockTtsClient.getTtsOptions()); - - // Let TTS finish all the utterance. - this.mockTts.finishPendingUtterance(); - - const receivedEvent = this.mockTtsClient.receivedEvent; - assertEquals(receivedEvent.type, chrome.tts.EventType.END); - // The ending index will be the length of the entire string. - assertEquals(receivedEvent.charIndex, 17); - assertFalse(this.ttsManager.isSpeaking()); -}); - -AX_TEST_F( - 'SelectToSpeakMV2TtsManagerUnitTest', 'SendWordEventsWhenSpeaking', - function() { - this.ttsManager.speak( - ' text with space ', this.mockTtsClient.getTtsOptions()); - - // Let TTS finish the first word. - this.mockTts.speakUntilCharIndex(6); - let receivedEvent = this.mockTtsClient.receivedEvent; - assertEquals(receivedEvent.type, chrome.tts.EventType.WORD); - assertEquals(receivedEvent.charIndex, 6); - assertTrue(this.ttsManager.isSpeaking()); - - // Let TTS finish the second word. - this.mockTts.speakUntilCharIndex(11); - receivedEvent = this.mockTtsClient.receivedEvent; - assertEquals(receivedEvent.type, chrome.tts.EventType.WORD); - assertEquals(receivedEvent.charIndex, 11); - assertTrue(this.ttsManager.isSpeaking()); - }); - -AX_TEST_F('SelectToSpeakMV2TtsManagerUnitTest', 'PauseAndResume', function() { - const options = this.mockTtsClient.getTtsOptions(); - options.rate = 0.5; - this.ttsManager.speak(' text with space ', options); - - this.mockTts.speakUntilCharIndex(6); - assertTrue(this.ttsManager.isSpeaking()); - assertEquals(this.mockTts.getOptions().rate, 0.5); - assertEquals(this.mockTts.pendingUtterances()[0], ' text with space '); - - // Pause will stop speaking. - this.ttsManager.pause(); - let receivedEvent = this.mockTtsClient.receivedEvent; - assertEquals(receivedEvent.type, chrome.tts.EventType.PAUSE); - assertEquals(receivedEvent.charIndex, 6); - assertFalse(this.ttsManager.isSpeaking()); - - // Resume will generate new utterance for tts engine. - this.ttsManager.resume(); - receivedEvent = this.mockTtsClient.receivedEvent; - assertEquals(receivedEvent.type, chrome.tts.EventType.RESUME); - assertEquals(receivedEvent.charIndex, 6); - assertTrue(this.ttsManager.isSpeaking()); - assertEquals(this.mockTts.getOptions().rate, 0.5); - assertEquals(this.mockTts.pendingUtterances()[0], 'with space '); - - // Finish the next word and pause. - this.mockTts.speakUntilCharIndex(5); - this.ttsManager.pause(); - receivedEvent = this.mockTtsClient.receivedEvent; - assertEquals(receivedEvent.type, chrome.tts.EventType.PAUSE); - assertEquals(receivedEvent.charIndex, 11); - assertFalse(this.ttsManager.isSpeaking()); - - // Resume with a different rate. - options.rate = 1.5; - this.ttsManager.resume(options); - receivedEvent = this.mockTtsClient.receivedEvent; - assertEquals(receivedEvent.type, chrome.tts.EventType.RESUME); - assertEquals(receivedEvent.charIndex, 11); - assertTrue(this.ttsManager.isSpeaking()); - assertEquals(this.mockTts.getOptions().rate, 1.5); - assertEquals(this.mockTts.pendingUtterances()[0], 'space '); -}); - -AX_TEST_F( - 'SelectToSpeakMV2TtsManagerUnitTest', 'ResumeWithNoRemainingContent', - function() { - const options = this.mockTtsClient.getTtsOptions(); - this.ttsManager.speak(' text ', options); - - this.mockTts.speakUntilCharIndex(5); - assertTrue(this.ttsManager.isSpeaking()); - assertEquals(this.mockTts.pendingUtterances()[0], ' text '); - - // Pause will stop speaking. - this.ttsManager.pause(); - assertFalse(this.ttsManager.isSpeaking()); - - // Resume will trigger an error event. - this.ttsManager.resume(); - const receivedEvent = this.mockTtsClient.receivedEvent; - assertEquals(receivedEvent.type, chrome.tts.EventType.ERROR); - assertEquals( - receivedEvent.errorMessage, - TtsManager.ErrorMessage.RESUME_WITH_EMPTY_CONTENT); - assertFalse(this.ttsManager.isSpeaking()); - assertEquals(this.mockTts.pendingUtterances().length, 0); - });
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/ui_manager_unittest.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/ui_manager_unittest.js deleted file mode 100644 index f2017c5..0000000 --- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/mv2/ui_manager_unittest.js +++ /dev/null
@@ -1,371 +0,0 @@ -// Copyright 2021 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -GEN_INCLUDE(['select_to_speak_e2e_test_base.js']); -GEN_INCLUDE(['../../common/testing/mock_accessibility_private.js']); - -/** Focus ring color to use in testing. */ -const FOCUS_RING_COLOR = '#ff0'; - -/** Highlight color to use in testing. */ -const HIGHLIGHT_COLOR = '#00f'; - -/** Mock SelectToSpeakUiListener. */ -class MockUiListener { - constructor() { - this.onNextParagraphRequestedCalled = false; - this.onPreviousParagraphRequestedCalled = false; - this.onNextSentenceRequestedCalled = false; - this.onPreviousSentenceRequestedCalled = false; - this.onPauseRequestedCalled = false; - this.onResumeRequestedCalled = false; - this.onChangeSpeedRequestedValue = undefined; - this.onExitRequestedCalled = false; - this.onStateChangeRequestedCalled = false; - } - - onNextParagraphRequested() { - this.onNextParagraphRequestedCalled = true; - } - - onPreviousParagraphRequested() { - this.onPreviousParagraphRequestedCalled = true; - } - - onNextSentenceRequested() { - this.onNextSentenceRequestedCalled = true; - } - - onPreviousSentenceRequested() { - this.onPreviousSentenceRequestedCalled = true; - } - - onPauseRequested() { - this.onPauseRequestedCalled = true; - } - - onResumeRequested() { - this.onResumeRequestedCalled = true; - } - - onChangeSpeedRequested(speed) { - this.onChangeSpeedRequestedValue = speed; - } - - onExitRequested() { - this.onExitRequestedCalled = true; - } - - onStateChangeRequested() { - this.onStateChangeRequestedCalled = true; - } -} - -/** Mock PrefsManager. Currently just returns hard-coded values. */ -class MockPrefsManager { - backgroundShadingEnabled() { - return true; - } - - focusRingColor() { - return FOCUS_RING_COLOR; - } - - highlightColor() { - return HIGHLIGHT_COLOR; - } -} - -/** - * Test fixture for ui_manager.js. - */ -SelectToSpeakMV2UiManagerUnitTest = class extends SelectToSpeakE2ETest { - constructor() { - super(); - this.mockAccessibilityPrivate = new MockAccessibilityPrivate(); - chrome.accessibilityPrivate = this.mockAccessibilityPrivate; - - this.mockPrefsManager = null; - this.mockListener = null; - this.uiManager = null; - } - - /** @override */ - async setUpDeferred() { - await super.setUpDeferred(); - - this.mockPrefsManager = new MockPrefsManager(); - this.mockListener = new MockUiListener(); - this.uiManager = new UiManager(this.mockPrefsManager, this.mockListener); - } -}; - -AX_TEST_F( - 'SelectToSpeakMV2UiManagerUnitTest', 'NextParagraphActionCallsListener', - function() { - this.mockAccessibilityPrivate.sendSelectToSpeakPanelAction( - 'nextParagraph'); - assertTrue(this.mockListener.onNextParagraphRequestedCalled); - }); - -AX_TEST_F( - 'SelectToSpeakMV2UiManagerUnitTest', 'PreviousParagraphActionCallsListener', - function() { - this.mockAccessibilityPrivate.sendSelectToSpeakPanelAction( - 'previousParagraph'); - assertTrue(this.mockListener.onPreviousParagraphRequestedCalled); - }); - -AX_TEST_F( - 'SelectToSpeakMV2UiManagerUnitTest', 'NextSentenceActionCallsListener', - function() { - this.mockAccessibilityPrivate.sendSelectToSpeakPanelAction( - 'nextSentence'); - assertTrue(this.mockListener.onNextSentenceRequestedCalled); - }); - -AX_TEST_F( - 'SelectToSpeakMV2UiManagerUnitTest', 'PreviousSentenceActionCallsListener', - function() { - this.mockAccessibilityPrivate.sendSelectToSpeakPanelAction( - 'previousSentence'); - assertTrue(this.mockListener.onPreviousSentenceRequestedCalled); - }); - -AX_TEST_F( - 'SelectToSpeakMV2UiManagerUnitTest', 'PauseActionCallsListener', - function() { - this.mockAccessibilityPrivate.sendSelectToSpeakPanelAction('pause'); - assertTrue(this.mockListener.onPauseRequestedCalled); - }); - -AX_TEST_F( - 'SelectToSpeakMV2UiManagerUnitTest', 'ResumeActionCallsListener', - function() { - this.mockAccessibilityPrivate.sendSelectToSpeakPanelAction('resume'); - assertTrue(this.mockListener.onResumeRequestedCalled); - }); - -AX_TEST_F( - 'SelectToSpeakMV2UiManagerUnitTest', 'ChangeSpeedActionCallsListener', - function() { - this.mockAccessibilityPrivate.sendSelectToSpeakPanelAction( - 'changeSpeed', 1.2); - assertEquals(1.2, this.mockListener.onChangeSpeedRequestedValue); - }); - -AX_TEST_F( - 'SelectToSpeakMV2UiManagerUnitTest', 'ExitActionCallsListener', function() { - this.mockAccessibilityPrivate.sendSelectToSpeakPanelAction('exit'); - assertTrue(this.mockListener.onExitRequestedCalled); - }); - -AX_TEST_F( - 'SelectToSpeakMV2UiManagerUnitTest', 'StateChangeCallsListener', - function() { - this.mockAccessibilityPrivate.sendSelectToSpeakStateChangeRequest(); - assertTrue(this.mockListener.onStateChangeRequestedCalled); - }); - -AX_TEST_F('SelectToSpeakMV2UiManagerUnitTest', 'SetSelectionRect', function() { - const selectionRect = {left: 0, top: 10, width: 400, height: 200}; - - // No focus rings to start. - let focusRings = this.mockAccessibilityPrivate.getFocusRings(); - assertEquals(0, focusRings.length); - - this.uiManager.setSelectionRect(selectionRect); - - // Focus ring created to given rect. - focusRings = this.mockAccessibilityPrivate.getFocusRings(); - assertEquals(1, focusRings.length); - assertEquals(1, focusRings[0].rects.length); - assertEquals(selectionRect, focusRings[0].rects[0]); - assertEquals(FOCUS_RING_COLOR, focusRings[0].color); -}); - -AX_TEST_F('SelectToSpeakMV2UiManagerUnitTest', 'UpdatesUi', function() { - const textNode = { - role: 'staticText', - name: 'Test', - location: {left: 20, top: 10, width: 100, height: 50}, - }; - const nodeGroup = ParagraphUtils.buildNodeGroup( - [textNode], 0 /* index */, {splitOnLanguage: false}); - - // No focus rings to start. - let focusRings = this.mockAccessibilityPrivate.getFocusRings(); - assertEquals(0, focusRings.length); - - this.uiManager.update( - nodeGroup, textNode, null, - {showPanel: true, paused: false, speechRateMultiplier: 1}); - - // Focus ring created highlighting the text node. - focusRings = this.mockAccessibilityPrivate.getFocusRings(); - assertEquals(1, focusRings.length); - assertEquals(1, focusRings[0].rects.length); - assertEquals(textNode.location, focusRings[0].rects[0]); - assertEquals(FOCUS_RING_COLOR, focusRings[0].color); - - // Panel created with correct state. - const panelState = this.mockAccessibilityPrivate.getSelectToSpeakPanelState(); - assertTrue(panelState.show); - assertEquals(textNode.location, panelState.anchor); - assertFalse(panelState.isPaused); - assertEquals(1, panelState.speed); - - // No highlights. - const highlightRects = this.mockAccessibilityPrivate.getHighlightRects(); - assertEquals(0, highlightRects.length); -}); - -// This represents how Google Docs renders Canvas accessibility as of -// October 24 2022. -AX_TEST_F( - 'SelectToSpeakMV2UiManagerUnitTest', 'UpdatesUiMultipleNodesInBlock', - function() { - const root = { - role: 'svgRoot', - location: {left: 0, top: 0, width: 500, height: 50}, - }; - const group = { - role: 'paragraph', - parent: root, - display: 'inline', - location: {left: 20, top: 10, width: 200, height: 10}, - }; - const text1 = { - role: 'inlineTextBox', - name: '1973', - parent: group, - location: {left: 20, top: 10, width: 100, height: 10}, - }; - const text2 = { - role: 'inlineTextBox', - name: 'Roe', - parent: group, - location: {left: 120, top: 10, width: 100, height: 10}, - }; - const nodeGroup = ParagraphUtils.buildNodeGroup( - [text1, text2], /*index=*/ 0, {splitOnLanguage: false}); - - assertEquals(group, nodeGroup.blockParent); - - this.uiManager.update( - nodeGroup, text1, null, - {showPanel: true, paused: false, speechRateMultiplier: 1}); - - // When there are multiple nodes in a group, the parent should be - // highlighted instead of the individual node. - focusRings = this.mockAccessibilityPrivate.getFocusRings(); - assertEquals(1, focusRings.length); - assertEquals(1, focusRings[0].rects.length); - assertEquals(group.location, focusRings[0].rects[0]); - }); - -AX_TEST_F('SelectToSpeakMV2UiManagerUnitTest', 'UpdatesUiNoPanel', function() { - const textNode = { - role: 'staticText', - name: 'Test', - location: {left: 20, top: 10, width: 100, height: 50}, - }; - const nodeGroup = ParagraphUtils.buildNodeGroup( - [textNode], 0 /* index */, {splitOnLanguage: false}); - - // No focus rings to start. - let focusRings = this.mockAccessibilityPrivate.getFocusRings(); - assertEquals(0, focusRings.length); - - this.uiManager.update(nodeGroup, textNode, null, {showPanel: false}); - - // Focus ring created highlighting the text node. - focusRings = this.mockAccessibilityPrivate.getFocusRings(); - assertEquals(1, focusRings.length); - assertEquals(1, focusRings[0].rects.length); - assertEquals(textNode.location, focusRings[0].rects[0]); - assertEquals(FOCUS_RING_COLOR, focusRings[0].color); - - // No panel. - const panelState = this.mockAccessibilityPrivate.getSelectToSpeakPanelState(); - assertFalse(panelState.show); -}); - -AX_TEST_F( - 'SelectToSpeakMV2UiManagerUnitTest', 'UpdatesUiWithWordHighlight', - function() { - const wordBounds = {left: 25, top: 5, width: 20, height: 40}; - const wordStart = 0; - const wordEnd = 5; - const textNode = { - role: 'staticText', - name: 'Hello world', - location: {left: 20, top: 10, width: 100, height: 50}, - boundsForRange: (start, end, callback) => { - assertEquals(wordStart, start); - assertEquals(wordEnd, end); - callback(wordBounds); - }, - }; - const nodeGroup = ParagraphUtils.buildNodeGroup( - [textNode], 0 /* index */, {splitOnLanguage: false}); - - // No highlights to start. - let highlightRects = this.mockAccessibilityPrivate.getHighlightRects(); - assertEquals(0, highlightRects.length); - - this.uiManager.update( - nodeGroup, textNode, {start: 0, end: 5}, - {showPanel: true, paused: false, speechRateMultiplier: 1}); - - // Word highlight created. - highlightRects = this.mockAccessibilityPrivate.getHighlightRects(); - assertEquals(1, highlightRects.length); - assertEquals(wordBounds, highlightRects[0]); - assertEquals( - HIGHLIGHT_COLOR, this.mockAccessibilityPrivate.getHighlightColor()); - - // AccessibilityPrivate informed of change. - assertEquals( - wordBounds, this.mockAccessibilityPrivate.getSelectToSpeakFocus()); - - // Reset mockAccessibilityPrivate. - this.mockAccessibilityPrivate.clearHighlightRects(); - this.mockAccessibilityPrivate.clearSelectToSpeakFocus(); - - // When paused, AccessibilityPrivate is not informed of the change, but - // highlights are still drawn visually. - this.uiManager.update( - nodeGroup, textNode, {start: 0, end: 5}, - {showPanel: true, paused: true, speechRateMultiplier: 1}); - - highlightRects = this.mockAccessibilityPrivate.getHighlightRects(); - assertEquals(1, highlightRects.length); - assertEquals(wordBounds, highlightRects[0]); - assertEquals( - HIGHLIGHT_COLOR, this.mockAccessibilityPrivate.getHighlightColor()); - assertEquals(null, this.mockAccessibilityPrivate.getSelectToSpeakFocus()); - }); - -AX_TEST_F('SelectToSpeakMV2UiManagerUnitTest', 'ClearsUI', function() { - // Start with a focus ring. - this.uiManager.setSelectionRect({left: 0, top: 10, width: 400, height: 200}); - let focusRings = this.mockAccessibilityPrivate.getFocusRings(); - assertEquals(1, focusRings.length); - - this.uiManager.clear(); - - // Focus rings are gone. - focusRings = this.mockAccessibilityPrivate.getFocusRings(); - assertEquals(1, focusRings.length); - assertEquals(0, focusRings[0].rects.length); - - // Panel is not visible. - const panelState = this.mockAccessibilityPrivate.getSelectToSpeakPanelState(); - assertFalse(panelState.show); - - // No highlights. - const highlightRects = this.mockAccessibilityPrivate.getHighlightRects(); - assertEquals(0, highlightRects.length); -});
diff --git a/chrome/browser/resources/settings/autofill_page/autofill_ai_section.ts b/chrome/browser/resources/settings/autofill_page/autofill_ai_section.ts index b4f54c5..4eb32ba 100644 --- a/chrome/browser/resources/settings/autofill_page/autofill_ai_section.ts +++ b/chrome/browser/resources/settings/autofill_page/autofill_ai_section.ts
@@ -203,7 +203,7 @@ this.entityDataManager_.addEntityInstancesChangedListener( this.entityInstancesChangedListener_); - this.entityDataManager_.getAllEntityTypes().then( + this.entityDataManager_.getWritableEntityTypes().then( (entityTypes: EntityType[]) => { this.completeEntityTypesList_ = entityTypes.sort(this.entityTypesComparator_);
diff --git a/chrome/browser/resources/settings/autofill_page/entity_data_manager_proxy.ts b/chrome/browser/resources/settings/autofill_page/entity_data_manager_proxy.ts index 6d41948..f07bd30d 100644 --- a/chrome/browser/resources/settings/autofill_page/entity_data_manager_proxy.ts +++ b/chrome/browser/resources/settings/autofill_page/entity_data_manager_proxy.ts
@@ -37,9 +37,9 @@ getEntityInstanceByGuid(guid: string): Promise<EntityInstance>; /** - * Returns a list of all enabled entity types. + * Returns a list of all enabled entity types which are not read only. */ - getAllEntityTypes(): Promise<EntityType[]>; + getWritableEntityTypes(): Promise<EntityType[]>; /** * Returns a list of all attribute types that can be set on an entity @@ -90,8 +90,8 @@ return chrome.autofillPrivate.getEntityInstanceByGuid(guid); } - getAllEntityTypes() { - return chrome.autofillPrivate.getAllEntityTypes(); + getWritableEntityTypes() { + return chrome.autofillPrivate.getWritableEntityTypes(); } getAllAttributeTypesForEntityTypeName(entityTypeName: number) {
diff --git a/chrome/browser/resources/settings/people_page/sync_controls.ts b/chrome/browser/resources/settings/people_page/sync_controls.ts index be8ab78b..b10d25c7 100644 --- a/chrome/browser/resources/settings/people_page/sync_controls.ts +++ b/chrome/browser/resources/settings/people_page/sync_controls.ts
@@ -71,7 +71,8 @@ value: false, computed: 'syncControlsHidden_(' + 'syncStatus.signedIn, syncStatus.disabled, ' + - 'syncStatus.hasError, isAccountSettingsPage_)', + 'syncStatus.hasError, isAccountSettingsPage_, ' + + 'syncPrefs.localSyncEnabled)', reflectToAttribute: true, }, @@ -342,11 +343,12 @@ // The account page is not shown when the user is not signed in or if they // are in sign in pending state, so we don't need to check for the signed in // state here. However, the controls should be hidden if there is a - // passphrase error. + // passphrase error or the user has local sync enabled. // <if expr="not is_chromeos"> if (this.isAccountSettingsPage_) { return !!this.syncStatus.hasError || - this.syncStatus.statusAction === StatusAction.ENTER_PASSPHRASE; + this.syncStatus.statusAction === StatusAction.ENTER_PASSPHRASE || + (!!this.syncPrefs && this.syncPrefs.localSyncEnabled); } // </if>
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.ts b/chrome/browser/resources/settings/privacy_page/privacy_page.ts index f832d42a..18ced3e 100644 --- a/chrome/browser/resources/settings/privacy_page/privacy_page.ts +++ b/chrome/browser/resources/settings/privacy_page/privacy_page.ts
@@ -24,7 +24,7 @@ import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js'; import {assert, assertNotReached} from 'chrome://resources/js/assert.js'; import {focusWithoutInk} from 'chrome://resources/js/focus_without_ink.js'; -import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {afterNextRender, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; import {HatsBrowserProxyImpl, TrustSafetyInteraction} from '../hats_browser_proxy.js'; import {loadTimeData} from '../i18n_setup.js'; @@ -147,9 +147,10 @@ this.shouldShowDbdDeletionConfirmationToast_ = false; } - setTimeout(() => { - // Focus after a timeout to ensure any a11y messages get read before - // screen readers read out the newly focused element. + afterNextRender(this, () => { + // Focus after next render has completed to ensure any a11y messages get + // read and the UI has updated before screen readers read out the newly + // focused element. const toFocus = this.shadowRoot!.querySelector<HTMLElement>('#clearBrowsingData'); assert(toFocus);
diff --git a/chrome/browser/resources/settings_shared/people_page/sync_browser_proxy.ts b/chrome/browser/resources/settings_shared/people_page/sync_browser_proxy.ts index 67e53e4..6bec3ba 100644 --- a/chrome/browser/resources/settings_shared/people_page/sync_browser_proxy.ts +++ b/chrome/browser/resources/settings_shared/people_page/sync_browser_proxy.ts
@@ -91,6 +91,7 @@ extensionsManaged: boolean; extensionsRegistered: boolean; extensionsSynced: boolean; + localSyncEnabled: boolean; passphraseRequired: boolean; passwordsManaged: boolean; passwordsRegistered: boolean;
diff --git a/chrome/browser/safe_browsing/android/client_side_detection_intelligent_scan_delegate_android.cc b/chrome/browser/safe_browsing/android/client_side_detection_intelligent_scan_delegate_android.cc index 461cbea..720c7db 100644 --- a/chrome/browser/safe_browsing/android/client_side_detection_intelligent_scan_delegate_android.cc +++ b/chrome/browser/safe_browsing/android/client_side_detection_intelligent_scan_delegate_android.cc
@@ -8,6 +8,7 @@ #include "base/debug/dump_without_crashing.h" #include "base/metrics/histogram_functions.h" #include "base/time/time.h" +#include "base/unguessable_token.h" #include "chrome/browser/safe_browsing/client_side_detection_intelligent_scan_delegate_util.h" #include "components/optimization_guide/core/model_execution/model_broker_client.h" #include "components/optimization_guide/public/mojom/model_broker.mojom-shared.h" @@ -27,6 +28,7 @@ class ClientSideDetectionIntelligentScanDelegateAndroid::Inquiry { public: Inquiry(ClientSideDetectionIntelligentScanDelegateAndroid* parent, + const base::UnguessableToken& session_id, InquireOnDeviceModelDoneCallback callback); ~Inquiry(); @@ -46,6 +48,7 @@ // owns this object. const raw_ptr<ClientSideDetectionIntelligentScanDelegateAndroid> parent_; std::unique_ptr<ModelExecutorSession> session_; + base::UnguessableToken session_id_; InquireOnDeviceModelDoneCallback callback_; std::string rendered_texts_; base::TimeTicks session_creation_start_time_; @@ -56,15 +59,18 @@ ClientSideDetectionIntelligentScanDelegateAndroid::Inquiry::Inquiry( ClientSideDetectionIntelligentScanDelegateAndroid* parent, + const base::UnguessableToken& session_id, InquireOnDeviceModelDoneCallback callback) - : parent_(parent), callback_(std::move(callback)) {} + : parent_(parent), + session_id_(session_id), + callback_(std::move(callback)) {} ClientSideDetectionIntelligentScanDelegateAndroid::Inquiry::~Inquiry() = default; void ClientSideDetectionIntelligentScanDelegateAndroid::Inquiry::Start( const std::string& rendered_texts) { - CHECK(!session_) << "Start() should only be called once."; + CHECK(!session_) << "Start() should only be called once per inquiry."; rendered_texts_ = rendered_texts; using ::optimization_guide::SessionConfigParams; @@ -148,7 +154,7 @@ // Reset session immediately so that future inference is not affected by the // old context. - parent_->ResetOnDeviceSession(); + parent_->CancelSession(session_id_); } ClientSideDetectionIntelligentScanDelegateAndroid:: @@ -216,29 +222,36 @@ return true; } -void ClientSideDetectionIntelligentScanDelegateAndroid::InquireOnDeviceModel( +std::optional<base::UnguessableToken> +ClientSideDetectionIntelligentScanDelegateAndroid::InquireOnDeviceModel( std::string rendered_texts, InquireOnDeviceModelDoneCallback callback) { if (!IsOnDeviceModelAvailable(/*log_failed_eligibility_reason=*/false)) { std::move(callback).Run(IntelligentScanResult::Failure( IntelligentScanResult::kModelVersionUnavailable)); - return; + return std::nullopt; } - // TODO(crbug.com/444148365): Intelligent scan delegate is per profile and may - // be shared with multiple ClientSideDetectionHost, so it is possible that one - // session created by one ClientSideDetectionHost is still alive when another - // ClientSideDetectionHost tries to create a new session. We should support - // multiple sessions per delegate. - ResetOnDeviceSession(); - - current_inquiry_ = std::make_unique<Inquiry>(this, std::move(callback)); - current_inquiry_->Start(rendered_texts); + base::UnguessableToken session_id = base::UnguessableToken::Create(); + std::unique_ptr<Inquiry> new_inquiry = + std::make_unique<Inquiry>(this, session_id, std::move(callback)); + inquiries_[session_id] = std::move(new_inquiry); + inquiries_[session_id]->Start(rendered_texts); + return session_id; } -bool ClientSideDetectionIntelligentScanDelegateAndroid::ResetOnDeviceSession() { - bool did_reset_session = !!current_inquiry_; - current_inquiry_.reset(); +bool ClientSideDetectionIntelligentScanDelegateAndroid::CancelSession( + const base::UnguessableToken& session_id) { + if (!inquiries_.contains(session_id)) { + return false; + } + inquiries_.erase(session_id); + return true; +} + +bool ClientSideDetectionIntelligentScanDelegateAndroid::ResetAllSessions() { + bool did_reset_session = !inquiries_.empty(); + inquiries_.clear(); return did_reset_session; } @@ -264,8 +277,8 @@ void ClientSideDetectionIntelligentScanDelegateAndroid::Shutdown() { client_side_detection::LogOnDeviceModelSessionAliveOnDelegateShutdown( - !!current_inquiry_); - ResetOnDeviceSession(); + !inquiries_.empty()); + ResetAllSessions(); model_broker_client_.reset(); pref_change_registrar_.RemoveAll(); } @@ -279,7 +292,7 @@ if (IsEnhancedProtectionEnabled(*pref_) && is_feature_enabled) { StartModelDownload(); } else { - ResetOnDeviceSession(); + ResetAllSessions(); } }
diff --git a/chrome/browser/safe_browsing/android/client_side_detection_intelligent_scan_delegate_android.h b/chrome/browser/safe_browsing/android/client_side_detection_intelligent_scan_delegate_android.h index 421bc61..56d3647 100644 --- a/chrome/browser/safe_browsing/android/client_side_detection_intelligent_scan_delegate_android.h +++ b/chrome/browser/safe_browsing/android/client_side_detection_intelligent_scan_delegate_android.h
@@ -5,9 +5,11 @@ #ifndef CHROME_BROWSER_SAFE_BROWSING_ANDROID_CLIENT_SIDE_DETECTION_INTELLIGENT_SCAN_DELEGATE_ANDROID_H_ #define CHROME_BROWSER_SAFE_BROWSING_ANDROID_CLIENT_SIDE_DETECTION_INTELLIGENT_SCAN_DELEGATE_ANDROID_H_ +#include "base/containers/flat_map.h" #include "base/memory/raw_ptr.h" #include "base/memory/raw_ref.h" #include "base/memory/weak_ptr.h" +#include "base/unguessable_token.h" #include "components/optimization_guide/core/optimization_guide_model_executor.h" #include "components/prefs/pref_change_registrar.h" #include "components/safe_browsing/content/browser/client_side_detection_host.h" @@ -39,22 +41,24 @@ // IntelligentScanDelegate implementation. bool ShouldRequestIntelligentScan(ClientPhishingRequest* verdict) override; bool IsOnDeviceModelAvailable(bool log_failed_eligibility_reason) override; - void InquireOnDeviceModel(std::string rendered_texts, - InquireOnDeviceModelDoneCallback callback) override; - bool ResetOnDeviceSession() override; + std::optional<base::UnguessableToken> InquireOnDeviceModel( + std::string rendered_texts, + InquireOnDeviceModelDoneCallback callback) override; + bool CancelSession(const base::UnguessableToken& session_id) override; bool ShouldShowScamWarning( std::optional<IntelligentScanVerdict> verdict) override; // KeyedService implementation. void Shutdown() override; - bool IsSessionAliveForTesting() { return !!current_inquiry_; } + int GetAliveSessionCountForTesting() { return inquiries_.size(); } void SetPauseSessionExecutionForTesting(bool pause) { pause_session_execution_for_testing_ = pause; } private: class Inquiry; + bool ResetAllSessions(); void OnPrefsUpdated(); @@ -68,7 +72,7 @@ // A wrapper of the current on-device model session. This is null if there is // no active inquiry. - std::unique_ptr<Inquiry> current_inquiry_; + base::flat_map<base::UnguessableToken, std::unique_ptr<Inquiry>> inquiries_; // PrefChangeRegistrar used to track when the enhanced protection state // changes.
diff --git a/chrome/browser/safe_browsing/android/client_side_detection_intelligent_scan_delegate_android_unittest.cc b/chrome/browser/safe_browsing/android/client_side_detection_intelligent_scan_delegate_android_unittest.cc index 1a2da07e..31aeba5 100644 --- a/chrome/browser/safe_browsing/android/client_side_detection_intelligent_scan_delegate_android_unittest.cc +++ b/chrome/browser/safe_browsing/android/client_side_detection_intelligent_scan_delegate_android_unittest.cc
@@ -9,6 +9,7 @@ #include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_feature_list.h" #include "base/test/test_future.h" +#include "base/unguessable_token.h" #include "components/optimization_guide/core/model_execution/test/fake_model_broker.h" #include "components/optimization_guide/core/model_execution/test/feature_config_builder.h" #include "components/optimization_guide/core/model_execution/test/substitution_builder.h" @@ -292,7 +293,7 @@ EXPECT_EQ(future.Get().brand, "test_brand"); EXPECT_EQ(future.Get().intent, "test_intent"); // Session should be reset after a successful response. - EXPECT_FALSE(delegate_->IsSessionAliveForTesting()); + EXPECT_EQ(delegate_->GetAliveSessionCountForTesting(), 0); histogram_tester_.ExpectTotalCount( "SBClientPhishing.OnDeviceModelSessionCreationTime", 1); histogram_tester_.ExpectUniqueSample( @@ -347,7 +348,7 @@ base::test::TestFuture<IntelligentScanResult> future1; delegate_->InquireOnDeviceModel("test rendered text", future1.GetCallback()); task_environment_.RunUntilIdle(); - EXPECT_TRUE(delegate_->IsSessionAliveForTesting()); + EXPECT_EQ(delegate_->GetAliveSessionCountForTesting(), 1); // The second inquire is sent before the first one completes. delegate_->SetPauseSessionExecutionForTesting(false); @@ -361,17 +362,19 @@ } TEST_F(ClientSideDetectionIntelligentScanDelegateAndroidTest, - ResetOnDeviceSession_AfterSessionCreation) { + CancelOnDeviceSession_AfterSessionCreation) { CreateDelegate(/*is_enhanced_protection_enabled=*/true, ModelExecutionFeature::MODEL_EXECUTION_FEATURE_SCAM_DETECTION); task_environment_.RunUntilIdle(); delegate_->SetPauseSessionExecutionForTesting(true); - delegate_->InquireOnDeviceModel("test rendered text", base::DoNothing()); + std::optional<base::UnguessableToken> session_id = + delegate_->InquireOnDeviceModel("test rendered text", base::DoNothing()); task_environment_.RunUntilIdle(); - EXPECT_TRUE(delegate_->IsSessionAliveForTesting()); + EXPECT_EQ(delegate_->GetAliveSessionCountForTesting(), 1); + // Reset the session after session is created. - EXPECT_TRUE(delegate_->ResetOnDeviceSession()); - EXPECT_FALSE(delegate_->IsSessionAliveForTesting()); + EXPECT_TRUE(delegate_->CancelSession(*session_id)); + EXPECT_EQ(delegate_->GetAliveSessionCountForTesting(), 0); } TEST_F(ClientSideDetectionIntelligentScanDelegateAndroidTest, @@ -382,11 +385,11 @@ delegate_->SetPauseSessionExecutionForTesting(true); delegate_->InquireOnDeviceModel("test rendered text", base::DoNothing()); task_environment_.RunUntilIdle(); - EXPECT_TRUE(delegate_->IsSessionAliveForTesting()); + EXPECT_EQ(delegate_->GetAliveSessionCountForTesting(), 1); SetEnhancedProtectionPrefForTests(&pref_service_, false); task_environment_.RunUntilIdle(); // Session should be reset after the enhanced protection is disabled. - EXPECT_FALSE(delegate_->IsSessionAliveForTesting()); + EXPECT_EQ(delegate_->GetAliveSessionCountForTesting(), 0); } class ClientSideDetectionIntelligentScanDelegateAndroidTestWithFeatureDisabled
diff --git a/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc b/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc index 20cecc9..b71867e 100644 --- a/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc +++ b/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc
@@ -293,11 +293,11 @@ (ClientPhishingRequest*), (override)); MOCK_METHOD(bool, IsOnDeviceModelAvailable, (bool), (override)); - MOCK_METHOD(void, + MOCK_METHOD(std::optional<base::UnguessableToken>, InquireOnDeviceModel, (std::string, InquireOnDeviceModelDoneCallback), (override)); - MOCK_METHOD(bool, ResetOnDeviceSession, (), (override)); + MOCK_METHOD(bool, CancelSession, (const base::UnguessableToken&), (override)); MOCK_METHOD(bool, ShouldShowScamWarning, (std::optional<IntelligentScanVerdict>), @@ -3134,19 +3134,21 @@ base::OnceCallback<void( ClientSideDetectionHost::IntelligentScanDelegate:: IntelligentScanResult)> callback) { + base::UnguessableToken token = base::UnguessableToken::Create(); ClientSideDetectionHost::IntelligentScanDelegate:: IntelligentScanResult scam_detection_response; scam_detection_response.execution_success = false; scam_detection_response.model_version = -1; if (!should_return_response) { std::move(callback).Run(scam_detection_response); - return; + return token; } scam_detection_response.execution_success = true; scam_detection_response.model_version = example_model_version_; scam_detection_response.brand = example_brand_; scam_detection_response.intent = example_intent_; std::move(callback).Run(scam_detection_response); + return token; }); }
diff --git a/chrome/browser/safe_browsing/client_side_detection_intelligent_scan_delegate_desktop.cc b/chrome/browser/safe_browsing/client_side_detection_intelligent_scan_delegate_desktop.cc index dfe14c2f..092ffc6 100644 --- a/chrome/browser/safe_browsing/client_side_detection_intelligent_scan_delegate_desktop.cc +++ b/chrome/browser/safe_browsing/client_side_detection_intelligent_scan_delegate_desktop.cc
@@ -51,6 +51,127 @@ namespace safe_browsing { +class ClientSideDetectionIntelligentScanDelegateDesktop::Inquiry { + public: + Inquiry(ClientSideDetectionIntelligentScanDelegateDesktop* parent, + const base::UnguessableToken& session_id, + InquireOnDeviceModelDoneCallback callback); + ~Inquiry(); + + void Start(const std::string& rendered_texts); + + private: + void ModelExecutionCallback( + optimization_guide::OptimizationGuideModelStreamingExecutionResult + result); + + const raw_ptr<ClientSideDetectionIntelligentScanDelegateDesktop> parent_; + std::unique_ptr<optimization_guide::OptimizationGuideModelExecutor::Session> + session_; + base::UnguessableToken session_id_; + InquireOnDeviceModelDoneCallback callback_; + std::string rendered_texts_; + base::TimeTicks session_execution_start_time_; + + base::WeakPtrFactory<Inquiry> weak_factory_{this}; +}; + +ClientSideDetectionIntelligentScanDelegateDesktop::Inquiry::Inquiry( + ClientSideDetectionIntelligentScanDelegateDesktop* parent, + const base::UnguessableToken& session_id, + InquireOnDeviceModelDoneCallback callback) + : parent_(parent), + session_id_(session_id), + callback_(std::move(callback)) {} + +ClientSideDetectionIntelligentScanDelegateDesktop::Inquiry::~Inquiry() = + default; + +void ClientSideDetectionIntelligentScanDelegateDesktop::Inquiry::Start( + const std::string& rendered_texts) { + session_ = parent_->GetModelExecutorSession(); + + base::TimeTicks session_creation_start_time = base::TimeTicks::Now(); + + if (!session_) { + LogOnDeviceModelSessionCreationSuccess(false); + std::move(callback_).Run(IntelligentScanResult::Failure( + IntelligentScanResult::kModelVersionUnavailable)); + return; + } + + client_side_detection::LogOnDeviceModelSessionCreationTime( + session_creation_start_time); + LogOnDeviceModelSessionCreationSuccess(true); + + ScamDetectionRequest request; + request.set_rendered_text(rendered_texts); + + session_execution_start_time_ = base::TimeTicks::Now(); + session_->ExecuteModel( + *std::make_unique<ScamDetectionRequest>(request), + base::BindRepeating(&ClientSideDetectionIntelligentScanDelegateDesktop:: + Inquiry::ModelExecutionCallback, + weak_factory_.GetWeakPtr())); +} + +void ClientSideDetectionIntelligentScanDelegateDesktop::Inquiry:: + ModelExecutionCallback( + optimization_guide::OptimizationGuideModelStreamingExecutionResult + result) { + int model_version = IntelligentScanResult::kModelVersionUnavailable; + if (result.execution_info) { + model_version = result.execution_info->on_device_model_execution_info() + .model_versions() + .on_device_model_service_version() + .model_adaptation_version(); + } + + if (!result.response.has_value()) { + client_side_detection::LogOnDeviceModelExecutionSuccessAndTime( + /*success=*/false, session_execution_start_time_); + if (callback_) { + std::move(callback_).Run(IntelligentScanResult::Failure(model_version)); + } + return; + } + + // This is a non-error response, but it's not completed, yet so we wait till + // it's complete. We will not respond to the callback yet because of this. + if (!result.response->is_complete) { + return; + } + + client_side_detection::LogOnDeviceModelExecutionSuccessAndTime( + /*success=*/true, session_execution_start_time_); + + auto scam_detection_response = optimization_guide::ParsedAnyMetadata< + optimization_guide::proto::ScamDetectionResponse>( + result.response->response); + + if (!scam_detection_response) { + LogOnDeviceModelExecutionParse(false); + if (callback_) { + std::move(callback_).Run(IntelligentScanResult::Failure(model_version)); + } + return; + } + + LogOnDeviceModelExecutionParse(true); + LogOnDeviceModelCallbackStateOnSuccessfulResponse(!!callback_); + + if (callback_) { + std::move(callback_).Run({.brand = scam_detection_response->brand(), + .intent = scam_detection_response->intent(), + .model_version = model_version, + .execution_success = true}); + } + + // Reset session immediately so that future inference is not affected by the + // old context. + parent_->CancelSession(session_id_); +} + ClientSideDetectionIntelligentScanDelegateDesktop:: ClientSideDetectionIntelligentScanDelegateDesktop( PrefService& pref, @@ -125,7 +246,8 @@ } } -void ClientSideDetectionIntelligentScanDelegateDesktop::InquireOnDeviceModel( +std::optional<base::UnguessableToken> +ClientSideDetectionIntelligentScanDelegateDesktop::InquireOnDeviceModel( std::string rendered_texts, InquireOnDeviceModelDoneCallback callback) { // We have checked the model availability prior to calling this function, but @@ -133,107 +255,30 @@ if (!IsOnDeviceModelAvailable(/*log_failed_eligibility_reason=*/false)) { std::move(callback).Run(IntelligentScanResult::Failure( IntelligentScanResult::kModelVersionUnavailable)); - return; + return std::nullopt; } - // TODO(crbug.com/444148365): Intelligent scan delegate is per profile and may - // be shared with multiple ClientSideDetectionHost, so it is possible that one - // session created by one ClientSideDetectionHost is still alive when another - // ClientSideDetectionHost tries to create a new session. We should support - // multiple sessions per delegate. - ResetOnDeviceSession(); - - base::TimeTicks session_creation_start_time = base::TimeTicks::Now(); - - session_ = GetModelExecutorSession(); - - if (!session_) { - LogOnDeviceModelSessionCreationSuccess(false); - std::move(callback).Run(IntelligentScanResult::Failure( - IntelligentScanResult::kModelVersionUnavailable)); - return; - } - - client_side_detection::LogOnDeviceModelSessionCreationTime( - session_creation_start_time); - LogOnDeviceModelSessionCreationSuccess(true); - - ScamDetectionRequest request; - request.set_rendered_text(rendered_texts); - - inquire_on_device_model_callback_ = std::move(callback); - session_execution_start_time_ = base::TimeTicks::Now(); - session_->ExecuteModel( - *std::make_unique<ScamDetectionRequest>(request), - base::BindRepeating(&ClientSideDetectionIntelligentScanDelegateDesktop:: - ModelExecutionCallback, - weak_factory_.GetWeakPtr())); + base::UnguessableToken session_id = base::UnguessableToken::Create(); + std::unique_ptr<Inquiry> new_inquiry = + std::make_unique<Inquiry>(this, session_id, std::move(callback)); + inquiries_[session_id] = std::move(new_inquiry); + inquiries_[session_id]->Start(rendered_texts); + return session_id; } -void ClientSideDetectionIntelligentScanDelegateDesktop::ModelExecutionCallback( - optimization_guide::OptimizationGuideModelStreamingExecutionResult result) { - int model_version = IntelligentScanResult::kModelVersionUnavailable; - if (result.execution_info) { - model_version = result.execution_info->on_device_model_execution_info() - .model_versions() - .on_device_model_service_version() - .model_adaptation_version(); +bool ClientSideDetectionIntelligentScanDelegateDesktop::CancelSession( + const base::UnguessableToken& session_id) { + if (!inquiries_.contains(session_id)) { + return false; } - if (!result.response.has_value()) { - client_side_detection::LogOnDeviceModelExecutionSuccessAndTime( - /*success=*/false, session_execution_start_time_); - if (inquire_on_device_model_callback_) { - std::move(inquire_on_device_model_callback_) - .Run(IntelligentScanResult::Failure(model_version)); - } - return; - } - - // This is a non-error response, but it's not completed, yet so we wait till - // it's complete. We will not respond to the callback yet because of this. - if (!result.response->is_complete) { - return; - } - - client_side_detection::LogOnDeviceModelExecutionSuccessAndTime( - /*success=*/true, session_execution_start_time_); - - auto scam_detection_response = optimization_guide::ParsedAnyMetadata< - optimization_guide::proto::ScamDetectionResponse>( - result.response->response); - - if (!scam_detection_response) { - LogOnDeviceModelExecutionParse(false); - if (inquire_on_device_model_callback_) { - std::move(inquire_on_device_model_callback_) - .Run(IntelligentScanResult::Failure(model_version)); - } - return; - } - - LogOnDeviceModelExecutionParse(true); - - // Reset session immediately so that future inference is not affected by the - // old context. - ResetOnDeviceSession(); - - LogOnDeviceModelCallbackStateOnSuccessfulResponse( - !!inquire_on_device_model_callback_); - if (inquire_on_device_model_callback_) { - std::move(inquire_on_device_model_callback_) - .Run({.brand = scam_detection_response->brand(), - .intent = scam_detection_response->intent(), - .model_version = model_version, - .execution_success = true}); - } + inquiries_.erase(session_id); + return true; } -bool ClientSideDetectionIntelligentScanDelegateDesktop::ResetOnDeviceSession() { - bool did_reset_session = !!session_; - if (session_) { - session_.reset(); - } +bool ClientSideDetectionIntelligentScanDelegateDesktop::ResetAllSessions() { + bool did_reset_session = !inquiries_.empty(); + inquiries_.clear(); return did_reset_session; } @@ -258,7 +303,7 @@ void ClientSideDetectionIntelligentScanDelegateDesktop:: StopListeningToOnDeviceModelUpdate() { on_device_model_available_ = false; - ResetOnDeviceSession(); + ResetAllSessions(); if (!observing_on_device_model_availability_) { return; } @@ -270,7 +315,7 @@ void ClientSideDetectionIntelligentScanDelegateDesktop::Shutdown() { client_side_detection::LogOnDeviceModelSessionAliveOnDelegateShutdown( - !!session_); + !inquiries_.empty()); StopListeningToOnDeviceModelUpdate(); pref_change_registrar_.RemoveAll(); }
diff --git a/chrome/browser/safe_browsing/client_side_detection_intelligent_scan_delegate_desktop.h b/chrome/browser/safe_browsing/client_side_detection_intelligent_scan_delegate_desktop.h index ae072d5..39d759e 100644 --- a/chrome/browser/safe_browsing/client_side_detection_intelligent_scan_delegate_desktop.h +++ b/chrome/browser/safe_browsing/client_side_detection_intelligent_scan_delegate_desktop.h
@@ -5,7 +5,11 @@ #ifndef CHROME_BROWSER_SAFE_BROWSING_CLIENT_SIDE_DETECTION_INTELLIGENT_SCAN_DELEGATE_DESKTOP_H_ #define CHROME_BROWSER_SAFE_BROWSING_CLIENT_SIDE_DETECTION_INTELLIGENT_SCAN_DELEGATE_DESKTOP_H_ +#include "base/containers/flat_map.h" +#include "base/gtest_prod_util.h" #include "base/memory/weak_ptr.h" +#include "base/time/time.h" +#include "base/unguessable_token.h" #include "components/optimization_guide/core/model_execution/feature_keys.h" #include "components/optimization_guide/core/optimization_guide_model_executor.h" #include "components/safe_browsing/content/browser/client_side_detection_host.h" @@ -35,18 +39,24 @@ // IntelligentScanDelegate implementation. bool ShouldRequestIntelligentScan(ClientPhishingRequest* verdict) override; bool IsOnDeviceModelAvailable(bool log_failed_eligibility_reason) override; - void InquireOnDeviceModel(std::string rendered_texts, - InquireOnDeviceModelDoneCallback callback) override; - bool ResetOnDeviceSession() override; + std::optional<base::UnguessableToken> InquireOnDeviceModel( + std::string rendered_texts, + InquireOnDeviceModelDoneCallback callback) override; + bool CancelSession(const base::UnguessableToken& session_id) override; bool ShouldShowScamWarning( std::optional<IntelligentScanVerdict> verdict) override; // KeyedService implementation. void Shutdown() override; - bool IsSessionAliveForTesting() { return !!session_; } + int GetAliveSessionCountForTesting() { return inquiries_.size(); } private: + friend class ClientSideDetectionIntelligentScanDelegateDesktopTest; + FRIEND_TEST_ALL_PREFIXES( + ClientSideDetectionIntelligentScanDelegateDesktopTest, + ResetOnDeviceSession); + class Inquiry; void OnPrefsUpdated(); // Starts listening to the on-device model update through OptimizationGuide. @@ -71,9 +81,12 @@ GetModelExecutorSession(); void ModelExecutionCallback( + const base::UnguessableToken& session_id, optimization_guide::OptimizationGuideModelStreamingExecutionResult result); + bool ResetAllSessions(); + // It is set to true when the on-device model is not readily available, but // it's expected to be ready soon. See `kWaitableReasons` for more details. bool observing_on_device_model_availability_ = false; @@ -82,11 +95,9 @@ bool on_device_model_available_ = false; base::TimeTicks on_device_fetch_time_; - base::TimeTicks session_execution_start_time_; - // The underlying session provided by optimization guide component. - std::unique_ptr<optimization_guide::OptimizationGuideModelExecutor::Session> - session_; - InquireOnDeviceModelDoneCallback inquire_on_device_model_callback_; + // A wrapper of the current on-device model session. This is null if there is + // no active inquiry. + base::flat_map<base::UnguessableToken, std::unique_ptr<Inquiry>> inquiries_; const raw_ref<PrefService> pref_; const raw_ptr<OptimizationGuideKeyedService> opt_guide_;
diff --git a/chrome/browser/safe_browsing/client_side_detection_intelligent_scan_delegate_desktop_unittest.cc b/chrome/browser/safe_browsing/client_side_detection_intelligent_scan_delegate_desktop_unittest.cc index eb594d9..ae3787ac 100644 --- a/chrome/browser/safe_browsing/client_side_detection_intelligent_scan_delegate_desktop_unittest.cc +++ b/chrome/browser/safe_browsing/client_side_detection_intelligent_scan_delegate_desktop_unittest.cc
@@ -544,9 +544,7 @@ histogram_tester_.ExpectUniqueSample( "SBClientPhishing.OnDeviceModelSessionCreationSuccess", true, 1); - // We will expect a second time, but since the InquireOnDeviceModel function - // wasn't finished and the future callback wasn't completed, we will remove - // the "old" session and recreate a new one. + // A second session can be created while the first one is still alive. EXPECT_CALL(mock_opt_guide_, StartSession(_, _)) .WillOnce( [&](optimization_guide::ModelBasedCapabilityKey feature, @@ -562,9 +560,100 @@ "SBClientPhishing.OnDeviceModelSessionCreationSuccess", true, 2); histogram_tester_.ExpectTotalCount( "SBClientPhishing.OnDeviceModelSessionCreationTime", 2); - // We do not test for execution_success field here because the session - // creation has succeeded, but model execution callback is not set, so the - // future callback won't be answered. +} + +TEST_F(ClientSideDetectionIntelligentScanDelegateDesktopTest, + TestMultipleSessions) { + EnableOnDeviceModel(); + + EXPECT_CALL(mock_opt_guide_, StartSession(_, _)) + .WillOnce( + [&](optimization_guide::ModelBasedCapabilityKey feature, + const std::optional<optimization_guide::SessionConfigParams>& + config_params) { + return std::make_unique<NiceMock<MockSession>>(&session_); + }); + + base::test::TestFuture<IntelligentScanResult> future1; + std::optional<base::UnguessableToken> session_id1 = + delegate_->InquireOnDeviceModel("", future1.GetCallback()); + EXPECT_FALSE(session_id1->is_empty()); + + testing::NiceMock<MockSession> session2; + EXPECT_CALL(mock_opt_guide_, StartSession(_, _)) + .WillOnce( + [&](optimization_guide::ModelBasedCapabilityKey feature, + const std::optional<optimization_guide::SessionConfigParams>& + config_params) { + return std::make_unique<NiceMock<MockSession>>(&session2); + }); + + base::test::TestFuture<IntelligentScanResult> future2; + std::optional<base::UnguessableToken> session_id2 = + delegate_->InquireOnDeviceModel("", future2.GetCallback()); + + // Both session IDs should still be alive. + EXPECT_FALSE(session_id1->is_empty()); + EXPECT_FALSE(session_id2->is_empty()); + EXPECT_EQ(delegate_->GetAliveSessionCountForTesting(), 2); +} + +TEST_F(ClientSideDetectionIntelligentScanDelegateDesktopTest, + TestCancelSession) { + EnableOnDeviceModelWithSession(); + + base::test::TestFuture<IntelligentScanResult> future; + std::optional<base::UnguessableToken> session_id = + delegate_->InquireOnDeviceModel("", future.GetCallback()); + EXPECT_FALSE(session_id->is_empty()); + + EXPECT_EQ(delegate_->GetAliveSessionCountForTesting(), 1); + EXPECT_TRUE(delegate_->CancelSession(*session_id)); + EXPECT_EQ(delegate_->GetAliveSessionCountForTesting(), 0); + + // The callback should not be called. + EXPECT_FALSE(future.IsReady()); +} + +TEST_F(ClientSideDetectionIntelligentScanDelegateDesktopTest, + TestMultipleSessionsCancellation) { + EnableOnDeviceModel(); + + EXPECT_CALL(mock_opt_guide_, StartSession(_, _)) + .WillOnce( + [&](optimization_guide::ModelBasedCapabilityKey feature, + const std::optional<optimization_guide::SessionConfigParams>& + config_params) { + return std::make_unique<NiceMock<MockSession>>(&session_); + }); + + base::test::TestFuture<IntelligentScanResult> future1; + std::optional<base::UnguessableToken> session_id1 = + delegate_->InquireOnDeviceModel("", future1.GetCallback()); + EXPECT_FALSE(session_id1->is_empty()); + + testing::NiceMock<MockSession> session2; + EXPECT_CALL(mock_opt_guide_, StartSession(_, _)) + .WillOnce( + [&](optimization_guide::ModelBasedCapabilityKey feature, + const std::optional<optimization_guide::SessionConfigParams>& + config_params) { + return std::make_unique<NiceMock<MockSession>>(&session2); + }); + + base::test::TestFuture<IntelligentScanResult> future2; + std::optional<base::UnguessableToken> session_id2 = + delegate_->InquireOnDeviceModel("", future2.GetCallback()); + + // Both session IDs should still be alive. + EXPECT_FALSE(session_id1->is_empty()); + EXPECT_FALSE(session_id2->is_empty()); + + EXPECT_EQ(delegate_->GetAliveSessionCountForTesting(), 2); + EXPECT_TRUE(delegate_->CancelSession(*session_id1)); + EXPECT_EQ(delegate_->GetAliveSessionCountForTesting(), 1); + EXPECT_TRUE(delegate_->CancelSession(*session_id2)); + EXPECT_EQ(delegate_->GetAliveSessionCountForTesting(), 0); } TEST_F(ClientSideDetectionIntelligentScanDelegateDesktopTest, @@ -752,18 +841,31 @@ ResetOnDeviceSession) { EnableOnDeviceModelWithSession(); - bool did_reset = delegate_->ResetOnDeviceSession(); + bool did_reset = delegate_->ResetAllSessions(); EXPECT_FALSE(did_reset); base::test::TestFuture<IntelligentScanResult> future; delegate_->InquireOnDeviceModel("", future.GetCallback()); - EXPECT_TRUE(delegate_->IsSessionAliveForTesting()); + EXPECT_EQ(delegate_->GetAliveSessionCountForTesting(), 1); - did_reset = delegate_->ResetOnDeviceSession(); + // Create a second session + EXPECT_CALL(mock_opt_guide_, StartSession(_, _)) + .WillOnce( + [&](optimization_guide::ModelBasedCapabilityKey feature, + const std::optional<optimization_guide::SessionConfigParams>& + config_params) { + return std::make_unique<NiceMock<MockSession>>(&session_); + }); + base::test::TestFuture<IntelligentScanResult> future2; + delegate_->InquireOnDeviceModel("", future2.GetCallback()); + + EXPECT_EQ(delegate_->GetAliveSessionCountForTesting(), 2); + + did_reset = delegate_->ResetAllSessions(); EXPECT_TRUE(did_reset); - EXPECT_FALSE(delegate_->IsSessionAliveForTesting()); + EXPECT_EQ(delegate_->GetAliveSessionCountForTesting(), 0); } TEST_F(ClientSideDetectionIntelligentScanDelegateDesktopTest,
diff --git a/chrome/browser/shortcuts/shortcut_creator_win.cc b/chrome/browser/shortcuts/shortcut_creator_win.cc index 3e72afe2..eb78321 100644 --- a/chrome/browser/shortcuts/shortcut_creator_win.cc +++ b/chrome/browser/shortcuts/shortcut_creator_win.cc
@@ -23,8 +23,8 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/shortcuts/platform_util_win.h" #include "chrome/common/chrome_switches.h" -#include "ui/gfx/icon_util.h" #include "ui/gfx/image/image_family.h" +#include "ui/gfx/win/icon_util.h" #include "url/gurl.h" namespace shortcuts {
diff --git a/chrome/browser/signin/dice_browsertest.cc b/chrome/browser/signin/dice_browsertest.cc index 9b350ff4..3939e7a 100644 --- a/chrome/browser/signin/dice_browsertest.cc +++ b/chrome/browser/signin/dice_browsertest.cc
@@ -1225,11 +1225,7 @@ histogram_tester.ExpectBucketCount( "Signin.SigninManager.SetPrimaryAccountSigninInStage", PrimaryAccountSettingGaiaIntegrationState::kOnSyncHeaderReceived, - /*expected_count=*/ - base::FeatureList::IsEnabled( - switches::kBrowserSigninInSyncHeaderOnGaiaIntegration) - ? 1 - : 0); + /*expected_count=*/1); // The interception bubble should not have been shown. histogram_tester.ExpectBucketCount( "Signin.Intercept.HeuristicOutcome", @@ -1401,11 +1397,6 @@ // has not arrived within a timeout window. IN_PROC_BROWSER_TEST_F(DiceBrowserSiginInInterceptionInteractiveTest, ShowsUnoBubbleWhenSyncHeaderArrivalExceedsTimeout) { - if (!base::FeatureList::IsEnabled( - switches::kBrowserSigninInSyncHeaderOnGaiaIntegration)) { - GTEST_SKIP(); - } - base::HistogramTester histogram_tester; EXPECT_EQ(0, reconcilor_started_count_); auto uno_bubble_retry_delay = base::Milliseconds(500); @@ -1460,22 +1451,9 @@ "Signin.SigninManager.SyncHeaderArrivalTimeWindowAfterLst", 0); } -class DiceAddAccountTabBrowserTest : public DiceBrowserTest, - public base::test::WithFeatureOverride { - public: - DiceAddAccountTabBrowserTest() - : base::test::WithFeatureOverride( - switches::kBrowserSigninInSyncHeaderOnGaiaIntegration) {} - - bool IsFixGaiaIntegrationEnabled() const { return IsParamFeatureEnabled(); } -}; - -INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(DiceAddAccountTabBrowserTest); - // Tests that user is signed in to the browser when the Dice "add account" tab // is used. -IN_PROC_BROWSER_TEST_P(DiceAddAccountTabBrowserTest, - BrowserSignInFromAddAccountTab) { +IN_PROC_BROWSER_TEST_F(DiceBrowserTest, BrowserSignInFromAddAccountTab) { base::HistogramTester histogram_tester; // Signin using the Add account endpoint. browser()->GetFeatures().signin_view_controller()->ShowDiceAddAccountTab( @@ -1489,10 +1467,8 @@ EXPECT_TRUE( GetIdentityManager()->HasAccountWithRefreshToken(GetMainAccountID())); - if (IsFixGaiaIntegrationEnabled()) { - // Receive ENABLE_SYNC. - SendEnableSyncResponse(); - } + // Receive ENABLE_SYNC. + SendEnableSyncResponse(); WaitForSigninSucceeded(); EXPECT_TRUE( @@ -1504,7 +1480,7 @@ histogram_tester.ExpectBucketCount( "Signin.SigninManager.SetPrimaryAccountSigninInStage", PrimaryAccountSettingGaiaIntegrationState::kOnSyncHeaderReceived, - /*expected_count=*/IsFixGaiaIntegrationEnabled() ? 1 : 0); + /*expected_count=*/1); } class DiceBrowserTestWithSyncOptinScreen : public DiceBrowserTest { @@ -1878,53 +1854,6 @@ EXPECT_TRUE(prefs->GetBoolean(prefs::kExplicitBrowserSignin)); } -class DiceBrowserTestWithLegacyGaiaAndReplaceSyncPromosWithSignInPromos - : public DiceBrowserTestWithExplicitSignin { - public: - DiceBrowserTestWithLegacyGaiaAndReplaceSyncPromosWithSignInPromos() { - scoped_feature_list_with_set_disabled.InitWithFeatures( - /*enabled_features=*/{syncer::kReplaceSyncPromosWithSignInPromos}, - /*disabled_features=*/{switches::kBrowserSigninInSyncHeaderOnGaiaIntegration}); - } - - private: - base::test::ScopedFeatureList scoped_feature_list_with_set_disabled; -}; - -IN_PROC_BROWSER_TEST_F( - DiceBrowserTestWithLegacyGaiaAndReplaceSyncPromosWithSignInPromos, - SigninWhenAccountAllowedByPattern) { - g_browser_process->local_state()->SetString( - prefs::kGoogleServicesUsernamePattern, ".*@gmail.com"); - - SetChromeSigninChoice(ChromeSigninUserChoice::kSignin); - SimulateWebSigninMainAccount(); - - EXPECT_EQ( - GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin), - true); - EXPECT_EQ( - browser()->GetFeatures().signin_view_controller()->ShowsModalDialog(), - false); -} - -IN_PROC_BROWSER_TEST_F( - DiceBrowserTestWithLegacyGaiaAndReplaceSyncPromosWithSignInPromos, - SigninDisallowedWhenAccountNotAllowedByPattern) { - g_browser_process->local_state()->SetString( - prefs::kGoogleServicesUsernamePattern, ".*@restricted.com"); - - SetChromeSigninChoice(ChromeSigninUserChoice::kSignin); - SimulateWebSigninMainAccount(); - - EXPECT_EQ( - GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin), - false); - EXPECT_EQ( - browser()->GetFeatures().signin_view_controller()->ShowsModalDialog(), - true); -} - class DiceBrowserTestWithExplicitSigninReplaceSyncPromosWithSignInPromos : public DiceBrowserTestWithExplicitSignin { private:
diff --git a/chrome/browser/signin/dice_tab_helper.cc b/chrome/browser/signin/dice_tab_helper.cc index e9915db..5695dd39 100644 --- a/chrome/browser/signin/dice_tab_helper.cc +++ b/chrome/browser/signin/dice_tab_helper.cc
@@ -43,6 +43,12 @@ "Signin.SigninManager.SyncHeaderTimeout"; constexpr char kDiceSyncHeaderArrivalTimeWindowHistogramName[] = "Signin.SigninManager.SyncHeaderArrivalTimeWindowAfterLst"; + +void RecordDiceSyncHeaderTimeout(bool timeout) { + base::UmaHistogramBoolean(kDiceSyncHeaderTimeoutHistogramNameHistogramName, + timeout); +} + } // namespace // static @@ -280,17 +286,9 @@ // stopped and recorded in the histogram `SyncHeaderArrivalTimeWindow`. void DiceTabHelper::StartInterceptionBubbleTimer( base::OnceClosure retry_interception_bubble_callback) { - base::OnceClosure record_timeout_callback = base::BindOnce([] { - base::UmaHistogramBoolean(kDiceSyncHeaderTimeoutHistogramNameHistogramName, - true); - }); base::OnceClosure timer_callback = - base::FeatureList::IsEnabled( - switches::kBrowserSigninInSyncHeaderOnGaiaIntegration) - ? std::move(record_timeout_callback) - .Then(std::move(retry_interception_bubble_callback)) - : std::move(record_timeout_callback); - + std::move(base::BindOnce(&RecordDiceSyncHeaderTimeout, true)) + .Then(std::move(retry_interception_bubble_callback)); state_->elapsed_time_since_lst_arrival_timer = std::make_unique<base::ElapsedTimer>(); state_->retry_interception_bubble_timer.Start( @@ -303,8 +301,7 @@ // Unexpected, edge case where a token exchange hasn't been requested yet // by the time Chrome processes the Sync header. || !IsTokenExchangeDone()) { - base::UmaHistogramBoolean(kDiceSyncHeaderTimeoutHistogramNameHistogramName, - false); + RecordDiceSyncHeaderTimeout(false); } state_->retry_interception_bubble_timer.Stop();
diff --git a/chrome/browser/signin/e2e_tests/live_sign_in_test.cc b/chrome/browser/signin/e2e_tests/live_sign_in_test.cc index 4b00f098..b841a8e 100644 --- a/chrome/browser/signin/e2e_tests/live_sign_in_test.cc +++ b/chrome/browser/signin/e2e_tests/live_sign_in_test.cc
@@ -558,7 +558,6 @@ IN_PROC_BROWSER_TEST_F(LiveSignInTest, MANUAL_AccountCapabilities_FetchedOnSignIn) { - // Test primary adult account. { AccountCapabilitiesObserver capabilities_observer(identity_manager()); @@ -673,21 +672,12 @@ "Signin.SigninManager.SyncHeaderArrivalTimeWindowAfterLst", 1); } -class LiveSignInGaiaIntegrationTest - : public base::test::WithFeatureOverride, - public InteractiveBrowserTestT<LiveSignInTest> { - public: - LiveSignInGaiaIntegrationTest() - : base::test::WithFeatureOverride( - switches::kBrowserSigninInSyncHeaderOnGaiaIntegration) {} - - bool IsFixGaiaIntegrationEnabled() const { return IsParamFeatureEnabled(); } -}; +using LiveSignInGaiaIntegrationTest = InteractiveBrowserTestT<LiveSignInTest>; // Regression test for crbug.com/420635510. // Tests that a doing a web signin from a tab that was previously opened for // a browser signin, does not sign in the user in the browser. -IN_PROC_BROWSER_TEST_P(LiveSignInGaiaIntegrationTest, +IN_PROC_BROWSER_TEST_F(LiveSignInGaiaIntegrationTest, MANUAL_WebSignInFromExistingChromeSignInTab) { base::HistogramTester histogram_tester; sign_in_functions.StartSignInFromSettings(); @@ -708,24 +698,17 @@ browser()->tab_strip_model()->GetActiveWebContents(), *test_account, 0); ASSERT_EQ(current_tab_count, browser()->tab_strip_model()->GetTabCount()); - // When the updated Gaia integration is used, the user should not be signed-in - // in the browser. - EXPECT_EQ(IsFixGaiaIntegrationEnabled(), - !identity_manager()->HasPrimaryAccount(ConsentLevel::kSignin)); + // The user should not be signed-in in the browser. + EXPECT_FALSE(identity_manager()->HasPrimaryAccount(ConsentLevel::kSignin)); - if (IsFixGaiaIntegrationEnabled()) { - RunTestSequence(WaitForShow( - DiceWebSigninInterceptionBubbleView::kDiceWebSigninInterceptionBubble)); - } + RunTestSequence(WaitForShow( + DiceWebSigninInterceptionBubbleView::kDiceWebSigninInterceptionBubble)); histogram_tester.ExpectBucketCount( "Signin.Intercept.HeuristicOutcome", - SigninInterceptionHeuristicOutcome::kInterceptChromeSignin, - IsFixGaiaIntegrationEnabled() ? 1 : 0); + SigninInterceptionHeuristicOutcome::kInterceptChromeSignin, 1); } -INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(LiveSignInGaiaIntegrationTest); - #endif // BUILDFLAG(ENABLE_DICE_SUPPORT) } // namespace
diff --git a/chrome/browser/signin/process_dice_header_delegate_impl.cc b/chrome/browser/signin/process_dice_header_delegate_impl.cc index cf01568..6e597744 100644 --- a/chrome/browser/signin/process_dice_header_delegate_impl.cc +++ b/chrome/browser/signin/process_dice_header_delegate_impl.cc
@@ -84,13 +84,10 @@ // Try to show the interceptions bubble by treating this case as WebSignin // on any regular tab. // TODO(crbug.com/426555085): Remove the access_point argument. - if (base::FeatureList::IsEnabled( - switches::kRetryInterceptionBubbleOnDiceSyncHeaderTimeout)) { - interceptor->MaybeInterceptWebSignin( - web_contents.get(), bubble_params.account_id, - signin_metrics::AccessPoint::kWebSignin, bubble_params.is_new_account, - /*is_sync_signin=*/false); - } + interceptor->MaybeInterceptWebSignin( + web_contents.get(), bubble_params.account_id, + signin_metrics::AccessPoint::kWebSignin, bubble_params.is_new_account, + /*is_sync_signin=*/false); } } // namespace @@ -221,8 +218,8 @@ const SigninUIError error = CanOfferSignin( &profile_.get(), account_info.gaia, account_info.email, /*allow_account_from_other_profile=*/allow_account_from_other_profile); - if (error.IsOk() || - !base::FeatureList::IsEnabled(syncer::kReplaceSyncPromosWithSignInPromos)) { + if (error.IsOk() || !base::FeatureList::IsEnabled( + syncer::kReplaceSyncPromosWithSignInPromos)) { signin::IdentityManager* identity_manager = IdentityManagerFactory::GetForProfile(&profile_.get()); identity_manager->GetPrimaryAccountMutator()->SetPrimaryAccount( @@ -238,14 +235,16 @@ return false; } -// Should Sign in to Chrome for all access points when Uno is enabled. Except -// for Web Signin where we first check the user choice first on whether to -// automatically sign in or not. +// Attempts to sign-in to Chrome from a web sign-in. This is only done for the +// `kWebSignin` access point, and only if the user has enabled the "Remember +// sign-in choice" setting or if the `kBrowserSigninAutoAccept` command line +// flag is set. Other access points are signed in to Chrome through the +// `EnableSync()` method. // TODO(crbug.com/425645725): Rename using a more appropriate name once the // signin to browser is cleaned-up. void ProcessDiceHeaderDelegateImpl::AttemptChromeSignin( CoreAccountId account_id) { - CHECK(!account_id.empty()); + CHECK(!account_id.empty()); // Do not sign in if the access point is unknown. if (access_point_ == signin_metrics::AccessPoint::kUnknown) { @@ -284,25 +283,16 @@ const bool has_primary_account = identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin); - if (base::FeatureList::IsEnabled( - switches::kBrowserSigninInSyncHeaderOnGaiaIntegration)) { - if (should_auto_sign_in && !has_primary_account) { - // Sign-in the user in the browser if user can sign. If not, we fail - // silently as the signin attempt was not an explicit user action. - AttemptSettingPrimaryAccount(account_info, /*show_signin_error=*/false); - } - RecordLegacyGaiaIntegrationStageMetrics(should_auto_sign_in, - has_primary_account); - return; + if (should_auto_sign_in && !has_primary_account) { + // Sign-in the user in the browser if user can sign. If not, we fail + // silently as the signin attempt was not an explicit user action. + AttemptSettingPrimaryAccount(account_info, /*show_signin_error=*/false); } - // Legacy Gaia flow integration. - if (!has_primary_account) { - base::UmaHistogramEnumeration("Signin.SigninManager.SigninAccessPoint", - access_point_); - AttemptSettingPrimaryAccount(account_info); - RecordLegacyGaiaIntegrationStageMetrics(should_auto_sign_in, - has_primary_account); - } + // TODO(crbug.com/425645725): Once this metric is removed, the whole function + // can be simplified. See example at: + // https://crrev.com/c/7027059/3..5/chrome/browser/signin/process_dice_header_delegate_impl.cc + RecordLegacyGaiaIntegrationStageMetrics(should_auto_sign_in, + has_primary_account); } void ProcessDiceHeaderDelegateImpl::HandleTokenExchangeSuccess( @@ -335,23 +325,20 @@ const CoreAccountInfo& account_info) { // TODO(crbug.com/420635510): Address the case of a flashing Interception // bubble which gets self-dismissed when the browser user is signed in. - if (base::FeatureList::IsEnabled( - switches::kBrowserSigninInSyncHeaderOnGaiaIntegration)) { - signin::IdentityManager* identity_manager = - IdentityManagerFactory::GetForProfile(&profile_.get()); - CHECK(identity_manager); - if (!identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) { - base::UmaHistogramEnumeration("Signin.SigninManager.SigninAccessPoint", - access_point_); - if (!AttemptSettingPrimaryAccount(account_info)) { - return; - } - // Record an entry marks the place where the user is signed-in in the - // new Gaia integration flow. - base::UmaHistogramEnumeration( - "Signin.SigninManager.SetPrimaryAccountSigninInStage", - PrimaryAccountSettingGaiaIntegrationState::kOnSyncHeaderReceived); + signin::IdentityManager* identity_manager = + IdentityManagerFactory::GetForProfile(&profile_.get()); + CHECK(identity_manager); + if (!identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) { + base::UmaHistogramEnumeration("Signin.SigninManager.SigninAccessPoint", + access_point_); + if (!AttemptSettingPrimaryAccount(account_info)) { + return; } + // Record an entry marks the place where the user is signed-in in the + // new Gaia integration flow. + base::UmaHistogramEnumeration( + "Signin.SigninManager.SetPrimaryAccountSigninInStage", + PrimaryAccountSettingGaiaIntegrationState::kOnSyncHeaderReceived); } content::WebContents* web_contents = web_contents_.get();
diff --git a/chrome/browser/signin/signin_ui_util_browsertest.cc b/chrome/browser/signin/signin_ui_util_browsertest.cc index 281eb956..b29ff4d2 100644 --- a/chrome/browser/signin/signin_ui_util_browsertest.cc +++ b/chrome/browser/signin/signin_ui_util_browsertest.cc
@@ -215,16 +215,9 @@ base::AutoReset<SigninUiDelegate*> delegate_auto_reset_; }; -class SigninUiUtilTest : public SigninUiUtilTestBase, - public base::test::WithFeatureOverride { +class SigninUiUtilTest : public SigninUiUtilTestBase { public: - SigninUiUtilTest() - : base::test::WithFeatureOverride( - switches::kBrowserSigninInSyncHeaderOnGaiaIntegration) {} - - bool WithUpdatedGaiaIntegrationEnabled() const { - return IsParamFeatureEnabled(); - } + SigninUiUtilTest() = default; void ExpectTurnSyncOn(signin_metrics::AccessPoint access_point, signin_metrics::PromoAction promo_action, @@ -241,34 +234,14 @@ class SigninUiUtilTest_ReplaceSyncPromosWithSignInPromos : public SigninUiUtilTestBase, - public testing::WithParamInterface<std::tuple< - /*BrowserSigninInSyncHeaderOnGaiaIntegration=*/bool, - /*ReplaceSyncPromosWithSignInPromos=*/bool>> { + public base::test::WithFeatureOverride { public: - SigninUiUtilTest_ReplaceSyncPromosWithSignInPromos() { - std::vector<base::test::FeatureRef> enabled_features; - std::vector<base::test::FeatureRef> disabled_features; - if (WithUpdatedGaiaIntegrationEnabled()) { - enabled_features.push_back( - switches::kBrowserSigninInSyncHeaderOnGaiaIntegration); - } else { - disabled_features.push_back( - switches::kBrowserSigninInSyncHeaderOnGaiaIntegration); - } - if (IsReplaceSyncPromosWithSignInPromosEnabled()) { - enabled_features.push_back(syncer::kReplaceSyncPromosWithSignInPromos); - } else { - disabled_features.push_back(syncer::kReplaceSyncPromosWithSignInPromos); - } - feature_list_.InitWithFeatures(enabled_features, disabled_features); - } - - bool WithUpdatedGaiaIntegrationEnabled() const { - return std::get<0>(GetParam()); - } + SigninUiUtilTest_ReplaceSyncPromosWithSignInPromos() + : base::test::WithFeatureOverride( + syncer::kReplaceSyncPromosWithSignInPromos) {} bool IsReplaceSyncPromosWithSignInPromosEnabled() const { - return std::get<1>(GetParam()); + return IsParamFeatureEnabled(); } void ExpectTurnSyncOn(signin_metrics::AccessPoint access_point, @@ -317,14 +290,8 @@ base::test::ScopedFeatureList feature_list_; }; -INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(SigninUiUtilTest); - -INSTANTIATE_TEST_SUITE_P( - All, - SigninUiUtilTest_ReplaceSyncPromosWithSignInPromos, - testing::Combine( - /*BrowserSigninInSyncHeaderOnGaiaIntegration=*/testing::Bool(), - /*ReplaceSyncPromosWithSignInPromos=*/testing::Bool())); +INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE( + SigninUiUtilTest_ReplaceSyncPromosWithSignInPromos); IN_PROC_BROWSER_TEST_P(SigninUiUtilTest_ReplaceSyncPromosWithSignInPromos, EnableSyncWithExistingAccount) { @@ -493,7 +460,7 @@ active_contents->GetVisibleURL()); } -IN_PROC_BROWSER_TEST_P(SigninUiUtilTest, SignInWithAlreadySignedInAccount) { +IN_PROC_BROWSER_TEST_F(SigninUiUtilTest, SignInWithAlreadySignedInAccount) { ui_test_utils::NavigateToURLWithDisposition( browser(), GURL("https://example.com"), WindowOpenDisposition::NEW_FOREGROUND_TAB, @@ -523,7 +490,7 @@ TabCloseTypes::CLOSE_USER_GESTURE); } -IN_PROC_BROWSER_TEST_P(SigninUiUtilTest, SignInWithAccountThatNeedsReauth) { +IN_PROC_BROWSER_TEST_F(SigninUiUtilTest, SignInWithAccountThatNeedsReauth) { ui_test_utils::NavigateToURLWithDisposition( browser(), GURL("http://example.com"), WindowOpenDisposition::NEW_FOREGROUND_TAB, @@ -554,23 +521,20 @@ TabCloseTypes::CLOSE_USER_GESTURE); } -IN_PROC_BROWSER_TEST_P(SigninUiUtilTest, SignInForNewAccountWithNoTab) { +IN_PROC_BROWSER_TEST_F(SigninUiUtilTest, SignInForNewAccountWithNoTab) { SignIn(CoreAccountInfo()); // Verify that the active tab has the correct DICE sign-in URL. content::WebContents* active_contents = browser()->tab_strip_model()->GetActiveWebContents(); ASSERT_TRUE(active_contents); - EXPECT_EQ(WithUpdatedGaiaIntegrationEnabled() - ? signin::GetChromeSyncURLForDice( - {.email = std::string(), - .continue_url = GURL(google_util::kGoogleHomepageURL)}) - : signin::GetAddAccountURLForDice( - std::string(), GURL(google_util::kGoogleHomepageURL)), + EXPECT_EQ(signin::GetChromeSyncURLForDice( + {.email = std::string(), + .continue_url = GURL(google_util::kGoogleHomepageURL)}), active_contents->GetVisibleURL()); } -IN_PROC_BROWSER_TEST_P(SigninUiUtilTest, SignInForNewAccountWithOneTab) { +IN_PROC_BROWSER_TEST_F(SigninUiUtilTest, SignInForNewAccountWithOneTab) { ui_test_utils::NavigateToURLWithDisposition( browser(), GURL("http://foo/1"), WindowOpenDisposition::NEW_FOREGROUND_TAB, @@ -582,16 +546,13 @@ content::WebContents* active_contents = browser()->tab_strip_model()->GetActiveWebContents(); ASSERT_TRUE(active_contents); - EXPECT_EQ(WithUpdatedGaiaIntegrationEnabled() - ? signin::GetChromeSyncURLForDice( - {.email = std::string(), - .continue_url = GURL(google_util::kGoogleHomepageURL)}) - : signin::GetAddAccountURLForDice( - std::string(), GURL(google_util::kGoogleHomepageURL)), + EXPECT_EQ(signin::GetChromeSyncURLForDice( + {.email = std::string(), + .continue_url = GURL(google_util::kGoogleHomepageURL)}), active_contents->GetVisibleURL()); } -IN_PROC_BROWSER_TEST_P(SigninUiUtilTest, GetOrderedAccountsForDisplay) { +IN_PROC_BROWSER_TEST_F(SigninUiUtilTest, GetOrderedAccountsForDisplay) { auto enable_disclaimer_on_primary_account_change_resetter = enterprise_util::DisableAutomaticManagementDisclaimerUntilReset( browser()->profile()); @@ -703,7 +664,7 @@ EXPECT_EQ(2, tab_strip->active_index()); } -IN_PROC_BROWSER_TEST_P(SigninUiUtilTest, ShowReauthTab) { +IN_PROC_BROWSER_TEST_F(SigninUiUtilTest, ShowReauthTab) { ui_test_utils::NavigateToURLWithDisposition( browser(), GURL("http://example.com"), WindowOpenDisposition::NEW_FOREGROUND_TAB, @@ -767,7 +728,7 @@ std::string::npos); } -IN_PROC_BROWSER_TEST_P(SigninUiUtilTest, +IN_PROC_BROWSER_TEST_F(SigninUiUtilTest, ShowExtensionSigninPrompt_AsLockedProfile) { signin_util::ScopedForceSigninSetterForTesting force_signin_setter(true); Profile* profile = browser()->profile(); @@ -797,13 +758,11 @@ ASSERT_TRUE(tab); EXPECT_TRUE(base::StartsWith( tab->GetVisibleURL().spec(), - WithUpdatedGaiaIntegrationEnabled() - ? GaiaUrls::GetInstance()->signin_chrome_sync_dice().spec() - : GaiaUrls::GetInstance()->add_account_url().spec(), + GaiaUrls::GetInstance()->signin_chrome_sync_dice().spec(), base::CompareCase::INSENSITIVE_ASCII)); } -IN_PROC_BROWSER_TEST_P(SigninUiUtilTest, +IN_PROC_BROWSER_TEST_F(SigninUiUtilTest, ShowSigninPromptFromPromoWithExistingAccount) { signin::MakePrimaryAccountAvailable(GetIdentityManager(), "foo@example.com", signin::ConsentLevel::kSignin); @@ -815,7 +774,7 @@ EXPECT_EQ(1, tab_strip->count()); } -IN_PROC_BROWSER_TEST_P(SigninUiUtilTest, GetSignInTabWithAccessPoint) { +IN_PROC_BROWSER_TEST_F(SigninUiUtilTest, GetSignInTabWithAccessPoint) { signin::MakePrimaryAccountAvailable(GetIdentityManager(), "foo@example.com", signin::ConsentLevel::kSignin); @@ -907,7 +866,7 @@ TestEnableSyncPromoWithExistingWebOnlyAccount(); } -IN_PROC_BROWSER_TEST_P(SigninUiUtilTest, SignInWithExistingWebOnlyAccount) { +IN_PROC_BROWSER_TEST_F(SigninUiUtilTest, SignInWithExistingWebOnlyAccount) { CoreAccountId account_id = GetIdentityManager()->GetAccountsMutator()->AddOrUpdateAccount( kMainGaiaID, kMainEmail, "refresh_token", false, @@ -925,7 +884,7 @@ GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin)); } -IN_PROC_BROWSER_TEST_P(SigninUiUtilTest, ShowExtensionSigninPromptReauth) { +IN_PROC_BROWSER_TEST_F(SigninUiUtilTest, ShowExtensionSigninPromptReauth) { CoreAccountId account_id = GetIdentityManager()->GetAccountsMutator()->AddOrUpdateAccount( kMainGaiaID, kMainEmail, "refresh_token", false, @@ -951,7 +910,7 @@ base::CompareCase::INSENSITIVE_ASCII)); } -IN_PROC_BROWSER_TEST_P( +IN_PROC_BROWSER_TEST_F( SigninUiUtilTest, ShouldShowAnimatedIdentityOnOpeningWindowIfMultipleWindowsAtStartup) { EXPECT_TRUE(ShouldShowAnimatedIdentityOnOpeningWindow(*browser()->profile())); @@ -980,8 +939,6 @@ run_loop.Run(); return new_profile; } - - private: }; // Tests that `ShowExtensionSigninPrompt()` doesn't crash when it cannot create
diff --git a/chrome/browser/taskbar/taskbar_decorator_win.cc b/chrome/browser/taskbar/taskbar_decorator_win.cc index 2612e0be..14ad00a 100644 --- a/chrome/browser/taskbar/taskbar_decorator_win.cc +++ b/chrome/browser/taskbar/taskbar_decorator_win.cc
@@ -38,8 +38,8 @@ #include "third_party/skia/include/core/SkImageInfo.h" #include "third_party/skia/include/core/SkRRect.h" #include "third_party/skia/include/core/SkStream.h" -#include "ui/gfx/icon_util.h" #include "ui/gfx/image/image.h" +#include "ui/gfx/win/icon_util.h" #include "ui/views/win/hwnd_util.h" namespace taskbar {
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn index a682e31..ba253f71 100644 --- a/chrome/browser/ui/BUILD.gn +++ b/chrome/browser/ui/BUILD.gn
@@ -1102,6 +1102,8 @@ "views/frame/contents_rounded_corner.h", "views/search_engine_choice/search_engine_choice_dialog_view.cc", "views/search_engine_choice/search_engine_choice_dialog_view.h", + "views/search_engines/dse_reset_dialog.cc", + "views/search_engines/dse_reset_dialog.h", "webui/bookmarks/bookmark_prefs.cc", "webui/bookmarks/bookmark_prefs.h", "webui/bookmarks/bookmarks_message_handler.cc", @@ -4226,6 +4228,7 @@ "views/profiles/avatar_toolbar_button_state_manager.h", "views/promos/ios_promo_bubble.cc", "views/promos/ios_promo_bubble.h", + "views/promos/ios_promo_constants.cc", "views/promos/ios_promo_constants.h", "views/qrcode_generator/qrcode_generator_bubble.cc", "views/qrcode_generator/qrcode_generator_bubble.h",
diff --git a/chrome/browser/ui/android/web_app_header/OWNERS b/chrome/browser/ui/android/web_app_header/OWNERS index bc59b0d..a0e1e51 100644 --- a/chrome/browser/ui/android/web_app_header/OWNERS +++ b/chrome/browser/ui/android/web_app_header/OWNERS
@@ -1,4 +1,3 @@ -asraine@chromium.org aixba@chromium.org japhet@chromium.org vkorotkevich@google.com
diff --git a/chrome/browser/ui/omnibox/chrome_omnibox_client.cc b/chrome/browser/ui/omnibox/chrome_omnibox_client.cc index 573f3c1..b91bd1d5 100644 --- a/chrome/browser/ui/omnibox/chrome_omnibox_client.cc +++ b/chrome/browser/ui/omnibox/chrome_omnibox_client.cc
@@ -70,6 +70,7 @@ #include "chrome/browser/ui/omnibox/omnibox_tab_helper.h" #include "chrome/browser/ui/views/frame/browser_view.h" #include "chrome/browser/ui/views/location_bar/location_bar_view.h" +#include "chrome/browser/ui/views/search_engines/dse_reset_dialog.h" #include "chrome/common/channel_info.h" #include "chrome/common/pref_names.h" #include "components/bookmarks/browser/bookmark_model.h" @@ -795,6 +796,8 @@ auto navigation = chrome::OpenCurrentURL(browser_); ChromeOmniboxNavigationObserver::Create(navigation.get(), profile_, text, match, alternative_nav_match); + search_engines::MaybeShowSearchEngineResetNotification(browser_, + match_type); } #if BUILDFLAG(ENABLE_EXTENSIONS)
diff --git a/chrome/browser/ui/promos/BUILD.gn b/chrome/browser/ui/promos/BUILD.gn index 319c081..d277507d 100644 --- a/chrome/browser/ui/promos/BUILD.gn +++ b/chrome/browser/ui/promos/BUILD.gn
@@ -25,6 +25,7 @@ "//chrome/browser/ui", "//chrome/browser/ui/views/frame:toolbar_button_provider", "//chrome/browser/ui/views/location_bar", + "//chrome/browser/ui/views/toolbar", "//components/feature_engagement/public", "//components/segmentation_platform/embedder/default_model", "//components/segmentation_platform/public",
diff --git a/chrome/browser/ui/promos/ios_promos_utils.cc b/chrome/browser/ui/promos/ios_promos_utils.cc index 10e3c61..8e8d6fd3 100644 --- a/chrome/browser/ui/promos/ios_promos_utils.cc +++ b/chrome/browser/ui/promos/ios_promos_utils.cc
@@ -17,6 +17,8 @@ #include "chrome/browser/ui/views/location_bar/icon_label_bubble_view.h" #include "chrome/browser/ui/views/page_action/page_action_icon_view.h" #include "chrome/browser/ui/views/promos/ios_promo_bubble.h" +#include "chrome/browser/ui/views/toolbar/browser_app_menu_button.h" +#include "chrome/browser/ui/views/toolbar/toolbar_view.h" #include "components/feature_engagement/public/feature_constants.h" #include "components/segmentation_platform/embedder/default_model/device_switcher_model.h" #include "components/segmentation_platform/public/constants.h" @@ -30,7 +32,9 @@ void ShowIOSDesktopPromoBubble(IOSPromoType promo_type, IOSPromoBubbleType bubble_type, Profile* profile, - ToolbarButtonProvider* toolbar_button_provider) { + BrowserView* browser_view) { + ToolbarButtonProvider* toolbar_button_provider = + browser_view->toolbar_button_provider(); switch (promo_type) { case IOSPromoType::kPassword: IOSPromoBubble::ShowPromoBubble( @@ -62,10 +66,16 @@ profile, IOSPromoType::kPayment, bubble_type); break; case IOSPromoType::kEnhancedBrowsing: - // TODO(crbug.com/438769954): Create and show promo bubble. + IOSPromoBubble::ShowPromoBubble( + browser_view->toolbar()->app_menu_button(), + /*highlighted_button=*/nullptr, profile, + IOSPromoType::kEnhancedBrowsing, bubble_type); break; case IOSPromoType::kLens: - // TODO(crbug.com/438769954): Create and show promo bubble. + IOSPromoBubble::ShowPromoBubble( + browser_view->toolbar()->app_menu_button(), + /*highlighted_button=*/nullptr, profile, IOSPromoType::kLens, + bubble_type); break; } } @@ -103,7 +113,7 @@ promos_utils::IOSDesktopPromoShown(browser->profile(), promo_type); ShowIOSDesktopPromoBubble( promo_type, bubble_type, browser->profile(), - browser->GetBrowserView().toolbar_button_provider()); + BrowserView::GetBrowserViewForBrowser(browser.get())); return; }
diff --git a/chrome/browser/ui/safety_hub/abusive_notification_permissions_manager.cc b/chrome/browser/ui/safety_hub/abusive_notification_permissions_manager.cc index 742e77e..78c2ab3 100644 --- a/chrome/browser/ui/safety_hub/abusive_notification_permissions_manager.cc +++ b/chrome/browser/ui/safety_hub/abusive_notification_permissions_manager.cc
@@ -7,6 +7,7 @@ #include <utility> #include "base/metrics/histogram_functions.h" +#include "base/strings/strcat.h" #include "base/time/default_clock.h" #include "chrome/browser/permissions/permission_revocation_request.h" #include "chrome/browser/ui/safety_hub/safety_hub_constants.h" @@ -24,6 +25,10 @@ #include "url/origin.h" namespace { +// Histogram names. +constexpr char kAbusiveNotificationPermissionRevocationHistogram[] = + "Settings.SafetyHub.AbusiveNotificationPermissionRevocation"; +constexpr char kPermissionChangedHistogramSuffix[] = "PermissionChanged"; void UpdateNotificationPermission(HostContentSettingsMap* hcsm, GURL url, @@ -34,6 +39,30 @@ setting_value); } +void RecordAbusiveNotificationPermissionChangedHistogram( + bool is_ignored, + safe_browsing::NotificationRevocationSource revocation_source, + ContentSetting setting_value) { + std::string_view revoke_status = is_ignored ? "Ignored" : "Revoked"; + std::string_view source_str; + switch (revocation_source) { + case safe_browsing::NotificationRevocationSource:: + kSocialEngineeringBlocklist: + source_str = "SocialEngineeringBlocklist"; + break; + case safe_browsing::NotificationRevocationSource:: + kManualSafeBrowsingRevocation: + source_str = "ManualSafeBrowsingRevocation"; + break; + default: + source_str = "Unknown"; + } + base::UmaHistogramEnumeration( + base::StrCat({kAbusiveNotificationPermissionRevocationHistogram, ".", + source_str, ".", revoke_status, ".", + kPermissionChangedHistogramSuffix}), + setting_value, ContentSetting::CONTENT_SETTING_NUM_SETTINGS); +} } // namespace AbusiveNotificationPermissionsManager::AbusiveNotificationPermissionsManager( @@ -134,24 +163,13 @@ if (stored_value.is_none()) { return safe_browsing::NotificationRevocationSource::kUnknown; } - const std::string* revocation_type = + const std::string* source_str = stored_value.GetDict().FindString(kAbusiveRevocationSourceKeyStr); - if (!revocation_type) { + if (!source_str) { return safe_browsing::NotificationRevocationSource::kUnknown; } - if (*revocation_type == kSocialEngineeringBlocklistStr) { - return safe_browsing::NotificationRevocationSource:: - kSocialEngineeringBlocklist; - } - if (*revocation_type == kManualSafeBrowsingRevocationStr) { - return safe_browsing::NotificationRevocationSource:: - kManualSafeBrowsingRevocation; - } - // Only `kSocialEngineeringBlocklist` and `kManualSafeBrowsingRevocation` are - // stored in `REVOKED_ABUSIVE_NOTIFICATION_PERMISSIONS`, other type of - // `NotificationRevocationSource` should never be the reason for abusive - // notification revocation. - return safe_browsing::NotificationRevocationSource::kUnknown; + return AbusiveNotificationPermissionsManager::GetNotificationRevocationSource( + *source_str); } void AbusiveNotificationPermissionsManager:: @@ -262,6 +280,38 @@ ContentSettingsType::REVOKED_ABUSIVE_NOTIFICATION_PERMISSIONS, {}); } +void AbusiveNotificationPermissionsManager::OnPermissionChanged( + const ContentSettingsPattern& primary_pattern, + const ContentSettingsPattern& secondary_pattern) { + GURL setting_url = primary_pattern.ToRepresentativeUrl(); + if (!setting_url.is_valid()) { + return; + } + base::Value stored_value = + safety_hub_util::GetRevokedAbusiveNotificationPermissionsSettingValue( + hcsm_.get(), setting_url); + if (stored_value.is_none()) { + // This permission change is unrelated to abusive revocation; do nothing. + return; + } + bool is_ignored = safety_hub_util::IsAbusiveNotificationRevocationIgnored( + hcsm_.get(), setting_url); + safe_browsing::NotificationRevocationSource revocation_source = + GetRevokedAbusiveNotificationRevocationSource(hcsm_.get(), setting_url); + RecordAbusiveNotificationPermissionChangedHistogram( + is_ignored, revocation_source, + hcsm_->GetContentSetting(setting_url, + secondary_pattern.ToRepresentativeUrl(), + ContentSettingsType::NOTIFICATIONS)); + + // Delete entry from abusive notification list as we assume the user is taking + // an active decision on the revocation. Note removal of entry with revoked + // status "ignored" will result in notification being auto-revoked again once + // criteria are met. + DeletePatternFromRevokedAbusiveNotificationList(primary_pattern, + secondary_pattern); +} + void AbusiveNotificationPermissionsManager::RestoreDeletedRevokedPermission( const ContentSettingsPattern& primary_pattern, content_settings::ContentSettingConstraints constraints) { @@ -493,3 +543,22 @@ return std::nullopt; } } + +// static +safe_browsing::NotificationRevocationSource +AbusiveNotificationPermissionsManager::GetNotificationRevocationSource( + std::string source_str) { + if (source_str == kSocialEngineeringBlocklistStr) { + return safe_browsing::NotificationRevocationSource:: + kSocialEngineeringBlocklist; + } + if (source_str == kManualSafeBrowsingRevocationStr) { + return safe_browsing::NotificationRevocationSource:: + kManualSafeBrowsingRevocation; + } + // Only `kSocialEngineeringBlocklist` and `kManualSafeBrowsingRevocation` are + // stored in `REVOKED_ABUSIVE_NOTIFICATION_PERMISSIONS`, other type of + // `NotificationRevocationSource` should never be the reason for abusive + // notification revocation. + return safe_browsing::NotificationRevocationSource::kUnknown; +}
diff --git a/chrome/browser/ui/safety_hub/abusive_notification_permissions_manager.h b/chrome/browser/ui/safety_hub/abusive_notification_permissions_manager.h index 01b23d7..be51b5a 100644 --- a/chrome/browser/ui/safety_hub/abusive_notification_permissions_manager.h +++ b/chrome/browser/ui/safety_hub/abusive_notification_permissions_manager.h
@@ -132,6 +132,12 @@ const ContentSettingsPattern& primary_pattern, const ContentSettingsPattern& secondary_pattern); + // Log metric for permission changed and remove + // `REVOKED_ABUSIVE_NOTIFICATION_PERMISSIONS` setting for the given pattern + // pairs. + void OnPermissionChanged(const ContentSettingsPattern& primary_pattern, + const ContentSettingsPattern& secondary_pattern); + // Restores REVOKED_ABUSIVE_NOTIFICATION_PERMISSIONS entry for the // primary_pattern after it was deleted after user // has accepted the revocation (via `ClearRevokedPermissionsList()`). @@ -278,6 +284,12 @@ static std::optional<std::string> GetRevocationSourceString( safe_browsing::NotificationRevocationSource source); + // Convert string representation for storing in + // `REVOKED_ABUSIVE_NOTIFICATION_PERMISSIONS` into + // `NotificationRevocationSource`. + static safe_browsing::NotificationRevocationSource + GetNotificationRevocationSource(std::string source_str); + // Used for interactions with the local database, when checking the blocklist. scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager> database_manager_;
diff --git a/chrome/browser/ui/safety_hub/abusive_notification_permissions_manager_unittest.cc b/chrome/browser/ui/safety_hub/abusive_notification_permissions_manager_unittest.cc index afd4d3d..78b565f 100644 --- a/chrome/browser/ui/safety_hub/abusive_notification_permissions_manager_unittest.cc +++ b/chrome/browser/ui/safety_hub/abusive_notification_permissions_manager_unittest.cc
@@ -35,6 +35,7 @@ const char url1[] = "https://example1.com"; const char url2[] = "https://example2.com"; const char url3[] = "https://example3.com"; +const char url4[] = "https://example4.com"; const ContentSettingsType notifications_type = ContentSettingsType::NOTIFICATIONS; @@ -70,17 +71,26 @@ GURL(url), safe_browsing::SBThreatType::SB_THREAT_TYPE_URL_PHISHING); } - void AddRevokedAbusiveNotification(std::string url, - ContentSetting cs, - bool is_ignored) { + void AddRevokedAbusiveNotification( + std::string url, + ContentSetting cs, + bool is_ignored, + safe_browsing::NotificationRevocationSource source = + safe_browsing::NotificationRevocationSource::kUnknown) { AddAbusiveNotification(url, cs); content_settings::ContentSettingConstraints constraint; + base::Value::Dict value; + value.Set(safety_hub::kRevokedStatusDictKeyStr, + is_ignored ? safety_hub::kIgnoreStr : safety_hub::kRevokeStr); + std::optional<std::string> source_str = + AbusiveNotificationPermissionsManager::GetRevocationSourceString( + source); + if (source_str) { + value.Set(kAbusiveRevocationSourceKeyStr, source_str.value()); + } hcsm()->SetWebsiteSettingDefaultScope( GURL(url), GURL(url), revoked_notifications_type, - base::Value(base::Value::Dict().Set( - safety_hub::kRevokedStatusDictKeyStr, - is_ignored ? safety_hub::kIgnoreStr : safety_hub::kRevokeStr)), - constraint); + base::Value(std::move(value)), constraint); } void AddSafeNotification(std::string url, ContentSetting cs) { @@ -756,6 +766,77 @@ /* expected_count */ 1); } +TEST_F(AbusiveNotificationPermissionsManagerTest, OnPermissionChanged) { + AddRevokedAbusiveNotification(url1, ContentSetting::CONTENT_SETTING_ASK, + /*is_ignored=*/false); + AddRevokedAbusiveNotification(url2, ContentSetting::CONTENT_SETTING_ASK, + /*is_ignored=*/false, + safe_browsing::NotificationRevocationSource:: + kManualSafeBrowsingRevocation); + AddRevokedAbusiveNotification( + url3, ContentSetting::CONTENT_SETTING_ALLOW, + /*is_ignored=*/false, + safe_browsing::NotificationRevocationSource::kSocialEngineeringBlocklist); + AddRevokedAbusiveNotification( + url4, ContentSetting::CONTENT_SETTING_ALLOW, + /*is_ignored=*/true, + safe_browsing::NotificationRevocationSource::kSocialEngineeringBlocklist); + EXPECT_EQ( + safety_hub_util::GetRevokedAbusiveNotificationPermissions(hcsm()).size(), + 3u); + EXPECT_TRUE(safety_hub_util::IsAbusiveNotificationRevocationIgnored( + hcsm(), GURL(url4))); + auto manager = AbusiveNotificationPermissionsManager( + mock_database_manager(), hcsm(), profile()->GetTestingPrefService()); + base::HistogramTester histogram_tester; + + // Simulate permission changed by the user. + manager.OnPermissionChanged( + ContentSettingsPattern::FromURLNoWildcard(GURL(url1)), + ContentSettingsPattern::Wildcard()); + manager.OnPermissionChanged( + ContentSettingsPattern::FromURLNoWildcard(GURL(url2)), + ContentSettingsPattern::Wildcard()); + manager.OnPermissionChanged( + ContentSettingsPattern::FromURLNoWildcard(GURL(url3)), + ContentSettingsPattern::Wildcard()); + manager.OnPermissionChanged( + ContentSettingsPattern::FromURLNoWildcard(GURL(url4)), + ContentSettingsPattern::Wildcard()); + + // Verify that entries are deleted from revoked abusive notification content + // setting. + ASSERT_EQ( + safety_hub_util::GetRevokedAbusiveNotificationPermissions(hcsm()).size(), + 0u); + ASSERT_FALSE(safety_hub_util::IsAbusiveNotificationRevocationIgnored( + hcsm(), GURL(url4))); + // Verify that appropriate metrics are logged. + histogram_tester.ExpectUniqueSample( + "Settings.SafetyHub.AbusiveNotificationPermissionRevocation.Unknown." + "Revoked.PermissionChanged", + /* sample */ ContentSetting::CONTENT_SETTING_ASK, + /* expected_count */ 1); + histogram_tester.ExpectUniqueSample( + "Settings.SafetyHub.AbusiveNotificationPermissionRevocation." + "ManualSafeBrowsingRevocation." + "Revoked.PermissionChanged", + /* sample */ ContentSetting::CONTENT_SETTING_ASK, + /* expected_count */ 1); + histogram_tester.ExpectUniqueSample( + "Settings.SafetyHub.AbusiveNotificationPermissionRevocation." + "SocialEngineeringBlocklist." + "Revoked.PermissionChanged", + /* sample */ ContentSetting::CONTENT_SETTING_ALLOW, + /* expected_count */ 1); + histogram_tester.ExpectUniqueSample( + "Settings.SafetyHub.AbusiveNotificationPermissionRevocation." + "SocialEngineeringBlocklist." + "Ignored.PermissionChanged", + /* sample */ ContentSetting::CONTENT_SETTING_ALLOW, + /* expected_count */ 1); +} + class ShowManualNotificationRevocationsTest : public AbusiveNotificationPermissionsManagerTest { public:
diff --git a/chrome/browser/ui/safety_hub/revoked_permissions_service.cc b/chrome/browser/ui/safety_hub/revoked_permissions_service.cc index 3e6b694..50cab122 100644 --- a/chrome/browser/ui/safety_hub/revoked_permissions_service.cc +++ b/chrome/browser/ui/safety_hub/revoked_permissions_service.cc
@@ -252,9 +252,8 @@ // There should be at most one active revocation per site: either abusive or // disruptive. if (IsAbusiveNotificationAutoRevocationEnabled()) { - abusive_notification_manager_ - ->DeletePatternFromRevokedAbusiveNotificationList(primary_pattern, - secondary_pattern); + abusive_notification_manager_->OnPermissionChanged(primary_pattern, + secondary_pattern); } if (disruptive_notification_manager_) { disruptive_notification_manager_->OnPermissionChanged(primary_pattern,
diff --git a/chrome/browser/ui/signin/signin_view_controller.cc b/chrome/browser/ui/signin/signin_view_controller.cc index a4e0c76..a96a201 100644 --- a/chrome/browser/ui/signin/signin_view_controller.cc +++ b/chrome/browser/ui/signin/signin_view_controller.cc
@@ -249,11 +249,7 @@ {.email = email_hint, .continue_url = continue_url}); } - bool use_chrome_sync_url = - base::FeatureList::IsEnabled( - switches::kBrowserSigninInSyncHeaderOnGaiaIntegration) || - access_point == signin_metrics::AccessPoint::kExtensions; - + bool use_chrome_sync_url = true; // A reauth is requested, or the account is already signed in (which is // effectively a reauth). if (signin_reason == signin_metrics::Reason::kReauthentication ||
diff --git a/chrome/browser/ui/tabs/glic_actor_task_icon_controller.cc b/chrome/browser/ui/tabs/glic_actor_task_icon_controller.cc index f8e996f..99568c3 100644 --- a/chrome/browser/ui/tabs/glic_actor_task_icon_controller.cc +++ b/chrome/browser/ui/tabs/glic_actor_task_icon_controller.cc
@@ -98,13 +98,6 @@ // Determines highlight + tooltip styling. if (is_showing) { - if (current_view == CurrentView::kConversation) { - tab_strip_action_container_->UnhighlightGlicActorTaskIcon(); - tab_strip_action_container_->HighlightGlicButton(); - } else if (current_view == CurrentView::kActuation) { - tab_strip_action_container_->UnhighlightGlicButton(); - tab_strip_action_container_->HighlightGlicActorTaskIcon(); - } tab_strip_action_container_->glic_actor_task_icon() ->SetFloatyOpenTooltipText(); } else {
diff --git a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_win.cc b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_win.cc index 371396d3..91c8593 100644 --- a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_win.cc +++ b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_win.cc
@@ -42,8 +42,8 @@ #include "ui/base/win/hwnd_metrics.h" #include "ui/display/win/screen_win.h" #include "ui/gfx/geometry/point.h" -#include "ui/gfx/icon_util.h" #include "ui/gfx/image/image_family.h" +#include "ui/gfx/win/icon_util.h" #include "ui/views/controls/menu/native_menu_win.h" class VirtualDesktopHelper
diff --git a/chrome/browser/ui/views/frame/browser_frame_view_win.cc b/chrome/browser/ui/views/frame/browser_frame_view_win.cc index fcacb8a8..b5de535b 100644 --- a/chrome/browser/ui/views/frame/browser_frame_view_win.cc +++ b/chrome/browser/ui/views/frame/browser_frame_view_win.cc
@@ -48,9 +48,9 @@ #include "ui/gfx/canvas.h" #include "ui/gfx/geometry/dip_util.h" #include "ui/gfx/geometry/rect_conversions.h" -#include "ui/gfx/icon_util.h" #include "ui/gfx/image/image.h" #include "ui/gfx/scoped_canvas.h" +#include "ui/gfx/win/icon_util.h" #include "ui/strings/grit/ui_strings.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_observer.h"
diff --git a/chrome/browser/ui/views/promos/ios_promo_bubble.cc b/chrome/browser/ui/views/promos/ios_promo_bubble.cc index 965f471..b21ddf0 100644 --- a/chrome/browser/ui/views/promos/ios_promo_bubble.cc +++ b/chrome/browser/ui/views/promos/ios_promo_bubble.cc
@@ -28,6 +28,7 @@ #include "components/feature_engagement/public/tracker.h" #include "components/prefs/pref_service.h" #include "components/qr_code_generator/bitmap_generator.h" +#include "components/sharing_message/features.h" #include "components/strings/grit/components_strings.h" #include "content/public/browser/page_navigator.h" #include "ui/base/l10n/l10n_util.h" @@ -37,6 +38,7 @@ #include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "ui/views/bubble/bubble_dialog_model_host.h" #include "ui/views/interaction/element_tracker_views.h" +#include "ui/views/layout/fill_layout.h" #include "ui/views/layout/flex_layout_types.h" #include "ui/views/layout/flex_layout_view.h" #include "ui/views/layout/layout_types.h" @@ -45,21 +47,141 @@ #include "ui/views/widget/widget.h" namespace { -// Get the correct URL for the promo's QR code. -std::string GetIOSDesktopPromoQRCodeURL(IOSPromoType promo_type) { +// Creates and returns IOSPromoTypeConfigs for the Password bubble. +IOSPromoConstants::IOSPromoTypeConfigs SetUpPasswordBubble( + IOSPromoBubbleType bubble_type) { + IOSPromoConstants::IOSPromoTypeConfigs config; + config.with_header = true; + config.promo_title_id = IDS_IOS_DESKTOP_PASSWORD_PROMO_BUBBLE_FOOTER_TITLE; + config.bubble_title_id = IDS_IOS_DESKTOP_PASSWORD_PROMO_BUBBLE_TITLE; + config.bubble_subtitle_id = IDS_IOS_DESKTOP_PASSWORD_PROMO_BUBBLE_SUBTITLE; + config.decline_button_text_id = + IDS_IOS_DESKTOP_PASSWORD_PROMO_BUBBLE_BUTTON_DECLINE; + switch (bubble_type) { + case IOSPromoBubbleType::kQRCode: + config.promo_description_id = + IDS_IOS_DESKTOP_PASSWORD_PROMO_BUBBLE_FOOTER_DESCRIPTION_QR; + config.accept_button_text_id = + IDS_IOS_DESKTOP_PROMO_BUBBLE_BUTTON_ACCEPT_QR; + config.promo_qr_code_url = + IOSPromoConstants::kIOSPromoPasswordBubbleQRCodeURL; + break; + case IOSPromoBubbleType::kReminder: + config.accept_button_text_id = + IDS_IOS_DESKTOP_PROMO_BUBBLE_BUTTON_ACCEPT_REMINDER; + config.promo_description_id = + IDS_IOS_DESKTOP_PASSWORD_PROMO_BUBBLE_FOOTER_DESCRIPTION_REMINDER; + break; + } + return config; +} + +// Creates and returns IOSPromoTypeConfigs for the Address bubble. +IOSPromoConstants::IOSPromoTypeConfigs SetUpAddressBubble( + IOSPromoBubbleType bubble_type) { + CHECK_EQ(bubble_type, IOSPromoBubbleType::kQRCode); + IOSPromoConstants::IOSPromoTypeConfigs config; + config.with_header = true; + config.bubble_title_id = IDS_IOS_DESKTOP_ADDRESS_PROMO_BUBBLE_TITLE; + config.bubble_subtitle_id = IDS_IOS_DESKTOP_ADDRESS_PROMO_BUBBLE_SUBTITLE; + config.promo_title_id = IDS_IOS_DESKTOP_ADDRESS_PROMO_BUBBLE_FOOTER_TITLE; + config.promo_description_id = + IDS_IOS_DESKTOP_ADDRESS_PROMO_BUBBLE_FOOTER_DESCRIPTION_QR; + config.decline_button_text_id = + IDS_IOS_DESKTOP_ADDRESS_PROMO_BUBBLE_BUTTON_DECLINE; + config.accept_button_text_id = IDS_IOS_DESKTOP_PROMO_BUBBLE_BUTTON_ACCEPT_QR; + config.promo_qr_code_url = IOSPromoConstants::kIOSPromoAddressBubbleQRCodeURL; + return config; +} + +// Creates and returns IOSPromoTypeConfigs for the Payment bubble. +IOSPromoConstants::IOSPromoTypeConfigs SetUpPaymentBubble( + IOSPromoBubbleType bubble_type) { + CHECK_EQ(bubble_type, IOSPromoBubbleType::kQRCode); + IOSPromoConstants::IOSPromoTypeConfigs config; + config.with_header = true; + config.bubble_title_id = + IDS_AUTOFILL_SAVE_CARD_CONFIRMATION_SUCCESS_TITLE_TEXT; + config.bubble_subtitle_id = + IDS_AUTOFILL_SAVE_CARD_CONFIRMATION_SUCCESS_DESCRIPTION_TEXT; + config.promo_title_id = IDS_IOS_DESKTOP_PAYMENT_PROMO_BUBBLE_FOOTER_TITLE; + config.promo_description_id = + IDS_IOS_DESKTOP_PAYMENT_PROMO_BUBBLE_FOOTER_DESCRIPTION_QR; + config.decline_button_text_id = + IDS_IOS_DESKTOP_PAYMENT_PROMO_BUBBLE_BUTTON_DECLINE; + config.accept_button_text_id = IDS_IOS_DESKTOP_PROMO_BUBBLE_BUTTON_ACCEPT_QR; + config.promo_qr_code_url = IOSPromoConstants::kIOSPromoPaymentBubbleQRCodeURL; + return config; +} + +// Creates and returns IOSPromoTypeConfigs for the Enhanced Browsing bubble. +IOSPromoConstants::IOSPromoTypeConfigs SetUpEnhancedBrowsingBubble( + IOSPromoBubbleType bubble_type) { + IOSPromoConstants::IOSPromoTypeConfigs config; + config.with_header = false; + config.decline_button_text_id = IDS_IOS_DESKTOP_PROMO_BUBBLE_BUTTON_DECLINE; + switch (bubble_type) { + case IOSPromoBubbleType::kQRCode: + config.promo_title_id = IDS_IOS_DESKTOP_ESB_PROMO_BUBBLE_TITLE_QR; + config.promo_description_id = + IDS_IOS_DESKTOP_ESB_PROMO_BUBBLE_DESCRIPTION_QR; + config.accept_button_text_id = + IDS_IOS_DESKTOP_PROMO_BUBBLE_BUTTON_ACCEPT_QR; + // TODO(crbug.com/442562546): Create URL for kEnhancedBrowsing promo. + config.promo_qr_code_url = + IOSPromoConstants::kIOSPromoPaymentBubbleQRCodeURL; + break; + case IOSPromoBubbleType::kReminder: + config.promo_title_id = IDS_IOS_DESKTOP_ESB_PROMO_BUBBLE_TITLE_REMINDER; + config.promo_description_id = + IDS_IOS_DESKTOP_ESB_PROMO_BUBBLE_DESCRIPTION_REMINDER; + config.accept_button_text_id = + IDS_IOS_DESKTOP_PROMO_BUBBLE_BUTTON_ACCEPT_REMINDER; + break; + } + return config; +} + +// Creates and returns IOSPromoTypeConfigs for the Lens bubble. +IOSPromoConstants::IOSPromoTypeConfigs SetUpLensBubble( + IOSPromoBubbleType bubble_type) { + IOSPromoConstants::IOSPromoTypeConfigs config; + config.with_header = false; + config.promo_description_id = IDS_IOS_DESKTOP_LENS_PROMO_BUBBLE_DESCRIPTION; + config.decline_button_text_id = IDS_IOS_DESKTOP_PROMO_BUBBLE_BUTTON_DECLINE; + switch (bubble_type) { + case IOSPromoBubbleType::kQRCode: + config.promo_title_id = IDS_IOS_DESKTOP_LENS_PROMO_BUBBLE_TITLE_QR; + config.accept_button_text_id = + IDS_IOS_DESKTOP_PROMO_BUBBLE_BUTTON_ACCEPT_QR; + // TODO(crbug.com/442562546): Placeholder, set URL for kLens promo. + config.promo_qr_code_url = + IOSPromoConstants::kIOSPromoPasswordBubbleQRCodeURL; + break; + case IOSPromoBubbleType::kReminder: + config.promo_title_id = IDS_IOS_DESKTOP_LENS_PROMO_BUBBLE_TITLE_REMINDER; + config.accept_button_text_id = + IDS_IOS_DESKTOP_PROMO_BUBBLE_BUTTON_ACCEPT_REMINDER; + break; + } + return config; +} + +// Returns the config for the given `promo_type` and `bubble_type`. +IOSPromoConstants::IOSPromoTypeConfigs SetUpBubble( + IOSPromoType promo_type, + IOSPromoBubbleType bubble_type) { switch (promo_type) { case IOSPromoType::kPassword: - return IOSPromoConstants::kIOSPromoPasswordBubbleQRCodeURL; + return SetUpPasswordBubble(bubble_type); case IOSPromoType::kAddress: - return IOSPromoConstants::kIOSPromoAddressBubbleQRCodeURL; + return SetUpAddressBubble(bubble_type); case IOSPromoType::kPayment: - return IOSPromoConstants::kIOSPromoPaymentBubbleQRCodeURL; + return SetUpPaymentBubble(bubble_type); case IOSPromoType::kEnhancedBrowsing: - // TODO(crbug.com/442562546): Return URL for kEnhancedBrowsing promo. - return IOSPromoConstants::kIOSPromoPaymentBubbleQRCodeURL; + return SetUpEnhancedBrowsingBubble(bubble_type); case IOSPromoType::kLens: - // TODO(crbug.com/442562546): Return URL for kLens promo. - return IOSPromoConstants::kIOSPromoPasswordBubbleQRCodeURL; + return SetUpLensBubble(bubble_type); } } } // namespace @@ -113,6 +235,12 @@ ios_promo_delegate_->GetWidget()->Close(); } + // Callback for when the primary action/acceptance button is clicked. + void AcceptButtonClicked(IOSPromoBubbleType bubble_type) { + // TODO(crbug.com/438769954): Handle user action and record metrics. + ios_promo_delegate_->GetWidget()->Close(); + } + private: // Pointer to the current Profile. const raw_ptr<Profile> profile_; @@ -129,37 +257,49 @@ }; // static -// CreateFooter creates the view that is inserted as footer to the bubble. -std::unique_ptr<views::View> IOSPromoBubble::CreateFooter( +std::unique_ptr<views::View> IOSPromoBubble::CreateContentView( IOSPromoBubble::IOSPromoBubbleDelegate* bubble_delegate, - const IOSPromoConstants::IOSPromoTypeConfigs& ios_promo_config) { - views::LayoutProvider* provider = views::LayoutProvider::Get(); - - auto footer_title_container = - views::Builder<views::Label>() - .SetText(l10n_util::GetStringUTF16(ios_promo_config.promo_title_id)) - .SetTextStyle(views::style::STYLE_BODY_2_MEDIUM) - .SetMultiLine(true) - .SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_TO_HEAD) - .SetProperty(views::kMarginsKey, - gfx::Insets::TLBR( - (views::LayoutProvider::Get()->GetDistanceMetric( - views::DISTANCE_DIALOG_CONTENT_MARGIN_TOP_TEXT)), - 0, - - (views::LayoutProvider::Get()->GetDistanceMetric( - views::DISTANCE_DIALOG_CONTENT_MARGIN_TOP_TEXT)), - 0)); - - auto footer_view = + const IOSPromoConstants::IOSPromoTypeConfigs& ios_promo_config, + bool with_title, + IOSPromoBubbleType bubble_type) { + auto content_view = views::Builder<views::BoxLayoutView>() .SetOrientation(views::BoxLayout::Orientation::kVertical) - .SetCrossAxisAlignment(views::BoxLayout::MainAxisAlignment::kStart) - .SetMainAxisAlignment(views::BoxLayout::MainAxisAlignment::kStart) - .SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kStretch) - .SetBetweenChildSpacing(provider->GetDistanceMetric( - views::DistanceMetric::DISTANCE_VECTOR_ICON_PADDING)); + .SetBetweenChildSpacing( + views::LayoutProvider::Get()->GetDistanceMetric( + views::DistanceMetric::DISTANCE_UNRELATED_CONTROL_VERTICAL)) + .Build(); + if (with_title) { + auto title_view = + views::Builder<views::Label>() + .SetText(l10n_util::GetStringUTF16(ios_promo_config.promo_title_id)) + .SetTextStyle(views::style::STYLE_BODY_2_MEDIUM) + .SetMultiLine(true) + .SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_TO_HEAD) + .SetProperty( + views::kMarginsKey, + gfx::Insets::TLBR( + (views::LayoutProvider::Get()->GetDistanceMetric( + views::DISTANCE_DIALOG_CONTENT_MARGIN_TOP_TEXT)), + 0, 0, 0)) + .Build(); + content_view->AddChildView(std::move(title_view)); + } + + content_view->AddChildView( + CreateImageAndBodyTextView(ios_promo_config, bubble_type)); + content_view->AddChildView( + CreateButtonsView(bubble_delegate, ios_promo_config, bubble_type)); + + return content_view; +} + +// static +std::unique_ptr<views::View> IOSPromoBubble::CreateButtonsView( + IOSPromoBubble::IOSPromoBubbleDelegate* bubble_delegate, + const IOSPromoConstants::IOSPromoTypeConfigs& ios_promo_config, + IOSPromoBubbleType bubble_type) { auto decline_button_callback = base::BindRepeating( &IOSPromoBubble::IOSPromoBubbleDelegate::OnNoThanksButtonClicked, base::Unretained(bubble_delegate)); @@ -170,6 +310,84 @@ .SetIsDefault(false) .SetCallback(decline_button_callback); + auto button_container_builder = + views::Builder<views::BoxLayoutView>() + .SetOrientation(views::BoxLayout::Orientation::kHorizontal) + .SetMainAxisAlignment(views::BoxLayout::MainAxisAlignment::kEnd) + .SetBetweenChildSpacing( + views::LayoutProvider::Get()->GetDistanceMetric( + views::DistanceMetric::DISTANCE_RELATED_BUTTON_HORIZONTAL)); + button_container_builder.AddChild(decline_button); + + if (MobilePromoOnDesktopTypeEnabled() != + MobilePromoOnDesktopPromoType::kDisabled) { + auto accept_button_callback = base::BindRepeating( + &IOSPromoBubble::IOSPromoBubbleDelegate::AcceptButtonClicked, + base::Unretained(bubble_delegate), bubble_type); + auto accept_button = views::Builder<views::MdTextButton>() + .SetText(l10n_util::GetStringUTF16( + ios_promo_config.accept_button_text_id)) + .SetIsDefault(true) + .SetCallback(accept_button_callback); + button_container_builder.AddChild(accept_button); + } + + return std::move(button_container_builder).Build(); +} + +// static +std::unique_ptr<views::View> IOSPromoBubble::CreateImageAndBodyTextView( + const IOSPromoConstants::IOSPromoTypeConfigs& ios_promo_config, + IOSPromoBubbleType bubble_type) { + views::ImageView* image_view = nullptr; + views::Builder<views::View> image_or_qr_code_view; + + switch (bubble_type) { + case IOSPromoBubbleType::kQRCode: { + auto qr_code_container = + views::Builder<views::View>() + .SetBorder(views::CreateRoundedRectBorder( + /*thickness=*/1, + views::LayoutProvider::Get()->GetCornerRadiusMetric( + views::Emphasis::kHigh), + SK_ColorLTGRAY)) + .SetLayoutManager(std::make_unique<views::FillLayout>()) + .AddChild( + views::Builder<views::ImageView>() + .CopyAddressTo(&image_view) + .SetHorizontalAlignment( + views::ImageView::Alignment::kLeading) + .SetImageSize( + gfx::Size(IOSPromoConstants::kQrCodeImageSize, + IOSPromoConstants::kQrCodeImageSize)) + .SetCornerRadius( + views::LayoutProvider::Get()->GetCornerRadiusMetric( + views::Emphasis::kHigh)) + .SetVisible(true)); + + // Note that the absence of a quiet zone may interfere with decoding + // of QR codes even for small codes. + auto qr_image = qr_code_generator::GenerateImage( + base::as_byte_span( + std::string_view(ios_promo_config.promo_qr_code_url)), + qr_code_generator::ModuleStyle::kCircles, + qr_code_generator::LocatorStyle::kRounded, + qr_code_generator::CenterImage::kProductLogo, + qr_code_generator::QuietZone::kIncluded); + + // Generating QR code for `kQRCodeURL` should always succeed (e.g. it + // can't result in input-too-long error or other errors). + CHECK(qr_image.has_value()); + image_view->SetImage(ui::ImageModel::FromImageSkia(qr_image.value())); + image_or_qr_code_view = std::move(qr_code_container); + break; + } + case IOSPromoBubbleType::kReminder: { + // TODO(crbug.com/438769954): Add icon image for kReminder type bubbles. + break; + } + } + auto description_label = views::Builder<views::Label>() .SetText( @@ -185,153 +403,14 @@ /*adjust_height_for_width=*/true)) .SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_TO_HEAD); - auto label_and_button_container = - views::Builder<views::FlexLayoutView>() - .SetOrientation(views::LayoutOrientation::kVertical) - .SetCrossAxisAlignment(views::LayoutAlignment::kEnd) - .AddChild(description_label) - .AddChild(decline_button) - .SetProperty(views::kFlexBehaviorKey, - views::FlexSpecification( - views::MinimumFlexSizeRule::kScaleToMinimum, - views::MaximumFlexSizeRule::kPreferred, - /*adjust_height_for_width=*/true)) - .SetProperty(views::kMarginsKey, - gfx::Insets::TLBR( - 0, - (views::LayoutProvider::Get()->GetDistanceMetric( - views::DISTANCE_DIALOG_CONTENT_MARGIN_TOP_TEXT)), - 0, 0)); - - views::ImageView* image_view; - - auto qr_code_container = - views::Builder<views::ImageView>() - .CopyAddressTo(&image_view) - .SetHorizontalAlignment(views::ImageView::Alignment::kLeading) - .SetImageSize(gfx::Size(IOSPromoConstants::kQrCodeImageSize, - IOSPromoConstants::kQrCodeImageSize)) - .SetBorder(views::CreateRoundedRectBorder( - /*thickness=*/2, - views::LayoutProvider::Get()->GetCornerRadiusMetric( - views::Emphasis::kHigh), - SK_ColorWHITE)) - .SetVisible(true); - - auto footer_content_container = - views::Builder<views::FlexLayoutView>() - .SetOrientation(views::LayoutOrientation::kHorizontal) - .SetCrossAxisAlignment(views::LayoutAlignment::kStart) - .AddChild(qr_code_container) - .AddChild(label_and_button_container); - - auto built_footer_view = - std::move(footer_view.AddChild(footer_title_container) - .AddChild(footer_content_container)) - .Build(); - - // Note that the absence of a quiet zone may interfere with decoding - // of QR codes even for small codes. - auto qr_image = qr_code_generator::GenerateImage( - base::as_byte_span(std::string_view(ios_promo_config.promo_qr_code_url)), - qr_code_generator::ModuleStyle::kCircles, - qr_code_generator::LocatorStyle::kRounded, - qr_code_generator::CenterImage::kProductLogo, - qr_code_generator::QuietZone::kIncluded); - - // Generating QR code for `kQRCodeURL` should always succeed (e.g. it - // can't result in input-too-long error or other errors). - CHECK(qr_image.has_value()); - - image_view->SetImage(ui::ImageModel::FromImageSkia(qr_image.value())); - - return built_footer_view; -} - -// static -IOSPromoConstants::IOSPromoTypeConfigs IOSPromoBubble::SetUpBubble( - IOSPromoType promo_type, - IOSPromoBubbleType bubble_type) { - IOSPromoConstants::IOSPromoTypeConfigs ios_promo_config; - - ios_promo_config.promo_qr_code_url = GetIOSDesktopPromoQRCodeURL(promo_type); - - switch (promo_type) { - case IOSPromoType::kPassword: - // Set up iOS Password Promo Bubble. - ios_promo_config.bubble_title_id = - IDS_IOS_DESKTOP_PASSWORD_PROMO_BUBBLE_TITLE; - ios_promo_config.bubble_subtitle_id = - IDS_IOS_DESKTOP_PASSWORD_PROMO_BUBBLE_SUBTITLE; - ios_promo_config.promo_title_id = - IDS_IOS_DESKTOP_PASSWORD_PROMO_BUBBLE_FOOTER_TITLE; - ios_promo_config.promo_description_id = - IDS_IOS_DESKTOP_PASSWORD_PROMO_BUBBLE_FOOTER_DESCRIPTION_QR; - ios_promo_config.decline_button_text_id = - IDS_IOS_DESKTOP_PASSWORD_PROMO_BUBBLE_BUTTON_DECLINE; - break; - case IOSPromoType::kAddress: - // Set up iOS Address Promo Bubble. - ios_promo_config.bubble_title_id = - IDS_IOS_DESKTOP_ADDRESS_PROMO_BUBBLE_TITLE; - ios_promo_config.bubble_subtitle_id = - IDS_IOS_DESKTOP_ADDRESS_PROMO_BUBBLE_SUBTITLE; - ios_promo_config.promo_title_id = - IDS_IOS_DESKTOP_ADDRESS_PROMO_BUBBLE_FOOTER_TITLE; - ios_promo_config.promo_description_id = - IDS_IOS_DESKTOP_ADDRESS_PROMO_BUBBLE_FOOTER_DESCRIPTION_QR; - ios_promo_config.decline_button_text_id = - IDS_IOS_DESKTOP_ADDRESS_PROMO_BUBBLE_BUTTON_DECLINE; - break; - case IOSPromoType::kPayment: - // Set up iOS Payment Promo Bubble. - ios_promo_config.bubble_title_id = - IDS_AUTOFILL_SAVE_CARD_CONFIRMATION_SUCCESS_TITLE_TEXT; - ios_promo_config.bubble_subtitle_id = - IDS_AUTOFILL_SAVE_CARD_CONFIRMATION_SUCCESS_DESCRIPTION_TEXT; - ios_promo_config.promo_title_id = - IDS_IOS_DESKTOP_PAYMENT_PROMO_BUBBLE_FOOTER_TITLE; - ios_promo_config.promo_description_id = - IDS_IOS_DESKTOP_PAYMENT_PROMO_BUBBLE_FOOTER_DESCRIPTION_QR; - ios_promo_config.decline_button_text_id = - IDS_IOS_DESKTOP_PAYMENT_PROMO_BUBBLE_BUTTON_DECLINE; - break; - case IOSPromoType::kEnhancedBrowsing: - switch (bubble_type) { - case IOSPromoBubbleType::kQRCode: - ios_promo_config.bubble_title_id = - IDS_IOS_DESKTOP_ESB_PROMO_BUBBLE_TITLE_QR; - ios_promo_config.bubble_subtitle_id = - IDS_IOS_DESKTOP_ESB_PROMO_BUBBLE_DESCRIPTION_QR; - break; - case IOSPromoBubbleType::kReminder: - ios_promo_config.bubble_title_id = - IDS_IOS_DESKTOP_ESB_PROMO_BUBBLE_TITLE_REMINDER; - ios_promo_config.bubble_subtitle_id = - IDS_IOS_DESKTOP_ESB_PROMO_BUBBLE_DESCRIPTION_REMINDER; - break; - } - ios_promo_config.decline_button_text_id = - IDS_IOS_DESKTOP_PROMO_BUBBLE_BUTTON_DECLINE; - break; - case IOSPromoType::kLens: - switch (bubble_type) { - case IOSPromoBubbleType::kQRCode: - ios_promo_config.bubble_title_id = - IDS_IOS_DESKTOP_LENS_PROMO_BUBBLE_TITLE_QR; - break; - case IOSPromoBubbleType::kReminder: - ios_promo_config.bubble_title_id = - IDS_IOS_DESKTOP_LENS_PROMO_BUBBLE_TITLE_REMINDER; - break; - } - ios_promo_config.bubble_subtitle_id = - IDS_IOS_DESKTOP_LENS_PROMO_BUBBLE_DESCRIPTION; - ios_promo_config.decline_button_text_id = - IDS_IOS_DESKTOP_PROMO_BUBBLE_BUTTON_DECLINE; - break; - } - return ios_promo_config; + return views::Builder<views::BoxLayoutView>() + .SetOrientation(views::BoxLayout::Orientation::kHorizontal) + .SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kCenter) + .SetBetweenChildSpacing(views::LayoutProvider::Get()->GetDistanceMetric( + views::DistanceMetric::DISTANCE_RELATED_CONTROL_HORIZONTAL)) + .AddChild(std::move(image_or_qr_code_view)) + .AddChild(description_label) + .Build(); } // static @@ -360,27 +439,42 @@ dialog_model_builder.SetCloseActionCallback(base::BindOnce( &IOSPromoBubbleDelegate::OnDismissal, base::Unretained(bubble_delegate))); - ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); - auto banner_image = ui::ImageModel::FromImageSkia( - *bundle.GetImageSkiaNamed(IDR_SUCCESS_GREEN_CHECKMARK)); - dialog_model_builder.SetBannerImage(banner_image); - - dialog_model_builder.SetTitle( - l10n_util::GetStringUTF16(ios_promo_config.bubble_title_id)); - - dialog_model_builder.SetSubtitle( - l10n_util::GetStringUTF16(ios_promo_config.bubble_subtitle_id)); + if (ios_promo_config.with_header) { + ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); + auto banner_image = ui::ImageModel::FromImageSkia( + *bundle.GetImageSkiaNamed(IDR_SUCCESS_GREEN_CHECKMARK)); + dialog_model_builder.SetBannerImage(banner_image); + dialog_model_builder.SetTitle( + l10n_util::GetStringUTF16(ios_promo_config.bubble_title_id)); + dialog_model_builder.SetSubtitle( + l10n_util::GetStringUTF16(ios_promo_config.bubble_subtitle_id)); + } else { + dialog_model_builder.SetTitle( + l10n_util::GetStringUTF16(ios_promo_config.promo_title_id)); + dialog_model_builder.AddCustomField( + std::make_unique<views::BubbleDialogModelHost::CustomView>( + CreateContentView(bubble_delegate, ios_promo_config, + /*with_title=*/false, bubble_type), + views::BubbleDialogModelHost::FieldType::kControl)); + } auto promo_bubble = std::make_unique<views::BubbleDialogModelHost>( dialog_model_builder.Build(), anchor_view, views::BubbleBorder::TOP_RIGHT); + if (ios_promo_config.with_header) { + promo_bubble->SetFootnoteView(CreateContentView( + bubble_delegate, ios_promo_config, /*with_title=*/true, bubble_type)); + } + ios_promo_delegate_ = promo_bubble.get(); current_promo_type_ = promo_type; - promo_bubble->SetHighlightedButton(highlighted_button); - promo_bubble->SetFootnoteView( - CreateFooter(bubble_delegate, ios_promo_config)); + if (highlighted_button) { + promo_bubble->SetHighlightedButton(highlighted_button); + } else { + promo_bubble->set_highlight_button_when_shown(false); + } views::Widget* const widget = views::BubbleDialogDelegate::CreateBubble(std::move(promo_bubble));
diff --git a/chrome/browser/ui/views/promos/ios_promo_bubble.h b/chrome/browser/ui/views/promos/ios_promo_bubble.h index 7bcb3c08..e988699 100644 --- a/chrome/browser/ui/views/promos/ios_promo_bubble.h +++ b/chrome/browser/ui/views/promos/ios_promo_bubble.h
@@ -22,30 +22,22 @@ enum class IOSPromoBubbleType; enum class IOSPromoType; -// A view for the iOS bubble promo which displays a QR code. +// A view for the bubble promo that encourages feature usage on iOS. class IOSPromoBubble { - private: - // SetUpBubble sets up the promo constants (such as strings) depending - // on the given promo type and returns the current IOSPromoBubble's config. - static IOSPromoConstants::IOSPromoTypeConfigs SetUpBubble( - IOSPromoType promo_type, - IOSPromoBubbleType bubble_type); - - static views::BubbleDialogDelegate* ios_promo_delegate_; - static IOSPromoType current_promo_type_; - + public: class IOSPromoBubbleDelegate; - static std::unique_ptr<views::View> CreateFooter( - IOSPromoBubble::IOSPromoBubbleDelegate* bubble_delegate, - const IOSPromoConstants::IOSPromoTypeConfigs& ios_promo_config); - - public: IOSPromoBubble(const IOSPromoBubble&) = delete; IOSPromoBubble& operator=(const IOSPromoBubble&) = delete; - // ShowBubble creates the view and shows the bubble to the user, attached - // to the feature icon. + // Creates and shows the promo bubble. + // + // Parameters: + // anchor_view: The view to which the bubble will be anchored. + // highlighted_button: The button to highlight when the bubble is shown. May + // be null if no button should be highlighted. profile: The user's profile. + // promo_type: The feature being highlighted in the promo. + // bubble_type: The type of bubble to show (e.g., QR code or reminder). static void ShowPromoBubble(views::View* anchor_view, views::Button* highlighted_button, Profile* profile, @@ -58,6 +50,32 @@ // Returns true if the bubble is currently being shown and is of type // `promo_type`. static bool IsPromoTypeVisible(IOSPromoType promo_type); + + private: + // Creates the content view for the promo bubble, which includes the body and + // buttons. Depedning on the BubbleLayout, the content view takes up either + // the entire bubble, or is added as a footer to the bubble. + static std::unique_ptr<views::View> CreateContentView( + IOSPromoBubble::IOSPromoBubbleDelegate* bubble_delegate, + const IOSPromoConstants::IOSPromoTypeConfigs& ios_promo_config, + bool with_title, + IOSPromoBubbleType bubble_type); + + // Creates the body of the promo bubble, which includes the QR code or + // icon, and the description. + static std::unique_ptr<views::View> CreateImageAndBodyTextView( + const IOSPromoConstants::IOSPromoTypeConfigs& ios_promo_config, + IOSPromoBubbleType bubble_type); + + // Creates the buttons view for the promo bubble. + static std::unique_ptr<views::View> CreateButtonsView( + IOSPromoBubble::IOSPromoBubbleDelegate* bubble_delegate, + const IOSPromoConstants::IOSPromoTypeConfigs& ios_promo_config, + IOSPromoBubbleType bubble_type); + + static views::BubbleDialogDelegate* ios_promo_delegate_; + + static IOSPromoType current_promo_type_; }; #endif // CHROME_BROWSER_UI_VIEWS_PROMOS_IOS_PROMO_BUBBLE_H_
diff --git a/chrome/browser/ui/views/promos/ios_promo_bubble_browsertest.cc b/chrome/browser/ui/views/promos/ios_promo_bubble_browsertest.cc index cef3df2..84ca4479 100644 --- a/chrome/browser/ui/views/promos/ios_promo_bubble_browsertest.cc +++ b/chrome/browser/ui/views/promos/ios_promo_bubble_browsertest.cc
@@ -15,6 +15,8 @@ #include "chrome/browser/ui/views/frame/toolbar_button_provider.h" #include "chrome/browser/ui/views/location_bar/icon_label_bubble_view.h" #include "chrome/browser/ui/views/page_action/page_action_icon_view.h" +#include "chrome/browser/ui/views/toolbar/browser_app_menu_button.h" +#include "chrome/browser/ui/views/toolbar/toolbar_view.h" #include "chrome/test/base/chrome_test_utils.h" #include "components/feature_engagement/public/feature_constants.h" #include "content/public/test/browser_test.h" @@ -90,6 +92,48 @@ } }; +class IOSEnhancedBrowsingPromoBubbleTest : public DialogBrowserTest { + public: + IOSEnhancedBrowsingPromoBubbleTest() = default; + + IOSEnhancedBrowsingPromoBubbleTest( + const IOSEnhancedBrowsingPromoBubbleTest&) = delete; + + IOSEnhancedBrowsingPromoBubbleTest& operator=( + const IOSEnhancedBrowsingPromoBubbleTest&) = delete; + + // DialogBrowserTest + void ShowUi(const std::string& name) override { + BrowserView* browser_view = + BrowserView::GetBrowserViewForBrowser(browser()); + // Test for iOS Promo Bubble for Enhanced Browsing promo. + IOSPromoBubble::ShowPromoBubble( + browser_view->toolbar()->app_menu_button(), + /*highlighted_button=*/nullptr, browser()->profile(), + IOSPromoType::kEnhancedBrowsing, IOSPromoBubbleType::kReminder); + } +}; + +class IOSLensPromoBubbleTest : public DialogBrowserTest { + public: + IOSLensPromoBubbleTest() = default; + + IOSLensPromoBubbleTest(const IOSLensPromoBubbleTest&) = delete; + + IOSLensPromoBubbleTest& operator=(const IOSLensPromoBubbleTest&) = delete; + + // DialogBrowserTest + void ShowUi(const std::string& name) override { + BrowserView* browser_view = + BrowserView::GetBrowserViewForBrowser(browser()); + // Test for iOS Promo Bubble for Lens promo. + IOSPromoBubble::ShowPromoBubble(browser_view->toolbar()->app_menu_button(), + /*highlighted_button=*/nullptr, + browser()->profile(), IOSPromoType::kLens, + IOSPromoBubbleType::kReminder); + } +}; + IN_PROC_BROWSER_TEST_F(IOSPasswordPromoBubbleTest, InvokeUi_default) { ShowAndVerifyUi(); } @@ -118,3 +162,11 @@ IN_PROC_BROWSER_TEST_F(IOSPaymentPromoBubbleTest, InvokeUi_default) { ShowAndVerifyUi(); } + +IN_PROC_BROWSER_TEST_F(IOSEnhancedBrowsingPromoBubbleTest, InvokeUi_default) { + ShowAndVerifyUi(); +} + +IN_PROC_BROWSER_TEST_F(IOSLensPromoBubbleTest, InvokeUi_default) { + ShowAndVerifyUi(); +}
diff --git a/chrome/browser/ui/views/promos/ios_promo_constants.cc b/chrome/browser/ui/views/promos/ios_promo_constants.cc new file mode 100644 index 0000000..238d067c1 --- /dev/null +++ b/chrome/browser/ui/views/promos/ios_promo_constants.cc
@@ -0,0 +1,18 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/views/promos/ios_promo_constants.h" + +namespace IOSPromoConstants { + +IOSPromoTypeConfigs::IOSPromoTypeConfigs() = default; + +IOSPromoTypeConfigs::~IOSPromoTypeConfigs() = default; + +IOSPromoTypeConfigs::IOSPromoTypeConfigs(const IOSPromoTypeConfigs&) = default; + +IOSPromoTypeConfigs& IOSPromoTypeConfigs::operator=( + const IOSPromoTypeConfigs&) = default; + +} // namespace IOSPromoConstants
diff --git a/chrome/browser/ui/views/promos/ios_promo_constants.h b/chrome/browser/ui/views/promos/ios_promo_constants.h index 1355554..aea43f2 100644 --- a/chrome/browser/ui/views/promos/ios_promo_constants.h +++ b/chrome/browser/ui/views/promos/ios_promo_constants.h
@@ -5,6 +5,8 @@ #ifndef CHROME_BROWSER_UI_VIEWS_PROMOS_IOS_PROMO_CONSTANTS_H_ #define CHROME_BROWSER_UI_VIEWS_PROMOS_IOS_PROMO_CONSTANTS_H_ +#include <string> + namespace IOSPromoConstants { // iOS promo QR code URLs. @@ -21,15 +23,22 @@ // Size of the QR code image view including the quiet zone margin added by the // QR code generator. -inline constexpr int kQrCodeImageSize = 90; +inline constexpr int kQrCodeImageSize = 80; struct IOSPromoTypeConfigs { + IOSPromoTypeConfigs(); + ~IOSPromoTypeConfigs(); + IOSPromoTypeConfigs(const IOSPromoTypeConfigs&); + IOSPromoTypeConfigs& operator=(const IOSPromoTypeConfigs&); + int bubble_title_id = -1; int bubble_subtitle_id = -1; int promo_title_id = -1; int promo_description_id = -1; int decline_button_text_id = -1; + int accept_button_text_id = -1; std::string promo_qr_code_url; + bool with_header; }; } // namespace IOSPromoConstants
diff --git a/chrome/browser/ui/views/search_engines/dse_reset_dialog.cc b/chrome/browser/ui/views/search_engines/dse_reset_dialog.cc new file mode 100644 index 0000000..f09814d --- /dev/null +++ b/chrome/browser/ui/views/search_engines/dse_reset_dialog.cc
@@ -0,0 +1,99 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/views/search_engines/dse_reset_dialog.h" + +#include "base/functional/bind.h" +#include "base/functional/callback_helpers.h" +#include "chrome/browser/ui/browser_finder.h" +#include "chrome/browser/ui/views/frame/app_menu_button.h" +#include "chrome/browser/ui/views/frame/browser_view.h" +#include "chrome/browser/ui/views/frame/toolbar_button_provider.h" +#include "chrome/grit/branded_strings.h" +#include "components/search_engines/search_engines_switches.h" +#include "content/public/browser/page_navigator.h" +#include "content/public/common/referrer.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/models/dialog_model.h" +#include "ui/base/window_open_disposition.h" +#include "ui/views/bubble/bubble_dialog_model_host.h" +#include "ui/views/controls/link.h" +#include "url/gurl.h" + +namespace search_engines { + +#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) +namespace { + +void OpenLearnMoreLink(Browser* browser, const ui::Event& event) { + const GURL kLearnMoreUrl( + "https://support.google.com/chrome/answer/" + "3296214#zippy=%2Cchrome-reset-my-browser-settings"); + browser->OpenURL( + content::OpenURLParams(kLearnMoreUrl, content::Referrer(), + WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui::PAGE_TRANSITION_LINK, false), + {}); +} + +void ShowSearchEngineResetNotification(Browser* browser) { + BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser); + if (!browser_view) { + return; + } + + views::View* anchor_view = + browser_view->toolbar_button_provider()->GetAppMenuButton(); + + auto bubble_delegate_unique = std::make_unique<ui::DialogModelDelegate>(); + + ui::DialogModel::Builder dialog_builder(std::move(bubble_delegate_unique)); + + dialog_builder + .SetTitle(l10n_util::GetStringUTF16( + IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_TITLE)) + .AddParagraph(ui::DialogModelLabel( + l10n_util::GetStringUTF16( + IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_BODY)) + .set_is_secondary()) + .AddExtraButton( + base::BindRepeating(&OpenLearnMoreLink, browser), + ui::DialogModel::Button::Params().SetLabel(l10n_util::GetStringUTF16( + IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_LEARN_MORE_BUTTON))) + .AddOkButton( + base::DoNothing(), + ui::DialogModel::Button::Params() + .SetLabel(l10n_util::GetStringUTF16( + IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_GOT_IT_BUTTON)) + .SetStyle(ui::ButtonStyle::kProminent)) + .DisableCloseOnDeactivate() + .SetIsAlertDialog(); + + auto bubble = std::make_unique<views::BubbleDialogModelHost>( + dialog_builder.Build(), anchor_view, views::BubbleBorder::TOP_RIGHT); + + views::BubbleDialogDelegate::CreateBubble(std::move(bubble))->Show(); +} + +} // namespace +#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) + +void MaybeShowSearchEngineResetNotification( + Browser* browser, + AutocompleteMatch::Type match_type) { +#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) + // Ensure it is a non-navigation search query + if (!AutocompleteMatch::IsSearchType(match_type)) { + return; + } + if (!base::FeatureList::IsEnabled( + switches::kResetTamperedDefaultSearchEngine)) { + return; + } + + ShowSearchEngineResetNotification(browser); +#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) +} + +} // namespace search_engines
diff --git a/chrome/browser/ui/views/search_engines/dse_reset_dialog.h b/chrome/browser/ui/views/search_engines/dse_reset_dialog.h new file mode 100644 index 0000000..06935aa --- /dev/null +++ b/chrome/browser/ui/views/search_engines/dse_reset_dialog.h
@@ -0,0 +1,18 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_VIEWS_SEARCH_ENGINES_DSE_RESET_DIALOG_H_ +#define CHROME_BROWSER_UI_VIEWS_SEARCH_ENGINES_DSE_RESET_DIALOG_H_ + +#include "chrome/browser/ui/views/frame/browser_view.h" +#include "components/omnibox/browser/autocomplete_match.h" + +namespace search_engines { +// Shows a bubble informing the user that their +// default search engine settings have been reset. +void MaybeShowSearchEngineResetNotification(Browser* browser, + AutocompleteMatch::Type match_type); +} // namespace search_engines + +#endif // CHROME_BROWSER_UI_VIEWS_SEARCH_ENGINES_DSE_RESET_DIALOG_H_
diff --git a/chrome/browser/ui/views/search_engines/dse_reset_dialog_view_browsertest.cc b/chrome/browser/ui/views/search_engines/dse_reset_dialog_view_browsertest.cc new file mode 100644 index 0000000..27b5a3c0 --- /dev/null +++ b/chrome/browser/ui/views/search_engines/dse_reset_dialog_view_browsertest.cc
@@ -0,0 +1,166 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/scoped_feature_list.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/test/test_browser_dialog.h" +#include "chrome/browser/ui/views/frame/browser_view.h" +#include "chrome/browser/ui/views/frame/toolbar_button_provider.h" +#include "chrome/browser/ui/views/search_engines/dse_reset_dialog.h" +#include "chrome/grit/branded_strings.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "components/search_engines/search_engines_switches.h" +#include "content/public/test/browser_test.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/events/event_constants.h" +#include "ui/events/event_utils.h" +#include "ui/views/bubble/bubble_dialog_delegate_view.h" +#include "ui/views/controls/button/label_button.h" +#include "ui/views/test/button_test_api.h" +#include "ui/views/test/widget_test.h" +#include "ui/views/widget/widget.h" +#include "url/gurl.h" + +#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) +namespace { + +// Returns the DSE reset bubble if it is currently showing, otherwise nullptr. +views::BubbleDialogDelegate* GetDseResetBubble(Browser* browser) { + BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser); + if (!browser_view || !browser_view->toolbar_button_provider()) { + return nullptr; + } + + // Iterate over all active widgets to find the bubble anchored to the app + // menu. This is a standard approach for testing bubbles in a browser test + // environment. + for (views::Widget* widget : views::test::WidgetTest::GetAllWidgets()) { + if (!widget->IsVisible()) { + continue; + } + const std::u16string expected_title = l10n_util::GetStringUTF16( + IDS_DEFAULT_SEARCH_ENGINE_RESET_NOTIFICATION_TITLE); + auto* bubble_delegate = widget->widget_delegate()->AsBubbleDialogDelegate(); + if (bubble_delegate && + bubble_delegate->GetWindowTitle() == expected_title) { + return bubble_delegate; + } + } + return nullptr; +} + +void Click(views::View* clickable_view) { + // Simulate a mouse click. Note: Buttons are either fired when pressed or + // when released, so the corresponding methods need to be called. + clickable_view->OnMousePressed( + ui::MouseEvent(ui::EventType::kMousePressed, gfx::Point(), gfx::Point(), + ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, 0)); + clickable_view->OnMouseReleased( + ui::MouseEvent(ui::EventType::kMouseReleased, gfx::Point(), gfx::Point(), + ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, 0)); +} + +} // namespace + +class DseResetDialogBrowserTest : public DialogBrowserTest { + public: + DseResetDialogBrowserTest() { + feature_list_.InitAndEnableFeature( + switches::kResetTamperedDefaultSearchEngine); + } + + void ShowUi(const std::string& name) override { + search_engines::MaybeShowSearchEngineResetNotification( + browser(), AutocompleteMatch::Type::SEARCH_WHAT_YOU_TYPED); + } + + private: + base::test::ScopedFeatureList feature_list_; +}; + +// Verifies the dialog is shown correctly using the DialogBrowserTest framework. +IN_PROC_BROWSER_TEST_F(DseResetDialogBrowserTest, InvokeUi_Show) { + ShowAndVerifyUi(); +} + +// Verifies the "Got It" button closes the dialog. +IN_PROC_BROWSER_TEST_F(DseResetDialogBrowserTest, GotItButtonClosesDialog) { + ShowUi("default"); + views::BubbleDialogDelegate* bubble = GetDseResetBubble(browser()); + ASSERT_NE(nullptr, bubble); + ASSERT_NE(nullptr, bubble->GetWidget()); + + views::test::WidgetDestroyedWaiter waiter(bubble->GetWidget()); + // The "Got It" button is the dialog's OK button. + bubble->AcceptDialog(); + waiter.Wait(); + + EXPECT_EQ(nullptr, GetDseResetBubble(browser())); +} + +// Verifies the "Learn More" button opens a new tab with the correct URL. +IN_PROC_BROWSER_TEST_F(DseResetDialogBrowserTest, LearnMoreButtonOpensNewTab) { + ShowUi("default"); + views::BubbleDialogDelegate* bubble = GetDseResetBubble(browser()); + ASSERT_NE(nullptr, bubble); + + ui_test_utils::TabAddedWaiter tab_waiter(browser()); + + // The "Learn More" button is the dialog's "extra" view. + views::View* learn_more_button = bubble->GetExtraView(); + ASSERT_NE(nullptr, learn_more_button); + + Click(learn_more_button); + + tab_waiter.Wait(); + + content::WebContents* new_tab = + browser()->tab_strip_model()->GetActiveWebContents(); + const GURL kExpectedLearnMoreUrl( + "https://support.google.com/chrome/answer/" + "3296214#zippy=%2Cchrome-reset-my-browser-settings"); + EXPECT_EQ(kExpectedLearnMoreUrl, new_tab->GetVisibleURL()); +} + +// Test fixture where the controlling feature is disabled. +class DseResetDialogFeatureDisabledBrowserTest : public InProcessBrowserTest { + public: + DseResetDialogFeatureDisabledBrowserTest() { + feature_list_.InitAndDisableFeature( + switches::kResetTamperedDefaultSearchEngine); + } + + private: + base::test::ScopedFeatureList feature_list_; +}; + +// Verifies the dialog is not shown when the feature flag is disabled. +IN_PROC_BROWSER_TEST_F(DseResetDialogFeatureDisabledBrowserTest, + DialogNotShown) { + search_engines::MaybeShowSearchEngineResetNotification( + browser(), AutocompleteMatch::Type::SEARCH_WHAT_YOU_TYPED); + EXPECT_EQ(nullptr, GetDseResetBubble(browser())); +} + +// Test fixture for testing different autocomplete match types. +class DseResetDialogMatchTypesBrowserTest : public InProcessBrowserTest { + public: + DseResetDialogMatchTypesBrowserTest() { + feature_list_.InitAndEnableFeature( + switches::kResetTamperedDefaultSearchEngine); + } + + private: + base::test::ScopedFeatureList feature_list_; +}; + +// Verifies the dialog is not shown for non-search match types (e.g., a URL). +IN_PROC_BROWSER_TEST_F(DseResetDialogMatchTypesBrowserTest, + DialogNotShownForUrlMatch) { + search_engines::MaybeShowSearchEngineResetNotification( + browser(), AutocompleteMatch::Type::URL_WHAT_YOU_TYPED); + EXPECT_EQ(nullptr, GetDseResetBubble(browser())); +} +#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
diff --git a/chrome/browser/ui/views/side_panel/glic/glic_legacy_side_panel_coordinator.cc b/chrome/browser/ui/views/side_panel/glic/glic_legacy_side_panel_coordinator.cc index 0b727ff6..35a8901 100644 --- a/chrome/browser/ui/views/side_panel/glic/glic_legacy_side_panel_coordinator.cc +++ b/chrome/browser/ui/views/side_panel/glic/glic_legacy_side_panel_coordinator.cc
@@ -72,7 +72,7 @@ void GlicLegacySidePanelCoordinator::OnEntryShown(SidePanelEntry* entry) { SidePanelEntry::Key glic_key = SidePanelEntry::Key(SidePanelEntry::Id::kGlic); if (side_panel_coordinator_->IsSidePanelEntryShowing(glic_key)) { - glic_service_->window_controller().SidePanelShown(browser_); + glic_service_->GetSingleInstanceWindowController().SidePanelShown(browser_); } } @@ -107,8 +107,9 @@ if (!tab) { return nullptr; } - return glic_service_->window_controller().CreateViewForSidePanel( - *scope.GetBrowserWindowInterface().GetActiveTabInterface()); + return glic_service_->GetSingleInstanceWindowController() + .CreateViewForSidePanel( + *scope.GetBrowserWindowInterface().GetActiveTabInterface()); } } // namespace glic
diff --git a/chrome/browser/ui/views/status_icons/status_icon_win.cc b/chrome/browser/ui/views/status_icons/status_icon_win.cc index 5b9b69a..b83e063 100644 --- a/chrome/browser/ui/views/status_icons/status_icon_win.cc +++ b/chrome/browser/ui/views/status_icons/status_icon_win.cc
@@ -15,8 +15,8 @@ #include "ui/base/mojom/menu_source_type.mojom.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" -#include "ui/gfx/icon_util.h" #include "ui/gfx/image/image_skia.h" +#include "ui/gfx/win/icon_util.h" #include "ui/message_center/public/cpp/notifier_id.h" #include "ui/views/controls/menu/menu_runner.h"
diff --git a/chrome/browser/ui/views/tab_icon_view.cc b/chrome/browser/ui/views/tab_icon_view.cc index bd9a72c..4b13b60 100644 --- a/chrome/browser/ui/views/tab_icon_view.cc +++ b/chrome/browser/ui/views/tab_icon_view.cc
@@ -28,7 +28,7 @@ #include "base/win/scoped_gdi_object.h" #include "chrome/browser/win/app_icon.h" -#include "ui/gfx/icon_util.h" +#include "ui/gfx/win/icon_util.h" #endif namespace {
diff --git a/chrome/browser/ui/views/tabs/glic_actor_task_icon.cc b/chrome/browser/ui/views/tabs/glic_actor_task_icon.cc index fae51c5..611ed6c 100644 --- a/chrome/browser/ui/views/tabs/glic_actor_task_icon.cc +++ b/chrome/browser/ui/views/tabs/glic_actor_task_icon.cc
@@ -102,9 +102,9 @@ void GlicActorTaskIcon::ShowCheckTasksLabel() { // TODO(crbug.com/431015299): Replace with finalized strings when ready. const std::u16string glic_actor_task_icon_check_task_label = - u"Your task needs attention"; + u"Check your task"; const std::u16string glic_actor_task_icon_check_task_tooltip_text = - u"Your task needs attention"; + u"Check your task"; HighlightTaskIcon(); SetText(glic_actor_task_icon_check_task_label);
diff --git a/chrome/browser/ui/views/tabs/tab_strip_action_container_browsertest.cc b/chrome/browser/ui/views/tabs/tab_strip_action_container_browsertest.cc index b964ce5..c1e2f6a 100644 --- a/chrome/browser/ui/views/tabs/tab_strip_action_container_browsertest.cc +++ b/chrome/browser/ui/views/tabs/tab_strip_action_container_browsertest.cc
@@ -608,7 +608,7 @@ EXPECT_TRUE(GlicActorTaskIcon()->GetVisible()); EXPECT_TRUE(GlicActorTaskIcon()->GetIsShowingNudge()); // TODO(crbug.com/431015299): Replace with finalized strings when ready. - EXPECT_EQ(GlicActorTaskIcon()->GetText(), u"Your task needs attention"); + EXPECT_EQ(GlicActorTaskIcon()->GetText(), u"Check your task"); ResetAnimation(1); @@ -644,7 +644,7 @@ EXPECT_TRUE(GlicActorTaskIcon()->GetVisible()); EXPECT_TRUE(GlicActorTaskIcon()->GetIsShowingNudge()); // TODO(crbug.com/431015299): Replace with finalized strings when ready. - EXPECT_EQ(GlicActorTaskIcon()->GetText(), u"Your task needs attention"); + EXPECT_EQ(GlicActorTaskIcon()->GetText(), u"Check your task"); ResetAnimation(1);
diff --git a/chrome/browser/ui/views/web_apps/web_app_update_review_dialog_browsertest.cc b/chrome/browser/ui/views/web_apps/web_app_update_review_dialog_browsertest.cc index 7aeebbe..74fb947 100644 --- a/chrome/browser/ui/views/web_apps/web_app_update_review_dialog_browsertest.cc +++ b/chrome/browser/ui/views/web_apps/web_app_update_review_dialog_browsertest.cc
@@ -435,10 +435,42 @@ IN_PROC_BROWSER_TEST_F(WebAppUpdateDialogBrowserTests, Accept) { views::NamedWidgetShownWaiter update_dialog_waiter( views::test::AnyWidgetTestPasskey(), "WebAppUpdateReviewDialog"); - InstallAppAndTriggerAppUpdateDialog(); + const webapps::AppId& app_id = InstallAppAndTriggerAppUpdateDialog(); views::Widget* dialog_widget = update_dialog_waiter.WaitIfNeededAndGet(); ASSERT_NE(nullptr, dialog_widget); - views::test::AcceptDialog(dialog_widget); + EXPECT_EQ("Web app for updating", + provider().registrar_unsafe().GetAppShortName(app_id)); + + BrowserWindowInterface* app_browser = + AppBrowserController::FindForWebApp(*profile(), app_id); + ASSERT_NE(app_browser, nullptr); + BrowserView* app_browser_view = + BrowserView::GetBrowserViewForBrowser(app_browser); + ASSERT_NE(app_browser_view, nullptr); + // Verify that the dialog is showing in the browser, and that the menu button + // is present and expanded. + EXPECT_TRUE(app_browser_view->GetProperty(kIsPwaUpdateDialogShowingKey)); + WebAppMenuButton* const menu_button = views::AsViewClass<WebAppMenuButton>( + app_browser_view->toolbar_button_provider()->GetAppMenuButton()); + EXPECT_TRUE(menu_button->IsLabelPresentAndVisible()); + + // Accept the dialog, and verify name was updated as part of updating from + // `index.html` to `new_icon_page_masking.html`. Icons are also updated, + // however that verification is skipped here for brevity. Tests for that are + // part of the pending update application command unit-tests. + { + base::test::TestFuture<void> menu_update_future; + base::CallbackListSubscription subscription = + menu_button->AwaitLabelTextUpdated( + menu_update_future.GetRepeatingCallback()); + views::test::AcceptDialog(dialog_widget); + EXPECT_TRUE(menu_update_future.Wait()); + provider().command_manager().AwaitAllCommandsCompleteForTesting(); + } + + EXPECT_FALSE(menu_button->IsLabelPresentAndVisible()); + EXPECT_EQ("Web app update with masking", + provider().registrar_unsafe().GetAppShortName(app_id)); EXPECT_THAT(tester_.GetAllSamples(kAppUpdateDialogResultHistogram), BucketsAre(base::Bucket(WebAppIdentityUpdateResult::kAccept, 1))); }
diff --git a/chrome/browser/ui/webui/settings/people_handler.cc b/chrome/browser/ui/webui/settings/people_handler.cc index e6612103..7bec3f4 100644 --- a/chrome/browser/ui/webui/settings/people_handler.cc +++ b/chrome/browser/ui/webui/settings/people_handler.cc
@@ -1271,6 +1271,7 @@ // passphrase was set (in milliseconds since the Unix // epoch); undefined if the time is unknown or no explicit // passphrase is set. + // localSyncEnabled: true if the user has local sync enabled. // base::Value::Dict args; @@ -1313,6 +1314,8 @@ base::TimeFormatShortDate(passphrase_time)); } + args.Set("localSyncEnabled", service->IsLocalSyncEnabled()); + FireWebUIListener("sync-prefs-changed", args); }
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc index e08743e..703e2e5e 100644 --- a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc +++ b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
@@ -2272,7 +2272,7 @@ { // Grant a temporary permission to create heuristic data. - permission_actions_history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( + permission_actions_history->RecordTemporaryGrant( GURL(kOrigin), ContentSettingsType::GEOLOCATION); // Verify that heuristic data exists. @@ -2298,7 +2298,7 @@ }); for (const ContentSetting content_setting : kContentSettings) { // Grant a temporary permission to create heuristic data. - permission_actions_history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( + permission_actions_history->RecordTemporaryGrant( GURL(kOrigin), ContentSettingsType::GEOLOCATION); // Verify that heuristic data exists.
diff --git a/chrome/browser/web_applications/commands/apply_pending_manifest_update_command.cc b/chrome/browser/web_applications/commands/apply_pending_manifest_update_command.cc index bf6d42d..33f4e9e5 100644 --- a/chrome/browser/web_applications/commands/apply_pending_manifest_update_command.cc +++ b/chrome/browser/web_applications/commands/apply_pending_manifest_update_command.cc
@@ -282,6 +282,9 @@ app_to_update->SetPendingUpdateInfo(std::nullopt); } + lock_->registrar().NotifyPendingUpdateInfoChanged( + app_id_, /*pending_update_available=*/false, + WebAppRegistrar::PendingUpdateInfoChangePassKey()); CompleteCommandAndSelfDestruct(expected_result); }
diff --git a/chrome/browser/web_applications/os_integration/web_app_shortcut.cc b/chrome/browser/web_applications/os_integration/web_app_shortcut.cc index 03fa77b..26aa4a6e 100644 --- a/chrome/browser/web_applications/os_integration/web_app_shortcut.cc +++ b/chrome/browser/web_applications/os_integration/web_app_shortcut.cc
@@ -52,7 +52,7 @@ #include "ui/gfx/image/image_skia_rep_default.h" #if BUILDFLAG(IS_WIN) -#include "ui/gfx/icon_util.h" +#include "ui/gfx/win/icon_util.h" #endif #if BUILDFLAG(IS_MAC)
diff --git a/chrome/browser/web_applications/os_integration/web_app_shortcut_win.cc b/chrome/browser/web_applications/os_integration/web_app_shortcut_win.cc index b9fb6157..e2d1fe6 100644 --- a/chrome/browser/web_applications/os_integration/web_app_shortcut_win.cc +++ b/chrome/browser/web_applications/os_integration/web_app_shortcut_win.cc
@@ -46,9 +46,9 @@ #include "crypto/obsolete/md5.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/base/win/shell.h" -#include "ui/gfx/icon_util.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_family.h" +#include "ui/gfx/win/icon_util.h" namespace web_app { namespace {
diff --git a/chrome/browser/web_applications/os_integration/web_app_shortcuts_menu_win.cc b/chrome/browser/web_applications/os_integration/web_app_shortcuts_menu_win.cc index d1560e8..6cb74628 100644 --- a/chrome/browser/web_applications/os_integration/web_app_shortcuts_menu_win.cc +++ b/chrome/browser/web_applications/os_integration/web_app_shortcuts_menu_win.cc
@@ -33,9 +33,9 @@ #include "chrome/common/chrome_switches.h" #include "components/webapps/common/web_app_id.h" #include "third_party/skia/include/core/SkBitmap.h" -#include "ui/gfx/icon_util.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_family.h" +#include "ui/gfx/win/icon_util.h" namespace web_app {
diff --git a/chrome/browser/web_applications/test/os_integration_test_override_impl.cc b/chrome/browser/web_applications/test/os_integration_test_override_impl.cc index 6b6492a..f33b631 100644 --- a/chrome/browser/web_applications/test/os_integration_test_override_impl.cc +++ b/chrome/browser/web_applications/test/os_integration_test_override_impl.cc
@@ -88,7 +88,7 @@ #include "chrome/installer/util/install_util.h" #include "chrome/installer/util/shell_util.h" #include "third_party/re2/src/re2/re2.h" -#include "ui/gfx/icon_util.h" +#include "ui/gfx/win/icon_util.h" #endif namespace web_app {
diff --git a/chrome/browser/web_applications/web_app_registrar.h b/chrome/browser/web_applications/web_app_registrar.h index f05db1b..2bbc105 100644 --- a/chrome/browser/web_applications/web_app_registrar.h +++ b/chrome/browser/web_applications/web_app_registrar.h
@@ -67,6 +67,8 @@ class WebAppRegistrarObserver; class WebAppScope; class AppLock; +class ManifestSilentUpdateCommand; +class ApplyPendingManifestUpdateCommand; using Registry = std::map<webapps::AppId, std::unique_ptr<WebApp>>; @@ -592,6 +594,7 @@ friend void SetWebAppPendingUpdateAsIgnored(const webapps::AppId&, AppLock& lock, base::Value::Dict& debug_value); + friend class ApplyPendingManifestUpdateCommand; PendingUpdateInfoChangePassKey() = default; };
diff --git a/chrome/browser/win/app_icon.cc b/chrome/browser/win/app_icon.cc index db721b7..85b5979 100644 --- a/chrome/browser/win/app_icon.cc +++ b/chrome/browser/win/app_icon.cc
@@ -8,8 +8,8 @@ #include "chrome/install_static/install_details.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/gfx/geometry/size.h" -#include "ui/gfx/icon_util.h" #include "ui/gfx/image/image_family.h" +#include "ui/gfx/win/icon_util.h" namespace {
diff --git a/chrome/browser/win/jumplist.cc b/chrome/browser/win/jumplist.cc index 52ee0bb..ea35fc51 100644 --- a/chrome/browser/win/jumplist.cc +++ b/chrome/browser/win/jumplist.cc
@@ -51,11 +51,11 @@ #include "ui/base/resource/resource_scale_factor.h" #include "ui/gfx/codec/png_codec.h" #include "ui/gfx/favicon_size.h" -#include "ui/gfx/icon_util.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_family.h" #include "ui/gfx/image/image_skia.h" #include "ui/gfx/image/image_skia_rep.h" +#include "ui/gfx/win/icon_util.h" #include "url/gurl.h" namespace {
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt index 294137d..c14399ae 100644 --- a/chrome/build/android-arm64.pgo.txt +++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@ -chrome-android64-main-1760349543-2c31510edf6c15a00c042d6853f19bbf50d22af5-e354f8e88a6e70e8b1aba9682bb5fdf679499932.profdata +chrome-android64-main-1760363828-a032d14703050d9e13ac285044f867565a7d17be-6a8871b2d4fc05b72ac667dd622e21e288057ae4.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt index 86cb329..45885b09 100644 --- a/chrome/build/linux.pgo.txt +++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@ -chrome-linux-main-1760335045-15876602378d308bec41621abbbac22f08ee65a0-eaf25ee87d45b916078b95a6bfc29ab451fee7dc.profdata +chrome-linux-main-1760356796-b745daa224a368c34fea58b4114ad20b8f70f512-798e070c8b744fce2dd14a1c8427d8382a86b809.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt index b42ff6ac..89f18bbb 100644 --- a/chrome/build/mac-arm.pgo.txt +++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@ -chrome-mac-arm-main-1760356796-074133b09ce96a6ea2ca124f5252af433abff6d2-798e070c8b744fce2dd14a1c8427d8382a86b809.profdata +chrome-mac-arm-main-1760371097-09f6c1c184a5ae1b0b690580b2278483a6ec5a26-4cbdbbf4c6f2ceeea9a71f21dcca435f7335c709.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt index 95460b7..0c89fa1 100644 --- a/chrome/build/mac.pgo.txt +++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@ -chrome-mac-main-1760291319-a6fe4df6dfb898c56853e88b7ea32de4d853c3a9-9ca7fd38c3170daf1d5813059f78bac9cca67593.profdata +chrome-mac-main-1760356796-691246375a49fcc3648f407c68062c8954375555-798e070c8b744fce2dd14a1c8427d8382a86b809.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt index f7ad15fa..59ddac7 100644 --- a/chrome/build/win32.pgo.txt +++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@ -chrome-win32-main-1760335045-f925ec0bbdc380456bfa7174b33bfd1e9502ab96-eaf25ee87d45b916078b95a6bfc29ab451fee7dc.profdata +chrome-win32-main-1760345802-cb6afcc3fc3f520772270187f86ba1127e2c96dc-2e45a9f756c7a37a09de2f9a0e34fccfbef1bef1.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt index 5f1bdb84..7013f41 100644 --- a/chrome/build/win64.pgo.txt +++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@ -chrome-win64-main-1760323483-d29260fc97a01d63846772c557d1f84baafc8346-84508a2d6fe29132e09f392cd62504efadbf0ec7.profdata +chrome-win64-main-1760345802-29ddf81223c4af612ea8a7ccda8a8ddd6703571b-2e45a9f756c7a37a09de2f9a0e34fccfbef1bef1.profdata
diff --git a/chrome/common/extensions/api/autofill_private.idl b/chrome/common/extensions/api/autofill_private.idl index e8340527..31dbd49 100644 --- a/chrome/common/extensions/api/autofill_private.idl +++ b/chrome/common/extensions/api/autofill_private.idl
@@ -459,7 +459,7 @@ callback CheckForDeviceAuthCallback = void(boolean isDeviceAuthAvailable); callback LoadEntityInstancesCallback = void(EntityInstanceWithLabels[] entries); callback GetEntityInstanceByGuid = void(EntityInstance entityInstance); - callback GetAllEntityTypesCallback = void(EntityType[] entityTypes); + callback GetWritableEntityTypesCallback = void(EntityType[] entityTypes); callback GetAllAttributeTypesForEntityTypeNameCallback = void(AttributeType[] attributeTypes); callback GetAutofillAiOptInStatusCallback = void(boolean optedIn); @@ -597,9 +597,9 @@ static void getEntityInstanceByGuid( DOMString guid, GetEntityInstanceByGuid callback); - // Returns a list of all possible entity types. Used for the user to decide + // Returns a list of writable entity types. Used for the user to decide // what entity instance to add. - static void getAllEntityTypes(GetAllEntityTypesCallback callback); + static void getWritableEntityTypes(GetWritableEntityTypesCallback callback); // Returns a list of all possible attribute types that can be set on an // entity instance with the respective entity type name. Used for
diff --git a/chrome/services/printing/pdf_to_emf_converter.cc b/chrome/services/printing/pdf_to_emf_converter.cc index 6f4e933..6bd02d65 100644 --- a/chrome/services/printing/pdf_to_emf_converter.cc +++ b/chrome/services/printing/pdf_to_emf_converter.cc
@@ -14,7 +14,7 @@ #include "pdf/pdf.h" #include "printing/emf_win.h" #include "printing/mojom/print.mojom.h" -#include "ui/gfx/gdi_util.h" +#include "ui/gfx/win/gdi_util.h" namespace printing {
diff --git a/chrome/services/util_win/util_read_icon.cc b/chrome/services/util_win/util_read_icon.cc index cfa061ec..35a9482 100644 --- a/chrome/services/util_win/util_read_icon.cc +++ b/chrome/services/util_win/util_read_icon.cc
@@ -15,8 +15,8 @@ #include "base/win/scoped_gdi_object.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/gfx/geometry/size.h" -#include "ui/gfx/icon_util.h" #include "ui/gfx/image/image_skia.h" +#include "ui/gfx/win/icon_util.h" using chrome::mojom::IconSize;
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index 016f3d15..875be4a 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn
@@ -2402,6 +2402,7 @@ "//chrome/browser/browsing_data:constants", "//chrome/browser/btm:browser_tests", "//chrome/browser/chooser_controller:browser_tests", + "//chrome/browser/client_hints:browser_tests", "//chrome/browser/content_extraction", "//chrome/browser/content_settings", "//chrome/browser/content_settings:browser_tests", @@ -3253,7 +3254,6 @@ "../browser/chrome_shared_array_buffer_browsertest.cc", "../browser/chrome_web_platform_security_metrics_browsertest.cc", "../browser/chrome_worker_browsertest.cc", - "../browser/client_hints/client_hints_browsertest.cc", "../browser/component_updater/pki_metadata_component_installer_browsertest.cc", "../browser/content_extraction/inner_html_browsertest.cc", "../browser/content_index/content_index_browsertest.cc", @@ -5171,6 +5171,7 @@ "../browser/ui/views/profiles/profile_menu_view_browsertest.cc", "../browser/ui/views/safe_browsing/password_reuse_modal_warning_dialog_browsertest.cc", "../browser/ui/views/safe_browsing/tailored_security_desktop_dialog_manager_browsertest.cc", + "../browser/ui/views/search_engines/dse_reset_dialog_view_browsertest.cc", "../browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_browsertest.cc", "../browser/ui/views/side_panel/extensions/extension_side_panel_browsertest.cc", "../browser/ui/views/site_data/page_specific_site_data_dialog_browsertest.cc", @@ -7837,7 +7838,6 @@ "../browser/android/oom_intervention/near_oom_reduction_message_delegate_unittest.cc", "../browser/android/oom_intervention/oom_intervention_decider_unittest.cc", "../browser/android/persisted_tab_data/leveldb_persisted_tab_data_storage_android_factory_unittest.cc", - "../browser/android/quick_delete/quick_delete_bridge_unittest.cc", "../browser/android/recently_closed_tabs_bridge_unittest.cc", "../browser/android/search_permissions/search_permissions_service_unittest.cc", "../browser/android/tab_android_unittest.cc",
diff --git a/chrome/test/data/extensions/api_test/autofill_private/test.js b/chrome/test/data/extensions/api_test/autofill_private/test.js index 616391a..188a762 100644 --- a/chrome/test/data/extensions/api_test/autofill_private/test.js +++ b/chrome/test/data/extensions/api_test/autofill_private/test.js
@@ -1275,8 +1275,9 @@ chrome.test.succeed(); }, - async function getAllEntityTypes() { - const entityTypesList = await chrome.autofillPrivate.getAllEntityTypes(); + async function getWritableEntityTypes() { + const entityTypesList = + await chrome.autofillPrivate.getWritableEntityTypes(); const expectedEntityTypesList = [ { typeName: 0, @@ -1307,6 +1308,16 @@ chrome.test.succeed(); }, + async function verifyWritableEntityTypesDoesNotIncludeReadOnlyTypes() { + const entityTypesList = + await chrome.autofillPrivate.getWritableEntityTypes(); + for (const index in entityTypesList) { + chrome.test.assertFalse( + entityTypesList[index].typeName === 6); // Flight reservation + } + chrome.test.succeed(); + }, + async function getAllAttributeTypesForEntityTypeName() { const attributeTypesList = await chrome.autofillPrivate.getAllAttributeTypesForEntityTypeName( @@ -1422,7 +1433,9 @@ 'loadFirstEntityInstance': ['loadFirstEntityInstance'], 'loadUpdatedEntityInstance': ['loadUpdatedEntityInstance'], 'getEntityInstanceByGuid': ['getEntityInstanceByGuid'], - 'getAllEntityTypes': ['getAllEntityTypes'], + 'getWritableEntityTypes': ['getWritableEntityTypes'], + 'verifyWritableEntityTypesDoesNotIncludeReadOnlyTypes': + ['verifyWritableEntityTypesDoesNotIncludeReadOnlyTypes'], 'getAllAttributeTypesForEntityTypeName': ['getAllAttributeTypesForEntityTypeName'], 'testExpectedLabelsAreGenerated': ['testExpectedLabelsAreGenerated'],
diff --git a/chrome/test/data/webui/chromeos/settings/os_privacy_page/os_privacy_page_test.ts b/chrome/test/data/webui/chromeos/settings/os_privacy_page/os_privacy_page_test.ts index 944e343..34775e9 100644 --- a/chrome/test/data/webui/chromeos/settings/os_privacy_page/os_privacy_page_test.ts +++ b/chrome/test/data/webui/chromeos/settings/os_privacy_page/os_privacy_page_test.ts
@@ -86,6 +86,7 @@ extensionsManaged: false, extensionsRegistered: false, extensionsSynced: false, + localSyncEnabled: false, passwordsManaged: false, passwordsRegistered: false, passwordsSynced: false,
diff --git a/chrome/test/data/webui/glic/browser_tests/glic_api_browsertest.ts b/chrome/test/data/webui/glic/browser_tests/glic_api_browsertest.ts index 62e32ab..1f77470 100644 --- a/chrome/test/data/webui/glic/browser_tests/glic_api_browsertest.ts +++ b/chrome/test/data/webui/glic/browser_tests/glic_api_browsertest.ts
@@ -110,6 +110,61 @@ this.host.getPanelState().getCurrentValue()?.kind); } + async testGetPanelStateAttachedHidden() { + assertDefined(this.host.getPanelState); + // getPanelState and notifyPanelWillOpen should signal the ATTACHED state. + const panelStates = observeSequence(this.host.getPanelState()); + await panelStates.waitFor(state => state.kind === PanelStateKind.ATTACHED); + + // Open and select a second tab. + await this.advanceToNextStep(); + await panelStates.waitFor(state => state.kind === PanelStateKind.HIDDEN); + + // Select the first tab again. + await this.advanceToNextStep(); + await panelStates.waitFor(state => state.kind === PanelStateKind.ATTACHED); + } + + async testDetachPanel() { + assertDefined(this.host.getPanelState); + assertDefined(this.host.detachPanel); + assertDefined(this.host.attachPanel); + // getPanelState and notifyPanelWillOpen should signal the ATTACHED state. + const panelStates = observeSequence(this.host.getPanelState()); + await panelStates.waitFor(state => state.kind === PanelStateKind.ATTACHED); + + this.host.detachPanel(); + await panelStates.waitFor(state => state.kind === PanelStateKind.DETACHED); + + // TODO(harringtond): Not implemented yet. + // this.host.attachPanel(); + // await panelStates.waitFor(state => state.kind === + // PanelStateKind.ATTACHED); + } + + async testMultiplePanelsDetachedAndFloating() { + assertDefined(this.host.getPanelState); + assertDefined(this.host.detachPanel); + + if (this.testParams === 'first') { + const panelStates = observeSequence(this.host.getPanelState()); + await panelStates.waitFor( + state => state.kind === PanelStateKind.ATTACHED); + await this.advanceToNextStep(); + // Ensure the panel state stays attached. Note that currently, we do see + // the panel state go to hidden momentarily, so we only assert that the + // state eventually transitions again to attached. + await sleep(100); + observeSequence(this.host.getPanelState()) + .waitFor(state => state.kind === PanelStateKind.ATTACHED); + } else if (this.testParams === 'second') { + this.host.detachPanel(); + const panelStates = observeSequence(this.host.getPanelState()); + await panelStates.waitFor( + state => state.kind === PanelStateKind.DETACHED); + } + } + async testClosePanel() { assertDefined(this.host.closePanel);
diff --git a/chrome/test/data/webui/settings/autofill_ai_section_test.ts b/chrome/test/data/webui/settings/autofill_ai_section_test.ts index 2c529e45..9607957 100644 --- a/chrome/test/data/webui/settings/autofill_ai_section_test.ts +++ b/chrome/test/data/webui/settings/autofill_ai_section_test.ts
@@ -239,7 +239,7 @@ }, ]; entityDataManager.setGetOptInStatusResponse(true); - entityDataManager.setGetAllEntityTypesResponse( + entityDataManager.setGetWritableEntityTypesResponse( structuredClone(testEntityTypes)); entityDataManager.setLoadEntityInstancesResponse( testEntityInstancesWithLabels);
diff --git a/chrome/test/data/webui/settings/people_page_sync_controls_test.ts b/chrome/test/data/webui/settings/people_page_sync_controls_test.ts index 48ca5c54..fca0c51 100644 --- a/chrome/test/data/webui/settings/people_page_sync_controls_test.ts +++ b/chrome/test/data/webui/settings/people_page_sync_controls_test.ts
@@ -430,6 +430,22 @@ assertTrue(syncControls.hidden); }); + test('SignedInLocalSyncEnabled', async function() { + setupPrefs(); + + // Controls are available by default. + assertFalse(syncControls.hidden); + + const syncPrefs = getSyncAllPrefs(); + syncPrefs.localSyncEnabled = true; + webUIListenerCallback('sync-prefs-changed', syncPrefs); + await flushTasks(); + await waitAfterNextRender(syncControls); + + // Controls are hidden when signed in and local sync is enabled. + assertTrue(syncControls.hidden); + }); + test('ChangeDataTypeToggle', async function() { setupPrefs();
diff --git a/chrome/test/data/webui/settings/privacy_guide_integration_test.ts b/chrome/test/data/webui/settings/privacy_guide_integration_test.ts index 80838b31..7529fe1 100644 --- a/chrome/test/data/webui/settings/privacy_guide_integration_test.ts +++ b/chrome/test/data/webui/settings/privacy_guide_integration_test.ts
@@ -91,11 +91,53 @@ 'recordPrivacyGuideStepsEligibleAndReachedHistogram')); } +// Programmatically generate all possible permutations of Privacy Guide steps. +// Each permutation is represented by a binary vector, where the i-th bit +// corresponds to the eligibility of the i-th step in the `optionalSteps` array. +function generateTestCases(optionalSteps: PrivacyGuideStep[]): + Map<string, Map<PrivacyGuideStep, boolean>> { + const permutationsCount = 2 ** optionalSteps.length; + + const masks: number[] = []; + for (let i = 0; i < optionalSteps.length; i++) { + masks.push(2 ** i); + } + + const testCases: Map<string, Map<PrivacyGuideStep, boolean>> = new Map(); + for (let testCase = 0; testCase < permutationsCount; testCase++) { + let stepNames = ''; + const stepEligibilities = new Map(); + + optionalSteps.forEach((step, index) => { + const isStepEligible = !!(testCase & masks[index]!); + if (isStepEligible) { + stepNames += '_' + step.toString(); + } + stepEligibilities.set(step, isStepEligible); + }); + + if (stepNames === '') { + stepNames = '_allDisabled'; + } + + testCases.set(stepNames, stepEligibilities); + } + + return testCases; +} + suite('PrivacyGuideEligibleReachedMetrics', function() { let page: SettingsPrivacyGuidePageElement; let settingsPrefs: SettingsPrefsElement; let syncBrowserProxy: TestSyncBrowserProxy; let testMetricsBrowserProxy: TestMetricsBrowserProxy; + const optionalSteps: PrivacyGuideStep[] = []; + optionalSteps.push(PrivacyGuideStep.HISTORY_SYNC); + optionalSteps.push(PrivacyGuideStep.SAFE_BROWSING); + // The COOKIES step only exists if the 3PCD redesign is not enabled. + if (!loadTimeData.getBoolean('is3pcdCookieSettingsRedesignEnabled')) { + optionalSteps.push(PrivacyGuideStep.COOKIES); + } suiteSetup(function() { settingsPrefs = document.createElement('settings-prefs'); @@ -133,57 +175,49 @@ return flushTasks(); } - test('recordStepsAreEligibleReached', async function() { - const optionalSteps: PrivacyGuideStep[] = []; - optionalSteps.push(PrivacyGuideStep.HISTORY_SYNC); - optionalSteps.push(PrivacyGuideStep.SAFE_BROWSING); - if (!loadTimeData.getBoolean('is3pcdCookieSettingsRedesignEnabled')) { - optionalSteps.push(PrivacyGuideStep.COOKIES); - } + for (const [stepNames, stepsEligibility] of generateTestCases( + optionalSteps)) { + test('recordStepsAreEligibleReached' + stepNames, async function() { + // Setup the test based on the eligibility of optional cards. + for (const [step, isEligible] of stepsEligibility) { + setParametersForStep(page, syncBrowserProxy, step, isEligible); + } - const masks: number[] = []; - for (let i = 0; i < optionalSteps.length; i++) { - masks.push(2 ** i); - } - - // Each optional step can be either eligible or not eligible to be shown. - // To test all possible permutations of steps a binary vector and bit - // masking is used. i-th element of the vector represents eligibility of the - // i-th step in optionalSteps array. - for (let testCase = 0; testCase < 2 ** optionalSteps.length; testCase++) { - Router.getInstance().navigateTo(routes.PRIVACY_GUIDE); - await flushTasks(); - await testSetup(); - + // `expectedArguments` represents what is expected to be recorded into + // metrics. It is first filled with the cards that are eligible to be + // shown then with cards that were actually shown (aka reached). const expectedArguments = new Set<number>(); - expectedArguments.add(PrivacyGuideStepsEligibleAndReached.MSBB_ELIGIBLE); - optionalSteps.forEach((step, index) => { - const isStepEligible = !!(testCase & masks[index]!); - setParametersForStep(page, syncBrowserProxy, step, isStepEligible); - if (isStepEligible) { + // Add mandatory and eligible optional steps into expected metrics record. + expectedArguments.add(PrivacyGuideStepsEligibleAndReached.MSBB_ELIGIBLE); + for (const [step, isEligible] of stepsEligibility) { + if (isEligible) { expectedArguments.add( privacyGuideStepToEligibleReachedValueMap.get(step)!.eligible); } - }); - + } expectedArguments.add( PrivacyGuideStepsEligibleAndReached.COMPLETION_ELIGIBLE); + // Navigate to Privacy Guide page. + Router.getInstance().navigateTo(routes.PRIVACY_GUIDE); + await flushTasks(); + + // Click through the flow and assert that each step is recorded as + // reached. + // The first step is a mandatory MSBB card. await clickNextOnWelcomeStep(page); expectedArguments.add(PrivacyGuideStepsEligibleAndReached.MSBB_REACHED); - assertTrue( isSetEqual( expectedArguments, await getPromiseArguments(testMetricsBrowserProxy)), 'Sets differ for the step: MSBB_REACHED'); - const nextButtonElementOnMSBBStep = page.shadowRoot!.querySelector<HTMLElement>('#nextButton'); assertTrue(!!nextButtonElementOnMSBBStep); nextButtonElementOnMSBBStep.click(); - + // The next is optional steps. for (const step of optionalSteps) { if (!shouldStepBeShown(page, syncBrowserProxy, step)) { continue; @@ -203,15 +237,14 @@ assertTrue(!!nextButtonElementOnStep); nextButtonElementOnStep.click(); } - + // The last is mandatory COMPLETION step. expectedArguments.add( PrivacyGuideStepsEligibleAndReached.COMPLETION_REACHED); - assertTrue( isSetEqual( expectedArguments, await getPromiseArguments(testMetricsBrowserProxy)), 'Sets differ for the step: COMPLETION_REACHED'); - } - }); + }); + } });
diff --git a/chrome/test/data/webui/settings/settings_browsertest.cc b/chrome/test/data/webui/settings/settings_browsertest.cc index 890b497..2fd825d 100644 --- a/chrome/test/data/webui/settings/settings_browsertest.cc +++ b/chrome/test/data/webui/settings/settings_browsertest.cc
@@ -1129,15 +1129,9 @@ RunTest("settings/privacy_guide_page_test.js", "runMochaSuite('3pcdOff')"); } -// Privacy guide integration tests. -// TODO(crbug.com/424171352): Flaky, supposedly due to timeouts on debug builds. -#if !defined(NDEBUG) -#define MAYBE_Integration DISABLED_Integration -#else -#define MAYBE_Integration Integration -#endif -IN_PROC_BROWSER_TEST_F(SettingsBrowserTest, MAYBE_Integration) { - RunTest("settings/privacy_guide_integration_test.js", "mocha.run()"); +IN_PROC_BROWSER_TEST_F(SettingsPrivacyGuideTest, Integration) { + RunTest("settings/privacy_guide_integration_test.js", + "runMochaSuite('PrivacyGuideEligibleReachedMetrics')"); } // Privacy guide fragment tests.
diff --git a/chrome/test/data/webui/settings/sync_test_util.ts b/chrome/test/data/webui/settings/sync_test_util.ts index d4c1fae..1aafc47 100644 --- a/chrome/test/data/webui/settings/sync_test_util.ts +++ b/chrome/test/data/webui/settings/sync_test_util.ts
@@ -30,6 +30,7 @@ extensionsManaged: false, extensionsRegistered: true, extensionsSynced: true, + localSyncEnabled: false, passphraseRequired: false, passwordsManaged: false, passwordsRegistered: true,
diff --git a/chrome/test/data/webui/settings/test_entity_data_manager_proxy.ts b/chrome/test/data/webui/settings/test_entity_data_manager_proxy.ts index d16165c1..da4c0e4 100644 --- a/chrome/test/data/webui/settings/test_entity_data_manager_proxy.ts +++ b/chrome/test/data/webui/settings/test_entity_data_manager_proxy.ts
@@ -27,7 +27,7 @@ 'addEntityInstancesChangedListener', 'addOrUpdateEntityInstance', 'getAllAttributeTypesForEntityTypeName', - 'getAllEntityTypes', + 'getWritableEntityTypes', 'getEntityInstanceByGuid', 'loadEntityInstances', 'removeEntityInstance', @@ -46,7 +46,7 @@ this.entityInstance_ = entityInstance; } - setGetAllEntityTypesResponse(entityTypes: EntityType[]): void { + setGetWritableEntityTypesResponse(entityTypes: EntityType[]): void { this.entityTypes_ = entityTypes; } @@ -89,8 +89,8 @@ return Promise.resolve(structuredClone(this.entityInstance_)); } - getAllEntityTypes(): Promise<EntityType[]> { - this.methodCalled('getAllEntityTypes'); + getWritableEntityTypes(): Promise<EntityType[]> { + this.methodCalled('getWritableEntityTypes'); return Promise.resolve(structuredClone(this.entityTypes_)); }
diff --git a/chrome/updater/app/app_install.cc b/chrome/updater/app/app_install.cc index 3e09e54..c8eb95cc 100644 --- a/chrome/updater/app/app_install.cc +++ b/chrome/updater/app/app_install.cc
@@ -125,7 +125,7 @@ base::BindOnce( [](base::OnceClosure callback, UpdaterScope scope, int exit_code) { - if (exit_code == kErrorOk || !AnyAppEnablesUsageStats(scope)) { + if (exit_code == kErrorOk) { std::move(callback).Run(); return; }
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc index d66e7174..a855b6de5 100644 --- a/chrome/updater/test/integration_tests.cc +++ b/chrome/updater/test/integration_tests.cc
@@ -1768,7 +1768,7 @@ } class IntegrationMetainstallerTest - : public ::testing::WithParamInterface<std::tuple<int, std::string>>, + : public ::testing::WithParamInterface<std::string>, public IntegrationTest { protected: void SetUp() override { @@ -1785,36 +1785,30 @@ IntegrationTest::TearDown(); } - int usagestats() const { return std::get<0>(GetParam()); } - std::string appname() const { return std::get<1>(GetParam()); } + std::string appname() const { return GetParam(); } std::unique_ptr<ScopedServer> test_server_; static constexpr char kAppId[] = "test1"; }; -INSTANTIATE_TEST_SUITE_P( - IntegrationMetainstallerTestCases, - IntegrationMetainstallerTest, - ::testing::Combine(::testing::Values(1, 0), - ::testing::Values("&appname=MetainstallerUI%20Test", - ""))); +INSTANTIATE_TEST_SUITE_P(IntegrationMetainstallerTestCases, + IntegrationMetainstallerTest, + ::testing::Values("&appname=MetainstallerUI%20Test", + "")); TEST_P(IntegrationMetainstallerTest, UIAndPings) { - if (usagestats()) { - ASSERT_NO_FATAL_FAILURE(ExpectPingRequest( - test_server_.get(), kUpdaterAppId, - { - .event_type = update_client::protocol_request::kEventInstall, - .result = 0, - .error_code = 73118, // ExitCode::INVALID_OPTION - .extra_code1 = 0, - })); - } + ASSERT_NO_FATAL_FAILURE(ExpectPingRequest( + test_server_.get(), kUpdaterAppId, + { + .event_type = update_client::protocol_request::kEventInstall, + .result = 0, + .error_code = 73118, // ExitCode::INVALID_OPTION + .extra_code1 = 0, + })); ASSERT_NO_FATAL_FAILURE(InstallUpdaterAndApp( kAppId, /*is_silent_install=*/appname().empty(), /*tag=*/ - base::StrCat({"appguid=", kAppId, appname(), - "&usagestats=", base::NumberToString(usagestats())}), + base::StrCat({"appguid=", kAppId, appname(), "&usagestats=0"}), /*child_window_text_to_find=*/appname().empty() ? "" : "INVALID_OPTION", /*always_launch_cmd=*/false, /*verify_app_logo_loaded=*/false, /*expect_success=*/false, @@ -1852,6 +1846,14 @@ ::testing::Values("en", "de", "ar", "hi")); TEST_P(IntegrationMetainstallerLangTest, Test) { + ASSERT_NO_FATAL_FAILURE(ExpectPingRequest( + test_server_.get(), kUpdaterAppId, + { + .event_type = update_client::protocol_request::kEventInstall, + .result = 0, + .error_code = 73118, // ExitCode::INVALID_OPTION + .extra_code1 = 0, + })); ASSERT_NO_FATAL_FAILURE(InstallUpdaterAndApp( kAppId, /*is_silent_install=*/false, /*tag=*/
diff --git a/chrome/updater/win/installer/installer.cc b/chrome/updater/win/installer/installer.cc index 2444634..543e55d 100644 --- a/chrome/updater/win/installer/installer.cc +++ b/chrome/updater/win/installer/installer.cc
@@ -624,9 +624,7 @@ ui::GetInstallerDisplayName(bundle_name, lang).c_str(), MB_OK | MB_ICONERROR | MB_SETFOREGROUND, 0); } - if (usage_stats_enable) { - SendPing(result.exit_code, result.windows_error); - } + SendPing(result.exit_code, result.windows_error); } base::ThreadPoolInstance::Get()->Shutdown(); return wmain_exit_code;
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM index d1453b4..04cb3efb 100644 --- a/chromeos/CHROMEOS_LKGM +++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@ -16447.0.0-1072378 \ No newline at end of file +16448.0.0-1072386 \ No newline at end of file
diff --git a/clank b/clank index 9239a72..5c6055a 160000 --- a/clank +++ b/clank
@@ -1 +1 @@ -Subproject commit 9239a72a8bfc1673b2683f630e943092bb6686fb +Subproject commit 5c6055aa5052256bc67ff1a033d2852d0b205282
diff --git a/components/autofill/core/browser/form_parsing/internal_resources b/components/autofill/core/browser/form_parsing/internal_resources index 317b38a..8563faf 160000 --- a/components/autofill/core/browser/form_parsing/internal_resources +++ b/components/autofill/core/browser/form_parsing/internal_resources
@@ -1 +1 @@ -Subproject commit 317b38a3a7f50da4a2d28f3e46be947fd4349afb +Subproject commit 8563faf0edafd32896f5bd9fedc58c31df574213
diff --git a/components/certificate_transparency/data/log_list.json b/components/certificate_transparency/data/log_list.json index 30a8811..a3e01c1 100644 --- a/components/certificate_transparency/data/log_list.json +++ b/components/certificate_transparency/data/log_list.json
@@ -1,6 +1,6 @@ { - "version": "72.3", - "log_list_timestamp": "2025-10-12T12:54:04Z", + "version": "72.4", + "log_list_timestamp": "2025-10-13T12:54:06Z", "operators": [ { "name": "Google",
diff --git a/components/commerce/core/BUILD.gn b/components/commerce/core/BUILD.gn index c88d327..d159ac13 100644 --- a/components/commerce/core/BUILD.gn +++ b/components/commerce/core/BUILD.gn
@@ -364,6 +364,7 @@ ":feature_utils", ":metrics", ":pref_names", + ":prefs", ":shopping_service_test_support", "compare:unit_tests", "product_specifications", @@ -383,12 +384,17 @@ } source_set("pref_names") { + sources = [ "pref_names.h" ] +} + +source_set("prefs") { sources = [ - "pref_names.cc", - "pref_names.h", + "prefs.cc", + "prefs.h", ] deps = [ + ":pref_names", "//base", "//components/pref_registry", "//components/prefs", @@ -433,6 +439,7 @@ deps = [ ":account_checker", ":pref_names", + ":prefs", "//base", "//components/optimization_guide/core:features", "//components/prefs", @@ -451,6 +458,7 @@ ":commerce_constants", ":feature_list", ":pref_names", + ":prefs", "//base", "//base/test:test_support", "//components/endpoint_fetcher:test_support",
diff --git a/components/commerce/core/account_checker_unittest.cc b/components/commerce/core/account_checker_unittest.cc index b50068e..8586743 100644 --- a/components/commerce/core/account_checker_unittest.cc +++ b/components/commerce/core/account_checker_unittest.cc
@@ -17,6 +17,7 @@ #include "components/commerce/core/commerce_constants.h" #include "components/commerce/core/commerce_feature_list.h" #include "components/commerce/core/pref_names.h" +#include "components/commerce/core/prefs.h" #include "components/endpoint_fetcher/mock_endpoint_fetcher.h" #include "components/prefs/testing_pref_service.h" #include "components/signin/public/identity_manager/identity_test_environment.h" @@ -87,7 +88,7 @@ void SetUp() override { test_features_.InitAndEnableFeature(kShoppingList); - RegisterPrefs(pref_service_.registry()); + RegisterProfilePrefs(pref_service_.registry()); scoped_refptr<network::SharedURLLoaderFactory> test_url_loader_factory = base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( &test_url_loader_factory_);
diff --git a/components/commerce/core/bookmark_update_manager_unittest.cc b/components/commerce/core/bookmark_update_manager_unittest.cc index 5553fc1..5e7551d 100644 --- a/components/commerce/core/bookmark_update_manager_unittest.cc +++ b/components/commerce/core/bookmark_update_manager_unittest.cc
@@ -2,7 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "components/commerce/core/bookmark_update_manager.h" + #include <math.h> + #include <map> #include <memory> #include <vector> @@ -17,10 +20,10 @@ #include "components/bookmarks/browser/bookmark_model.h" #include "components/bookmarks/browser/bookmark_node.h" #include "components/bookmarks/test/test_bookmark_client.h" -#include "components/commerce/core/bookmark_update_manager.h" #include "components/commerce/core/commerce_feature_list.h" #include "components/commerce/core/mock_shopping_service.h" #include "components/commerce/core/pref_names.h" +#include "components/commerce/core/prefs.h" #include "components/commerce/core/test_utils.h" #include "components/power_bookmarks/core/power_bookmark_utils.h" #include "components/power_bookmarks/core/proto/power_bookmark_meta.pb.h" @@ -50,7 +53,7 @@ // The update manager should not have an update scheduled by default. EXPECT_FALSE(IsUpdateScheduled()); - RegisterPrefs(pref_service_->registry()); + RegisterProfilePrefs(pref_service_->registry()); pref_service_->SetTime(kShoppingListBookmarkLastUpdateTime, base::Time()); }
diff --git a/components/commerce/core/mock_account_checker.cc b/components/commerce/core/mock_account_checker.cc index 5f3a591..465a6cb4 100644 --- a/components/commerce/core/mock_account_checker.cc +++ b/components/commerce/core/mock_account_checker.cc
@@ -5,6 +5,7 @@ #include "components/commerce/core/mock_account_checker.h" #include "components/commerce/core/pref_names.h" +#include "components/commerce/core/prefs.h" #include "components/optimization_guide/core/feature_registry/feature_registration.h" #include "components/prefs/pref_service.h" #include "components/prefs/testing_pref_service.h" @@ -71,7 +72,7 @@ } void MockAccountChecker::RegisterCommercePrefs(PrefRegistrySimple* registry) { - RegisterPrefs(registry); + RegisterProfilePrefs(registry); registry->RegisterIntegerPref( optimization_guide::prefs::kProductSpecificationsEnterprisePolicyAllowed,
diff --git a/components/commerce/core/pref_names.h b/components/commerce/core/pref_names.h index 267c80a..4ba2f20 100644 --- a/components/commerce/core/pref_names.h +++ b/components/commerce/core/pref_names.h
@@ -5,33 +5,33 @@ #ifndef COMPONENTS_COMMERCE_CORE_PREF_NAMES_H_ #define COMPONENTS_COMMERCE_CORE_PREF_NAMES_H_ -class PrefRegistrySimple; - namespace commerce { +// keep-sorted start block=yes newline_separated=yes skip_lines=1 + inline constexpr char kCommerceDailyMetricsLastUpdateTime[] = "commerce_daily_metrics_last_update_time"; -inline constexpr char kShoppingListBookmarkLastUpdateTime[] = - "shopping_list_bookmark_last_update_time"; - -// This setting is primarily for enabling or disabling the shopping list feature -// in enterprise settings. -inline constexpr char kShoppingListEnabledPrefName[] = "shopping_list_enabled"; inline constexpr char kPriceEmailNotificationsEnabled[] = "price_tracking.email_notifications_enabled"; +inline constexpr char kProductSpecificationsAcceptedDisclosureVersion[] = + "product_specifications.accepted_disclosure_version"; + inline constexpr char kProductSpecificationsEntryPointLastDismissedTime[] = "product_specifications_entry_point_last_dismissed_time"; inline constexpr char kProductSpecificationsEntryPointShowIntervalInDays[] = "product_specifications_entry_point_show_interval_in_days"; -inline constexpr char kProductSpecificationsAcceptedDisclosureVersion[] = - "product_specifications.accepted_disclosure_version"; +inline constexpr char kShoppingListBookmarkLastUpdateTime[] = + "shopping_list_bookmark_last_update_time"; -// Register preference names for commerce features. -void RegisterPrefs(PrefRegistrySimple* registry); +// This setting is primarily for enabling or disabling the shopping list feature +// in enterprise settings. +inline constexpr char kShoppingListEnabledPrefName[] = "shopping_list_enabled"; + +// keep-sorted end } // namespace commerce
diff --git a/components/commerce/core/pref_names.cc b/components/commerce/core/prefs.cc similarity index 88% rename from components/commerce/core/pref_names.cc rename to components/commerce/core/prefs.cc index 08215e259..38954e0 100644 --- a/components/commerce/core/pref_names.cc +++ b/components/commerce/core/prefs.cc
@@ -1,16 +1,17 @@ -// Copyright 2022 The Chromium Authors +// Copyright 2025 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "components/commerce/core/pref_names.h" +#include "components/commerce/core/prefs.h" #include "base/time/time.h" +#include "components/commerce/core/pref_names.h" #include "components/pref_registry/pref_registry_syncable.h" #include "components/prefs/pref_registry_simple.h" namespace commerce { -void RegisterPrefs(PrefRegistrySimple* registry) { +void RegisterProfilePrefs(PrefRegistrySimple* registry) { registry->RegisterBooleanPref( kPriceEmailNotificationsEnabled, false, user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
diff --git a/components/commerce/core/prefs.h b/components/commerce/core/prefs.h new file mode 100644 index 0000000..1ec3361 --- /dev/null +++ b/components/commerce/core/prefs.h
@@ -0,0 +1,17 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_COMMERCE_CORE_PREFS_H_ +#define COMPONENTS_COMMERCE_CORE_PREFS_H_ + +class PrefRegistrySimple; + +namespace commerce { + +// Register preference names for commerce features. +void RegisterProfilePrefs(PrefRegistrySimple* registry); + +} // namespace commerce + +#endif // COMPONENTS_COMMERCE_CORE_PREFS_H_
diff --git a/components/commerce/core/price_tracking_utils_unittest.cc b/components/commerce/core/price_tracking_utils_unittest.cc index c8eb2e3b..d7687b7 100644 --- a/components/commerce/core/price_tracking_utils_unittest.cc +++ b/components/commerce/core/price_tracking_utils_unittest.cc
@@ -20,6 +20,7 @@ #include "components/commerce/core/commerce_feature_list.h" #include "components/commerce/core/mock_shopping_service.h" #include "components/commerce/core/pref_names.h" +#include "components/commerce/core/prefs.h" #include "components/commerce/core/subscriptions/commerce_subscription.h" #include "components/commerce/core/test_utils.h" #include "components/power_bookmarks/core/power_bookmark_utils.h" @@ -42,7 +43,7 @@ bookmarks::TestBookmarkClient::CreateModelWithClient(std::move(client)); shopping_service_ = std::make_unique<MockShoppingService>(); pref_service_ = std::make_unique<TestingPrefServiceSimple>(); - RegisterPrefs(pref_service_->registry()); + RegisterProfilePrefs(pref_service_->registry()); } base::test::ScopedFeatureList test_features_;
diff --git a/components/commerce/core/shopping_service_unittest.cc b/components/commerce/core/shopping_service_unittest.cc index d1bdb29..b4aff2f7 100644 --- a/components/commerce/core/shopping_service_unittest.cc +++ b/components/commerce/core/shopping_service_unittest.cc
@@ -21,6 +21,7 @@ #include "components/commerce/core/mock_account_checker.h" #include "components/commerce/core/mock_discount_infos_storage.h" #include "components/commerce/core/pref_names.h" +#include "components/commerce/core/prefs.h" #include "components/commerce/core/proto/shopping_page_types.pb.h" #include "components/commerce/core/shopping_service_test_base.h" #include "components/commerce/core/test_utils.h" @@ -1360,7 +1361,7 @@ test_features_.InitWithFeatures({kShoppingList}, {}); TestingPrefServiceSimple prefs; - RegisterPrefs(prefs.registry()); + RegisterProfilePrefs(prefs.registry()); SetShoppingListEnterprisePolicyPref(&prefs, true); MockAccountChecker checker; @@ -1378,7 +1379,7 @@ test_features_.InitWithFeatures({}, {kShoppingList}); TestingPrefServiceSimple prefs; - RegisterPrefs(prefs.registry()); + RegisterProfilePrefs(prefs.registry()); SetShoppingListEnterprisePolicyPref(&prefs, true); MockAccountChecker checker; @@ -1393,7 +1394,7 @@ test_features_.InitWithFeatures({kShoppingList}, {}); TestingPrefServiceSimple prefs; - RegisterPrefs(prefs.registry()); + RegisterProfilePrefs(prefs.registry()); SetShoppingListEnterprisePolicyPref(&prefs, true); MockAccountChecker checker; @@ -1412,7 +1413,7 @@ test_features_.InitWithFeatures({kShoppingList}, {}); TestingPrefServiceSimple prefs; - RegisterPrefs(prefs.registry()); + RegisterProfilePrefs(prefs.registry()); SetShoppingListEnterprisePolicyPref(&prefs, true); MockAccountChecker checker; @@ -1431,7 +1432,7 @@ test_features_.InitWithFeatures({kShoppingList}, {}); TestingPrefServiceSimple prefs; - RegisterPrefs(prefs.registry()); + RegisterProfilePrefs(prefs.registry()); SetShoppingListEnterprisePolicyPref(&prefs, true); MockAccountChecker checker; @@ -1450,7 +1451,7 @@ test_features_.InitWithFeatures({kShoppingList}, {}); TestingPrefServiceSimple prefs; - RegisterPrefs(prefs.registry()); + RegisterProfilePrefs(prefs.registry()); SetShoppingListEnterprisePolicyPref(&prefs, true); MockAccountChecker checker; @@ -1469,7 +1470,7 @@ test_features_.InitWithFeatures({kShoppingList}, {}); TestingPrefServiceSimple prefs; - RegisterPrefs(prefs.registry()); + RegisterProfilePrefs(prefs.registry()); SetShoppingListEnterprisePolicyPref(&prefs, true); MockAccountChecker checker; @@ -1492,7 +1493,7 @@ test_features_.InitWithFeatures({kShoppingList}, {}); TestingPrefServiceSimple prefs; - RegisterPrefs(prefs.registry()); + RegisterProfilePrefs(prefs.registry()); SetShoppingListEnterprisePolicyPref(&prefs, true); MockAccountChecker checker; @@ -1514,7 +1515,7 @@ test_features_.InitWithFeatures({}, {kShoppingList}); TestingPrefServiceSimple prefs; - RegisterPrefs(prefs.registry()); + RegisterProfilePrefs(prefs.registry()); SetShoppingListEnterprisePolicyPref(&prefs, true); MockAccountChecker checker; @@ -1534,7 +1535,7 @@ test_features_.InitWithEmptyFeatureAndFieldTrialLists(); TestingPrefServiceSimple prefs; - RegisterPrefs(prefs.registry()); + RegisterProfilePrefs(prefs.registry()); SetShoppingListEnterprisePolicyPref(&prefs, true); MockAccountChecker checker; @@ -1565,7 +1566,7 @@ test_features_.InitWithFeatures({}, {kShoppingList}); TestingPrefServiceSimple prefs; - RegisterPrefs(prefs.registry()); + RegisterProfilePrefs(prefs.registry()); SetShoppingListEnterprisePolicyPref(&prefs, true); MockAccountChecker checker;
diff --git a/components/history/core/browser/history_backend.cc b/components/history/core/browser/history_backend.cc index f177a2f..bbb65965 100644 --- a/components/history/core/browser/history_backend.cc +++ b/components/history/core/browser/history_backend.cc
@@ -2871,7 +2871,6 @@ return {}; URLID to_url_id = db_->GetRowForURL(to_url, nullptr); - // TODO: crbug.com/448407141 Take in a 404 policy param and pass in here VisitID cur_visit = db_->GetMostRecentVisitForURL( to_url_id, nullptr, VisitQuery404sPolicy::kInclude404s); if (!cur_visit)
diff --git a/components/history/core/browser/history_backend.h b/components/history/core/browser/history_backend.h index 10cd4ab3..7334589 100644 --- a/components/history/core/browser/history_backend.h +++ b/components/history/core/browser/history_backend.h
@@ -321,6 +321,7 @@ // given URL. Stores the most recent list of redirects ending at `url` in the // given RedirectList. For example, if we have the redirect list A -> B -> C, // then calling this function with url=C would fill redirects with {B, A}. + // Includes redirects that result in a 404 response. RedirectList QueryRedirectsTo(const GURL& url); VisibleVisitCountToHostResult GetVisibleVisitCountToHost(const GURL& url); @@ -791,7 +792,8 @@ // Bookmarks ----------------------------------------------------------------- // Notification that a URL is no longer bookmarked. If there are no visits - // for the specified url, it is deleted. + // for the specified url, it is deleted. Includes visits that result in a 404 + // status code. void URLsNoLongerBookmarked(const std::set<GURL>& urls); // Callbacks To Kill Database When It Gets Corrupted -------------------------
diff --git a/components/history/core/browser/visit_database.h b/components/history/core/browser/visit_database.h index 562d9369..4d4b6a0 100644 --- a/components/history/core/browser/visit_database.h +++ b/components/history/core/browser/visit_database.h
@@ -109,7 +109,8 @@ // // The results will be in no particular order. Also, no duplicate // detection is performed, so if `times` has duplicate times, - // `visits` may have duplicate visits. + // `visits` may have duplicate visits. Includes visits that result in 404 + // error response codes. bool GetVisitsForTimes(const std::vector<base::Time>& times, VisitVector* visits); @@ -122,7 +123,8 @@ // there are more results than that, the oldest ones will be returned. (This // is used for history expiration.) // - // The results will be in increasing order of date. + // The results will be in increasing order of date. Includes visits that + // result in 404 error response codes. bool GetAllVisitsInRange(base::Time begin_time, base::Time end_time, std::optional<std::string> app_id, @@ -137,7 +139,8 @@ // there are more results than that, the oldest ones will be returned. (This // is used for history expiration.) // - // The results will be in increasing order of date. + // The results will be in increasing order of date. Includes visits that + // result in 404 error response codes. bool GetVisitsInRangeForTransition(base::Time begin_time, base::Time end_time, int max_results, @@ -147,6 +150,7 @@ // Fills some foreign visits (i.e. with a non-empty `originator_cache_guid`) // into `visits` - at most `max_visits` of them, and only those with a (local) // visit_id <= `max_visit_id`. Returns true on success and false otherwise. + // Includes visits that result in 404 error response codes. // NOTE: This returns only redirect-chain-ends (including individual visits // without redirects). bool GetSomeForeignVisits(VisitID max_visit_id,
diff --git a/components/metrics/private_metrics/BUILD.gn b/components/metrics/private_metrics/BUILD.gn index 5f5e299..058689a 100644 --- a/components/metrics/private_metrics/BUILD.gn +++ b/components/metrics/private_metrics/BUILD.gn
@@ -15,12 +15,12 @@ deps = [ "//base" ] } -component("private_metrics_recorder") { +component("private_metrics_recorders") { sources = [ "dkm_recorder.cc", "dkm_recorder.h", ] - defines = [ "IS_PRIVATE_METRICS_RECORDER_IMPL" ] + defines = [ "IS_PRIVATE_METRICS_RECORDERS_IMPL" ] public_deps = [ "//components/metrics/private_metrics/mojom", "//third_party/metrics_proto", @@ -60,7 +60,7 @@ ] public_deps = [ ":dwa_recorder", - ":private_metrics_recorder", + ":private_metrics_recorders", "//components/metrics/dwa/mojom", "//components/metrics/private_metrics/mojom", "//third_party/federated_compute:confidential_compute_proto",
diff --git a/components/metrics/private_metrics/dkm_recorder.h b/components/metrics/private_metrics/dkm_recorder.h index df0342e..af07c0e 100644 --- a/components/metrics/private_metrics/dkm_recorder.h +++ b/components/metrics/private_metrics/dkm_recorder.h
@@ -19,7 +19,7 @@ // dkm::builders::MyEvent(source_id) // .SetMyMetric(metric_value) // .Record(DkmRecorder::Get()); -class COMPONENT_EXPORT(PRIVATE_METRICS_RECORDER) DkmRecorder { +class COMPONENT_EXPORT(PRIVATE_METRICS_RECORDERS) DkmRecorder { public: DkmRecorder();
diff --git a/components/ntp_tiles/pref_names.h b/components/ntp_tiles/pref_names.h index 8ac84f4..02a316d 100644 --- a/components/ntp_tiles/pref_names.h +++ b/components/ntp_tiles/pref_names.h
@@ -66,6 +66,9 @@ inline constexpr char kTabResumptionHomeModuleEnabled[] = "home.module.tab_resumption.enabled"; +// The pref that stores if the Tips Home Module is enabled. +inline constexpr char kTipsHomeModuleEnabled[] = "home.module.tips.enabled"; + } // namespace ntp_tiles::prefs #endif // COMPONENTS_NTP_TILES_PREF_NAMES_H_
diff --git a/components/password_manager/core/browser/password_form_manager.cc b/components/password_manager/core/browser/password_form_manager.cc index 438bce6..b33c3f1 100644 --- a/components/password_manager/core/browser/password_form_manager.cc +++ b/components/password_manager/core/browser/password_form_manager.cc
@@ -1074,7 +1074,6 @@ is_submitted_ = true; CalculateSubmittedFormFrameMetric(); CalculateSubmittedFormTypeMetric(); - metrics_recorder_->set_possible_username_used(false); if (votes_uploader_.has_value()) { votes_uploader_->clear_single_username_votes_data(); votes_uploader_->set_should_send_username_first_flow_votes(false); @@ -1744,7 +1743,6 @@ SetUsernameValueFromOutsideOfForm(picked_username.data.value, *parsed_submitted_form_.get()); } - metrics_recorder_->set_possible_username_used(true); } void PasswordFormManager::HandleForgotPasswordFormData() {
diff --git a/components/password_manager/core/browser/password_form_metrics_recorder.cc b/components/password_manager/core/browser/password_form_metrics_recorder.cc index 7ad5032..95e0e7d 100644 --- a/components/password_manager/core/browser/password_form_metrics_recorder.cc +++ b/components/password_manager/core/browser/password_form_metrics_recorder.cc
@@ -1175,19 +1175,6 @@ ukm_entry_builder_.SetSaving_Prompt_Interaction( static_cast<int64_t>(bubble_dismissal_reason)); } - - // Record saving on username first flow metric. - if (possible_username_used_) { - auto saving_on_username_first_flow = SavingOnUsernameFirstFlow::kNotSaved; - if (bubble_dismissal_reason == BubbleDismissalReason::kAccepted) { - saving_on_username_first_flow = - username_updated_in_bubble_ - ? SavingOnUsernameFirstFlow::kSavedWithEditedUsername - : SavingOnUsernameFirstFlow::kSaved; - } - UMA_HISTOGRAM_ENUMERATION("PasswordManager.SavingOnUsernameFirstFlow", - saving_on_username_first_flow); - } } current_bubble_ = CurrentBubbleOfInterest::kNone;
diff --git a/components/password_manager/core/browser/password_form_metrics_recorder.h b/components/password_manager/core/browser/password_form_metrics_recorder.h index 200cc4c..877ddfc 100644 --- a/components/password_manager/core/browser/password_form_metrics_recorder.h +++ b/components/password_manager/core/browser/password_form_metrics_recorder.h
@@ -506,10 +506,6 @@ // `SingleUsernameFillingAssistance`), returns an empty string. std::string FillingAssinstanceToHatsInProductDataString(); - void set_possible_username_used(bool value) { - possible_username_used_ = value; - } - void set_username_updated_in_bubble(bool value) { username_updated_in_bubble_ = value; } @@ -651,9 +647,6 @@ account_storage_usage_level_; std::optional<metrics_util::SubmittedFormFrame> submitted_form_frame_; - // Whether a single username candidate was populated in prompt. - bool possible_username_used_ = false; - bool username_updated_in_bubble_ = false; std::optional<JsOnlyInput> js_only_input_;
diff --git a/components/permissions/pepc_initiated_permission_request_unittest.cc b/components/permissions/pepc_initiated_permission_request_unittest.cc index d8be480..5d7fd80 100644 --- a/components/permissions/pepc_initiated_permission_request_unittest.cc +++ b/components/permissions/pepc_initiated_permission_request_unittest.cc
@@ -415,13 +415,13 @@ // Grant just below the threshold. for (int i = 0; i < kHeuristicGrantThreshold; ++i) { - EXPECT_FALSE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( + EXPECT_FALSE(history->RecordTemporaryGrant( origin(), ContentSettingsType::GEOLOCATION)); } // The next grant will trigger the auto-grant. - EXPECT_TRUE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - origin(), ContentSettingsType::GEOLOCATION)); + EXPECT_TRUE(history->RecordTemporaryGrant(origin(), + ContentSettingsType::GEOLOCATION)); EXPECT_TRUE(history->CheckHeuristicallyAutoGranted( origin(), ContentSettingsType::GEOLOCATION, /*needs_update*/ false)); @@ -446,9 +446,9 @@ // Let's test dismissing the prompt resets the heuristic. // Now, reset the heuristic grant and increase the count by 2. history->ResetHeuristicData(origin(), ContentSettingsType::GEOLOCATION); - EXPECT_FALSE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( + EXPECT_FALSE(history->RecordTemporaryGrant( origin(), ContentSettingsType::GEOLOCATION)); - EXPECT_FALSE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( + EXPECT_FALSE(history->RecordTemporaryGrant( origin(), ContentSettingsType::GEOLOCATION)); prompt_factory()->set_response_type( PermissionRequestManager::AutoResponseType::DISMISS);
diff --git a/components/permissions/permission_actions_history.cc b/components/permissions/permission_actions_history.cc index f1ea723..a9cee68 100644 --- a/components/permissions/permission_actions_history.cc +++ b/components/permissions/permission_actions_history.cc
@@ -42,9 +42,10 @@ // // Website settings are used to store data related to heuristic grants. This // data is stored per-origin and per-permission type, and includes: -// - kTempGrantCountKey: Stores the number of temporary grants for a permission. -// - kAutoGrantHeuristicallyKey: Stores the timestamp when an auto-grant -// started taking effect. This auto-grant has a 7-day expiration. +// - kTemporaryGrantCountKey: Stores the number of heuristic temporary grants +// for a permission. +// - kTemporaryGrantTimeStampKey: Stores the timestamp of the most recent +// temporary grant, including auto-grants. constexpr char kPermissionActionEntryActionKey[] = "action"; constexpr char kPermissionActionEntryTimestampKey[] = "time"; constexpr char kPermissionActionEntryPromptDispositionKey[] = @@ -57,11 +58,11 @@ constexpr int kHeuristicGrantThreshold = 3; // The duration after which the auto-grant expires. -constexpr base::TimeDelta kAutoGrantHeuristicallyExpiration = base::Days(7); +constexpr base::TimeDelta kAutoGrantHeuristicallyExpiration = base::Days(28); // Keys for storing data in website settings. -constexpr char kTempGrantCountKey[] = "temp_grant_count"; -constexpr char kAutoGrantHeuristicallyKey[] = "auto_grant_heuristically_days"; +constexpr char kTemporaryGrantCountKey[] = "temporary_grant_count"; +constexpr char kTemporaryGrantTimeStampKey[] = "temporary_grant_time_days"; base::Value::Dict GetOriginActionHistoryData(HostContentSettingsMap* settings, const GURL& origin_url) { @@ -82,25 +83,6 @@ PermissionUtil::GetPermissionString(content_type)); } -// Record incrementally by one the number of temporary grants for `permission` -// type at `url`. -int RecordTemporaryGrantCount(const GURL& url, - ContentSettingsType permission, - HostContentSettingsMap* settings_map) { - base::Value::Dict dict = GetOriginActionHistoryData(settings_map, url); - base::Value::Dict* permission_dict = EnsurePermissionDict(dict, permission); - - std::optional<int> value = permission_dict->FindInt(kTempGrantCountKey); - int current_count = value.value_or(0); - permission_dict->Set(kTempGrantCountKey, base::Value(++current_count)); - - settings_map->SetWebsiteSettingDefaultScope( - url, GURL(), ContentSettingsType::PERMISSION_ACTIONS_HISTORY, - base::Value(std::move(dict))); - - return current_count; -} - // Returns the current number of temporary grants recorded for `permission` // type at `url`. int GetTemporaryGrantCount(const GURL& url, @@ -109,7 +91,7 @@ base::Value::Dict dict = GetOriginActionHistoryData(settings_map, url); base::Value::Dict* permission_dict = EnsurePermissionDict(dict, permission); - std::optional<int> value = permission_dict->FindInt(kTempGrantCountKey); + std::optional<int> value = permission_dict->FindInt(kTemporaryGrantCountKey); return value.value_or(0); } @@ -208,17 +190,28 @@ } } -bool PermissionActionsHistory::RecordTemporaryGrantAndSetAutoGrantIfNecessary( +bool PermissionActionsHistory::RecordTemporaryGrant( const GURL& url, ContentSettingsType permission) { - int current_count = GetTemporaryGrantCount(url, permission, settings_map_); - if (current_count >= kHeuristicGrantThreshold) { - SetAutoGrantHeuristically(url, permission); - return true; - } + base::Value::Dict dict = GetOriginActionHistoryData(settings_map_, url); + base::Value::Dict* permission_dict = EnsurePermissionDict(dict, permission); - RecordTemporaryGrantCount(url, permission, settings_map_); - return false; + std::optional<int> value = permission_dict->FindInt(kTemporaryGrantCountKey); + int current_count = value.value_or(0); + + permission_dict->Set(kTemporaryGrantCountKey, current_count + 1); + permission_dict->Set(kTemporaryGrantTimeStampKey, + base::TimeToValue(base::Time::Now())); + + settings_map_->SetWebsiteSettingDefaultScope( + url, GURL(), ContentSettingsType::PERMISSION_ACTIONS_HISTORY, + base::Value(std::move(dict))); + + bool auto_granted = current_count >= kHeuristicGrantThreshold; + if (auto_granted) { + NotifyAutoGrantedHeuristically(url, permission); + } + return auto_granted; } void PermissionActionsHistory::ResetHeuristicData( @@ -249,20 +242,6 @@ } } -void PermissionActionsHistory::SetAutoGrantHeuristically( - const GURL& request_origin, - ContentSettingsType permission) { - base::Value::Dict dict = - GetOriginActionHistoryData(settings_map_, request_origin); - base::Value::Dict* permission_dict = EnsurePermissionDict(dict, permission); - permission_dict->Set(kAutoGrantHeuristicallyKey, - base::TimeToValue(base::Time::Now())); - settings_map_->SetWebsiteSettingDefaultScope( - request_origin, GURL(), ContentSettingsType::PERMISSION_ACTIONS_HISTORY, - base::Value(std::move(dict))); - NotifyAutoGrantedHeuristically(request_origin, permission); -} - bool PermissionActionsHistory::CheckHeuristicallyAutoGranted( const GURL& request_origin, ContentSettingsType permission, @@ -271,27 +250,46 @@ GetOriginActionHistoryData(settings_map_, request_origin); base::Value::Dict* permission_dict = EnsurePermissionDict(dict, permission); - std::optional<base::Time> auto_grant_time = - base::ValueToTime(permission_dict->Find(kAutoGrantHeuristicallyKey)); - if (!auto_grant_time.has_value()) { + std::optional<base::Time> last_grant_time = + base::ValueToTime(permission_dict->Find(kTemporaryGrantTimeStampKey)); + std::optional<int> grant_count_opt = + permission_dict->FindInt(kTemporaryGrantCountKey); + int grant_count = grant_count_opt.value_or(0); + + // Check if the last grant has expired. If the grant has expired, decay the + // count. If the count was at or above the threshold, it decays to 2. + // Otherwise, the heuristic data is reset completely. + if (last_grant_time.has_value() && base::Time::Now() - *last_grant_time > + kAutoGrantHeuristicallyExpiration) { + if (grant_count >= kHeuristicGrantThreshold) { + permission_dict->Set(kTemporaryGrantCountKey, + kHeuristicGrantThreshold - 1); + permission_dict->Set(kTemporaryGrantTimeStampKey, + base::TimeToValue(base::Time::Now())); + settings_map_->SetWebsiteSettingDefaultScope( + request_origin, GURL(), + ContentSettingsType::PERMISSION_ACTIONS_HISTORY, + base::Value(std::move(dict))); + } else { + ResetHeuristicData(request_origin, permission); + } return false; } - if (base::Time::Now() - *auto_grant_time > - kAutoGrantHeuristicallyExpiration) { - ResetHeuristicData(request_origin, permission); - return false; + if (grant_count >= kHeuristicGrantThreshold) { + if (needs_update) { + permission_dict->Set(kTemporaryGrantTimeStampKey, + base::TimeToValue(base::Time::Now())); + settings_map_->SetWebsiteSettingDefaultScope( + request_origin, GURL(), + ContentSettingsType::PERMISSION_ACTIONS_HISTORY, + base::Value(std::move(dict))); + } + return true; } - if (needs_update) { - permission_dict->Set(kAutoGrantHeuristicallyKey, - base::TimeToValue(base::Time::Now())); - settings_map_->SetWebsiteSettingDefaultScope( - request_origin, GURL(), ContentSettingsType::PERMISSION_ACTIONS_HISTORY, - base::Value(std::move(dict))); - } - - return true; + // The grant count is below the threshold, so it is not auto-granted. + return false; } void PermissionActionsHistory::AddObserver(Observer* obs) {
diff --git a/components/permissions/permission_actions_history.h b/components/permissions/permission_actions_history.h index 92ce4588..bb153d6 100644 --- a/components/permissions/permission_actions_history.h +++ b/components/permissions/permission_actions_history.h
@@ -90,14 +90,11 @@ // Registers the preferences related to blocklisting in the given PrefService. static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry); - // Incrementally records the number of temporary grants for a given - // `permission` type at `request_origin` by one. - // It then sets a heuristic auto-grant if the temporary grant count reaches a - // predefined threshold. Returns `true` if the counter reaches the threshold - // and auto-grant is activated; otherwise, it returns `false`. - bool RecordTemporaryGrantAndSetAutoGrantIfNecessary( - const GURL& request_origin, - ContentSettingsType permission); + // Incrementally records the number of temporary grants and the timestamp of + // the most recent temporary grant for a given `permission` type at + // `url` by one. Returns `true` if the counter reaches the + // threshold and auto-grant is activated; otherwise, it returns `false`. + bool RecordTemporaryGrant(const GURL& url, ContentSettingsType permission); // Resets the heuristic data for the given URL and permission, for // example when the user manually resets permissions. @@ -108,11 +105,6 @@ void ResetHeuristicData( base::RepeatingCallback<bool(const GURL& url)> filter); - // Sets the heuristic auto grant for |permission| on |request_origin| and - // notifies observers. - void SetAutoGrantHeuristically(const GURL& request_origin, - ContentSettingsType permission); - // Checks if a permission has been heuristically auto-granted. If // `needs_update` is true, update the auto-granted stored data if exists. // `needs_update` is expected to be true, it is only set `false` from test
diff --git a/components/permissions/permission_actions_history_unittest.cc b/components/permissions/permission_actions_history_unittest.cc index 348bbf6..2106e020 100644 --- a/components/permissions/permission_actions_history_unittest.cc +++ b/components/permissions/permission_actions_history_unittest.cc
@@ -364,21 +364,18 @@ history->AddObserver(&observer); for (int i = 0; i < kHeuristicGrantThreshold; ++i) { - EXPECT_FALSE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - url, permission)); + EXPECT_FALSE(history->RecordTemporaryGrant(url, permission)); EXPECT_EQ(0, observer.call_count()); } // The next time should trigger auto-grant. - EXPECT_TRUE( - history->RecordTemporaryGrantAndSetAutoGrantIfNecessary(url, permission)); + EXPECT_TRUE(history->RecordTemporaryGrant(url, permission)); EXPECT_EQ(1, observer.call_count()); EXPECT_EQ(url, observer.origin()); EXPECT_EQ(permission, observer.content_setting()); // Subsequent calls should also return true. - EXPECT_TRUE( - history->RecordTemporaryGrantAndSetAutoGrantIfNecessary(url, permission)); + EXPECT_TRUE(history->RecordTemporaryGrant(url, permission)); // The observer is notified again. EXPECT_EQ(2, observer.call_count()); @@ -391,22 +388,18 @@ auto* history = GetPermissionActionsHistory(); // Grant twice. - EXPECT_FALSE( - history->RecordTemporaryGrantAndSetAutoGrantIfNecessary(url, permission)); - EXPECT_FALSE( - history->RecordTemporaryGrantAndSetAutoGrantIfNecessary(url, permission)); + EXPECT_FALSE(history->RecordTemporaryGrant(url, permission)); + EXPECT_FALSE(history->RecordTemporaryGrant(url, permission)); // Reset. history->ResetHeuristicData(url, permission); for (int i = 0; i < kHeuristicGrantThreshold; ++i) { - EXPECT_FALSE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - url, permission)); + EXPECT_FALSE(history->RecordTemporaryGrant(url, permission)); } // Next time after reset should trigger auto-grant. - EXPECT_TRUE( - history->RecordTemporaryGrantAndSetAutoGrantIfNecessary(url, permission)); + EXPECT_TRUE(history->RecordTemporaryGrant(url, permission)); } TEST_F(PermissionActionHistoryHeuristicGrantTest, @@ -417,26 +410,22 @@ auto* history = GetPermissionActionsHistory(); for (int i = 0; i < kHeuristicGrantThreshold - 1; ++i) { - history->RecordTemporaryGrantAndSetAutoGrantIfNecessary(url1, permission1); - history->RecordTemporaryGrantAndSetAutoGrantIfNecessary(url2, permission1); + history->RecordTemporaryGrant(url1, permission1); + history->RecordTemporaryGrant(url2, permission1); } // Grant url1/permission1 one more time. Should not auto-grant. - EXPECT_FALSE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - url1, permission1)); + EXPECT_FALSE(history->RecordTemporaryGrant(url1, permission1)); // Grant url1/permission1 another time. Next check will auto-grant. - EXPECT_TRUE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - url1, permission1)); + EXPECT_TRUE(history->RecordTemporaryGrant(url1, permission1)); // The other permissions should not be auto-granted yet. // The next call will increment to counter and not auto-grant. - EXPECT_FALSE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - url2, permission1)); + EXPECT_FALSE(history->RecordTemporaryGrant(url2, permission1)); // The next call for these will auto-grant. - EXPECT_TRUE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - url2, permission1)); + EXPECT_TRUE(history->RecordTemporaryGrant(url2, permission1)); } TEST_F(PermissionActionHistoryHeuristicGrantTest, @@ -445,43 +434,18 @@ auto* history = GetPermissionActionsHistory(); // GEOLOCATION should work. - EXPECT_FALSE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - url, ContentSettingsType::GEOLOCATION)); + EXPECT_FALSE( + history->RecordTemporaryGrant(url, ContentSettingsType::GEOLOCATION)); // NOTIFICATIONS should crash. EXPECT_DEATH_IF_SUPPORTED( - history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - url, ContentSettingsType::NOTIFICATIONS), + history->RecordTemporaryGrant(url, ContentSettingsType::NOTIFICATIONS), ""); - EXPECT_DEATH_IF_SUPPORTED(history->SetAutoGrantHeuristically( - url, ContentSettingsType::NOTIFICATIONS), - ""); EXPECT_DEATH_IF_SUPPORTED(history->CheckHeuristicallyAutoGranted( url, ContentSettingsType::NOTIFICATIONS), ""); } -TEST_F(PermissionActionHistoryHeuristicGrantTest, HeuristicGrantExpiration) { - GURL url("https://www.example.com"); - ContentSettingsType permission = ContentSettingsType::GEOLOCATION; - auto* history = GetPermissionActionsHistory(); - - for (int i = 0; i < kHeuristicGrantThreshold; ++i) { - history->RecordTemporaryGrantAndSetAutoGrantIfNecessary(url, permission); - } - - // Trigger auto-grant. - EXPECT_TRUE( - history->RecordTemporaryGrantAndSetAutoGrantIfNecessary(url, permission)); - - // Advance clock past expiration date. - task_environment_.AdvanceClock(base::Days(8)); - - // The count should be reset, so the next grant is not an auto-grant. - EXPECT_FALSE(history->CheckHeuristicallyAutoGranted(url, permission, - /*needs_update*/ false)); -} - TEST_F(PermissionActionHistoryHeuristicGrantTest, CheckHeuristicallyAutoGranted) { GURL url("https://www.example.com"); @@ -492,18 +456,16 @@ /*needs_update*/ false)); for (int i = 0; i < kHeuristicGrantThreshold; ++i) { - history->RecordTemporaryGrantAndSetAutoGrantIfNecessary(url, permission); + history->RecordTemporaryGrant(url, permission); } // Trigger auto-grant. - EXPECT_TRUE( - history->RecordTemporaryGrantAndSetAutoGrantIfNecessary(url, permission)); + EXPECT_TRUE(history->RecordTemporaryGrant(url, permission)); EXPECT_TRUE(history->CheckHeuristicallyAutoGranted(url, permission, /*needs_update*/ false)); // Advance clock past expiration date. - task_environment_.AdvanceClock(base::Days(8)); - + task_environment_.AdvanceClock(base::Days(29)); EXPECT_FALSE(history->CheckHeuristicallyAutoGranted(url, permission, /*needs_update*/ false)); } @@ -520,10 +482,9 @@ // Trigger auto-grant. for (int i = 0; i < kHeuristicGrantThreshold; ++i) { - history->RecordTemporaryGrantAndSetAutoGrantIfNecessary(url, permission); + history->RecordTemporaryGrant(url, permission); } - EXPECT_TRUE( - history->RecordTemporaryGrantAndSetAutoGrantIfNecessary(url, permission)); + EXPECT_TRUE(history->RecordTemporaryGrant(url, permission)); // Check with needs_update = true. Timestamp should change. task_environment_.AdvanceClock(base::Days(2)); @@ -531,12 +492,66 @@ task_environment_.AdvanceClock(base::Days(6)); EXPECT_TRUE(history->CheckHeuristicallyAutoGranted(url, permission, /*needs_update*/ false)); - task_environment_.AdvanceClock(base::Days(2)); + task_environment_.AdvanceClock(base::Days(20)); + EXPECT_TRUE(history->CheckHeuristicallyAutoGranted(url, permission, + /*needs_update*/ false)); + task_environment_.AdvanceClock(base::Days(3)); EXPECT_FALSE(history->CheckHeuristicallyAutoGranted(url, permission, /*needs_update*/ false)); } TEST_F(PermissionActionHistoryHeuristicGrantTest, + HeuristicGrantExpirationDecaysCount) { + GURL url("https://www.example.com"); + ContentSettingsType permission = ContentSettingsType::GEOLOCATION; + auto* history = GetPermissionActionsHistory(); + + // Grant up to the threshold to enable auto-granting. + for (int i = 0; i < kHeuristicGrantThreshold; ++i) { + history->RecordTemporaryGrant(url, permission); + } + + // The next grant will trigger auto-grant. + EXPECT_TRUE(history->RecordTemporaryGrant(url, permission)); + EXPECT_EQ(kHeuristicGrantThreshold + 1, + history->GetTemporaryGrantCountForTesting(url, permission)); + EXPECT_TRUE(history->CheckHeuristicallyAutoGranted(url, permission, + /*needs_update*/ false)); + + // Advance clock past expiration date. + task_environment_.AdvanceClock(base::Days(29)); + + // The grant should have expired. + EXPECT_FALSE(history->CheckHeuristicallyAutoGranted(url, permission, + /*needs_update*/ false)); + + // The count should have decayed to 2. + EXPECT_EQ(2, history->GetTemporaryGrantCountForTesting(url, permission)); + + // Advance clock past expiration date again. + task_environment_.AdvanceClock(base::Days(29)); + + // The grant should still be expired. + EXPECT_FALSE(history->CheckHeuristicallyAutoGranted(url, permission, + /*needs_update*/ false)); + // The count should have decayed to 0. + EXPECT_EQ(0, history->GetTemporaryGrantCountForTesting(url, permission)); + + // The next grant should not auto-grant, but increment the count to 1. + EXPECT_FALSE(history->RecordTemporaryGrant(url, permission)); + EXPECT_EQ(1, history->GetTemporaryGrantCountForTesting(url, permission)); + + // Grant twice more to reach the threshold. + EXPECT_FALSE(history->RecordTemporaryGrant(url, permission)); + EXPECT_FALSE(history->RecordTemporaryGrant(url, permission)); + EXPECT_EQ(3, history->GetTemporaryGrantCountForTesting(url, permission)); + + // The next grant should now trigger auto-grant. + EXPECT_TRUE(history->RecordTemporaryGrant(url, permission)); + EXPECT_EQ(4, history->GetTemporaryGrantCountForTesting(url, permission)); +} + +TEST_F(PermissionActionHistoryHeuristicGrantTest, HeuristicGrantResetWithFilter) { GURL url1("https://www.example.com"); GURL url2("https://www.google.com"); @@ -544,15 +559,11 @@ auto* history = GetPermissionActionsHistory(); // Grant url1 and url2 twice. - EXPECT_FALSE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - url1, permission)); - EXPECT_FALSE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - url1, permission)); + EXPECT_FALSE(history->RecordTemporaryGrant(url1, permission)); + EXPECT_FALSE(history->RecordTemporaryGrant(url1, permission)); - EXPECT_FALSE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - url2, permission)); - EXPECT_FALSE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - url2, permission)); + EXPECT_FALSE(history->RecordTemporaryGrant(url2, permission)); + EXPECT_FALSE(history->RecordTemporaryGrant(url2, permission)); // Reset for urls matching "example.com". history->ResetHeuristicData(base::BindRepeating( @@ -561,19 +572,15 @@ // The counter for url1 should be reset. It should take // `kHeuristicGrantThreshold` more grants to trigger auto-grant. for (int i = 0; i < kHeuristicGrantThreshold; ++i) { - EXPECT_FALSE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - url1, permission)); + EXPECT_FALSE(history->RecordTemporaryGrant(url1, permission)); } - EXPECT_TRUE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - url1, permission)); + EXPECT_TRUE(history->RecordTemporaryGrant(url1, permission)); // The counter for url2 should not be reset. It was granted twice, so it // needs one more grant to reach the threshold. - EXPECT_FALSE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - url2, permission)); + EXPECT_FALSE(history->RecordTemporaryGrant(url2, permission)); // The next one should auto-grant. - EXPECT_TRUE(history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( - url2, permission)); + EXPECT_TRUE(history->RecordTemporaryGrant(url2, permission)); } } // namespace permissions
diff --git a/components/permissions/permission_request_manager.cc b/components/permissions/permission_request_manager.cc index b66c054..04fac7ff 100644 --- a/components/permissions/permission_request_manager.cc +++ b/components/permissions/permission_request_manager.cc
@@ -1199,7 +1199,7 @@ PermissionsClient::Get()->GetPermissionActionsHistory( browser_context); if (permission_action == PermissionAction::GRANTED_ONCE) { - actions_history->RecordTemporaryGrantAndSetAutoGrantIfNecessary( + actions_history->RecordTemporaryGrant( request->requesting_origin(), request->GetContentSettingsType()); } else if (permission_action == PermissionAction::DISMISSED) { actions_history->ResetHeuristicData(request->requesting_origin(),
diff --git a/components/policy/core/common/schema.cc b/components/policy/core/common/schema.cc index 6b48133..264c7f0 100644 --- a/components/policy/core/common/schema.cc +++ b/components/policy/core/common/schema.cc
@@ -839,6 +839,9 @@ } else if (schema.contains(schema::kEnum)) { RETURN_IF_ERROR(ParseEnum(schema, type, schema_node)); } else if (schema.contains(schema::kPattern)) { + if (type != base::Value::Type::STRING) { + return base::unexpected("Only strings can have a pattern"); + } RETURN_IF_ERROR(ParseStringPattern(schema, schema_node)); } else if (schema.contains(schema::kMinimum) || schema.contains(schema::kMaximum)) { @@ -943,6 +946,10 @@ schema.FindList(schema::kRequired); if (required_properties) { for (const base::Value& val : *required_properties) { + if (!val.is_string()) { + return base::unexpected( + "Items in the 'required' property must be strings."); + } strings_.push_back(val.GetString()); required_properties_.push_back(strings_.back().c_str()); }
diff --git a/components/safe_browsing/android/BUILD.gn b/components/safe_browsing/android/BUILD.gn index a9d60ca..0b945e3 100644 --- a/components/safe_browsing/android/BUILD.gn +++ b/components/safe_browsing/android/BUILD.gn
@@ -6,7 +6,6 @@ import("//build/config/android/rules.gni") import("//components/safe_browsing/buildflags.gni") import("//third_party/jni_zero/jni_zero.gni") -import("//third_party/libprotobuf-mutator/fuzzable_proto_library.gni") android_library("safe_browsing_java") { deps = [ @@ -36,11 +35,6 @@ class_name = "org.chromium.components.safe_browsing.SafeBrowsingFeatures" } -fuzzable_proto_library("realtimeallowlist_proto") { - proto_in_dir = "//" - sources = [ "proto/realtimeallowlist.proto" ] -} - generate_jni("jni_headers") { sources = [ "java/src/org/chromium/components/safe_browsing/SafeBrowsingApiBridge.java", @@ -73,9 +67,9 @@ deps = [ "//base", "//components/resources:components_resources", - "//components/safe_browsing/android:realtimeallowlist_proto", "//components/safe_browsing/core/browser/db:v4_protocol_manager_util", "//components/safe_browsing/core/common", + "//components/safe_browsing/core/common/proto:realtimeallowlist_proto", "//ui/base", "//url", ] @@ -193,12 +187,12 @@ ":unit_tests_mobile_test_support", "//base", "//components/resources:components_resources_grit", - "//components/safe_browsing/android:realtimeallowlist_proto", "//components/safe_browsing/core/browser/db:database_manager", "//components/safe_browsing/core/browser/db:unit_tests_shared", "//components/safe_browsing/core/browser/db:util", "//components/safe_browsing/core/browser/db:v4_test_util", "//components/safe_browsing/core/common", + "//components/safe_browsing/core/common/proto:realtimeallowlist_proto", "//components/variations", "//content/test:test_support", "//services/network:test_support",
diff --git a/components/safe_browsing/android/real_time_url_checks_allowlist.cc b/components/safe_browsing/android/real_time_url_checks_allowlist.cc index a12b313..a91c4bb9 100644 --- a/components/safe_browsing/android/real_time_url_checks_allowlist.cc +++ b/components/safe_browsing/android/real_time_url_checks_allowlist.cc
@@ -9,8 +9,8 @@ #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" #include "components/grit/components_resources.h" -#include "components/safe_browsing/android/proto/realtimeallowlist.pb.h" #include "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h" +#include "components/safe_browsing/core/common/proto/realtimeallowlist.pb.h" #include "ui/base/resource/resource_bundle.h" namespace safe_browsing {
diff --git a/components/safe_browsing/android/real_time_url_checks_allowlist_unittest.cc b/components/safe_browsing/android/real_time_url_checks_allowlist_unittest.cc index dfef061..209296c18 100644 --- a/components/safe_browsing/android/real_time_url_checks_allowlist_unittest.cc +++ b/components/safe_browsing/android/real_time_url_checks_allowlist_unittest.cc
@@ -3,7 +3,6 @@ // found in the LICENSE file. #include "components/safe_browsing/android/real_time_url_checks_allowlist.h" -#include "components/safe_browsing/android/proto/realtimeallowlist.pb.h" #include <string.h> @@ -11,6 +10,7 @@ #include "base/files/file_path.h" #include "base/test/metrics/histogram_tester.h" +#include "components/safe_browsing/core/common/proto/realtimeallowlist.pb.h" #include "crypto/sha2.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h"
diff --git a/components/safe_browsing/content/browser/client_side_detection_host.cc b/components/safe_browsing/content/browser/client_side_detection_host.cc index 127171ee..74034e0 100644 --- a/components/safe_browsing/content/browser/client_side_detection_host.cc +++ b/components/safe_browsing/content/browser/client_side_detection_host.cc
@@ -770,6 +770,9 @@ if (classification_request_.get()) { classification_request_->Cancel(); } + if (intelligent_scan_session_id_.has_value()) { + intelligent_scan_delegate_->CancelSession(*intelligent_scan_session_id_); + } } void ClientSideDetectionHost::RegisterPermissionRequestManager() { @@ -1043,11 +1046,14 @@ } if (should_classify) { - // Cancel any ongoing on device sessions. - bool did_reset_session = intelligent_scan_delegate_->ResetOnDeviceSession(); + bool intelligent_scan_session_ongoing = + intelligent_scan_session_id_.has_value(); base::UmaHistogramBoolean( "SBClientPhishing.OnDeviceModelSessionAliveOnNewPreclassification", - did_reset_session); + intelligent_scan_session_ongoing); + if (intelligent_scan_session_ongoing) { + intelligent_scan_delegate_->CancelSession(*intelligent_scan_session_id_); + } content::RenderFrameHost* rfh = web_contents()->GetPrimaryMainFrame(); @@ -1503,17 +1509,19 @@ return; } - intelligent_scan_delegate_->InquireOnDeviceModel( - inner_text, - base::BindOnce(&ClientSideDetectionHost::OnInquireOnDeviceModelDone, - weak_factory_.GetWeakPtr(), std::move(verdict), - did_match_high_confidence_allowlist)); + intelligent_scan_session_id_ = + intelligent_scan_delegate_->InquireOnDeviceModel( + inner_text, + base::BindOnce(&ClientSideDetectionHost::OnInquireOnDeviceModelDone, + weak_factory_.GetWeakPtr(), std::move(verdict), + did_match_high_confidence_allowlist)); } void ClientSideDetectionHost::OnInquireOnDeviceModelDone( std::unique_ptr<ClientPhishingRequest> verdict, std::optional<bool> did_match_high_confidence_allowlist, IntelligentScanDelegate::IntelligentScanResult response) { + intelligent_scan_session_id_.reset(); base::UmaHistogramBoolean( "SBClientPhishing.OnDeviceModelHasSuccessfulResponse", response.execution_success);
diff --git a/components/safe_browsing/content/browser/client_side_detection_host.h b/components/safe_browsing/content/browser/client_side_detection_host.h index 605105c..66966dbd 100644 --- a/components/safe_browsing/content/browser/client_side_detection_host.h +++ b/components/safe_browsing/content/browser/client_side_detection_host.h
@@ -18,6 +18,7 @@ #include "base/memory/raw_ptr.h" #include "base/memory/ref_counted.h" #include "base/time/time.h" +#include "base/unguessable_token.h" #include "components/autofill/core/browser/foundations/autofill_manager.h" #include "components/autofill/core/browser/foundations/scoped_autofill_managers_observation.h" #include "components/keyed_service/core/keyed_service.h" @@ -132,13 +133,14 @@ bool log_failed_eligibility_reason) = 0; // Gets the intelligent scan result from the on-device model. The callback // will return an empty optional if the on-device model is not available. - virtual void InquireOnDeviceModel( + // Returns a token that can be used to cancel the request. The token will be + // std::nullopt in case the inquiry fails immediately without start. + virtual std::optional<base::UnguessableToken> InquireOnDeviceModel( std::string rendered_texts, InquireOnDeviceModelDoneCallback callback) = 0; - // Resets the session that's created by the on-device model. Returns true if - // the session was reset. Does nothing and returns false if there is no - // session. - virtual bool ResetOnDeviceSession() = 0; + // Cancels a specific on-device model session. If the |session_id| is + // ongoing, it will return true, and false otherwise. + virtual bool CancelSession(const base::UnguessableToken& session_id) = 0; // Determines if a scam warning should be shown based on the intelligent // scan verdict. virtual bool ShouldShowScamWarning( @@ -554,6 +556,9 @@ // OnPhishingPreClassificationDone was called at the end of preclassification. PreclassificationDone preclassification_done_cb_for_testing_; + // The session ID for the current intelligent scan request. + std::optional<base::UnguessableToken> intelligent_scan_session_id_; + base::WeakPtrFactory<ClientSideDetectionHost> weak_factory_{this}; };
diff --git a/components/safe_browsing/content/resources/real_time_url_checks_allowlist/BUILD.gn b/components/safe_browsing/content/resources/real_time_url_checks_allowlist/BUILD.gn index 65eb5e5..b1619a5 100644 --- a/components/safe_browsing/content/resources/real_time_url_checks_allowlist/BUILD.gn +++ b/components/safe_browsing/content/resources/real_time_url_checks_allowlist/BUILD.gn
@@ -17,12 +17,12 @@ output_dir = target_gen_dir output_basename = "real_time_url_checks_allowlist.pb" python_path_safe_browsing = - "$proto_python_root/components/safe_browsing/android/proto" + "$proto_python_root/components/safe_browsing/core/common/proto" inputs = [ input_filename ] deps = [ - "//components/safe_browsing/android:realtimeallowlist_proto", + "//components/safe_browsing/core/common/proto:realtimeallowlist_proto", "//third_party/protobuf:py_proto", ] @@ -42,40 +42,40 @@ rebase_path(python_path_safe_browsing, root_build_dir), ] } +} - # Generate the android real time allowlist binary proto. This is only run - # manually when pushing the file to GCS for the component-updater to pick up. - action("make_real_time_url_allowlist_protobuf_for_gcs") { - script = "gen_real_time_url_allowlist_proto.py" +# Generate the android real time allowlist binary proto. This is run by linux +# builder to push the file to GCS for the component-updater to pick up. +action("make_real_time_url_allowlist_protobuf_for_gcs") { + script = "gen_real_time_url_allowlist_proto.py" - input_filename = "real_time_url_allowlist.asciipb" - output_dir = "$target_gen_dir/allowlist" - output_basename = "real_time_url_checks_allowlist.pb" - python_path_safe_browsing = - "$proto_python_root/components/safe_browsing/android/proto" + input_filename = "real_time_url_allowlist.asciipb" + output_dir = "$target_gen_dir/allowlist" + output_basename = "real_time_url_checks_allowlist.pb" + python_path_safe_browsing = + "$proto_python_root/components/safe_browsing/core/common/proto" - inputs = [ input_filename ] + inputs = [ input_filename ] - deps = [ - "//components/safe_browsing/android:realtimeallowlist_proto", - "//third_party/protobuf:py_proto", - ] + deps = [ + "//components/safe_browsing/core/common/proto:realtimeallowlist_proto", + "//third_party/protobuf:py_proto", + ] - outputs = [ output_dir ] + outputs = [ output_dir ] - args = [ - "-w", - "-g", - "-i", - rebase_path(input_filename, root_build_dir), - "-d", - rebase_path(output_dir, root_build_dir), - "-o", - output_basename, - "-p", - rebase_path(proto_python_root, root_build_dir), - "-p", - rebase_path(python_path_safe_browsing, root_build_dir), - ] - } + args = [ + "-w", + "-g", + "-i", + rebase_path(input_filename, root_build_dir), + "-d", + rebase_path(output_dir, root_build_dir), + "-o", + output_basename, + "-p", + rebase_path(proto_python_root, root_build_dir), + "-p", + rebase_path(python_path_safe_browsing, root_build_dir), + ] }
diff --git a/components/safe_browsing/content/resources/real_time_url_checks_allowlist/gen_real_time_url_allowlist_proto.py b/components/safe_browsing/content/resources/real_time_url_checks_allowlist/gen_real_time_url_allowlist_proto.py index 92afff1f..a2a3dce 100755 --- a/components/safe_browsing/content/resources/real_time_url_checks_allowlist/gen_real_time_url_allowlist_proto.py +++ b/components/safe_browsing/content/resources/real_time_url_checks_allowlist/gen_real_time_url_allowlist_proto.py
@@ -55,8 +55,8 @@ outfile = os.path.join(opts.outdir, opts.outbasename) if opts.gcs: # File path should be - # ../allowlist/{vers}/android/real_time_url_checks_allowlist.pb - outfile = os.path.join(opts.outdir, str(pb.version_id), + # ../allowlist/android/real_time_url_checks_allowlist.pb + outfile = os.path.join(opts.outdir, 'android', opts.outbasename) MakeSubDirs(outfile) WritePbFile(pb, outfile)
diff --git a/components/safe_browsing/core/common/proto/BUILD.gn b/components/safe_browsing/core/common/proto/BUILD.gn index 9c49fc4..1575a93 100644 --- a/components/safe_browsing/core/common/proto/BUILD.gn +++ b/components/safe_browsing/core/common/proto/BUILD.gn
@@ -72,3 +72,8 @@ sources = [ "safebrowsingv5.proto" ] deps = [ ":safebrowsingv5_proto" ] } + +fuzzable_proto_library("realtimeallowlist_proto") { + proto_in_dir = "//" + sources = [ "realtimeallowlist.proto" ] +}
diff --git a/components/safe_browsing/android/proto/realtimeallowlist.proto b/components/safe_browsing/core/common/proto/realtimeallowlist.proto similarity index 100% rename from components/safe_browsing/android/proto/realtimeallowlist.proto rename to components/safe_browsing/core/common/proto/realtimeallowlist.proto
diff --git a/components/signin/public/base/signin_switches.cc b/components/signin/public/base/signin_switches.cc index ae029356..5e28987 100644 --- a/components/signin/public/base/signin_switches.cc +++ b/components/signin/public/base/signin_switches.cc
@@ -84,14 +84,6 @@ base::FEATURE_DISABLED_BY_DEFAULT); #endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS) -#if BUILDFLAG(ENABLE_DICE_SUPPORT) -// Move the step of browser Signin into the Sync header processing logic. -// This flag is meant to be used as a kill switch, as the feature starts enabled -// by default. -BASE_FEATURE(kBrowserSigninInSyncHeaderOnGaiaIntegration, - base::FEATURE_ENABLED_BY_DEFAULT); -#endif // BUILDFLAG(ENABLE_DICE_SUPPORT) - #if BUILDFLAG(IS_IOS) BASE_FEATURE(kCacheIdentityListInChrome, base::FEATURE_DISABLED_BY_DEFAULT); #endif @@ -274,6 +266,9 @@ BASE_FEATURE(kFullscreenSignInPromoUseDate, base::FEATURE_DISABLED_BY_DEFAULT); #endif +BASE_FEATURE(kHandleMdmErrorsForDasherAccounts, + base::FEATURE_DISABLED_BY_DEFAULT); + #if BUILDFLAG(IS_ANDROID) // Enables a history sync educational tip in the magic stack on NTP. BASE_FEATURE(kHistoryOptInEducationalTip, base::FEATURE_ENABLED_BY_DEFAULT); @@ -328,15 +323,6 @@ BASE_FEATURE(kProfilesReordering, base::FEATURE_DISABLED_BY_DEFAULT); #if BUILDFLAG(ENABLE_DICE_SUPPORT) -// Whether we re-try showing the signing in interception bubble if the Dice -// sync header does not arrive within a time window from the LST token. -// This flag is meant to be used as a kill switch, as the feature starts enabled -// by default. -BASE_FEATURE(kRetryInterceptionBubbleOnDiceSyncHeaderTimeout, - base::FEATURE_ENABLED_BY_DEFAULT); -#endif // BUILDFLAG(ENABLE_DICE_SUPPORT) - -#if BUILDFLAG(ENABLE_DICE_SUPPORT) BASE_FEATURE(kRollbackDiceMigration, base::FEATURE_DISABLED_BY_DEFAULT); #endif // BUILDFLAG(ENABLE_DICE_SUPPORT)
diff --git a/components/signin/public/base/signin_switches.h b/components/signin/public/base/signin_switches.h index f1e0880..f3974bd4 100644 --- a/components/signin/public/base/signin_switches.h +++ b/components/signin/public/base/signin_switches.h
@@ -67,11 +67,6 @@ BASE_DECLARE_FEATURE(kBoundSessionCredentialsKillSwitch); #endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS) -#if BUILDFLAG(ENABLE_DICE_SUPPORT) -COMPONENT_EXPORT(SIGNIN_SWITCHES) -BASE_DECLARE_FEATURE(kBrowserSigninInSyncHeaderOnGaiaIntegration); -#endif // BUILDFLAG(ENABLE_DICE_SUPPORT) - #if BUILDFLAG(IS_IOS) // Feature flag to enable caching identities in ios_internal. COMPONENT_EXPORT(SIGNIN_SWITCHES) @@ -223,6 +218,10 @@ BASE_DECLARE_FEATURE(kFullscreenSignInPromoUseDate); #endif +// Feature to handle mdm errors on Enterprise and EDU accounts +COMPONENT_EXPORT(SIGNIN_SWITCHES) +BASE_DECLARE_FEATURE(kHandleMdmErrorsForDasherAccounts); + #if BUILDFLAG(IS_ANDROID) COMPONENT_EXPORT(SIGNIN_SWITCHES) BASE_DECLARE_FEATURE(kHistoryOptInEducationalTip); @@ -272,11 +271,6 @@ BASE_DECLARE_FEATURE(kProfilesReordering); #if BUILDFLAG(ENABLE_DICE_SUPPORT) -COMPONENT_EXPORT(SIGNIN_SWITCHES) -BASE_DECLARE_FEATURE(kRetryInterceptionBubbleOnDiceSyncHeaderTimeout); -#endif // BUILDFLAG(ENABLE_DICE_SUPPORT) - -#if BUILDFLAG(ENABLE_DICE_SUPPORT) // When enabled, rolls back the DICe migration for implicitly signed-in users. // Overrides `kOfferMigrationToDiceUsers` and `kForcedDiceMigration`. COMPONENT_EXPORT(SIGNIN_SWITCHES)
diff --git a/components/supervised_user/core/browser/supervised_user_log_record.cc b/components/supervised_user/core/browser/supervised_user_log_record.cc index 221fe79e..757c5e2 100644 --- a/components/supervised_user/core/browser/supervised_user_log_record.cc +++ b/components/supervised_user/core/browser/supervised_user_log_record.cc
@@ -6,6 +6,8 @@ #include <optional> +#include "base/debug/crash_logging.h" +#include "base/debug/dump_without_crashing.h" #include "components/content_settings/core/browser/host_content_settings_map.h" #include "components/prefs/pref_service.h" #include "components/signin/public/identity_manager/identity_manager.h" @@ -41,8 +43,12 @@ SupervisedUserService* supervised_user_service) { if (supervised_user_service && supervised_user_service->IsSupervisedLocally()) { - // This type of supervision is signin-status independent (but only available - // to non-incognito profiles). +// This type of supervision is signin-status independent (but only available +// to non-incognito profiles). +#if !BUILDFLAG(IS_ANDROID) + // Verify if any non-Android platform reaches this code. + base::debug::DumpWithoutCrashing(); // http://crbug.com/442625941 +#endif // !BUILDFLAG(IS_ANDROID) return SupervisedUserLogRecord::Segment::kSupervisionEnabledLocally; }
diff --git a/components/sync/base/features.cc b/components/sync/base/features.cc index 5c48941..1d660ce 100644 --- a/components/sync/base/features.cc +++ b/components/sync/base/features.cc
@@ -25,9 +25,15 @@ BASE_FEATURE(kSyncContextualTask, base::FEATURE_DISABLED_BY_DEFAULT); +#if !BUILDFLAG(IS_CHROMEOS) +BASE_FEATURE(kUnoPhase2FollowUp, #if BUILDFLAG(IS_ANDROID) -BASE_FEATURE(kUnoPhase2FollowUp, base::FEATURE_ENABLED_BY_DEFAULT); -#endif // BUILDFLAG(IS_ANDROID) + base::FEATURE_ENABLED_BY_DEFAULT +#else + base::FEATURE_DISABLED_BY_DEFAULT +#endif +); +#endif // !BUILDFLAG(IS_CHROMEOS) BASE_FEATURE(kSyncAutofillWalletCredentialData, #if BUILDFLAG(IS_IOS)
diff --git a/components/sync/base/features.h b/components/sync/base/features.h index 30f1176..581a58a 100644 --- a/components/sync/base/features.h +++ b/components/sync/base/features.h
@@ -42,14 +42,18 @@ // Enables syncing of contextual tasks. BASE_DECLARE_FEATURE(kSyncContextualTask); -#if BUILDFLAG(IS_ANDROID) +#if !BUILDFLAG(IS_CHROMEOS) // Flag that controls Uno fast-follow features which are: +// On Android: // - Batch upload of left-behind bookmarks from the bookmark manager // - Turn on bookmarks and reading list when signing in from bookmark manager // - Confirmation dialog when turning off “Allow Chrome sign-in” // - Promo for signed-in users with bookmarks toggle off +// On desktop: +// Adding history sync opt-in entry points, and other follow-ups to +// `kReplaceSyncPromosWithSignInPromos`. BASE_DECLARE_FEATURE(kUnoPhase2FollowUp); -#endif // BUILDFLAG(IS_ANDROID) +#endif // !BUILDFLAG(IS_CHROMEOS) // Controls whether to enable syncing of Autofill Wallet Credential Data. BASE_DECLARE_FEATURE(kSyncAutofillWalletCredentialData);
diff --git a/components/sync/engine/data_type_worker.cc b/components/sync/engine/data_type_worker.cc index f24ee94..bb59fc7 100644 --- a/components/sync/engine/data_type_worker.cc +++ b/components/sync/engine/data_type_worker.cc
@@ -121,57 +121,67 @@ base::SequencedTaskRunner::GetCurrentDefault(); }; -void AdaptClientTagForFullUpdateData(DataType data_type, - syncer::EntityData* data) { +void MaybeAdaptClientTagIfMissing(DataType data_type, + syncer::EntityData& data) { + CHECK(!data.specifics.has_encrypted()); + if (!data.client_tag_hash.value().empty()) { + // Client tag hash is already set, nothing to do. + return; + } // Server does not send any client tags for wallet data entities or offer data // entities. This code manually asks the bridge to create the client tags for // each entity, so that we can use ClientTagBasedDataTypeProcessor for // AUTOFILL_WALLET_DATA or AUTOFILL_WALLET_OFFER. - if (data->legacy_parent_id == "0") { + if (data.legacy_parent_id == "0") { // Ignore the permanent root node as that one should have no client tag // hash. return; } - DCHECK(!data->specifics.has_encrypted()); - if (data_type == AUTOFILL_WALLET_DATA) { - CHECK(data->specifics.has_autofill_wallet()); - data->client_tag_hash = ClientTagHash::FromUnhashed( - AUTOFILL_WALLET_DATA, - autofill::GetUnhashedClientTagFromAutofillWalletSpecifics( - data->specifics.autofill_wallet())); - } else if (data_type == AUTOFILL_WALLET_OFFER) { - CHECK(data->specifics.has_autofill_offer()); - data->client_tag_hash = ClientTagHash::FromUnhashed( - AUTOFILL_WALLET_OFFER, - autofill::GetUnhashedClientTagFromAutofillOfferSpecifics( - data->specifics.autofill_offer())); - } else if (data_type == AUTOFILL_VALUABLE) { - CHECK(data->specifics.has_autofill_valuable()); - data->client_tag_hash = ClientTagHash::FromUnhashed( - AUTOFILL_VALUABLE, - autofill::GetUnhashedClientTagFromAutofillValuableSpecifics( - data->specifics.autofill_valuable())); - } else { - NOTREACHED(); + switch (data_type) { + case AUTOFILL_WALLET_DATA: + CHECK(data.specifics.has_autofill_wallet()); + data.client_tag_hash = ClientTagHash::FromUnhashed( + AUTOFILL_WALLET_DATA, + autofill::GetUnhashedClientTagFromAutofillWalletSpecifics( + data.specifics.autofill_wallet())); + break; + case AUTOFILL_WALLET_OFFER: + CHECK(data.specifics.has_autofill_offer()); + data.client_tag_hash = ClientTagHash::FromUnhashed( + AUTOFILL_WALLET_OFFER, + autofill::GetUnhashedClientTagFromAutofillOfferSpecifics( + data.specifics.autofill_offer())); + break; + case AUTOFILL_VALUABLE: + CHECK(data.specifics.has_autofill_valuable()); + data.client_tag_hash = ClientTagHash::FromUnhashed( + AUTOFILL_VALUABLE, + autofill::GetUnhashedClientTagFromAutofillValuableSpecifics( + data.specifics.autofill_valuable())); + break; + default: + // Other datatypes populate the client tag hash in the protocol and there + // is no need to infer it client-side. + break; } } -void AdaptWebAuthnClientTagHash(syncer::EntityData* data) { +void AdaptWebAuthnClientTagHash(syncer::EntityData& data) { // Google Play Services may create entities where the client_tag_hash doesn't // conform to the form expected by Chromium. These values are the hex-encoded, // 16-byte random `sync_id` value, and will therefore always be 32 bytes long. // Valid ClientTagHash values are Base64(SHA1(protobuf_prefix + client_tag)) // and therefore always 28 bytes. - const std::string& client_tag_hash = data->client_tag_hash.value(); + const std::string& client_tag_hash = data.client_tag_hash.value(); std::string sync_id; if (client_tag_hash.size() == 32 && base::HexStringToString(client_tag_hash, &sync_id) && // Deletions don't include the specifics, only the client_tag_hash. - (!data->specifics.has_webauthn_credential() || + (!data.specifics.has_webauthn_credential() || // Otherwise, check that the client_tag_hash really is the hex encoded // sync_id. - sync_id == data->specifics.webauthn_credential().sync_id())) { - data->client_tag_hash = + sync_id == data.specifics.webauthn_credential().sync_id())) { + data.client_tag_hash = ClientTagHash::FromUnhashed(DataType::WEBAUTHN_CREDENTIAL, sync_id); } } @@ -710,13 +720,10 @@ // because the logic requires access to tracked entities. Hence, it is // done by BookmarkDataTypeProcessor, with logic implemented in // components/sync_bookmarks/parent_guid_preprocessing.cc. - } else if (data_type == AUTOFILL_WALLET_DATA || - data_type == AUTOFILL_WALLET_OFFER || - data_type == AUTOFILL_VALUABLE) { - AdaptClientTagForFullUpdateData(data_type, &data); } else if (data_type == WEBAUTHN_CREDENTIAL) { - AdaptWebAuthnClientTagHash(&data); + AdaptWebAuthnClientTagHash(data); } + MaybeAdaptClientTagIfMissing(data_type, data); response_data->entity = std::move(data); return SUCCESS;
diff --git a/components/sync/engine/data_type_worker_unittest.cc b/components/sync/engine/data_type_worker_unittest.cc index 345e6c8..c6233528 100644 --- a/components/sync/engine/data_type_worker_unittest.cc +++ b/components/sync/engine/data_type_worker_unittest.cc
@@ -1892,6 +1892,30 @@ } TEST(DataTypeWorkerPopulateUpdateResponseDataTest, + WalletDataIncludingClientTagHash) { + const ClientTagHash kTestClientTagHash = + ClientTagHash::FromUnhashed(AUTOFILL_WALLET_DATA, "12345"); + + UpdateResponseData response_data; + + // Set up the entity with an arbitrary value for an arbitrary field in the + // specifics (so that it _has_ autofill wallet specifics). + sync_pb::SyncEntity entity; + entity.set_client_tag_hash(kTestClientTagHash.value()); + entity.mutable_specifics()->mutable_autofill_wallet()->set_type( + sync_pb::AutofillWalletSpecifics::POSTAL_ADDRESS); + + ASSERT_EQ( + DataTypeWorker::SUCCESS, + DataTypeWorker::PopulateUpdateResponseData( + FakeCryptographer(), AUTOFILL_WALLET_DATA, entity, &response_data)); + + // The client tag hash makes it through. + EXPECT_EQ(response_data.entity.client_tag_hash.value(), + kTestClientTagHash.value()); +} + +TEST(DataTypeWorkerPopulateUpdateResponseDataTest, OfferDataWithMissingClientTagHash) { UpdateResponseData response_data;
diff --git a/components/sync/model/client_tag_based_remote_update_handler.cc b/components/sync/model/client_tag_based_remote_update_handler.cc index a91982e8..e597c6e 100644 --- a/components/sync/model/client_tag_based_remote_update_handler.cc +++ b/components/sync/model/client_tag_based_remote_update_handler.cc
@@ -285,14 +285,14 @@ // Local tombstone vs remote update (non-deletion). Should be undeleted. resolution_type = ConflictResolution::kUseRemote; } else if (entity->MatchesOwnBaseData()) { - // If there is no real local change, then the entity must be unsynced due to - // a pending local re-encryption request. In this case, the remote data - // should win. - resolution_type = ConflictResolution::kIgnoreLocalEncryption; + // If there is no real local change, the remote data should win (e.g. when + // the entity is unsynced due to a pending local re-encryption request). + resolution_type = ConflictResolution::kIgnoreLocalNoOpUpdate; } else if (entity->MatchesBaseData(remote_data)) { // The remote data isn't actually changing from the last remote data that - // was seen, so it must have been a re-encryption and can be ignored. - resolution_type = ConflictResolution::kIgnoreRemoteEncryption; + // was seen, so it can be ignored (e.g. in case of a re-encryption, or some + // remote update which was reverted). + resolution_type = ConflictResolution::kIgnoreRemoteNoOpUpdate; } else { // There's a real data conflict here; let the bridge resolve it. resolution_type = @@ -320,13 +320,13 @@ } break; case ConflictResolution::kUseLocal: - case ConflictResolution::kIgnoreRemoteEncryption: + case ConflictResolution::kIgnoreRemoteNoOpUpdate: // Record that we received the update from the server but leave the // pending commit intact. entity->RecordIgnoredRemoteUpdate(update); break; case ConflictResolution::kUseRemote: - case ConflictResolution::kIgnoreLocalEncryption: + case ConflictResolution::kIgnoreLocalNoOpUpdate: // Update client data to match server. if (update.entity.is_deleted()) { DCHECK(!entity->metadata().is_deleted());
diff --git a/components/sync/model/client_tag_based_remote_update_handler_unittest.cc b/components/sync/model/client_tag_based_remote_update_handler_unittest.cc index 8cf58b4..190605d 100644 --- a/components/sync/model/client_tag_based_remote_update_handler_unittest.cc +++ b/components/sync/model/client_tag_based_remote_update_handler_unittest.cc
@@ -436,7 +436,7 @@ ProcessSingleUpdate(std::move(update)); histogram_tester.ExpectUniqueSample( "Sync.DataTypeEntityConflictResolution.PREFERENCE", - ConflictResolution::kIgnoreLocalEncryption, /*expected_bucket_count=*/1); + ConflictResolution::kIgnoreLocalNoOpUpdate, /*expected_bucket_count=*/1); EXPECT_EQ(2U, db()->data_change_count()); ASSERT_EQ(0U, bridge()->trimmed_specifics_change_count()); @@ -470,7 +470,7 @@ ProcessSingleUpdate(std::move(update)); histogram_tester.ExpectUniqueSample( "Sync.DataTypeEntityConflictResolution.PREFERENCE", - ConflictResolution::kIgnoreRemoteEncryption, /*expected_bucket_count=*/1); + ConflictResolution::kIgnoreRemoteNoOpUpdate, /*expected_bucket_count=*/1); EXPECT_EQ(1U, db()->data_change_count()); ASSERT_EQ(0U, bridge()->trimmed_specifics_change_count());
diff --git a/components/sync/model/conflict_resolution.h b/components/sync/model/conflict_resolution.h index b420e48..9889df7a 100644 --- a/components/sync/model/conflict_resolution.h +++ b/components/sync/model/conflict_resolution.h
@@ -19,12 +19,10 @@ kChangesMatch = 0, kUseLocal = 1, kUseRemote = 2, - // TODO(crbug.com/40668179): rename the following to kIgnoreRemoteNoOpUpdate, - // and similarly for the local encryption case. - kIgnoreLocalEncryption = 3, - kIgnoreRemoteEncryption = 4, + kIgnoreLocalNoOpUpdate = 3, + kIgnoreRemoteNoOpUpdate = 4, - kMaxValue = kIgnoreRemoteEncryption + kMaxValue = kIgnoreRemoteNoOpUpdate }; // LINT.ThenChange(/tools/metrics/histograms/metadata/sync/enums.xml:SyncConflictResolution)
diff --git a/components/url_formatter/spoof_checks/idn_spoof_checker.cc b/components/url_formatter/spoof_checks/idn_spoof_checker.cc index c587f4f..8f173ae 100644 --- a/components/url_formatter/spoof_checks/idn_spoof_checker.cc +++ b/components/url_formatter/spoof_checks/idn_spoof_checker.cc
@@ -19,6 +19,7 @@ #include "base/threading/thread_local_storage.h" #include "build/build_config.h" #include "components/url_formatter/spoof_checks/skeleton_generator.h" +#include "components/url_formatter/spoof_checks/top_domains/domains-trie.h" #include "net/base/lookup_string_in_fixed_set.h" #include "net/base/registry_controlled_domains/registry_controlled_domain.h" #include "net/extras/preload_data/decoder.h" @@ -150,14 +151,12 @@ base::CompareCase::INSENSITIVE_ASCII); } -#include "components/url_formatter/spoof_checks/top_domains/domains-trie-inc.cc" - // All the domains in the above file have 4 or fewer labels. const size_t kNumberOfLabelsToCheck = 4; IDNSpoofChecker::HuffmanTrieParams g_trie_params{ - kTopDomainsHuffmanTree, sizeof(kTopDomainsHuffmanTree), kTopDomainsTrie, - kTopDomainsTrieBits, kTopDomainsRootPosition}; + kTopDomainsHuffmanTree.data(), kTopDomainsHuffmanTree.size(), + kTopDomainsTrie.data(), kTopDomainsTrieBits, kTopDomainsRootPosition}; // Allow these common words that are whole script confusables. They aren't // confusable with any words in Latin scripts. @@ -834,8 +833,8 @@ // static void IDNSpoofChecker::RestoreTrieParamsForTesting() { g_trie_params = HuffmanTrieParams{ - kTopDomainsHuffmanTree, sizeof(kTopDomainsHuffmanTree), kTopDomainsTrie, - kTopDomainsTrieBits, kTopDomainsRootPosition}; + kTopDomainsHuffmanTree.data(), kTopDomainsHuffmanTree.size(), + kTopDomainsTrie.data(), kTopDomainsTrieBits, kTopDomainsRootPosition}; } } // namespace url_formatter
diff --git a/components/url_formatter/spoof_checks/top_domains/BUILD.gn b/components/url_formatter/spoof_checks/top_domains/BUILD.gn index b449843..d9d6fb3 100644 --- a/components/url_formatter/spoof_checks/top_domains/BUILD.gn +++ b/components/url_formatter/spoof_checks/top_domains/BUILD.gn
@@ -43,9 +43,9 @@ # Inputs in order expected by the command line of the tool. inputs = [ "//components/url_formatter/spoof_checks/top_domains/domains.skeletons", - "//components/url_formatter/spoof_checks/top_domains/top_domains_trie.template", + "//components/url_formatter/spoof_checks/top_domains/top_domains_trie_header.template", ] - outputs = [ "$target_gen_dir/domains-trie-inc.cc" ] + outputs = [ "$target_gen_dir/domains-trie.h" ] args = rebase_path(inputs, root_build_dir) + rebase_path(outputs, root_build_dir) }
diff --git a/components/url_formatter/spoof_checks/top_domains/top_domains_trie_header.template b/components/url_formatter/spoof_checks/top_domains/top_domains_trie_header.template new file mode 100644 index 0000000..fce36cc --- /dev/null +++ b/components/url_formatter/spoof_checks/top_domains/top_domains_trie_header.template
@@ -0,0 +1,29 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This header was generated by `top_domain_generator`. + +// kTopDomainsHuffmanTree describes a Huffman tree. The nodes of the tree are +// pairs of uint8s. The last node in the array is the root of the tree. Each pair +// is two uint8_t values, the first is "left" and the second is "right". If a +// uint8_t value has the MSB set then it represents a literal leaf value. +// Otherwise it's a pointer to the n'th element of the array. + +#include <array> +#include <cstdint> + +namespace url_formatter { + +inline constexpr auto kTopDomainsHuffmanTree = std::to_array<uint8_t>( + [[HUFFMAN_TREE]] +); + +inline constexpr auto kTopDomainsTrie = std::to_array<uint8_t>( + [[TOP_DOMAINS_TRIE]] +); + +inline constexpr unsigned kTopDomainsTrieBits = [[TOP_DOMAINS_TRIE_BITS]]; +inline constexpr unsigned kTopDomainsRootPosition = [[TOP_DOMAINS_TRIE_ROOT]]; + +} // namespace url_formatter
diff --git a/components/viz/service/display_embedder/software_output_device_win.cc b/components/viz/service/display_embedder/software_output_device_win.cc index 83803138..b5154321 100644 --- a/components/viz/service/display_embedder/software_output_device_win.cc +++ b/components/viz/service/display_embedder/software_output_device_win.cc
@@ -20,8 +20,8 @@ #include "skia/ext/platform_canvas.h" #include "skia/ext/skia_utils_win.h" #include "third_party/perfetto/include/perfetto/tracing/track.h" -#include "ui/gfx/gdi_util.h" #include "ui/gfx/geometry/skia_conversions.h" +#include "ui/gfx/win/gdi_util.h" #include "ui/gfx/win/hwnd_util.h" #include "ui/gl/vsync_provider_win.h"
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn index 9e8831c..ede10f7 100644 --- a/content/browser/BUILD.gn +++ b/content/browser/BUILD.gn
@@ -2227,8 +2227,7 @@ "service_worker/service_worker_version.h", "service_worker/url_loader_client_checker.cc", "service_worker/url_loader_client_checker.h", - "shape_detection/shape_detection_service_host.cc", - "shape_detection/shape_detection_service_host.h", + "shape_detection/shape_detection_service.cc", "shared_storage/shared_storage_budget_charger.cc", "shared_storage/shared_storage_budget_charger.h", "shared_storage/shared_storage_code_cache_host_proxy.cc",
diff --git a/content/browser/browser_interface_binders.cc b/content/browser/browser_interface_binders.cc index 2f2a2be4..ed80242 100644 --- a/content/browser/browser_interface_binders.cc +++ b/content/browser/browser_interface_binders.cc
@@ -64,7 +64,6 @@ #include "content/browser/renderer_host/render_process_host_impl.h" #include "content/browser/service_worker/service_worker_context_core.h" #include "content/browser/service_worker/service_worker_host.h" -#include "content/browser/shape_detection/shape_detection_service_host.h" #include "content/browser/shared_storage/shared_storage_worklet_host.h" #include "content/browser/speech/speech_recognition_dispatcher_host.h" #include "content/browser/storage_access/storage_access_handle.h" @@ -85,6 +84,7 @@ #include "content/public/browser/global_routing_id.h" #include "content/public/browser/service_worker_context.h" #include "content/public/browser/service_worker_version_base_info.h" +#include "content/public/browser/shape_detection_service.h" #include "content/public/browser/shared_worker_instance.h" #include "content/public/browser/site_isolation_policy.h" #include "content/public/browser/storage_partition.h"
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.cc b/content/browser/renderer_host/render_widget_host_view_aura.cc index 39bbcb9..5ff22d2 100644 --- a/content/browser/renderer_host/render_widget_host_view_aura.cc +++ b/content/browser/renderer_host/render_widget_host_view_aura.cc
@@ -119,7 +119,7 @@ #include "ui/base/ime/win/tsf_input_scope.h" #include "ui/base/win/hidden_window.h" #include "ui/display/win/screen_win.h" -#include "ui/gfx/gdi_util.h" +#include "ui/gfx/win/gdi_util.h" #endif // BUILDFLAG(IS_WIN) #if BUILDFLAG(IS_LINUX)
diff --git a/content/browser/shape_detection/shape_detection_service_host.cc b/content/browser/shape_detection/shape_detection_service.cc similarity index 95% rename from content/browser/shape_detection/shape_detection_service_host.cc rename to content/browser/shape_detection/shape_detection_service.cc index faa1c9d..013e51bc 100644 --- a/content/browser/shape_detection/shape_detection_service_host.cc +++ b/content/browser/shape_detection/shape_detection_service.cc
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "content/browser/shape_detection/shape_detection_service_host.h" +#include "content/public/browser/shape_detection_service.h" #include "base/no_destructor.h" #include "build/branding_buildflags.h"
diff --git a/content/browser/shape_detection/shape_detection_service_host.h b/content/browser/shape_detection/shape_detection_service_host.h deleted file mode 100644 index 59c4e608d..0000000 --- a/content/browser/shape_detection/shape_detection_service_host.h +++ /dev/null
@@ -1,18 +0,0 @@ -// Copyright 2025 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CONTENT_BROWSER_SHAPE_DETECTION_SHAPE_DETECTION_SERVICE_HOST_H_ -#define CONTENT_BROWSER_SHAPE_DETECTION_SHAPE_DETECTION_SERVICE_HOST_H_ - -#include "services/shape_detection/public/mojom/shape_detection_service.mojom-forward.h" - -namespace content { - -// Returns the browser's remote interface to the global ShapeDetectionService -// instance. -shape_detection::mojom::ShapeDetectionService* GetShapeDetectionService(); - -} // namespace content - -#endif // CONTENT_BROWSER_SHAPE_DETECTION_SHAPE_DETECTION_SERVICE_HOST_H_
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn index e5d07ca..1b96fdf 100644 --- a/content/public/browser/BUILD.gn +++ b/content/public/browser/BUILD.gn
@@ -417,6 +417,7 @@ "session_storage_namespace.cc", "session_storage_namespace.h", "session_storage_usage_info.h", + "shape_detection_service.h", "shared_cors_origin_access_list.h", "shared_worker_instance.cc", "shared_worker_instance.h", @@ -584,6 +585,7 @@ "//services/on_device_model/public/mojom", "//services/resource_coordinator/public/cpp:resource_coordinator_cpp", "//services/service_manager/public/cpp", + "//services/shape_detection/public/mojom", "//services/tracing/public/cpp", "//services/tracing/public/mojom", "//services/video_capture/public/mojom",
diff --git a/content/public/browser/DEPS b/content/public/browser/DEPS index d9c21ea..97366a4 100644 --- a/content/public/browser/DEPS +++ b/content/public/browser/DEPS
@@ -23,6 +23,7 @@ "+services/network/public/cpp", "+services/on_device_model/public", "+services/resource_coordinator/public", + "+services/shape_detection/public/mojom", "+services/tracing/public", "+services/video_capture/public/mojom", "+services/video_effects/public/cpp/buildflags.h",
diff --git a/content/public/browser/shape_detection_service.h b/content/public/browser/shape_detection_service.h new file mode 100644 index 0000000..388741e --- /dev/null +++ b/content/public/browser/shape_detection_service.h
@@ -0,0 +1,20 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_PUBLIC_BROWSER_SHAPE_DETECTION_SERVICE_H_ +#define CONTENT_PUBLIC_BROWSER_SHAPE_DETECTION_SERVICE_H_ + +#include "content/common/content_export.h" +#include "services/shape_detection/public/mojom/shape_detection_service.mojom.h" + +namespace content { + +// Returns the browser's remote interface to the global ShapeDetectionService +// instance, which is started lazily. +CONTENT_EXPORT shape_detection::mojom::ShapeDetectionService* +GetShapeDetectionService(); + +} // namespace content + +#endif // CONTENT_PUBLIC_BROWSER_SHAPE_DETECTION_SERVICE_H_
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h index fd84d87..683718b 100644 --- a/extensions/browser/extension_function_histogram_value.h +++ b/extensions/browser/extension_function_histogram_value.h
@@ -1988,7 +1988,7 @@ AUTOFILLPRIVATE_ADDORUPDATEENTITYINSTANCE = 1925, AUTOFILLPRIVATE_REMOVEENTITYINSTANCE = 1926, ACCESSIBILITY_PRIVATE_ENABLELIVECAPTION = 1927, - AUTOFILLPRIVATE_GETALLENTITYTYPES = 1928, + AUTOFILLPRIVATE_GETWRITABLEENTITYTYPES = 1928, AUTOFILLPRIVATE_GETALLATTRIBUTETYPESFORENTITYTYPENAME = 1929, AUTOFILLPRIVATE_GETENTITYINSTANCEBYGUID = 1930, AUTOFILLPRIVATE_GETPAYOVERTIMEISSUERLIST = 1931,
diff --git a/headless/test/data/structured_doc_article.html b/headless/test/data/structured_doc_article.html new file mode 100644 index 0000000..0fcc267 --- /dev/null +++ b/headless/test/data/structured_doc_article.html
@@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html lang="en"> +<body> +<article> +Content in html article. +</article> +</body> +</html>
diff --git a/headless/test/data/structured_doc_article_expected.txt b/headless/test/data/structured_doc_article_expected.txt new file mode 100644 index 0000000..0b119ac --- /dev/null +++ b/headless/test/data/structured_doc_article_expected.txt
@@ -0,0 +1,10 @@ +{ + "lang": "en", + "type": "Document", + "~children": [ { + "type": "Art", + "~children": [ { + "type": "NonStruct" + } ] + } ] +}
diff --git a/headless/test/data/structured_doc_blockquote.html b/headless/test/data/structured_doc_blockquote.html new file mode 100644 index 0000000..88e1383 --- /dev/null +++ b/headless/test/data/structured_doc_blockquote.html
@@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html lang="en"> +<body> +<blockquote> +Content in html blockquote. +</blockquote> +</body> +</html>
diff --git a/headless/test/data/structured_doc_blockquote_expected.txt b/headless/test/data/structured_doc_blockquote_expected.txt new file mode 100644 index 0000000..82aabbd --- /dev/null +++ b/headless/test/data/structured_doc_blockquote_expected.txt
@@ -0,0 +1,10 @@ +{ + "lang": "en", + "type": "Document", + "~children": [ { + "type": "BlockQuote", + "~children": [ { + "type": "NonStruct" + } ] + } ] +}
diff --git a/headless/test/data/structured_doc_code.html b/headless/test/data/structured_doc_code.html new file mode 100644 index 0000000..3b3521dd --- /dev/null +++ b/headless/test/data/structured_doc_code.html
@@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html lang="en"> +<body> +<code> +Content in html code. +</code> +</body> +</html>
diff --git a/headless/test/data/structured_doc_code_expected.txt b/headless/test/data/structured_doc_code_expected.txt new file mode 100644 index 0000000..aacea99 --- /dev/null +++ b/headless/test/data/structured_doc_code_expected.txt
@@ -0,0 +1,13 @@ +{ + "lang": "en", + "type": "Document", + "~children": [ { + "type": "Div", + "~children": [ { + "type": "Code", + "~children": [ { + "type": "NonStruct" + } ] + } ] + } ] +}
diff --git a/headless/test/headless_printtopdf_browsertest.cc b/headless/test/headless_printtopdf_browsertest.cc index 8bdfe9d..b1bd770e 100644 --- a/headless/test/headless_printtopdf_browsertest.cc +++ b/headless/test/headless_printtopdf_browsertest.cc
@@ -578,10 +578,22 @@ std::string url_; }; +IN_PROC_BROWSER_TEST_F(HeadlessTaggedPDFBrowserTest, Article) { + RunTaggedPDFTest("/structured_doc_article.html"); +} + IN_PROC_BROWSER_TEST_F(HeadlessTaggedPDFBrowserTest, Aside) { RunTaggedPDFTest("/structured_doc_aside.html"); } +IN_PROC_BROWSER_TEST_F(HeadlessTaggedPDFBrowserTest, Blockquote) { + RunTaggedPDFTest("/structured_doc_blockquote.html"); +} + +IN_PROC_BROWSER_TEST_F(HeadlessTaggedPDFBrowserTest, Code) { + RunTaggedPDFTest("/structured_doc_code.html"); +} + IN_PROC_BROWSER_TEST_F(HeadlessTaggedPDFBrowserTest, Doc) { RunTaggedPDFTest("/structured_doc.html"); }
diff --git a/infra/config/generated/cq-builders.md b/infra/config/generated/cq-builders.md index df0cc9b..6f07783 100644 --- a/infra/config/generated/cq-builders.md +++ b/infra/config/generated/cq-builders.md
@@ -861,7 +861,7 @@ as required builders. ### chrome -* [win-perf-trigger](https://ci.chromium.org/p/chrome/builders/try/win-perf-trigger) ([definition](https://source.corp.google.com/search?q=+file:/try/.*\.star$+""win-perf-trigger"")) +* [linux-perf-trigger](https://ci.chromium.org/p/chrome/builders/try/linux-perf-trigger) ([definition](https://source.corp.google.com/search?q=+file:/try/.*\.star$+""linux-perf-trigger"")) * Experiment percentage: 100.0 ### chromium @@ -895,7 +895,7 @@ * Experiment percentage: 10.0 * [linux-presubmit](https://ci.chromium.org/p/chromium/builders/try/linux-presubmit) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""linux-presubmit"")) - * Experiment percentage: 5.0 + * Experiment percentage: 100.0 * [linux-rel-test-selection](https://ci.chromium.org/p/chromium/builders/try/linux-rel-test-selection) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""linux-rel-test-selection"")) * Experiment percentage: 10.0
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg index 9aa24c8..f85b5c8d 100644 --- a/infra/config/generated/luci/commit-queue.cfg +++ b/infra/config/generated/luci/commit-queue.cfg
@@ -476,6 +476,27 @@ owner_whitelist_group: "project-chromium-robot-committers" } builders { + name: "chrome/try/linux-perf-trigger" + result_visibility: COMMENT_LEVEL_RESTRICTED + experiment_percentage: 100 + location_filters { + gerrit_host_regexp: ".*" + gerrit_project_regexp: ".*" + gerrit_ref_regexp: ".*" + path_regexp: "infra/config/.+" + exclude: true + } + location_filters { + gerrit_host_regexp: ".*" + gerrit_project_regexp: ".*" + gerrit_ref_regexp: ".*" + path_regexp: "docs/.+" + exclude: true + } + owner_whitelist_group: "googlers" + owner_whitelist_group: "project-chromium-robot-committers" + } + builders { name: "chrome/try/linux-pgo" includable_only: true result_visibility: COMMENT_LEVEL_RESTRICTED @@ -779,27 +800,6 @@ owner_whitelist_group: "project-chromium-robot-committers" } builders { - name: "chrome/try/win-perf-trigger" - result_visibility: COMMENT_LEVEL_RESTRICTED - experiment_percentage: 100 - location_filters { - gerrit_host_regexp: ".*" - gerrit_project_regexp: ".*" - gerrit_ref_regexp: ".*" - path_regexp: "infra/config/.+" - exclude: true - } - location_filters { - gerrit_host_regexp: ".*" - gerrit_project_regexp: ".*" - gerrit_ref_regexp: ".*" - path_regexp: "docs/.+" - exclude: true - } - owner_whitelist_group: "googlers" - owner_whitelist_group: "project-chromium-robot-committers" - } - builders { name: "chrome/try/win-rel-ready" includable_only: true result_visibility: COMMENT_LEVEL_RESTRICTED @@ -5617,7 +5617,7 @@ builders { name: "chromium/try/linux-presubmit" disable_reuse: true - experiment_percentage: 5 + experiment_percentage: 100 mode_allowlist: "DRY_RUN" mode_allowlist: "FULL_RUN" }
diff --git a/infra/config/subprojects/chrome/try.star b/infra/config/subprojects/chrome/try.star index 8d28ba5..5e9f6f7 100644 --- a/infra/config/subprojects/chrome/try.star +++ b/infra/config/subprojects/chrome/try.star
@@ -326,6 +326,36 @@ ) chrome_internal_verifier( + builder = "linux-perf-trigger", + # The current whitelist includes: + # Googlers: internal users are always welcome + # project-chromium-robot-committers: this list includes autoroll bots, + # rubber stamper for reverts, etc. + # We definitely want to have autoroll bots here because we have no + # Perf tests on those sub repos, and we want to catch the regressions + # during rollout. + # For stamper, we should add the footer (TBD) to allow ignoring the + # perf result. + owner_whitelist = ["googlers", "project-chromium-robot-committers"], + tryjob = try_.job( + # In the current setting, we will use static mapping to decide whether + # changing a file can has impact on a certain benchmark. Due to the + # limitation on resources, we will run Speedometer3 benchmark only. + # As a result, only those CLs changing a file(s) listed in the static + # map will trigger a perf tests. + # As a result, while we have the experiment_percentage as X%, the + # actual number of CLs which trigger a Pinpoint try job should be far + # less than X% based on the following facts: + # - all CLs will trigger this try job. + # - most of the jobs triggered will not have match in the static map + # and thus will exist in a couple of minutes. + # - so far we only have 166 files listed in the map, which is a tiny + # amount compared to the number of files in the chromium repo. + experiment_percentage = 100, + ), +) + +chrome_internal_verifier( branch_selector = branches.selector.LINUX_BRANCHES, builder = "linux-pgo", ) @@ -458,36 +488,6 @@ ) chrome_internal_verifier( - builder = "win-perf-trigger", - # The current whitelist includes: - # Googlers: internal users are always welcome - # project-chromium-robot-committers: this list includes autoroll bots, - # rubber stamper for reverts, etc. - # We definitely want to have autoroll bots here because we have no - # Perf tests on those sub repos, and we want to catch the regressions - # during rollout. - # For stamper, we should add the footer (TBD) to allow ignoring the - # perf result. - owner_whitelist = ["googlers", "project-chromium-robot-committers"], - tryjob = try_.job( - # In the current setting, we will use static mapping to decide whether - # changing a file can has impact on a certain benchmark. Due to the - # limitation on resources, we will run Speedometer3 benchmark only. - # As a result, only those CLs changing a file(s) listed in the static - # map will trigger a perf tests. - # As a result, while we have the experiment_percentage as X%, the - # actual number of CLs which trigger a Pinpoint try job should be far - # less than X% based on the following facts: - # - all CLs will trigger this try job. - # - most of the jobs triggered will not have match in the static map - # and thus will exist in a couple of minutes. - # - so far we only have 166 files listed in the map, which is a tiny - # amount compared to the number of files in the chromium repo. - experiment_percentage = 100, - ), -) - -chrome_internal_verifier( branch_selector = branches.selector.WINDOWS_BRANCHES, builder = "win-rel-ready", )
diff --git a/infra/config/subprojects/chromium/try/presubmit.star b/infra/config/subprojects/chromium/try/presubmit.star index a062ca2..661e4cd 100644 --- a/infra/config/subprojects/chromium/try/presubmit.star +++ b/infra/config/subprojects/chromium/try/presubmit.star
@@ -203,7 +203,7 @@ }, tryjob = try_.job( # TODO(crbug.com/41495881): Promote out of experimental. - experiment_percentage = 5, + experiment_percentage = 100, ), )
diff --git a/ios/chrome/app/profile/BUILD.gn b/ios/chrome/app/profile/BUILD.gn index 3511eeb5..e3360b1f 100644 --- a/ios/chrome/app/profile/BUILD.gn +++ b/ios/chrome/app/profile/BUILD.gn
@@ -89,6 +89,7 @@ "//ios/chrome/browser/translate/model", "//ios/chrome/browser/web_state_list/model:session_metrics", "//ios/chrome/browser/web_state_list/model/web_usage_enabler", + "//ios/chrome/browser/welcome_back/model:features", "//ios/components/cookie_util", "//ios/public/provider/chrome/browser/raccoon:raccoon_api", "//ios/web/public/thread", @@ -331,7 +332,6 @@ "//components/signin/public/identity_manager", "//ios/chrome/app/profile", "//ios/chrome/browser/first_run/ui_bundled:features", - "//ios/chrome/browser/first_run/ui_bundled/welcome_back/model", "//ios/chrome/browser/metrics/model", "//ios/chrome/browser/promos_manager/model", "//ios/chrome/browser/promos_manager/model:constants", @@ -339,6 +339,8 @@ "//ios/chrome/browser/shared/model/profile", "//ios/chrome/browser/shared/public/features:system_flags", "//ios/chrome/browser/signin/model", + "//ios/chrome/browser/welcome_back/model", + "//ios/chrome/browser/welcome_back/model:features", ] frameworks = [ "UIKit.framework" ] } @@ -401,6 +403,7 @@ "//ios/chrome/browser/signin/model:fake_system_identity_manager", "//ios/chrome/browser/signin/model:signin_util", "//ios/chrome/browser/signin/model:test_support", + "//ios/chrome/browser/welcome_back/model:features", "//ios/chrome/test:test_support", "//ios/web/public/security", "//ios/web/public/session",
diff --git a/ios/chrome/app/profile/first_run_profile_agent_unittest.mm b/ios/chrome/app/profile/first_run_profile_agent_unittest.mm index 5dba459..4c984f6 100644 --- a/ios/chrome/app/profile/first_run_profile_agent_unittest.mm +++ b/ios/chrome/app/profile/first_run_profile_agent_unittest.mm
@@ -14,6 +14,7 @@ #import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h" #import "ios/chrome/browser/shared/public/commands/guided_tour_commands.h" #import "ios/chrome/browser/shared/public/features/features.h" +#import "ios/chrome/browser/welcome_back/model/features.h" #import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h" #import "ios/web/public/test/web_task_environment.h" #import "testing/platform_test.h" @@ -47,7 +48,7 @@ // promo. TEST_F(FirstRunProfileAgentTest, GuidedTourPromoMetrics) { enabled_feature_list_.InitAndEnableFeatureWithParameters( - kBestOfAppFRE, {{first_run::kWelcomeBackInFirstRunParam, "4"}}); + kBestOfAppFRE, {{kWelcomeBackInFirstRunParam, "4"}}); base::HistogramTester tester; [profile_agent_ dismissGuidedTourPromo]; tester.ExpectTotalCount("IOS.GuidedTour.Promo.DidAccept", 1); @@ -57,7 +58,7 @@ // finishes. TEST_F(FirstRunProfileAgentTest, GuidedTourStepMetrics) { enabled_feature_list_.InitAndEnableFeatureWithParameters( - kBestOfAppFRE, {{first_run::kWelcomeBackInFirstRunParam, "4"}}); + kBestOfAppFRE, {{kWelcomeBackInFirstRunParam, "4"}}); base::HistogramTester tester; [profile_agent_ nextTappedForStep:GuidedTourStep::kTabGridIncognito]; tester.ExpectBucketCount("IOS.GuidedTour.DidFinishStep", 1, 1);
diff --git a/ios/chrome/app/profile/profile_controller.mm b/ios/chrome/app/profile/profile_controller.mm index 18452ef..8c51e55 100644 --- a/ios/chrome/app/profile/profile_controller.mm +++ b/ios/chrome/app/profile/profile_controller.mm
@@ -93,6 +93,7 @@ #import "ios/chrome/browser/translate/model/chrome_ios_translate_client.h" #import "ios/chrome/browser/web_state_list/model/session_metrics.h" #import "ios/chrome/browser/web_state_list/model/web_usage_enabler/web_usage_enabler_browser_agent.h" +#import "ios/chrome/browser/welcome_back/model/features.h" #import "ios/components/cookie_util/cookie_util.h" #import "ios/public/provider/chrome/browser/raccoon/raccoon_api.h" #import "ios/web/public/thread/web_task_traits.h" @@ -683,7 +684,7 @@ } } - if (first_run::IsWelcomeBackInFirstRunEnabled()) { + if (IsWelcomeBackInFirstRunEnabled()) { [_state addAgent:[[WelcomeBackScreenProfileAgent alloc] init]]; } }
diff --git a/ios/chrome/app/profile/welcome_back_screen_profile_agent.mm b/ios/chrome/app/profile/welcome_back_screen_profile_agent.mm index 81e42f4..84237f3 100644 --- a/ios/chrome/app/profile/welcome_back_screen_profile_agent.mm +++ b/ios/chrome/app/profile/welcome_back_screen_profile_agent.mm
@@ -13,7 +13,6 @@ #import "ios/chrome/app/profile/profile_init_stage.h" #import "ios/chrome/app/profile/profile_state.h" #import "ios/chrome/browser/first_run/ui_bundled/features.h" -#import "ios/chrome/browser/first_run/ui_bundled/welcome_back/model/welcome_back_prefs.h" #import "ios/chrome/browser/metrics/model/ios_profile_session_durations_service.h" #import "ios/chrome/browser/metrics/model/ios_profile_session_durations_service_factory.h" #import "ios/chrome/browser/promos_manager/model/constants.h" @@ -23,6 +22,8 @@ #import "ios/chrome/browser/shared/model/profile/profile_manager_ios.h" #import "ios/chrome/browser/shared/public/features/system_flags.h" #import "ios/chrome/browser/signin/model/identity_manager_factory.h" +#import "ios/chrome/browser/welcome_back/model/features.h" +#import "ios/chrome/browser/welcome_back/model/welcome_back_prefs.h" @implementation WelcomeBackScreenProfileAgent @@ -37,16 +38,15 @@ DCHECK(profileState.profile); - switch (first_run::GetWelcomeBackScreenVariationType()) { - case first_run::WelcomeBackScreenVariationType::kDisabled: + switch (GetWelcomeBackScreenVariationType()) { + case WelcomeBackScreenVariationType::kDisabled: break; - case first_run::WelcomeBackScreenVariationType:: - kBasicsWithLockedIncognitoTabs: - case first_run::WelcomeBackScreenVariationType::kBasicsWithPasswords: - case first_run::WelcomeBackScreenVariationType::kProductivityAndShopping: + case WelcomeBackScreenVariationType::kBasicsWithLockedIncognitoTabs: + case WelcomeBackScreenVariationType::kBasicsWithPasswords: + case WelcomeBackScreenVariationType::kProductivityAndShopping: [self maybeRegisterPromo]; break; - case first_run::WelcomeBackScreenVariationType::kSignInBenefits: + case WelcomeBackScreenVariationType::kSignInBenefits: signin::IdentityManager* identityManager = IdentityManagerFactory::GetForProfile(profileState.profile); if (identityManager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) { @@ -74,8 +74,7 @@ // `kWelcomeBackInFirstRun` is enabled, `kBestFeaturesScreenInFirstRun` is // disabled, and there are at least two features eligible for display. size_t number_of_items = GetWelcomeBackEligibleItems().size(); - if (timeSinceActive > base::Days(28) && - first_run::IsWelcomeBackInFirstRunEnabled() && + if (timeSinceActive > base::Days(28) && IsWelcomeBackInFirstRunEnabled() && !base::FeatureList::IsEnabled(first_run::kBestFeaturesScreenInFirstRun) && number_of_items >= 2) { PromosManagerFactory::GetForProfile(self.profileState.profile)
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd index 8c053259..73c6465 100644 --- a/ios/chrome/app/strings/ios_strings.grd +++ b/ios/chrome/app/strings/ios_strings.grd
@@ -2598,6 +2598,15 @@ <message name="IDS_IOS_FEED_SWIPE_IPH" desc="Text for the in-product help bubble highlighting scrolling on the Feed."> You can swipe up to see more content tailored to you. </message> + <message name="IDS_IOS_FILE_UPLOAD_PANEL_CHOOSE_FILE_ACTION_LABEL" desc="Title for a single file chooser button used in HTML forms. [iOS only]"> + Choose File + </message> + <message name="IDS_IOS_FILE_UPLOAD_PANEL_PHOTO_LIBRARY_ACTION_LABEL" desc="Title for a button for choosing an existing media item from the Photo Library, used in HTML forms. [iOS only]"> + Photo Library + </message> + <message name="IDS_IOS_FILE_UPLOAD_PANEL_TAKE_PHOTO_OR_VIDEO_ACTION_LABEL" desc="Title for a button for taking a photo or video, used in HTML forms. [iOS only]"> + Take Photo or Video + </message> <message name="IDS_IOS_FIRSTRUN_TERMS_TITLE" desc="Title for the Terms of Service page shown to user on First Run. [Length: 20em] [iOS only]"> Terms of service </message> @@ -4047,6 +4056,9 @@ <message name="IDS_IOS_OMNIBOX_REMOVE_THUMBNAIL_LABEL" desc="The accessibility label for the button that will remove the image from the search box. [iOS only]."> Thumbnail </message> + <message name="IDS_IOS_OPENS_IN_NEW_TAB" desc="ARIA (accessibility) label describing a link which opens in a new tab."> + Opens in new tab + </message> <message name="IDS_IOS_OPEN_ANOTHER_APP_ALLOW" desc="Text on the button, when tapped will allow opening an external application. [iOS only]"> Allow </message>
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_FILE_UPLOAD_PANEL_CHOOSE_FILE_ACTION_LABEL.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_FILE_UPLOAD_PANEL_CHOOSE_FILE_ACTION_LABEL.png.sha1 new file mode 100644 index 0000000..1f90138 --- /dev/null +++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_FILE_UPLOAD_PANEL_CHOOSE_FILE_ACTION_LABEL.png.sha1
@@ -0,0 +1 @@ +3ccd0f386c9775817d2098eae1b05eeb891640aa \ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_FILE_UPLOAD_PANEL_PHOTO_LIBRARY_ACTION_LABEL.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_FILE_UPLOAD_PANEL_PHOTO_LIBRARY_ACTION_LABEL.png.sha1 new file mode 100644 index 0000000..1f90138 --- /dev/null +++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_FILE_UPLOAD_PANEL_PHOTO_LIBRARY_ACTION_LABEL.png.sha1
@@ -0,0 +1 @@ +3ccd0f386c9775817d2098eae1b05eeb891640aa \ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_FILE_UPLOAD_PANEL_TAKE_PHOTO_OR_VIDEO_ACTION_LABEL.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_FILE_UPLOAD_PANEL_TAKE_PHOTO_OR_VIDEO_ACTION_LABEL.png.sha1 new file mode 100644 index 0000000..1f90138 --- /dev/null +++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_FILE_UPLOAD_PANEL_TAKE_PHOTO_OR_VIDEO_ACTION_LABEL.png.sha1
@@ -0,0 +1 @@ +3ccd0f386c9775817d2098eae1b05eeb891640aa \ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_OPENS_IN_NEW_TAB.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_OPENS_IN_NEW_TAB.png.sha1 new file mode 100644 index 0000000..40752ee --- /dev/null +++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_OPENS_IN_NEW_TAB.png.sha1
@@ -0,0 +1 @@ +014a9c8b1f7fd50fed951e0b9ad2d22856e0faa9 \ No newline at end of file
diff --git a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.mm b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.mm index af7fc48..ba4e8063 100644 --- a/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.mm +++ b/ios/chrome/browser/authentication/ui_bundled/authentication_flow/authentication_flow.mm
@@ -119,7 +119,7 @@ // * there is already a profile that has been fully initialized for gaia_id, or // * a policy forces the browsing data to stay separated. bool ShouldSkipBrowsingDataMigration(signin_metrics::AccessPoint access_point, - NSString* gaia_id, + GaiaId gaia_id, PrefService* pref_service) { bool always_separate_browsing_data_per_policy = pref_service->GetInteger( @@ -136,7 +136,7 @@ // disabled by policy and not because of another reason. bool IsBrowsingDataMigrationDisabledByPolicy( signin_metrics::AccessPoint access_point, - NSString* gaia_id, + GaiaId gaia_id, PrefService* pref_service, signin::IdentityManager* identity_manager, policy::ProfileSeparationDataMigrationSettings @@ -157,15 +157,15 @@ // Returns if `identity` is available by AccountProfileMapper and if it is // available by IdentityManager. IOSIdentityAvailableInProfile IdentityAvailableInProfileStatus( - NSString* gaia_id, + GaiaId gaia_id, signin::IdentityManager* identity_manager, std::string_view profile_name) { bool is_identity_available_in_profile_mapper = false; AccountProfileMapper::IdentityIteratorCallback callback = base::BindRepeating( - [](BOOL* isIdentityAvailableInProfileMapper, - NSString* signinIdentityGaiaID, id<SystemIdentity> identity) { + [](BOOL* isIdentityAvailableInProfileMapper, GaiaId signinIdentityGaiaID, + id<SystemIdentity> identity) { *isIdentityAvailableInProfileMapper = - [identity.gaiaID isEqualToString:signinIdentityGaiaID]; + identity.gaiaId == signinIdentityGaiaID; return *isIdentityAvailableInProfileMapper ? AccountProfileMapper::IteratorResult::kInterruptIteration : AccountProfileMapper::IteratorResult::kContinueIteration; @@ -196,7 +196,7 @@ // Records `Signin.IOSIdentityAvailableInProfile` histogram. void RecordIOSIdentityAvailableInProfile( - NSString* gaia_id, + GaiaId gaia_id, signin::IdentityManager* identity_manager, std::string_view profile_name) { IOSIdentityAvailableInProfile identity_available = @@ -586,7 +586,7 @@ // Fetches ManagedAccountsSigninRestriction policy, if needed. - (void)fetchProfileSeparationPoliciesIfNeededStep { if (!ShouldShowManagedConfirmationForHostedDomain( - _identityToSignInHostedDomain, _accessPoint, _identityToSignIn.gaiaID, + _identityToSignInHostedDomain, _accessPoint, _identityToSignIn.gaiaId, [self prefs])) { // The managed confirmation dialog can be skipped, therefore, there is no // need to fetch the policy. @@ -594,7 +594,7 @@ return; } if (!AreSeparateProfilesForManagedAccountsEnabled() || - ShouldSkipBrowsingDataMigration(_accessPoint, _identityToSignIn.gaiaID, + ShouldSkipBrowsingDataMigration(_accessPoint, _identityToSignIn.gaiaId, [self prefs])) { // The profile-separation policy affects whether browsing-data-migration // is offered, so it's only needed if the migration isn't skipped. @@ -610,7 +610,7 @@ // Shows a confirmation dialog for signing in to an account managed. - (void)showManagedConfirmationIfNeededStep { if (!ShouldShowManagedConfirmationForHostedDomain( - _identityToSignInHostedDomain, _accessPoint, _identityToSignIn.gaiaID, + _identityToSignInHostedDomain, _accessPoint, _identityToSignIn.gaiaId, [self prefs])) { [self continueFlow]; return; @@ -627,7 +627,7 @@ PrefService* prefService = [self prefs]; skipBrowsingDataMigration = _profileSeparationDataMigrationSettings == policy::ALWAYS_SEPARATE || - ShouldSkipBrowsingDataMigration(_accessPoint, _identityToSignIn.gaiaID, + ShouldSkipBrowsingDataMigration(_accessPoint, _identityToSignIn.gaiaId, prefService); signin::IdentityManager* identityManager = @@ -635,7 +635,7 @@ browsingDataMigrationDisabledByPolicy = IsBrowsingDataMigrationDisabledByPolicy( - _accessPoint, _identityToSignIn.gaiaID, prefService, + _accessPoint, _identityToSignIn.gaiaId, prefService, identityManager, _profileSeparationDataMigrationSettings); // Merge browsing data by default if the data migration screen is shown to @@ -675,7 +675,7 @@ ProfileIOS* profile = [self profile]; signin::IdentityManager* identityManager = IdentityManagerFactory::GetForProfile(profile); - RecordIOSIdentityAvailableInProfile(_identityToSignIn.gaiaID, identityManager, + RecordIOSIdentityAvailableInProfile(_identityToSignIn.gaiaId, identityManager, profile->GetProfileName()); std::vector<AccountInfo> accountsOnDevice = identityManager->GetAccountsOnDevice();
diff --git a/ios/chrome/browser/authentication/ui_bundled/authentication_ui_util.h b/ios/chrome/browser/authentication/ui_bundled/authentication_ui_util.h index 175cdc00..5787e53 100644 --- a/ios/chrome/browser/authentication/ui_bundled/authentication_ui_util.h +++ b/ios/chrome/browser/authentication/ui_bundled/authentication_ui_util.h
@@ -16,6 +16,7 @@ @class AlertCoordinator; class AuthenticationService; class Browser; +class GaiaId; class PrefService; class ProfileIOS; @@ -108,7 +109,7 @@ BOOL ShouldShowManagedConfirmationForHostedDomain( NSString* hosted_domain, signin_metrics::AccessPoint access_point, - NSString* gaia_ID, + const GaiaId& gaia_ID, PrefService* prefs); // Returns the current sign-in&sync state.
diff --git a/ios/chrome/browser/authentication/ui_bundled/authentication_ui_util.mm b/ios/chrome/browser/authentication/ui_bundled/authentication_ui_util.mm index 4667249..9525f84b 100644 --- a/ios/chrome/browser/authentication/ui_bundled/authentication_ui_util.mm +++ b/ios/chrome/browser/authentication/ui_bundled/authentication_ui_util.mm
@@ -15,6 +15,7 @@ #import "components/signin/public/identity_manager/identity_manager.h" #import "components/strings/grit/components_strings.h" #import "components/sync/base/account_pref_utils.h" +#import "google_apis/gaia/gaia_id.h" #import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_feature.h" #import "ios/chrome/browser/policy/model/browser_policy_connector_ios.h" #import "ios/chrome/browser/shared/coordinator/alert/action_sheet_coordinator.h" @@ -241,7 +242,7 @@ BOOL ShouldShowManagedConfirmationForHostedDomain( NSString* hosted_domain, signin_metrics::AccessPoint access_point, - NSString* gaia_id, + const GaiaId& gaia_id, PrefService* prefs) { if ([hosted_domain length] == 0) { // No hosted domain, don't show the dialog as there is no host.
diff --git a/ios/chrome/browser/browser_view/ui_bundled/BUILD.gn b/ios/chrome/browser/browser_view/ui_bundled/BUILD.gn index 4d401c8..30da789 100644 --- a/ios/chrome/browser/browser_view/ui_bundled/BUILD.gn +++ b/ios/chrome/browser/browser_view/ui_bundled/BUILD.gn
@@ -120,7 +120,6 @@ "//ios/chrome/browser/first_run/ui_bundled", "//ios/chrome/browser/first_run/ui_bundled:utils", "//ios/chrome/browser/first_run/ui_bundled/omnibox_position", - "//ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator", "//ios/chrome/browser/follow/model:browser_agent", "//ios/chrome/browser/follow/model:service", "//ios/chrome/browser/follow/model:tab_helper", @@ -299,6 +298,7 @@ "//ios/chrome/browser/webauthn/coordinator", "//ios/chrome/browser/webui/model", "//ios/chrome/browser/webui/ui_bundled:coordinator", + "//ios/chrome/browser/welcome_back/coordinator", "//ios/chrome/browser/whats_new/coordinator", "//ios/chrome/common", "//ios/chrome/common/ui/colors",
diff --git a/ios/chrome/browser/browser_view/ui_bundled/DEPS b/ios/chrome/browser/browser_view/ui_bundled/DEPS index f4daf87..c5fdfd4 100644 --- a/ios/chrome/browser/browser_view/ui_bundled/DEPS +++ b/ios/chrome/browser/browser_view/ui_bundled/DEPS
@@ -135,4 +135,5 @@ "+ios/chrome/browser/webui", "+ios/chrome/browser/whats_new/coordinator", "+ios/chrome/browser/window_activities/model/window_activity_helpers.h", + "+ios/chrome/browser/welcome_back/coordinator", ]
diff --git a/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm b/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm index 1dddf33e..b23f1502 100644 --- a/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm +++ b/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm
@@ -116,7 +116,6 @@ #import "ios/chrome/browser/file_upload_panel/coordinator/file_upload_panel_coordinator.h" #import "ios/chrome/browser/find_in_page/model/find_tab_helper.h" #import "ios/chrome/browser/first_run/ui_bundled/omnibox_position/omnibox_position_choice_coordinator.h" -#import "ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_coordinator.h" #import "ios/chrome/browser/follow/model/follow_browser_agent.h" #import "ios/chrome/browser/follow/model/followed_web_site.h" #import "ios/chrome/browser/fullscreen/ui_bundled/fullscreen_controller.h" @@ -333,6 +332,7 @@ #import "ios/chrome/browser/webauthn/coordinator/credential_import_coordinator.h" #import "ios/chrome/browser/webui/model/net_export_tab_helper_delegate.h" #import "ios/chrome/browser/webui/ui_bundled/net_export_coordinator.h" +#import "ios/chrome/browser/welcome_back/coordinator/welcome_back_coordinator.h" #import "ios/chrome/browser/whats_new/coordinator/whats_new_coordinator.h" #import "ios/chrome/common/ui/util/ui_util.h" #import "ios/chrome/grit/ios_branded_strings.h" @@ -684,6 +684,7 @@ std::unique_ptr<WebUsageEnablerBrowserAgentObserverBridge> _webUsageEnablerObserver; ContextualSheetCoordinator* _contextualSheetCoordinator; + API_AVAILABLE(ios(18.4)) FileUploadPanelCoordinator* _fileUploadPanelCoordinator; RootDriveFilePickerCoordinator* _driveFilePickerCoordinator; GoogleOneCoordinator* _googleOneCoordinator; @@ -1147,16 +1148,16 @@ // Shuts down the BrowserViewController. - (void)destroyViewController { - self.viewController.active = NO; - self.viewController.webUsageEnabled = NO; - self.viewController.browserViewVisibilityAudience = nil; + _viewController.active = NO; + _viewController.webUsageEnabled = NO; + _viewController.browserViewVisibilityAudience = nil; [self.contextMenuProvider stop]; self.contextMenuProvider = nil; // TODO(crbug.com/40256480): Remove when BVC will no longer handle commands. - [self.dispatcher stopDispatchingToTarget:self.viewController]; - [self.viewController shutdown]; + [self.dispatcher stopDispatchingToTarget:_viewController]; + [_viewController shutdown]; _viewController = nil; } @@ -1408,24 +1409,7 @@ // Destroys the browser view controller dependencies. - (void)destroyViewControllerDependencies { - _viewControllerDependencies.toolbarAccessoryPresenter = nil; - _viewControllerDependencies.popupMenuCoordinator = nil; - _viewControllerDependencies.ntpCoordinator = nil; - _viewControllerDependencies.toolbarCoordinator = nil; - _viewControllerDependencies.tabStripCoordinator = nil; - _viewControllerDependencies.sideSwipeCoordinator = nil; - _viewControllerDependencies.bookmarksCoordinator = nil; - _viewControllerDependencies.fullscreenController = nil; - _viewControllerDependencies.textZoomHandler = nil; - _viewControllerDependencies.helpHandler = nil; - _viewControllerDependencies.popupMenuCommandsHandler = nil; - _viewControllerDependencies.applicationCommandsHandler = nil; - _viewControllerDependencies.findInPageCommandsHandler = nil; - _viewControllerDependencies.urlLoadingBrowserAgent = nil; - _viewControllerDependencies.tabUsageRecorderBrowserAgent = nil; - _viewControllerDependencies.layoutGuideCenter = nil; - _viewControllerDependencies.voiceSearchController = nil; - _viewControllerDependencies.safeAreaProvider = nil; + _viewControllerDependencies = BrowserViewControllerDependencies{}; [_voiceSearchController dismissMicPermissionHelp]; [_voiceSearchController disconnect]; @@ -2906,7 +2890,7 @@ return; } _fileUploadPanelCoordinator = [[FileUploadPanelCoordinator alloc] - initWithBaseViewController:self.baseViewController + initWithBaseViewController:self.viewController browser:self.browser]; [_fileUploadPanelCoordinator start]; }
diff --git a/ios/chrome/browser/browser_view/ui_bundled/browser_view_controller.h b/ios/chrome/browser/browser_view/ui_bundled/browser_view_controller.h index e6944f3..1ea4a9b 100644 --- a/ios/chrome/browser/browser_view/ui_bundled/browser_view_controller.h +++ b/ios/chrome/browser/browser_view/ui_bundled/browser_view_controller.h
@@ -32,7 +32,6 @@ @class KeyCommandsProvider; @class NewTabPageCoordinator; @protocol OmniboxCommands; -class PagePlaceholderBrowserAgent; @protocol PopupMenuCommands; @class PopupMenuCoordinator; @class SafeAreaProvider; @@ -64,7 +63,6 @@ id<FindInPageCommands> findInPageCommandsHandler; LayoutGuideCenter* layoutGuideCenter; BOOL isOffTheRecord; - raw_ptr<PagePlaceholderBrowserAgent> pagePlaceholderBrowserAgent; raw_ptr<UrlLoadingBrowserAgent> urlLoadingBrowserAgent; id<VoiceSearchController> voiceSearchController; raw_ptr<TabUsageRecorderBrowserAgent> tabUsageRecorderBrowserAgent;
diff --git a/ios/chrome/browser/browser_view/ui_bundled/browser_view_controller.mm b/ios/chrome/browser/browser_view/ui_bundled/browser_view_controller.mm index e3e545d8..0a475141 100644 --- a/ios/chrome/browser/browser_view/ui_bundled/browser_view_controller.mm +++ b/ios/chrome/browser/browser_view/ui_bundled/browser_view_controller.mm
@@ -230,11 +230,10 @@ UIView* _topBackgroundView; // The service used to load url parameters in current or new tab. - raw_ptr<UrlLoadingBrowserAgent, DanglingUntriaged> _urlLoadingBrowserAgent; + raw_ptr<UrlLoadingBrowserAgent> _urlLoadingBrowserAgent; // Used to report usage of a single Browser's tab. - raw_ptr<TabUsageRecorderBrowserAgent, DanglingUntriaged> - _tabUsageRecorderBrowserAgent; + raw_ptr<TabUsageRecorderBrowserAgent> _tabUsageRecorderBrowserAgent; // Used to get the layout guide center. LayoutGuideCenter* _layoutGuideCenter; @@ -807,8 +806,8 @@ _isShutdown = YES; // Disconnect child coordinators. - [self.tabStripCoordinator stop]; - self.tabStripCoordinator = nil; + [self.tabStripCoordinator stop]; + self.tabStripCoordinator = nil; self.tabStripView = nil; [self.contentArea removeGestureRecognizer:self.contentAreaGestureRecognizer]; @@ -819,6 +818,10 @@ [_voiceSearchController disconnect]; [[NSNotificationCenter defaultCenter] removeObserver:self]; _bookmarksCoordinator = nil; + + // Clears the pointer to C++ objects. + _urlLoadingBrowserAgent = nullptr; + _tabUsageRecorderBrowserAgent = nullptr; } #pragma mark - UIAccessibilityAction
diff --git a/ios/chrome/browser/bubble/ui_bundled/bubble_util.h b/ios/chrome/browser/bubble/ui_bundled/bubble_util.h index 041e967e..04f031c 100644 --- a/ios/chrome/browser/bubble/ui_bundled/bubble_util.h +++ b/ios/chrome/browser/bubble/ui_bundled/bubble_util.h
@@ -10,7 +10,6 @@ typedef NS_ENUM(NSInteger, BubbleAlignment); typedef NS_ENUM(NSInteger, BubbleArrowDirection); -// TODO(crbug.com/40277360): Rename parameters. namespace bubble_util { // The default fixed distance from the leading edge of the bubble to the anchor
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/BUILD.gn b/ios/chrome/browser/content_suggestions/ui_bundled/BUILD.gn index 8d40642a..7d6c373 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/BUILD.gn +++ b/ios/chrome/browser/content_suggestions/ui_bundled/BUILD.gn
@@ -26,6 +26,7 @@ "//components/feed/core/v2/public/ios:feed_ios_public", "//components/image_fetcher/core", "//components/ntp_tiles", + "//components/ntp_tiles:pref_names", "//components/password_manager/core/browser/ui", "//components/password_manager/core/browser/ui:credential_ui_entry", "//components/pref_registry", @@ -509,6 +510,7 @@ ":ui", ":ui_util", "//base/test:test_support", + "//components/ntp_tiles:pref_names", "//components/search_engines", "//ios/chrome/browser/content_suggestions/ui_bundled/set_up_list", "//ios/chrome/browser/content_suggestions/ui_bundled/set_up_list:constants", @@ -518,7 +520,6 @@ "//ios/chrome/browser/ntp/ui_bundled:theme", "//ios/chrome/browser/search_engines/model", "//ios/chrome/browser/shared/model/application_context", - "//ios/chrome/browser/shared/model/prefs:pref_names", "//ios/chrome/browser/shared/model/profile", "//ios/chrome/browser/shared/public/features", "//ios/chrome/browser/shared/public/features:system_flags",
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/app_bundle_promo/coordinator/BUILD.gn b/ios/chrome/browser/content_suggestions/ui_bundled/app_bundle_promo/coordinator/BUILD.gn index 7122d0e..c59b3ff 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/app_bundle_promo/coordinator/BUILD.gn +++ b/ios/chrome/browser/content_suggestions/ui_bundled/app_bundle_promo/coordinator/BUILD.gn
@@ -10,13 +10,13 @@ deps = [ "//base", + "//components/ntp_tiles:pref_names", "//components/prefs", "//components/prefs/ios", "//ios/chrome/browser/app_store_bundle/model", "//ios/chrome/browser/content_suggestions/ui_bundled:constants", "//ios/chrome/browser/content_suggestions/ui_bundled:public", "//ios/chrome/browser/content_suggestions/ui_bundled/app_bundle_promo/ui", - "//ios/chrome/browser/shared/model/prefs:pref_names", ] frameworks = [ "UIKit.framework" ] @@ -28,6 +28,7 @@ deps = [ ":coordinator", + "//components/ntp_tiles:pref_names", "//components/prefs:test_support", "//ios/chrome/browser/content_suggestions/ui_bundled:public", "//ios/chrome/browser/shared/model/prefs:pref_names",
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/app_bundle_promo/coordinator/app_bundle_promo_mediator.mm b/ios/chrome/browser/content_suggestions/ui_bundled/app_bundle_promo/coordinator/app_bundle_promo_mediator.mm index ae3d08ad..c6ab33d 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/app_bundle_promo/coordinator/app_bundle_promo_mediator.mm +++ b/ios/chrome/browser/content_suggestions/ui_bundled/app_bundle_promo/coordinator/app_bundle_promo_mediator.mm
@@ -6,6 +6,7 @@ #import "base/check.h" #import "base/memory/raw_ptr.h" +#import "components/ntp_tiles/pref_names.h" #import "components/prefs/ios/pref_observer_bridge.h" #import "components/prefs/pref_change_registrar.h" #import "components/prefs/pref_service.h" @@ -14,7 +15,6 @@ #import "ios/chrome/browser/content_suggestions/ui_bundled/app_bundle_promo/ui/app_bundle_promo_config.h" #import "ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_constants.h" #import "ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_view_controller_audience.h" -#import "ios/chrome/browser/shared/model/prefs/pref_names.h" @interface AppBundlePromoMediator () <AppBundlePromoAudience, PrefObserverDelegate> @@ -52,7 +52,7 @@ _profilePrefChangeRegistrar.Init(profilePrefService); _prefObserverBridge->ObserveChangesForPreference( - prefs::kHomeCustomizationMagicStackTipsEnabled, + ntp_tiles::prefs::kTipsHomeModuleEnabled, &_profilePrefChangeRegistrar); } } @@ -87,9 +87,9 @@ - (void)onPreferenceChanged:(const std::string&)preferenceName { CHECK(_profilePrefService); - CHECK_EQ(preferenceName, prefs::kHomeCustomizationMagicStackTipsEnabled); + CHECK_EQ(preferenceName, ntp_tiles::prefs::kTipsHomeModuleEnabled); if (!_profilePrefService->GetBoolean( - prefs::kHomeCustomizationMagicStackTipsEnabled)) { + ntp_tiles::prefs::kTipsHomeModuleEnabled)) { [self.delegate removeAppBundlePromoModuleWithCompletion:nil]; } }
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/app_bundle_promo/coordinator/app_bundle_promo_mediator_unittest.mm b/ios/chrome/browser/content_suggestions/ui_bundled/app_bundle_promo/coordinator/app_bundle_promo_mediator_unittest.mm index 6934104..4ff7838 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/app_bundle_promo/coordinator/app_bundle_promo_mediator_unittest.mm +++ b/ios/chrome/browser/content_suggestions/ui_bundled/app_bundle_promo/coordinator/app_bundle_promo_mediator_unittest.mm
@@ -4,10 +4,10 @@ #import "ios/chrome/browser/content_suggestions/ui_bundled/app_bundle_promo/coordinator/app_bundle_promo_mediator.h" +#import "components/ntp_tiles/pref_names.h" #import "components/prefs/pref_registry_simple.h" #import "components/prefs/testing_pref_service.h" #import "ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_view_controller_audience.h" -#import "ios/chrome/browser/shared/model/prefs/pref_names.h" #import "ios/chrome/test/providers/app_store_bundle/test_app_store_bundle_service.h" #import "ios/web/public/test/web_task_environment.h" #import "testing/platform_test.h" @@ -23,7 +23,7 @@ void SetUp() override { PlatformTest::SetUp(); profile_pref_service_.registry()->RegisterBooleanPref( - prefs::kHomeCustomizationMagicStackTipsEnabled, true); + ntp_tiles::prefs::kTipsHomeModuleEnabled, true); TestAppStoreBundleService* app_store_bundle_service = new TestAppStoreBundleService(); mediator_to_test_ = [[AppBundlePromoMediator alloc] @@ -57,8 +57,8 @@ TEST_F(AppBundlePromoMediatorTest, TestDisableModule) { OCMExpect( [delegate_mock_ removeAppBundlePromoModuleWithCompletion:[OCMArg any]]); - profile_pref_service_.SetBoolean( - prefs::kHomeCustomizationMagicStackTipsEnabled, false); + profile_pref_service_.SetBoolean(ntp_tiles::prefs::kTipsHomeModuleEnabled, + false); EXPECT_OCMOCK_VERIFY(delegate_mock_); }
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_coordinator.mm b/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_coordinator.mm index 288ce23..1535145 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_coordinator.mm +++ b/ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_coordinator.mm
@@ -23,6 +23,7 @@ #import "components/feed/core/v2/public/ios/pref_names.h" #import "components/image_fetcher/core/image_data_fetcher.h" #import "components/ntp_tiles/most_visited_sites.h" +#import "components/ntp_tiles/pref_names.h" #import "components/password_manager/core/browser/ui/credential_ui_entry.h" #import "components/password_manager/core/browser/ui/password_check_referrer.h" #import "components/prefs/pref_service.h" @@ -460,7 +461,7 @@ } BOOL areTipsCardsEnabled = - prefs->GetBoolean(prefs::kHomeCustomizationMagicStackTipsEnabled); + prefs->GetBoolean(ntp_tiles::prefs::kTipsHomeModuleEnabled); if (IsTipsMagicStackEnabled() && areTipsCardsEnabled) { _tipsMediator = [[TipsMagicStackMediator alloc] @@ -1229,9 +1230,7 @@ kSetUpList baseViewController:self.magicStackCollectionView]; break; - case SetUpListItemType::kFollow: case SetUpListItemType::kAllSet: - // TODO(crbug.com/40262090): Add a Follow item to the Set Up List. NOTREACHED(); } } @@ -1442,7 +1441,7 @@ // Disables Magic Stack cards with the "Chrome Tips" header. - (void)disableTipsModules { PrefService* prefs = self.profile->GetPrefs(); - prefs->SetBoolean(prefs::kHomeCustomizationMagicStackTipsEnabled, false); + prefs->SetBoolean(ntp_tiles::prefs::kTipsHomeModuleEnabled, false); } @end
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/default_browser/coordinator/BUILD.gn b/ios/chrome/browser/content_suggestions/ui_bundled/default_browser/coordinator/BUILD.gn index 1b286e2..1c64e5d 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/default_browser/coordinator/BUILD.gn +++ b/ios/chrome/browser/content_suggestions/ui_bundled/default_browser/coordinator/BUILD.gn
@@ -9,11 +9,11 @@ ] deps = [ "//base", + "//components/ntp_tiles:pref_names", "//components/prefs/ios", "//ios/chrome/browser/content_suggestions/ui_bundled:public", "//ios/chrome/browser/content_suggestions/ui_bundled/default_browser/public:features", "//ios/chrome/browser/content_suggestions/ui_bundled/default_browser/ui", - "//ios/chrome/browser/shared/model/prefs:pref_names", "//ios/chrome/browser/shared/public/commands", ] frameworks = [ "UIKit.framework" ]
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/default_browser/coordinator/default_browser_mediator.mm b/ios/chrome/browser/content_suggestions/ui_bundled/default_browser/coordinator/default_browser_mediator.mm index 3804bef9..c93d559 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/default_browser/coordinator/default_browser_mediator.mm +++ b/ios/chrome/browser/content_suggestions/ui_bundled/default_browser/coordinator/default_browser_mediator.mm
@@ -4,13 +4,13 @@ #import "ios/chrome/browser/content_suggestions/ui_bundled/default_browser/coordinator/default_browser_mediator.h" +#import "components/ntp_tiles/pref_names.h" #import "components/prefs/ios/pref_observer_bridge.h" #import "components/prefs/pref_change_registrar.h" #import "components/prefs/pref_service.h" #import "ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_view_controller_audience.h" #import "ios/chrome/browser/content_suggestions/ui_bundled/default_browser/ui/default_browser_commands.h" #import "ios/chrome/browser/content_suggestions/ui_bundled/default_browser/ui/default_browser_config.h" -#import "ios/chrome/browser/shared/model/prefs/pref_names.h" #import "ios/chrome/browser/shared/public/commands/settings_commands.h" @interface DefaultBrowserMediator () <DefaultBrowserCommands, @@ -42,7 +42,7 @@ _profilePrefChangeRegistrar.Init(profilePrefService); _prefObserverBridge->ObserveChangesForPreference( - prefs::kHomeCustomizationMagicStackTipsEnabled, + ntp_tiles::prefs::kTipsHomeModuleEnabled, &_profilePrefChangeRegistrar); } } @@ -70,9 +70,9 @@ - (void)onPreferenceChanged:(const std::string&)preferenceName { CHECK(_profilePrefService); - CHECK_EQ(preferenceName, prefs::kHomeCustomizationMagicStackTipsEnabled); + CHECK_EQ(preferenceName, ntp_tiles::prefs::kTipsHomeModuleEnabled); if (!_profilePrefService->GetBoolean( - prefs::kHomeCustomizationMagicStackTipsEnabled)) { + ntp_tiles::prefs::kTipsHomeModuleEnabled)) { [self.delegate removeDefaultBrowserPromoModuleWithCompletion:nil]; } }
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/BUILD.gn b/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/BUILD.gn index 1c6b7a22..50eb974d 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/BUILD.gn +++ b/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/BUILD.gn
@@ -34,6 +34,7 @@ "//components/bookmarks/browser", "//components/commerce/core:feature_list", "//components/commerce/core:shopping_service", + "//components/ntp_tiles:pref_names", "//components/password_manager/core/common", "//components/power_bookmarks/core", "//components/prefs", @@ -241,6 +242,7 @@ "//components/feature_engagement/test:test_support", "//components/image_fetcher/core", "//components/ntp_tiles", + "//components/ntp_tiles:pref_names", "//components/segmentation_platform/embedder/home_modules", "//components/segmentation_platform/embedder/home_modules:constants", "//components/segmentation_platform/embedder/home_modules/tips_manager:constants",
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_module_container.mm b/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_module_container.mm index fd5d0f2f..e7d4e2a 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_module_container.mm +++ b/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_module_container.mm
@@ -448,8 +448,10 @@ return kMagicStackContentSuggestionsModuleTabResumptionAccessibilityIdentifier; default: - // TODO(crbug.com/40946679): the code should use constants for - // accessibility identifiers, and not localized strings. + // Ideally the accessibility identifier should not depend on localized + // strings (as the test module only has access to the "en-US" locale, + // thus this forces the test to run the application in the same locale + // and prevents testing it in another configuration, i.e. LTR-language). return [self titleStringForModule:type config:config]; } }
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_ranking_model.mm b/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_ranking_model.mm index 277c6f2..3305055 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_ranking_model.mm +++ b/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_ranking_model.mm
@@ -17,6 +17,7 @@ #import "components/commerce/core/commerce_feature_list.h" #import "components/commerce/core/price_tracking_utils.h" #import "components/commerce/core/shopping_service.h" +#import "components/ntp_tiles/pref_names.h" #import "components/password_manager/core/common/password_manager_pref_names.h" #import "components/power_bookmarks/core/power_bookmark_utils.h" #import "components/power_bookmarks/core/proto/power_bookmark_meta.pb.h" @@ -612,7 +613,7 @@ MagicStackModule* card; BOOL areTipsCardsEnabled = - _prefService->GetBoolean(prefs::kHomeCustomizationMagicStackTipsEnabled); + _prefService->GetBoolean(ntp_tiles::prefs::kTipsHomeModuleEnabled); for (const std::string& label : result.ordered_labels) { if (label == segmentation_platform::kPriceTrackingNotificationPromo) {
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_ranking_model_unittest.mm b/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_ranking_model_unittest.mm index ea5eac9..6b2629e 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_ranking_model_unittest.mm +++ b/ios/chrome/browser/content_suggestions/ui_bundled/magic_stack/magic_stack_ranking_model_unittest.mm
@@ -23,6 +23,7 @@ #import "components/image_fetcher/core/image_data_fetcher.h" #import "components/ntp_tiles/icon_cacher.h" #import "components/ntp_tiles/most_visited_sites.h" +#import "components/ntp_tiles/pref_names.h" #import "components/segmentation_platform/embedder/home_modules/constants.h" #import "components/segmentation_platform/embedder/home_modules/tips_manager/constants.h" #import "components/segmentation_platform/public/constants.h" @@ -269,7 +270,7 @@ // Necessary set up for kIOSSetUpList. GetProfile()->GetPrefs()->SetBoolean( - prefs::kHomeCustomizationMagicStackTipsEnabled, true); + ntp_tiles::prefs::kTipsHomeModuleEnabled, true); ClearDefaultBrowserPromoData(); WriteFirstRunSentinel();
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/new_tab_page_app_interface.mm b/ios/chrome/browser/content_suggestions/ui_bundled/new_tab_page_app_interface.mm index 0cfcbcf1..1b4f402 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/new_tab_page_app_interface.mm +++ b/ios/chrome/browser/content_suggestions/ui_bundled/new_tab_page_app_interface.mm
@@ -7,6 +7,7 @@ #import "base/strings/string_number_conversions.h" #import "base/strings/sys_string_conversions.h" #import "base/strings/utf_string_conversions.h" +#import "components/ntp_tiles/pref_names.h" #import "components/prefs/pref_service.h" #import "ios/chrome/browser/content_suggestions/ui_bundled/content_suggestions_collection_utils.h" #import "ios/chrome/browser/content_suggestions/ui_bundled/ntp_home_test_utils.h" @@ -17,7 +18,6 @@ #import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_color_palette.h" #import "ios/chrome/browser/search_engines/model/template_url_service_factory.h" #import "ios/chrome/browser/shared/model/application_context/application_context.h" -#import "ios/chrome/browser/shared/model/prefs/pref_names.h" #import "ios/chrome/browser/shared/model/profile/profile_ios.h" #import "ios/chrome/browser/shared/public/features/system_flags.h" #import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h" @@ -58,13 +58,13 @@ + (void)disableTipsCards { chrome_test_util::GetOriginalProfile()->GetPrefs()->SetBoolean( - prefs::kHomeCustomizationMagicStackTipsEnabled, false); + ntp_tiles::prefs::kTipsHomeModuleEnabled, false); } + (void)resetSetUpListPrefs { PrefService* localState = GetApplicationContext()->GetLocalState(); PrefService* prefService = chrome_test_util::GetOriginalProfile()->GetPrefs(); - prefService->SetBoolean(prefs::kHomeCustomizationMagicStackTipsEnabled, true); + prefService->SetBoolean(ntp_tiles::prefs::kTipsHomeModuleEnabled, true); SetUpListItemState unknown = SetUpListItemState::kUnknown; set_up_list_prefs::SetItemState(localState, SetUpListItemType::kDefaultBrowser, unknown);
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/BUILD.gn b/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/BUILD.gn index a27ba70..d8d75a5 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/BUILD.gn +++ b/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/BUILD.gn
@@ -26,6 +26,7 @@ ":constants", ":utils", "//base", + "//components/ntp_tiles:pref_names", "//components/password_manager/core/common:features", "//components/prefs", "//components/prefs/ios", @@ -109,6 +110,7 @@ ] deps = [ "//base", + "//components/ntp_tiles:pref_names", "//components/prefs", "//ios/chrome/browser/ntp/model:features", "//ios/chrome/browser/ntp/model:set_up_list_prefs",
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/constants.h b/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/constants.h index 77227a2..8edf0e0d 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/constants.h +++ b/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/constants.h
@@ -14,7 +14,6 @@ extern NSString* const kAutofillItemID; extern NSString* const kContentNotificationItemID; extern NSString* const kAllSetItemID; -extern NSString* const kFollowItemID; extern NSString* const kAccessibilityID; extern NSString* const kExpandButtonID; extern NSString* const kMenuButtonID;
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/constants.mm b/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/constants.mm index 53845fdd..db93d99 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/constants.mm +++ b/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/constants.mm
@@ -12,7 +12,6 @@ constexpr NSString* const kContentNotificationItemID = @"set_up_list::kContentNotificationItemID"; constexpr NSString* const kAllSetItemID = @"set_up_list:kAllSetItemID"; -constexpr NSString* const kFollowItemID = @"set_up_list::kFollowItemID"; constexpr NSString* const kAccessibilityID = @"set_up_list::kAccessibilityID"; constexpr NSString* const kExpandButtonID = @"set_up_list::kExpandButtonID"; constexpr NSString* const kMenuButtonID = @"set_up_list::kMenuButtonID";
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/set_up_list_item_icon.mm b/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/set_up_list_item_icon.mm index 8a7f818d..6dd27e7 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/set_up_list_item_icon.mm +++ b/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/set_up_list_item_icon.mm
@@ -274,9 +274,6 @@ kCheckmarkSealFillSymbol, _compactLayout, @[ [UIColor whiteColor], [UIColor colorNamed:kBlue500Color] ]); } - case SetUpListItemType::kFollow: - // TODO(crbug.com/40262090): Add a Follow item to the Set Up List. - NOTREACHED(); } }
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/set_up_list_item_view.mm b/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/set_up_list_item_view.mm index ba84aa1..13aa68b 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/set_up_list_item_view.mm +++ b/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/set_up_list_item_view.mm
@@ -358,9 +358,6 @@ return l10n_util::GetNSString(IDS_IOS_SET_UP_LIST_NOTIFICATIONS_TITLE); case SetUpListItemType::kAllSet: return l10n_util::GetNSString(IDS_IOS_SET_UP_LIST_ALL_SET_TITLE); - case SetUpListItemType::kFollow: - // TODO(crbug.com/40262090): Add a Follow item to the Set Up List. - NOTREACHED(); } } @@ -375,9 +372,6 @@ return l10n_util::GetNSString(_config.notifications_description); case SetUpListItemType::kAllSet: return l10n_util::GetNSString(IDS_IOS_SET_UP_LIST_ALL_SET_DESCRIPTION); - case SetUpListItemType::kFollow: - // TODO(crbug.com/40262090): Add a Follow item to the Set Up List. - NOTREACHED(); } } @@ -391,8 +385,6 @@ return set_up_list::kContentNotificationItemID; case SetUpListItemType::kAllSet: return set_up_list::kAllSetItemID; - case SetUpListItemType::kFollow: - return set_up_list::kFollowItemID; } }
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/set_up_list_mediator.mm b/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/set_up_list_mediator.mm index 2fa4578..073b383 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/set_up_list_mediator.mm +++ b/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/set_up_list_mediator.mm
@@ -10,6 +10,7 @@ #import "base/memory/raw_ptr.h" #import "base/strings/sys_string_conversions.h" #import "base/task/sequenced_task_runner.h" +#import "components/ntp_tiles/pref_names.h" #import "components/prefs/ios/pref_observer_bridge.h" #import "components/prefs/pref_change_registrar.h" #import "components/prefs/pref_service.h" @@ -128,7 +129,7 @@ &_localStatePrefChangeRegistrar); _prefObserverBridge->ObserveChangesForPreference( - prefs::kHomeCustomizationMagicStackTipsEnabled, &_prefChangeRegistrar); + ntp_tiles::prefs::kTipsHomeModuleEnabled, &_prefChangeRegistrar); _prefObserverBridge->ObserveChangesForPreference( prefs::kAppLevelPushNotificationPermissions, @@ -307,9 +308,9 @@ if ([self hasOptedInToNotifications]) { [self markSetUpListItemPrefComplete:SetUpListItemType::kNotifications]; } - } else if (preferenceName == prefs::kHomeCustomizationMagicStackTipsEnabled && + } else if (preferenceName == ntp_tiles::prefs::kTipsHomeModuleEnabled && !_prefService->GetBoolean( - prefs::kHomeCustomizationMagicStackTipsEnabled)) { + ntp_tiles::prefs::kTipsHomeModuleEnabled)) { [self hideSetUpList]; } }
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/set_up_list_show_more_item_view.mm b/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/set_up_list_show_more_item_view.mm index 81fccee..3c2d707 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/set_up_list_show_more_item_view.mm +++ b/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/set_up_list_show_more_item_view.mm
@@ -198,9 +198,6 @@ return l10n_util::GetNSString(IDS_IOS_SET_UP_LIST_NOTIFICATIONS_TITLE); case SetUpListItemType::kAllSet: return l10n_util::GetNSString(IDS_IOS_SET_UP_LIST_ALL_SET_TITLE); - case SetUpListItemType::kFollow: - // TODO(crbug.com/40262090): Add a Follow item to the Set Up List. - NOTREACHED(); } } @@ -239,7 +236,6 @@ ? IDS_IOS_SET_UP_LIST_NOTIFICATIONS_DESCRIPTION : IDS_IOS_SET_UP_LIST_NOTIFICATIONS_SHORT_DESCRIPTION); case SetUpListItemType::kAllSet: - case SetUpListItemType::kFollow: NOTREACHED(); } }
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/utils.mm b/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/utils.mm index 3364ab68..8cd4a1e 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/utils.mm +++ b/ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/utils.mm
@@ -5,6 +5,7 @@ #import "ios/chrome/browser/content_suggestions/ui_bundled/set_up_list/utils.h" #import "base/time/time.h" +#import "components/ntp_tiles/pref_names.h" #import "components/prefs/pref_service.h" #import "ios/chrome/browser/ntp/model/features.h" #import "ios/chrome/browser/ntp/model/set_up_list_prefs.h" @@ -17,7 +18,7 @@ bool IsSetUpListActive(PrefService* local_prefs, PrefService* user_prefs, bool include_disable_pref) { - if (!user_prefs->GetBoolean(prefs::kHomeCustomizationMagicStackTipsEnabled)) { + if (!user_prefs->GetBoolean(ntp_tiles::prefs::kTipsHomeModuleEnabled)) { return false; } // Check if we are within the duration of the Set Up List, relevant to the
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/tab_resumption/tab_resumption_mediator.mm b/ios/chrome/browser/content_suggestions/ui_bundled/tab_resumption/tab_resumption_mediator.mm index e238acdd..57e71d8 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/tab_resumption/tab_resumption_mediator.mm +++ b/ios/chrome/browser/content_suggestions/ui_bundled/tab_resumption/tab_resumption_mediator.mm
@@ -852,9 +852,13 @@ if (!strongSelf || !strongSelf.delegate) { return; } - [strongSelf onPriceTrackedBookmarksReceived:subscriptions - url:url - item:item]; + if (subscriptions.empty()) { + [strongSelf fetchImageForItem:item]; + } else { + [strongSelf onPriceTrackedBookmarksReceived:subscriptions + url:url + item:item]; + } })); } else { // Fetch the favicon.
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/tips/coordinator/BUILD.gn b/ios/chrome/browser/content_suggestions/ui_bundled/tips/coordinator/BUILD.gn index 5269443..a0c0d77 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/tips/coordinator/BUILD.gn +++ b/ios/chrome/browser/content_suggestions/ui_bundled/tips/coordinator/BUILD.gn
@@ -15,6 +15,7 @@ "//components/bookmarks/browser", "//components/commerce/core:shopping_service", "//components/image_fetcher/core", + "//components/ntp_tiles:pref_names", "//components/power_bookmarks/core", "//components/prefs", "//components/prefs/ios", @@ -44,12 +45,12 @@ "//components/bookmarks/test", "//components/commerce/core:shopping_service_test_support", "//components/image_fetcher/core", + "//components/ntp_tiles:pref_names", "//components/segmentation_platform/embedder/home_modules/tips_manager:constants", "//components/sync_preferences:test_support", "//ios/chrome/browser/content_suggestions/ui_bundled/tips/ui", "//ios/chrome/browser/shared/model/application_context", "//ios/chrome/browser/shared/model/browser/test:test_support", - "//ios/chrome/browser/shared/model/prefs:pref_names", "//ios/chrome/browser/shared/model/profile/test", "//ios/chrome/common/ui/confirmation_alert", "//ios/chrome/test:test_support",
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/tips/coordinator/tips_magic_stack_mediator.mm b/ios/chrome/browser/content_suggestions/ui_bundled/tips/coordinator/tips_magic_stack_mediator.mm index b49ce910..db3b3203 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/tips/coordinator/tips_magic_stack_mediator.mm +++ b/ios/chrome/browser/content_suggestions/ui_bundled/tips/coordinator/tips_magic_stack_mediator.mm
@@ -14,6 +14,7 @@ #import "components/commerce/core/price_tracking_utils.h" #import "components/commerce/core/shopping_service.h" #import "components/image_fetcher/core/image_data_fetcher.h" +#import "components/ntp_tiles/pref_names.h" #import "components/power_bookmarks/core/power_bookmark_utils.h" #import "components/power_bookmarks/core/proto/power_bookmark_meta.pb.h" #import "components/prefs/ios/pref_observer_bridge.h" @@ -25,7 +26,6 @@ #import "ios/chrome/browser/content_suggestions/ui_bundled/tips/ui/tips_module_audience.h" #import "ios/chrome/browser/content_suggestions/ui_bundled/tips/ui/tips_module_consumer_source.h" #import "ios/chrome/browser/content_suggestions/ui_bundled/tips/ui/tips_module_state.h" -#import "ios/chrome/browser/shared/model/prefs/pref_names.h" #import "ios/chrome/browser/shared/public/features/features.h" #import "net/traffic_annotation/network_traffic_annotation.h" #import "url/gurl.h" @@ -90,7 +90,7 @@ _profilePrefChangeRegistrar.Init(profilePrefService); _prefObserverBridge->ObserveChangesForPreference( - prefs::kHomeCustomizationMagicStackTipsEnabled, + ntp_tiles::prefs::kTipsHomeModuleEnabled, &_profilePrefChangeRegistrar); } } @@ -129,9 +129,9 @@ - (void)onPreferenceChanged:(const std::string&)preferenceName { CHECK(_profilePrefService); - CHECK_EQ(preferenceName, prefs::kHomeCustomizationMagicStackTipsEnabled); + CHECK_EQ(preferenceName, ntp_tiles::prefs::kTipsHomeModuleEnabled); if (!_profilePrefService->GetBoolean( - prefs::kHomeCustomizationMagicStackTipsEnabled)) { + ntp_tiles::prefs::kTipsHomeModuleEnabled)) { [self.delegate removeTipsModuleWithCompletion:nil]; } }
diff --git a/ios/chrome/browser/content_suggestions/ui_bundled/tips/coordinator/tips_magic_stack_mediator_unittest.mm b/ios/chrome/browser/content_suggestions/ui_bundled/tips/coordinator/tips_magic_stack_mediator_unittest.mm index 3b40906..f2976cc 100644 --- a/ios/chrome/browser/content_suggestions/ui_bundled/tips/coordinator/tips_magic_stack_mediator_unittest.mm +++ b/ios/chrome/browser/content_suggestions/ui_bundled/tips/coordinator/tips_magic_stack_mediator_unittest.mm
@@ -12,11 +12,11 @@ #import "components/bookmarks/test/test_bookmark_client.h" #import "components/commerce/core/mock_shopping_service.h" #import "components/image_fetcher/core/image_data_fetcher.h" +#import "components/ntp_tiles/pref_names.h" #import "components/segmentation_platform/embedder/home_modules/tips_manager/constants.h" #import "components/sync_preferences/testing_pref_service_syncable.h" #import "ios/chrome/browser/content_suggestions/ui_bundled/tips/ui/tips_module_state.h" #import "ios/chrome/browser/shared/model/application_context/application_context.h" -#import "ios/chrome/browser/shared/model/prefs/pref_names.h" #import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h" #import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h" #import "ios/web/public/test/web_task_environment.h" @@ -38,7 +38,7 @@ profile_ = std::move(builder).Build(); profile_pref_service_.registry()->RegisterBooleanPref( - prefs::kHomeCustomizationMagicStackTipsEnabled, true); + ntp_tiles::prefs::kTipsHomeModuleEnabled, true); // Create a `TipsMagicStackMediator` with an initial unknown // `TipIdentifier`. @@ -90,8 +90,8 @@ // completionBlock. OCMExpect([delegate removeTipsModuleWithCompletion:[OCMArg any]]); - profile_pref_service_.SetBoolean( - prefs::kHomeCustomizationMagicStackTipsEnabled, false); + profile_pref_service_.SetBoolean(ntp_tiles::prefs::kTipsHomeModuleEnabled, + false); // Verify that the delegate method was called. EXPECT_OCMOCK_VERIFY(delegate);
diff --git a/ios/chrome/browser/file_upload_panel/coordinator/BUILD.gn b/ios/chrome/browser/file_upload_panel/coordinator/BUILD.gn index 4e53198a..8d6ec69 100644 --- a/ios/chrome/browser/file_upload_panel/coordinator/BUILD.gn +++ b/ios/chrome/browser/file_upload_panel/coordinator/BUILD.gn
@@ -7,5 +7,14 @@ "file_upload_panel_coordinator.h", "file_upload_panel_coordinator.mm", ] - deps = [ "//ios/chrome/browser/shared/coordinator/chrome_coordinator" ] + deps = [ + "//ios/chrome/app/strings:ios_strings_grit", + "//ios/chrome/browser/file_upload_panel/ui:constants", + "//ios/chrome/browser/file_upload_panel/ui:context_menu_presenter", + "//ios/chrome/browser/shared/coordinator/chrome_coordinator", + "//ios/chrome/browser/shared/model/browser", + "//ios/chrome/browser/shared/public/commands", + "//ios/chrome/browser/shared/ui/symbols", + "//ui/base", + ] }
diff --git a/ios/chrome/browser/file_upload_panel/coordinator/file_upload_panel_coordinator.h b/ios/chrome/browser/file_upload_panel/coordinator/file_upload_panel_coordinator.h index 7686fbd..5ab3f64 100644 --- a/ios/chrome/browser/file_upload_panel/coordinator/file_upload_panel_coordinator.h +++ b/ios/chrome/browser/file_upload_panel/coordinator/file_upload_panel_coordinator.h
@@ -9,7 +9,7 @@ // Coordinator for the file upload panel UI, which lets the user select files or // directories from different sources to be submitted to a web page file input. -// TODO(crbug.com/441659098): Implement the class start/stop methods. +API_AVAILABLE(ios(18.4)) @interface FileUploadPanelCoordinator : ChromeCoordinator @end
diff --git a/ios/chrome/browser/file_upload_panel/coordinator/file_upload_panel_coordinator.mm b/ios/chrome/browser/file_upload_panel/coordinator/file_upload_panel_coordinator.mm index 93196172..a4d7994 100644 --- a/ios/chrome/browser/file_upload_panel/coordinator/file_upload_panel_coordinator.mm +++ b/ios/chrome/browser/file_upload_panel/coordinator/file_upload_panel_coordinator.mm
@@ -4,6 +4,187 @@ #import "ios/chrome/browser/file_upload_panel/coordinator/file_upload_panel_coordinator.h" -@implementation FileUploadPanelCoordinator +#import "base/metrics/histogram_functions.h" +#import "ios/chrome/browser/file_upload_panel/ui/constants.h" +#import "ios/chrome/browser/file_upload_panel/ui/context_menu_presenter.h" +#import "ios/chrome/browser/shared/model/browser/browser.h" +#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h" +#import "ios/chrome/browser/shared/public/commands/file_upload_panel_commands.h" +#import "ios/chrome/browser/shared/ui/symbols/symbols.h" +#import "ios/chrome/grit/ios_strings.h" +#import "ui/base/l10n/l10n_util_mac.h" + +@interface FileUploadPanelCoordinator () <UIContextMenuInteractionDelegate> + +@end + +@implementation FileUploadPanelCoordinator { + ContextMenuPresenter* _contextMenuPresenter; +} + +#pragma mark - ChromeCoordinator + +- (void)start { + // TODO(crbug.com/441659098): Create a mediator to observe the file selection + // in the model layer. Skip the context menu if it is unnecessary e.g. + // directly show the camera. + [self showContextMenu]; +} + +- (void)stop { + // TODO(crbug.com/441659098): Disconnect the mediator from file selection in + // the model layer. Hide any other views presented beside the context menu. + [self hideContextMenu]; +} + +#pragma mark - UIContextMenuInteractionDelegate + +- (UIContextMenuConfiguration*)contextMenuInteraction: + (UIContextMenuInteraction*)interaction + configurationForMenuAtLocation:(CGPoint)location { + __weak __typeof(self) weakSelf = self; + UIContextMenuActionProvider actionProvider = + ^UIMenu*(NSArray<UIMenuElement*>*) { + return [weakSelf contextMenu]; + }; + return + [UIContextMenuConfiguration configurationWithIdentifier:nil + previewProvider:nil + actionProvider:actionProvider]; +} + +- (void)contextMenuInteraction:(UIContextMenuInteraction*)interaction + willEndForConfiguration:(UIContextMenuConfiguration*)configuration + animator:(id<UIContextMenuInteractionAnimating>)animator { + __weak __typeof(self) weakSelf = self; + [animator addCompletion:^{ + [weakSelf doContextMenuInteractionEndAnimationCompletion]; + }]; +} + +#pragma mark - Private (Context Menu) + +// Shows a context menu at the location of the last user interaction in the page +// with different options for file selection. +- (void)showContextMenu { + if (_contextMenuPresenter) { + return; + } + _contextMenuPresenter = [[ContextMenuPresenter alloc] + initWithRootView:self.baseViewController.view]; + _contextMenuPresenter.contextMenuInteractionDelegate = self; + // TODO(crbug.com/441659098): Choose the location of the last user interaction + // in the web page to present the context menu. + [_contextMenuPresenter presentAtLocationInRootView:CGPointZero]; +} + +// Returns the context menu to be presented by `-showContextMenu`. +- (UIMenu*)contextMenu { + NSArray<UIMenuElement*>* actions = nil; + __weak __typeof(self) weakSelf = self; + + UIAction* filePickerAction = [UIAction + actionWithTitle:[self filePickerActionLabel] + image:DefaultSymbolWithConfiguration(kFolderSymbol, nil) + identifier:@"chromium.uploadfile.choosefile" + handler:^(UIAction* action) { + [weakSelf showFilePicker]; + }]; + + UIAction* photoPickerAction = + [UIAction actionWithTitle:[self photoPickerActionLabel] + image:DefaultSymbolWithConfiguration( + kPhotoOnRectangleSymbol, nil) + identifier:@"chromium.uploadfile.choosephoto" + handler:^(UIAction* action) { + [weakSelf showPhotoPicker]; + }]; + + if ([UIImagePickerController + isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { + UIAction* cameraAction = [UIAction + actionWithTitle:[self cameraActionLabel] + image:DefaultSymbolWithConfiguration(kSystemCameraSymbol, nil) + identifier:@"chromium.uploadfile.usecamera" + handler:^(UIAction* action) { + [self showCamera]; + }]; + actions = @[ photoPickerAction, cameraAction, filePickerAction ]; + + base::UmaHistogramEnumeration( + "IOS.FileUploadPanel.ContextMenuVariant", + FileUploadPanelContextMenuVariant::kPhotoPickerAndCameraAndFilePicker); + } else { + actions = @[ photoPickerAction, filePickerAction ]; + base::UmaHistogramEnumeration( + "IOS.FileUploadPanel.ContextMenuVariant", + FileUploadPanelContextMenuVariant::kPhotoPickerAndFilePicker); + } + + return [UIMenu menuWithTitle:@"" children:actions]; +} + +// Hides the context menu presented by `-showContextMenu`. +- (void)hideContextMenu { + [_contextMenuPresenter dismiss]; + _contextMenuPresenter = nil; +} + +- (void)doContextMenuInteractionEndAnimationCompletion { + [self hideContextMenu]; + // TODO(crbug.com/441659098): Only hide the file upload panel if the context + // menu was dismissed without any action being selected. + [self hideFileUploadPanel]; +} + +#pragma mark - Private (File Picker) + +// Returns the label to use for the file picker action in the context menu. +- (NSString*)filePickerActionLabel { + // TODO(crbug.com/441659098): Use a plural label if multiple files can be + // selected. + return l10n_util::GetNSString( + IDS_IOS_FILE_UPLOAD_PANEL_CHOOSE_FILE_ACTION_LABEL); +} + +// Shows a file picker to select one or several files on the device. +- (void)showFilePicker { + // TODO(crbug.com/441659098): Show a file picker. +} + +#pragma mark - Private (Photo Picker) + +- (NSString*)photoPickerActionLabel { + return l10n_util::GetNSString( + IDS_IOS_FILE_UPLOAD_PANEL_PHOTO_LIBRARY_ACTION_LABEL); +} + +// Shows a photo picker to select one or several photos/videos on the device. +- (void)showPhotoPicker { + // TODO(crbug.com/441659098): Show a photo picker. +} + +#pragma mark - Private (Camera) + +- (NSString*)cameraActionLabel { + // TODO(crbug.com/441659098): Use only "Take Photo"/"Take Video" if + // videos/photos are not accepted by the web page. + return l10n_util::GetNSString( + IDS_IOS_FILE_UPLOAD_PANEL_TAKE_PHOTO_OR_VIDEO_ACTION_LABEL); +} + +// Shows a camera view to take a photo/video to submit to the web page. +- (void)showCamera { + // TODO(crbug.com/441659098): Show a camera view. +} + +#pragma mark - Private + +// Hides the file upload panel using the command dispatcher. +- (void)hideFileUploadPanel { + id<FileUploadPanelCommands> fileUploadPanelHandler = HandlerForProtocol( + self.browser->GetCommandDispatcher(), FileUploadPanelCommands); + [fileUploadPanelHandler hideFileUploadPanel]; +} @end
diff --git a/ios/chrome/browser/file_upload_panel/test/BUILD.gn b/ios/chrome/browser/file_upload_panel/test/BUILD.gn new file mode 100644 index 0000000..b44c33b77 --- /dev/null +++ b/ios/chrome/browser/file_upload_panel/test/BUILD.gn
@@ -0,0 +1,19 @@ +# Copyright 2025 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +source_set("eg2_tests") { + configs += [ "//build/config/ios:xctest_config" ] + testonly = true + sources = [ "file_upload_panel_egtest.mm" ] + deps = [ + "//ios/chrome/app/strings:ios_strings_grit", + "//ios/chrome/browser/file_upload_panel/ui:constants", + "//ios/chrome/browser/metrics/model:eg_test_support+eg2", + "//ios/chrome/browser/shared/public/features", + "//ios/chrome/test/earl_grey:eg_test_support+eg2", + "//ios/testing/earl_grey:eg_test_support+eg2", + "//ios/web/public/test:element_selector", + "//net:test_support", + ] +}
diff --git a/ios/chrome/browser/file_upload_panel/test/file_upload_panel_egtest.mm b/ios/chrome/browser/file_upload_panel/test/file_upload_panel_egtest.mm new file mode 100644 index 0000000..7ce0cce3 --- /dev/null +++ b/ios/chrome/browser/file_upload_panel/test/file_upload_panel_egtest.mm
@@ -0,0 +1,149 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "base/ios/ios_util.h" +#import "ios/chrome/browser/file_upload_panel/ui/constants.h" +#import "ios/chrome/browser/metrics/model/metrics_app_interface.h" +#import "ios/chrome/browser/shared/public/features/features.h" +#import "ios/chrome/grit/ios_strings.h" +#import "ios/chrome/test/earl_grey/chrome_actions.h" +#import "ios/chrome/test/earl_grey/chrome_earl_grey.h" +#import "ios/chrome/test/earl_grey/chrome_matchers.h" +#import "ios/chrome/test/earl_grey/chrome_test_case.h" +#import "ios/testing/earl_grey/earl_grey_test.h" +#import "ios/web/public/test/element_selector.h" +#import "net/test/embedded_test_server/embedded_test_server.h" + +namespace { + +// HTML for the test page. +constexpr char kPageHtml[] = R"( + +<html> + <body> + <h1>File input</h1> + <input type="file" id="fileInput" /> + <h1>File output</h1> + <p id="fileContent"></p> + <script> + const fileInput = document.getElementById('fileInput'); + const fileContent = document.getElementById('fileContent'); + fileInput.addEventListener('change', (event) => { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + fileContent.textContent = e.target.result; + }; + reader.onerror = (e) => { + fileContent.textContent = 'Error reading file.'; + console.error(e); + }; + reader.readAsText(file); // Assuming text file + } else { + fileContent.textContent = 'No file selected.'; + } + }); + </script> + </body> +</html> + +)"; + +// Identifier of the file input element. +constexpr char kFileInputElementID[] = "fileInput"; + +// Returns a test page response with a file upload element. +std::unique_ptr<net::test_server::HttpResponse> TestPageResponse( + const net::test_server::HttpRequest& request) { + std::unique_ptr<net::test_server::BasicHttpResponse> http_response = + std::make_unique<net::test_server::BasicHttpResponse>(); + http_response->set_code(net::HTTP_OK); + http_response->set_content(kPageHtml); + return std::move(http_response); +} + +} // namespace + +// Test case for the file upload panel UI. +@interface FileUploadPanelTestCase : ChromeTestCase + +@end + +@implementation FileUploadPanelTestCase + +- (AppLaunchConfiguration)appConfigurationForTestCase { + AppLaunchConfiguration config = [super appConfigurationForTestCase]; + config.features_enabled.push_back(kIOSCustomFileUploadMenu); + return config; +} + +- (void)setUp { + [super setUp]; + self.testServer->RegisterRequestHandler( + base::BindRepeating(&TestPageResponse)); + GREYAssertTrue(self.testServer->Start(), @"Server did not start."); + chrome_test_util::GREYAssertErrorNil( + [MetricsAppInterface setupHistogramTester]); +} + +- (void)tearDownHelper { + chrome_test_util::GREYAssertErrorNil( + [MetricsAppInterface releaseHistogramTester]); + [super tearDownHelper]; +} + +// Tests that the file upload panel context menu appears and contains expected +// elements when a file input element is tapped. +- (void)testFileUploadPanel { + // The file upload panel is only available on iOS 18.4+. + if (!base::ios::IsRunningOnOrLater(18, 4, 0)) { + EARL_GREY_TEST_SKIPPED(@"Test is only available for iOS 18.4+, skipping."); + } + GURL url = self.testServer->base_url(); + [ChromeEarlGrey loadURL:url]; + [ChromeEarlGrey waitForWebStateContainingText:"File input"]; + + // Tap the file input. + [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()] + performAction:chrome_test_util::TapWebElement([ElementSelector + selectorWithElementID:kFileInputElementID])]; + + const BOOL isCameraAvailable = [UIImagePickerController + isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]; + const auto expectedContextMenuVariant = + isCameraAvailable + ? FileUploadPanelContextMenuVariant:: + kPhotoPickerAndCameraAndFilePicker + : FileUploadPanelContextMenuVariant::kPhotoPickerAndFilePicker; + + // Test that the file upload panel context menu is the first UI presented for + // the file upload panel. + NSError* error = [MetricsAppInterface + expectTotalCount:1 + forHistogram:@"IOS.FileUploadPanel.ContextMenuVariant"]; + chrome_test_util::GREYAssertErrorNil(error); + error = [MetricsAppInterface + expectCount:1 + forBucket:static_cast<int>(expectedContextMenuVariant) + forHistogram:@"IOS.FileUploadPanel.ContextMenuVariant"]; + chrome_test_util::GREYAssertErrorNil(error); + + // Test that expected elements are present. + [ChromeEarlGrey waitForSufficientlyVisibleElementWithMatcher: + chrome_test_util::ContextMenuItemWithAccessibilityLabelId( + IDS_IOS_FILE_UPLOAD_PANEL_CHOOSE_FILE_ACTION_LABEL)]; + [ChromeEarlGrey + waitForSufficientlyVisibleElementWithMatcher: + chrome_test_util::ContextMenuItemWithAccessibilityLabelId( + IDS_IOS_FILE_UPLOAD_PANEL_PHOTO_LIBRARY_ACTION_LABEL)]; + [[EarlGrey + selectElementWithMatcher: + chrome_test_util::ContextMenuItemWithAccessibilityLabelId( + IDS_IOS_FILE_UPLOAD_PANEL_TAKE_PHOTO_OR_VIDEO_ACTION_LABEL)] + assertWithMatcher:isCameraAvailable ? grey_sufficientlyVisible() + : grey_nil()]; +} + +@end
diff --git a/ios/chrome/browser/file_upload_panel/ui/BUILD.gn b/ios/chrome/browser/file_upload_panel/ui/BUILD.gn new file mode 100644 index 0000000..a48e5ef --- /dev/null +++ b/ios/chrome/browser/file_upload_panel/ui/BUILD.gn
@@ -0,0 +1,14 @@ +# Copyright 2025 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +source_set("context_menu_presenter") { + sources = [ + "context_menu_presenter.h", + "context_menu_presenter.mm", + ] +} + +source_set("constants") { + sources = [ "constants.h" ] +}
diff --git a/ios/chrome/browser/file_upload_panel/ui/constants.h b/ios/chrome/browser/file_upload_panel/ui/constants.h new file mode 100644 index 0000000..336d8f81 --- /dev/null +++ b/ios/chrome/browser/file_upload_panel/ui/constants.h
@@ -0,0 +1,16 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IOS_CHROME_BROWSER_FILE_UPLOAD_PANEL_UI_CONSTANTS_H_ +#define IOS_CHROME_BROWSER_FILE_UPLOAD_PANEL_UI_CONSTANTS_H_ + +// LINT.IfChange(FileUploadPanelContextMenuVariant) +enum class FileUploadPanelContextMenuVariant { + kPhotoPickerAndCameraAndFilePicker, + kPhotoPickerAndFilePicker, + kMaxValue = kPhotoPickerAndFilePicker, +}; +// LINT.ThenChange(//tools/metrics/histograms/metadata/ios/enums.xml:IOSFileUploadPanelContextMenuVariant) + +#endif // IOS_CHROME_BROWSER_FILE_UPLOAD_PANEL_UI_CONSTANTS_H_
diff --git a/ios/chrome/browser/file_upload_panel/ui/context_menu_presenter.h b/ios/chrome/browser/file_upload_panel/ui/context_menu_presenter.h new file mode 100644 index 0000000..46140ba1 --- /dev/null +++ b/ios/chrome/browser/file_upload_panel/ui/context_menu_presenter.h
@@ -0,0 +1,29 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IOS_CHROME_BROWSER_FILE_UPLOAD_PANEL_UI_CONTEXT_MENU_PRESENTER_H_ +#define IOS_CHROME_BROWSER_FILE_UPLOAD_PANEL_UI_CONTEXT_MENU_PRESENTER_H_ + +#import <UIKit/UIKit.h> + +// Presents a context menu at a specified location. +API_AVAILABLE(ios(17.4)) +@interface ContextMenuPresenter : NSObject + +@property(nonatomic, weak) id<UIContextMenuInteractionDelegate> + contextMenuInteractionDelegate; +@property(nonatomic, strong, readonly) + UIContextMenuInteraction* contextMenuInteraction; + +- (instancetype)initWithRootView:(UIView*)rootView NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; + +// Presents a context menu at the specified location. +- (void)presentAtLocationInRootView:(CGPoint)locationInRootView; +// Dismisses the context menu. +- (void)dismiss; + +@end + +#endif // IOS_CHROME_BROWSER_FILE_UPLOAD_PANEL_UI_CONTEXT_MENU_PRESENTER_H_
diff --git a/ios/chrome/browser/file_upload_panel/ui/context_menu_presenter.mm b/ios/chrome/browser/file_upload_panel/ui/context_menu_presenter.mm new file mode 100644 index 0000000..fb463f5 --- /dev/null +++ b/ios/chrome/browser/file_upload_panel/ui/context_menu_presenter.mm
@@ -0,0 +1,116 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "ios/chrome/browser/file_upload_panel/ui/context_menu_presenter.h" + +@interface ContextMenuPresenterButton : UIButton +@property(nonatomic, weak) id<UIContextMenuInteractionDelegate> + contextMenuInteractionDelegate; +@end + +@implementation ContextMenuPresenterButton + +- (UIContextMenuConfiguration*)contextMenuInteraction: + (UIContextMenuInteraction*)interaction + configurationForMenuAtLocation:(CGPoint)location { + if ([_contextMenuInteractionDelegate + respondsToSelector:@selector(contextMenuInteraction: + configurationForMenuAtLocation:)]) { + return [_contextMenuInteractionDelegate contextMenuInteraction:interaction + configurationForMenuAtLocation:location]; + } + + return [super contextMenuInteraction:interaction + configurationForMenuAtLocation:location]; +} + +- (void)contextMenuInteraction:(UIContextMenuInteraction*)interaction + willDisplayMenuForConfiguration:(UIContextMenuConfiguration*)configuration + animator: + (id<UIContextMenuInteractionAnimating>)animator { + [super contextMenuInteraction:interaction + willDisplayMenuForConfiguration:configuration + animator:animator]; + + if ([_contextMenuInteractionDelegate + respondsToSelector:@selector(contextMenuInteraction: + willDisplayMenuForConfiguration:animator:)]) { + [_contextMenuInteractionDelegate contextMenuInteraction:interaction + willDisplayMenuForConfiguration:configuration + animator:animator]; + } +} + +- (void)contextMenuInteraction:(UIContextMenuInteraction*)interaction + willEndForConfiguration:(UIContextMenuConfiguration*)configuration + animator:(id<UIContextMenuInteractionAnimating>)animator { + [super contextMenuInteraction:interaction + willEndForConfiguration:configuration + animator:animator]; + + if ([_contextMenuInteractionDelegate + respondsToSelector:@selector(contextMenuInteraction: + willEndForConfiguration:animator:)]) { + [_contextMenuInteractionDelegate contextMenuInteraction:interaction + willEndForConfiguration:configuration + animator:animator]; + } +} + +@end + +@implementation ContextMenuPresenter { + __weak UIView* _rootView; + ContextMenuPresenterButton* _button; +} + +@dynamic contextMenuInteractionDelegate; + +- (instancetype)initWithRootView:(UIView*)rootView { + self = [super init]; + if (self) { + _rootView = rootView; + _button = [ContextMenuPresenterButton buttonWithType:UIButtonTypeSystem]; + _button.hidden = YES; + _button.userInteractionEnabled = NO; + _button.contextMenuInteractionEnabled = YES; + _button.showsMenuAsPrimaryAction = YES; + } + return self; +} + +#pragma mark - Public properties + +- (void)setContextMenuInteractionDelegate: + (id<UIContextMenuInteractionDelegate>)contextMenuInteractionDelegate { + _button.contextMenuInteractionDelegate = contextMenuInteractionDelegate; +} + +- (id<UIContextMenuInteractionDelegate>)contextMenuInteractionDelegate { + return _button.contextMenuInteractionDelegate; +} + +- (UIContextMenuInteraction*)contextMenuInteraction { + return _button.contextMenuInteraction; +} + +#pragma mark - Public + +- (void)presentAtLocationInRootView:(CGPoint)locationInRootView { + if (!_rootView.window) { + return; + } + _button.frame = CGRectMake(locationInRootView.x, locationInRootView.y, 0, 0); + if (!_button.superview) { + [_rootView addSubview:_button]; + } + [_button performPrimaryAction]; +} + +- (void)dismiss { + [_button.contextMenuInteraction dismissMenu]; + [_button removeFromSuperview]; +} + +@end
diff --git a/ios/chrome/browser/first_run/public/BUILD.gn b/ios/chrome/browser/first_run/public/BUILD.gn index f387e5a..1028f05 100644 --- a/ios/chrome/browser/first_run/public/BUILD.gn +++ b/ios/chrome/browser/first_run/public/BUILD.gn
@@ -2,21 +2,6 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -source_set("features") { - sources = [ - "features.h", - "features.mm", - ] - public_deps = [ "//base" ] - deps = [ - ":best_features_item", - "//components/prefs", - "//ios/chrome/browser/first_run/ui_bundled:features", - "//ios/chrome/browser/first_run/ui_bundled/welcome_back/model", - "//ios/chrome/browser/shared/model/application_context", - ] -} - source_set("best_features_item") { sources = [ "best_features_item.h",
diff --git a/ios/chrome/browser/first_run/public/best_features_item.h b/ios/chrome/browser/first_run/public/best_features_item.h index b1d2a5e..5d26f3b 100644 --- a/ios/chrome/browser/first_run/public/best_features_item.h +++ b/ios/chrome/browser/first_run/public/best_features_item.h
@@ -20,7 +20,7 @@ kAutofillPasswordsInOtherApps = 6, kSharePasswordsWithFamily = 7 }; -// LINT.ThenChange(/ios/chrome/browser/first_run/ui_bundled/welcome_back/model/welcome_back_prefs.mm:IntToBestFeaturesItemType) +// LINT.ThenChange(/ios/chrome/browser/welcome_back/model/welcome_back_prefs.mm:IntToBestFeaturesItemType) // Holds properties and values needed to configure the items in the Best // Features Screen.
diff --git a/ios/chrome/browser/first_run/public/features.h b/ios/chrome/browser/first_run/public/features.h deleted file mode 100644 index 9d4951ab..0000000 --- a/ios/chrome/browser/first_run/public/features.h +++ /dev/null
@@ -1,19 +0,0 @@ -// Copyright 2025 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef IOS_CHROME_BROWSER_FIRST_RUN_PUBLIC_FEATURES_H_ -#define IOS_CHROME_BROWSER_FIRST_RUN_PUBLIC_FEATURES_H_ - -#import "base/feature_list.h" - -enum class BestFeaturesItemType; - -// Whether `kWelcomeBackInFirstRun` is enabled. This experiment is disabled -// when `kBestFeaturesScreenInFirstRun` is enabled. -bool IsWelcomeBackInFirstRunEnabled(); - -// Erases an item from `kWelcomeBackEligibleItems`. -void MarkWelcomeBackFeatureUsed(BestFeaturesItemType item_type); - -#endif // IOS_CHROME_BROWSER_FIRST_RUN_PUBLIC_FEATURES_H_
diff --git a/ios/chrome/browser/first_run/public/features.mm b/ios/chrome/browser/first_run/public/features.mm deleted file mode 100644 index 4864da2..0000000 --- a/ios/chrome/browser/first_run/public/features.mm +++ /dev/null
@@ -1,25 +0,0 @@ -// Copyright 2025 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "ios/chrome/browser/first_run/public/features.h" - -#import "base/metrics/field_trial_params.h" -#import "components/prefs/scoped_user_pref_update.h" -#import "ios/chrome/browser/first_run/public/best_features_item.h" -#import "ios/chrome/browser/first_run/ui_bundled/features.h" -#import "ios/chrome/browser/first_run/ui_bundled/welcome_back/model/welcome_back_prefs.h" -#import "ios/chrome/browser/shared/model/application_context/application_context.h" - -bool IsWelcomeBackInFirstRunEnabled() { - return base::FeatureList::IsEnabled(first_run::kWelcomeBackInFirstRun) && - !base::FeatureList::IsEnabled( - first_run::kBestFeaturesScreenInFirstRun); -} - -void MarkWelcomeBackFeatureUsed(BestFeaturesItemType item_type) { - PrefService* local_state = GetApplicationContext()->GetLocalState(); - int pref = static_cast<int>(item_type); - ScopedListPrefUpdate update(local_state, kWelcomeBackEligibleItems); - update->EraseValue(base::Value(pref)); -}
diff --git a/ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/BUILD.gn b/ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/BUILD.gn index 6fbf364..bf0b2b85 100644 --- a/ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/BUILD.gn +++ b/ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/BUILD.gn
@@ -19,12 +19,9 @@ "//ios/chrome/browser/commerce/model:shopping_service", "//ios/chrome/browser/first_run/model", "//ios/chrome/browser/first_run/public:best_features_item", - "//ios/chrome/browser/first_run/public:best_features_item", - "//ios/chrome/browser/first_run/public:features", "//ios/chrome/browser/first_run/ui_bundled:features", "//ios/chrome/browser/first_run/ui_bundled:screen_delegate", "//ios/chrome/browser/first_run/ui_bundled/best_features/ui", - "//ios/chrome/browser/first_run/ui_bundled/welcome_back/ui", "//ios/chrome/browser/segmentation_platform/model", "//ios/chrome/browser/shared/coordinator/chrome_coordinator", "//ios/chrome/browser/shared/model/application_context", @@ -32,6 +29,8 @@ "//ios/chrome/browser/shared/model/profile", "//ios/chrome/browser/shared/public/features:system_flags", "//ios/chrome/browser/signin/model", + "//ios/chrome/browser/welcome_back/model:features", + "//ios/chrome/browser/welcome_back/ui", "//ios/chrome/common/ui/confirmation_alert", "//ios/chrome/common/ui/instruction_view:instructions_half_sheet", "//ios/chrome/common/ui/promo_style",
diff --git a/ios/chrome/browser/first_run/ui_bundled/features.h b/ios/chrome/browser/first_run/ui_bundled/features.h index 428c329..7b29e17 100644 --- a/ios/chrome/browser/first_run/ui_bundled/features.h +++ b/ios/chrome/browser/first_run/ui_bundled/features.h
@@ -57,23 +57,6 @@ kDBPromoFirstAndRemoveSignInSync, }; -// Enum to represent the arms of feature kWelcomeBackInFirstRun. -enum class WelcomeBackScreenVariationType { - kDisabled, - // Show the Search with Lens, Enhanced Safe Browsing, and Locked Incognito - // items. - kBasicsWithLockedIncognitoTabs, - // Show the Enhanced Safe Browsing, Search with Lens, and Save & Autofill - // Passwords items. - kBasicsWithPasswords, - // Show the Tab Groups, Locked Incognito, and Price Tracking & Insights items. - kProductivityAndShopping, - // Show the Search with Lens, Enhanced Safe Browsing, and Autofill Passwords - // in Other Apps items. If Credential Provider Extension (CPE) is already - // enabled, Autofill Passwords in Other Apps is replaced with Share Passwords. - kSignInBenefits, -}; - // Flag to enable the FRE Default Browser Experiment. BASE_DECLARE_FEATURE(kAnimatedDefaultBrowserPromoInFRE); @@ -90,9 +73,6 @@ // Feature to enable updates to the sequence of the first run screens. BASE_DECLARE_FEATURE(kUpdatedFirstRunSequence); -// Feature to enable the Welcome Back screen. -BASE_DECLARE_FEATURE(kWelcomeBackInFirstRun); - // Name of the parameter that controls the experiment type for the Animated // Default Browser Promo in the FRE experiment, which determines the layout of // the promo. @@ -106,10 +86,6 @@ // kUpdatedFirstRunSequence is enabled. extern const char kUpdatedFirstRunSequenceParam[]; -// Name of the param that indicates which variation of the -// kWelcomeBackInFirstRun is enabled. -extern const char kWelcomeBackInFirstRunParam[]; - // Returns which variation of the kBestFeaturesScreenInFirstRun feature is // enabled or `kDisabled` if the feature is disabled. BestFeaturesScreenVariationType GetBestFeaturesScreenVariationType(); @@ -120,14 +96,6 @@ UpdatedFRESequenceVariationType GetUpdatedFRESequenceVariation( ProfileIOS* profile); -// Returns which variation of the kWelcomeBackInFirstRun feature is enabled or -// `kDisabled` if the feature is disabled. -WelcomeBackScreenVariationType GetWelcomeBackScreenVariationType(); - -// Whether `kWelcomeBackInFirstRun` is enabled. This experiment is disabled when -// `kBestFeaturesScreenInFirstRun` is enabled. -bool IsWelcomeBackInFirstRunEnabled(); - // Whether the Default Browser Experiment in the FRE is enabled. This feature is // disabled when kUpdatedFirstRunSequence is enabled. bool IsAnimatedDefaultBrowserPromoInFREEnabled();
diff --git a/ios/chrome/browser/first_run/ui_bundled/features.mm b/ios/chrome/browser/first_run/ui_bundled/features.mm index 6140fa03..9e8dfb1 100644 --- a/ios/chrome/browser/first_run/ui_bundled/features.mm +++ b/ios/chrome/browser/first_run/ui_bundled/features.mm
@@ -25,8 +25,6 @@ BASE_FEATURE(kUpdatedFirstRunSequence, base::FEATURE_DISABLED_BY_DEFAULT); -BASE_FEATURE(kWelcomeBackInFirstRun, base::FEATURE_DISABLED_BY_DEFAULT); - const char kAnimatedDefaultBrowserPromoInFREExperimentType[] = "AnimatedDefaultBrowserPromoInFREExperimentType"; @@ -35,8 +33,6 @@ const char kUpdatedFirstRunSequenceParam[] = "updated-first-run-sequence-param"; -const char kWelcomeBackInFirstRunParam[] = "WelcomeBackInFirstRunParam"; - BestFeaturesScreenVariationType GetBestFeaturesScreenVariationType() { if (!base::FeatureList::IsEnabled(kBestFeaturesScreenInFirstRun)) { return BestFeaturesScreenVariationType::kDisabled; @@ -60,20 +56,6 @@ kUpdatedFirstRunSequenceParam, 1)); } -WelcomeBackScreenVariationType GetWelcomeBackScreenVariationType() { - if (!base::FeatureList::IsEnabled(kWelcomeBackInFirstRun)) { - return WelcomeBackScreenVariationType::kDisabled; - } - return static_cast<WelcomeBackScreenVariationType>( - base::GetFieldTrialParamByFeatureAsInt(kWelcomeBackInFirstRun, - kWelcomeBackInFirstRunParam, 1)); -} - -bool IsWelcomeBackInFirstRunEnabled() { - return base::FeatureList::IsEnabled(kWelcomeBackInFirstRun) && - !base::FeatureList::IsEnabled(kBestFeaturesScreenInFirstRun); -} - bool IsAnimatedDefaultBrowserPromoInFREEnabled() { return base::FeatureList::IsEnabled(kAnimatedDefaultBrowserPromoInFRE) && !base::FeatureList::IsEnabled(first_run::kUpdatedFirstRunSequence);
diff --git a/ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_coordinator.h b/ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_coordinator.h deleted file mode 100644 index fe73f31..0000000 --- a/ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_coordinator.h +++ /dev/null
@@ -1,17 +0,0 @@ -// Copyright 2025 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_WELCOME_BACK_COORDINATOR_WELCOME_BACK_COORDINATOR_H_ -#define IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_WELCOME_BACK_COORDINATOR_WELCOME_BACK_COORDINATOR_H_ - -#import "ios/chrome/browser/shared/coordinator/chrome_coordinator/chrome_coordinator.h" - -@protocol PromosManagerUIHandler; - -// Coordinator to present the Welcome Back screen. -@interface WelcomeBackCoordinator : ChromeCoordinator - -@end - -#endif // IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_WELCOME_BACK_COORDINATOR_WELCOME_BACK_COORDINATOR_H_
diff --git a/ios/chrome/browser/first_run/ui_bundled/welcome_back/model/BUILD.gn b/ios/chrome/browser/first_run/ui_bundled/welcome_back/model/BUILD.gn deleted file mode 100644 index 7cdebe6..0000000 --- a/ios/chrome/browser/first_run/ui_bundled/welcome_back/model/BUILD.gn +++ /dev/null
@@ -1,16 +0,0 @@ -# Copyright 2025 The Chromium Authors -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -source_set("model") { - sources = [ - "welcome_back_prefs.h", - "welcome_back_prefs.mm", - ] - public_deps = [ "//components/prefs" ] - deps = [ - "//ios/chrome/browser/first_run/public:best_features_item", - "//ios/chrome/browser/shared/model/application_context", - ] - frameworks = [ "UIKit.framework" ] -}
diff --git a/ios/chrome/browser/flags/BUILD.gn b/ios/chrome/browser/flags/BUILD.gn index 8a4c4ec..ce072365 100644 --- a/ios/chrome/browser/flags/BUILD.gn +++ b/ios/chrome/browser/flags/BUILD.gn
@@ -111,6 +111,7 @@ "//ios/chrome/browser/tabs/model/inactive_tabs:features", "//ios/chrome/browser/text_selection/model:model_utils", "//ios/chrome/browser/web/model:feature_flags", + "//ios/chrome/browser/welcome_back/model:features", "//ios/chrome/browser/whats_new/coordinator:util", "//ios/components/enterprise/data_controls:features", "//ios/components/security_interstitials/https_only_mode:feature",
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm index eb248bf..5092021 100644 --- a/ios/chrome/browser/flags/about_flags.mm +++ b/ios/chrome/browser/flags/about_flags.mm
@@ -127,6 +127,7 @@ #import "ios/chrome/browser/start_surface/ui_bundled/start_surface_features.h" #import "ios/chrome/browser/text_selection/model/text_selection_util.h" #import "ios/chrome/browser/web/model/features.h" +#import "ios/chrome/browser/welcome_back/model/features.h" #import "ios/chrome/browser/whats_new/coordinator/whats_new_util.h" #import "ios/chrome/grit/ios_strings.h" #import "ios/components/enterprise/data_controls/features.h" @@ -1195,13 +1196,13 @@ // LINT.ThenChange(/chrome/browser/about_flags.cc:AutofillVcnEnrollStrikeExpiryTime) const FeatureEntry::FeatureParam kWelcomeBackInFirstRunArm1[] = { - {first_run::kWelcomeBackInFirstRunParam, "1"}}; + {kWelcomeBackInFirstRunParam, "1"}}; const FeatureEntry::FeatureParam kWelcomeBackInFirstRunArm2[] = { - {first_run::kWelcomeBackInFirstRunParam, "2"}}; + {kWelcomeBackInFirstRunParam, "2"}}; const FeatureEntry::FeatureParam kWelcomeBackInFirstRunArm3[] = { - {first_run::kWelcomeBackInFirstRunParam, "3"}}; + {kWelcomeBackInFirstRunParam, "3"}}; const FeatureEntry::FeatureParam kWelcomeBackInFirstRunArm4[] = { - {first_run::kWelcomeBackInFirstRunParam, "4"}}; + {kWelcomeBackInFirstRunParam, "4"}}; const FeatureEntry::FeatureVariation kWelcomeBackInFirstRunVariations[] = { {" - Variant A: Basics with Locked Incognito", kWelcomeBackInFirstRunArm1, @@ -2454,7 +2455,7 @@ "AutofillVcnEnrollStrikeExpiryTime")}, {"ios-welcome-back-screen", flag_descriptions::kWelcomeBackInFirstRunName, flag_descriptions::kWelcomeBackInFirstRunDescription, flags_ui::kOsIos, - FEATURE_WITH_PARAMS_VALUE_TYPE(first_run::kWelcomeBackInFirstRun, + FEATURE_WITH_PARAMS_VALUE_TYPE(kWelcomeBackInFirstRun, kWelcomeBackInFirstRunVariations, "WelcomeBackInFirstRun")}, {"autofill-enable-flat-rate-card-benefits-from-curinos",
diff --git a/ios/chrome/browser/home_customization/coordinator/home_customization_mediator.mm b/ios/chrome/browser/home_customization/coordinator/home_customization_mediator.mm index f3b61d1d..d97ad8f 100644 --- a/ios/chrome/browser/home_customization/coordinator/home_customization_mediator.mm +++ b/ios/chrome/browser/home_customization/coordinator/home_customization_mediator.mm
@@ -126,8 +126,7 @@ return _prefService->GetBoolean( ntp_tiles::prefs::kTabResumptionHomeModuleEnabled); case CustomizationToggleType::kTips: { - return _prefService->GetBoolean( - prefs::kHomeCustomizationMagicStackTipsEnabled); + return _prefService->GetBoolean(ntp_tiles::prefs::kTipsHomeModuleEnabled); } case CustomizationToggleType::kShopCard: return _prefService->GetBoolean( @@ -166,7 +165,7 @@ ntp_tiles::prefs::kTabResumptionHomeModuleEnabled, enabled); break; case CustomizationToggleType::kTips: { - _prefService->SetBoolean(prefs::kHomeCustomizationMagicStackTipsEnabled, + _prefService->SetBoolean(ntp_tiles::prefs::kTipsHomeModuleEnabled, enabled); break; }
diff --git a/ios/chrome/browser/intelligence/bwg/coordinator/bwg_coordinator.mm b/ios/chrome/browser/intelligence/bwg/coordinator/bwg_coordinator.mm index b742c7daf..7a37120 100644 --- a/ios/chrome/browser/intelligence/bwg/coordinator/bwg_coordinator.mm +++ b/ios/chrome/browser/intelligence/bwg/coordinator/bwg_coordinator.mm
@@ -182,7 +182,6 @@ // Handles the dismissal of the UI. - (void)presentationControllerDidDismiss: (UIPresentationController*)presentationController { - // TODO(crbug.com/419064727): Add metric for dismissing coordinator. [_BWGCommandsHandler dismissBWGFlowWithCompletion:nil]; }
diff --git a/ios/chrome/browser/intelligence/bwg/model/bwg_configuration.h b/ios/chrome/browser/intelligence/bwg/model/bwg_configuration.h index 0d82d4a..c0530cb 100644 --- a/ios/chrome/browser/intelligence/bwg/model/bwg_configuration.h +++ b/ios/chrome/browser/intelligence/bwg/model/bwg_configuration.h
@@ -43,11 +43,6 @@ @property(nonatomic, assign) ios::provider::BWGLocationPermissionState BWGLocationPermissionState; -// TODO(crbug.com/434662294): Remove when migration is complete. -// The state of the BWG PageContext. -@property(nonatomic, assign) - ios::provider::BWGPageContextState BWGPageContextState; - // The state of the BWG PageContext computation. @property(nonatomic, assign) ios::provider::BWGPageContextComputationState BWGPageContextComputationState;
diff --git a/ios/chrome/browser/lens/ui_bundled/BUILD.gn b/ios/chrome/browser/lens/ui_bundled/BUILD.gn index b909314..c33f1a8 100644 --- a/ios/chrome/browser/lens/ui_bundled/BUILD.gn +++ b/ios/chrome/browser/lens/ui_bundled/BUILD.gn
@@ -22,7 +22,6 @@ "//ios/chrome/app/strings:ios_strings_grit", "//ios/chrome/browser/feature_engagement/model", "//ios/chrome/browser/first_run/public:best_features_item", - "//ios/chrome/browser/first_run/public:features", "//ios/chrome/browser/intents/model:model_donation_helper", "//ios/chrome/browser/search_engines/model:template_url_service_factory", "//ios/chrome/browser/shared/coordinator/chrome_coordinator", @@ -42,6 +41,7 @@ "//ios/chrome/browser/tips_manager/model:tips_manager", "//ios/chrome/browser/url_loading/model", "//ios/chrome/browser/web/model", + "//ios/chrome/browser/welcome_back/model:features", "//ios/chrome/common:string_conversions", "//ios/chrome/common/app_group", "//ios/public/provider/chrome/browser/lens:lens_api",
diff --git a/ios/chrome/browser/lens/ui_bundled/DEPS b/ios/chrome/browser/lens/ui_bundled/DEPS index db6c5201..180fc4f 100644 --- a/ios/chrome/browser/lens/ui_bundled/DEPS +++ b/ios/chrome/browser/lens/ui_bundled/DEPS
@@ -2,7 +2,7 @@ "+components/lens/lens_metrics.h", "+ios/chrome/browser/feature_engagement/model/tracker_factory.h", "+ios/chrome/browser/first_run/public/best_features_item.h", - "+ios/chrome/browser/first_run/public/features.h", + "+ios/chrome/browser/welcome_back/model/features.h", "+ios/chrome/browser/intents/model/intents_donation_helper.h", "+ios/chrome/browser/lens_overlay/coordinator/lens_overlay_availability.h", "+ios/chrome/browser/search_engines/model/template_url_service_factory.h",
diff --git a/ios/chrome/browser/lens/ui_bundled/lens_coordinator.mm b/ios/chrome/browser/lens/ui_bundled/lens_coordinator.mm index 367d09d..27b04665 100644 --- a/ios/chrome/browser/lens/ui_bundled/lens_coordinator.mm +++ b/ios/chrome/browser/lens/ui_bundled/lens_coordinator.mm
@@ -14,7 +14,6 @@ #import "components/segmentation_platform/embedder/home_modules/tips_manager/signal_constants.h" #import "ios/chrome/browser/feature_engagement/model/tracker_factory.h" #import "ios/chrome/browser/first_run/public/best_features_item.h" -#import "ios/chrome/browser/first_run/public/features.h" #import "ios/chrome/browser/intents/model/intents_donation_helper.h" #import "ios/chrome/browser/lens/ui_bundled/features.h" #import "ios/chrome/browser/lens/ui_bundled/lens_availability.h" @@ -46,6 +45,7 @@ #import "ios/chrome/browser/url_loading/model/url_loading_browser_agent.h" #import "ios/chrome/browser/url_loading/model/url_loading_params.h" #import "ios/chrome/browser/web/model/web_navigation_util.h" +#import "ios/chrome/browser/welcome_back/model/features.h" #import "ios/chrome/common/NSString+Chromium.h" #import "ios/chrome/common/app_group/app_group_constants.h" #import "ios/chrome/grit/ios_strings.h"
diff --git a/ios/chrome/browser/metrics/model/first_user_action_recorder_unittest.cc b/ios/chrome/browser/metrics/model/first_user_action_recorder_unittest.cc index f82d303..b49ccf3 100644 --- a/ios/chrome/browser/metrics/model/first_user_action_recorder_unittest.cc +++ b/ios/chrome/browser/metrics/model/first_user_action_recorder_unittest.cc
@@ -50,10 +50,8 @@ FirstUserActionRecorder::EXPIRATION, 1); // Verify the expiration histogram contains a single duration value. - // TODO(crbug.com/41211458): Ideally this would also verify the value is in - // the correct bucket. - histogram_tester_->ExpectTotalCount( - kFirstUserActionExpirationHistogramName[is_pad_], 1); + histogram_tester_->ExpectUniqueSample( + kFirstUserActionExpirationHistogramName[is_pad_], 1, 1); } TEST_F(FirstUserActionRecorderTest, RecordStartOnNTP) { @@ -76,10 +74,8 @@ FirstUserActionRecorder::CONTINUATION, 1); // Verify the continuation histogram contains a single duration value. - // TODO(crbug.com/41211458): Ideally this would also verify the value is in - // the correct bucket. - histogram_tester_->ExpectTotalCount( - kFirstUserActionContinuationHistogramName[is_pad_], 1); + histogram_tester_->ExpectUniqueSample( + kFirstUserActionContinuationHistogramName[is_pad_], 1, 1); } TEST_F(FirstUserActionRecorderTest, OnUserAction_NewTask) { @@ -92,10 +88,8 @@ FirstUserActionRecorder::NEW_TASK, 1); // Verify the 'new task' histogram contains a single duration value. - // TODO(crbug.com/41211458): Ideally this would also verify the value is in - // the correct bucket. - histogram_tester_->ExpectTotalCount( - kFirstUserActionNewTaskHistogramName[is_pad_], 1); + histogram_tester_->ExpectUniqueSample( + kFirstUserActionNewTaskHistogramName[is_pad_], 1, 1); } TEST_F(FirstUserActionRecorderTest, OnUserAction_Ignored) { @@ -125,10 +119,8 @@ FirstUserActionRecorder::CONTINUATION, 1); // Verify the continuation histogram contains a single duration value. - // TODO(crbug.com/41211458): Ideally this would also verify the value is in - // the correct bucket. - histogram_tester_->ExpectTotalCount( - kFirstUserActionContinuationHistogramName[is_pad_], 1); + histogram_tester_->ExpectUniqueSample( + kFirstUserActionContinuationHistogramName[is_pad_], 1, 1); } TEST_F(FirstUserActionRecorderTest, OnUserAction_RethrowAction_NewTask) { @@ -142,8 +134,6 @@ FirstUserActionRecorder::NEW_TASK, 1); // Verify the 'new task' histogram contains the a single duration value. - // TODO(crbug.com/41211458): Ideally this would also verify the value is in - // the correct bucket. - histogram_tester_->ExpectTotalCount( - kFirstUserActionNewTaskHistogramName[is_pad_], 1); + histogram_tester_->ExpectUniqueSample( + kFirstUserActionNewTaskHistogramName[is_pad_], 1, 1); }
diff --git a/ios/chrome/browser/metrics/model/ios_push_notifications_metrics_provider_unittest.mm b/ios/chrome/browser/metrics/model/ios_push_notifications_metrics_provider_unittest.mm index c696b1c2..8db17fa4 100644 --- a/ios/chrome/browser/metrics/model/ios_push_notifications_metrics_provider_unittest.mm +++ b/ios/chrome/browser/metrics/model/ios_push_notifications_metrics_provider_unittest.mm
@@ -9,6 +9,7 @@ #import "base/test/metrics/histogram_tester.h" #import "base/types/cxx23_to_underlying.h" #import "components/signin/public/base/signin_metrics.h" +#import "google_apis/gaia/gaia_id.h" #import "ios/chrome/browser/metrics/model/constants.h" #import "ios/chrome/browser/push_notification/model/push_notification_util.h" #import "ios/chrome/browser/shared/model/application_context/application_context.h"
diff --git a/ios/chrome/browser/ntp/model/BUILD.gn b/ios/chrome/browser/ntp/model/BUILD.gn index 88387a8..968df8a2 100644 --- a/ios/chrome/browser/ntp/model/BUILD.gn +++ b/ios/chrome/browser/ntp/model/BUILD.gn
@@ -70,6 +70,7 @@ ":set_up_list_metrics", ":set_up_list_prefs", "//base", + "//components/ntp_tiles:pref_names", "//components/password_manager/core/browser", "//components/prefs", "//components/prefs/ios", @@ -109,8 +110,8 @@ ":set_up_list_item_type", ":set_up_list_metrics", "//base", + "//components/ntp_tiles:pref_names", "//components/prefs", - "//ios/chrome/browser/shared/model/prefs:pref_names", "//ios/chrome/browser/shared/public/features", ] } @@ -129,6 +130,7 @@ ":set_up_list_prefs", ":util", "//base/test:test_support", + "//components/ntp_tiles:pref_names", "//components/password_manager/core/browser", "//components/prefs", "//components/strings",
diff --git a/ios/chrome/browser/ntp/model/set_up_list.mm b/ios/chrome/browser/ntp/model/set_up_list.mm index 8754b724..567815f 100644 --- a/ios/chrome/browser/ntp/model/set_up_list.mm +++ b/ios/chrome/browser/ntp/model/set_up_list.mm
@@ -8,6 +8,7 @@ #import "base/memory/raw_ptr.h" #import "base/strings/sys_string_conversions.h" +#import "components/ntp_tiles/pref_names.h" #import "components/password_manager/core/browser/password_manager_util.h" #import "components/prefs/ios/pref_observer_bridge.h" #import "components/prefs/pref_change_registrar.h" @@ -55,7 +56,6 @@ return push_notification_settings:: IsMobileNotificationsEnabledForAnyClient(account.gaia, prefs); } - case SetUpListItemType::kFollow: case SetUpListItemType::kAllSet: NOTREACHED(); } @@ -132,7 +132,7 @@ + (instancetype)buildFromPrefs:(PrefService*)prefs identityManager:(signin::IdentityManager*)identityManager localState:(PrefService*)localState { - if (!prefs->GetBoolean(prefs::kHomeCustomizationMagicStackTipsEnabled)) { + if (!prefs->GetBoolean(ntp_tiles::prefs::kTipsHomeModuleEnabled)) { return nil; } @@ -153,7 +153,6 @@ set_up_list_prefs::MarkAllItemsComplete(localState); } - // TODO(crbug.com/40262090): Add a Follow item to the Set Up List. return [[self alloc] initWithItems:items localState:localState]; } @@ -170,8 +169,6 @@ _prefObserverBridge->ObserveChangesForPreference( set_up_list_prefs::kAutofillItemState, &_prefChangeRegistrar); _prefObserverBridge->ObserveChangesForPreference( - set_up_list_prefs::kFollowItemState, &_prefChangeRegistrar); - _prefObserverBridge->ObserveChangesForPreference( set_up_list_prefs::kNotificationsItemState, &_prefChangeRegistrar); } return self;
diff --git a/ios/chrome/browser/ntp/model/set_up_list_item_type.h b/ios/chrome/browser/ntp/model/set_up_list_item_type.h index 1ee5a33..493a119 100644 --- a/ios/chrome/browser/ntp/model/set_up_list_item_type.h +++ b/ios/chrome/browser/ntp/model/set_up_list_item_type.h
@@ -13,7 +13,7 @@ // Removed: kSignInSync = 1, kDefaultBrowser = 2, kAutofill = 3, - kFollow = 4, + // Removed: kFollow = 4, kAllSet = 5, kNotifications = 6, // Removed: kDocking = 7,
diff --git a/ios/chrome/browser/ntp/model/set_up_list_prefs.h b/ios/chrome/browser/ntp/model/set_up_list_prefs.h index f6e7956..0e0ac5c 100644 --- a/ios/chrome/browser/ntp/model/set_up_list_prefs.h +++ b/ios/chrome/browser/ntp/model/set_up_list_prefs.h
@@ -17,7 +17,6 @@ // Prefs to store the state of each item in the list. extern const char kDefaultBrowserItemState[]; extern const char kAutofillItemState[]; -extern const char kFollowItemState[]; extern const char kNotificationsItemState[]; extern const char kAllItemsComplete[]; extern const char kDisabled[];
diff --git a/ios/chrome/browser/ntp/model/set_up_list_prefs.mm b/ios/chrome/browser/ntp/model/set_up_list_prefs.mm index 96ab8b0..7013718 100644 --- a/ios/chrome/browser/ntp/model/set_up_list_prefs.mm +++ b/ios/chrome/browser/ntp/model/set_up_list_prefs.mm
@@ -4,11 +4,11 @@ #import "ios/chrome/browser/ntp/model/set_up_list_prefs.h" +#import "components/ntp_tiles/pref_names.h" #import "components/prefs/pref_registry_simple.h" #import "components/prefs/pref_service.h" #import "ios/chrome/browser/ntp/model/set_up_list_item_type.h" #import "ios/chrome/browser/ntp/model/set_up_list_metrics.h" -#import "ios/chrome/browser/shared/model/prefs/pref_names.h" #import "ios/chrome/browser/shared/public/features/features.h" namespace set_up_list_prefs { @@ -16,7 +16,6 @@ const char kDefaultBrowserItemState[] = "set_up_list.default_browser_item.state"; const char kAutofillItemState[] = "set_up_list.autofill_item.state"; -const char kFollowItemState[] = "set_up_list.follow_item.state"; const char kNotificationsItemState[] = "set_up_list.content_notification_item.state"; const char kAllItemsComplete[] = "set_up_list.all_items_complete"; @@ -27,7 +26,6 @@ int unknown = static_cast<int>(SetUpListItemState::kUnknown); registry->RegisterIntegerPref(kDefaultBrowserItemState, unknown); registry->RegisterIntegerPref(kAutofillItemState, unknown); - registry->RegisterIntegerPref(kFollowItemState, unknown); registry->RegisterIntegerPref(kNotificationsItemState, unknown); registry->RegisterBooleanPref(kAllItemsComplete, false); registry->RegisterBooleanPref(kDisabled, false); @@ -40,8 +38,6 @@ return kDefaultBrowserItemState; case SetUpListItemType::kAutofill: return kAutofillItemState; - case SetUpListItemType::kFollow: - return kFollowItemState; case SetUpListItemType::kNotifications: return kNotificationsItemState; case SetUpListItemType::kAllSet: @@ -90,7 +86,7 @@ } bool IsSetUpListDisabled(PrefService* prefs) { - return !prefs->GetBoolean(prefs::kHomeCustomizationMagicStackTipsEnabled); + return !prefs->GetBoolean(ntp_tiles::prefs::kTipsHomeModuleEnabled); } void RecordInteraction(PrefService* prefs) {
diff --git a/ios/chrome/browser/ntp/model/set_up_list_unittest.mm b/ios/chrome/browser/ntp/model/set_up_list_unittest.mm index 9c1e604..8d5f607 100644 --- a/ios/chrome/browser/ntp/model/set_up_list_unittest.mm +++ b/ios/chrome/browser/ntp/model/set_up_list_unittest.mm
@@ -9,6 +9,7 @@ #import "base/test/gtest_util.h" #import "base/test/metrics/histogram_tester.h" #import "base/test/scoped_feature_list.h" +#import "components/ntp_tiles/pref_names.h" #import "components/password_manager/core/browser/password_manager_util.h" #import "components/prefs/scoped_user_pref_update.h" #import "components/signin/public/base/signin_metrics.h" @@ -274,13 +275,6 @@ SetUpListItemState::kCompleteNotInList); } -// Tests that the SetUpList uses the correct criteria when including the -// Follow item. -TEST_F(SetUpListTest, BuildListWithFollow) { - BuildSetUpList(); - ExpectListToNotInclude(SetUpListItemType::kFollow); -} - // Tests that SetUpList observes local state changes, updates the item, and // calls the delegate. TEST_F(SetUpListTest, ObservesPrefs) { @@ -342,7 +336,7 @@ // Tests that the Set Up List can be disabled. TEST_F(SetUpListTest, Disable) { EXPECT_FALSE(set_up_list_prefs::IsSetUpListDisabled(prefs_)); - prefs_->SetBoolean(prefs::kHomeCustomizationMagicStackTipsEnabled, false); + prefs_->SetBoolean(ntp_tiles::prefs::kTipsHomeModuleEnabled, false); EXPECT_TRUE(set_up_list_prefs::IsSetUpListDisabled(prefs_)); BuildSetUpList();
diff --git a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator.mm b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator.mm index b43c41a..8e61534f 100644 --- a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator.mm +++ b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator.mm
@@ -1625,8 +1625,8 @@ safety_check::prefs::kSafetyCheckHomeModuleEnabled); BOOL tabResumptionEnabled = prefService->GetBoolean( ntp_tiles::prefs::kTabResumptionHomeModuleEnabled); - BOOL tipsEnabled = prefService->GetBoolean( - prefs::kHomeCustomizationMagicStackTipsEnabled); + BOOL tipsEnabled = + prefService->GetBoolean(ntp_tiles::prefs::kTipsHomeModuleEnabled); [self.NTPMetricsRecorder recordMagicStackCustomizationStateWithSafetyCheck:safetyCheckEnabled tabResumption:tabResumptionEnabled
diff --git a/ios/chrome/browser/omnibox/eg_tests/omnibox_popup_egtest.mm b/ios/chrome/browser/omnibox/eg_tests/omnibox_popup_egtest.mm index e1f3855..c203468 100644 --- a/ios/chrome/browser/omnibox/eg_tests/omnibox_popup_egtest.mm +++ b/ios/chrome/browser/omnibox/eg_tests/omnibox_popup_egtest.mm
@@ -721,4 +721,38 @@ [ChromeEarlGrey clearPasteboard]; } +- (void)testHardwareKeyboardSelectLinkYouCopiedAsFirstElement { + // Start a server to be able to navigate to a web page. + self.testServer->RegisterRequestHandler( + base::BindRepeating(&omnibox::OmniboxHTTPResponses)); + GREYAssertTrue(self.testServer->Start(), @"Test server failed to start."); + const GURL pageURL = self.testServer->GetURL(omnibox::PageURL(1)); + // Copy link in clipboard. + [ChromeEarlGrey + copyLinkAsURLToPasteBoard:[NSString cr_fromString:pageURL.spec()]]; + + // Focus the fake omnibox. + [[EarlGrey selectElementWithMatcher:chrome_test_util::FakeOmnibox()] + performAction:grey_tap()]; + [ChromeEarlGrey + waitForSufficientlyVisibleElementWithMatcher:chrome_test_util::Omnibox()]; + + // Wait for the clipboard suggestion to show. + [ChromeEarlGrey waitForUIElementToAppearWithMatcher:LinkYouCopiedRow()]; + + // The omnibox popup may update multiple times. Don't downArrow until this + // is done. + base::test::ios::SpinRunLoopWithMinDelay(base::Seconds(1)); + + // Highlight the text you copied row. Accept with Return. + [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"downArrow" flags:0]; + base::test::ios::SpinRunLoopWithMinDelay(base::Seconds(0.1)); + [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"\r" flags:0]; + + // The web page should load. + [ChromeEarlGrey waitForWebStateContainingText:omnibox::PageContent(1)]; + + [ChromeEarlGrey clearPasteboard]; +} + @end
diff --git a/ios/chrome/browser/omnibox/ui/omnibox_text_field_ios.mm b/ios/chrome/browser/omnibox/ui/omnibox_text_field_ios.mm index bc1b7aa..695d1f90 100644 --- a/ios/chrome/browser/omnibox/ui/omnibox_text_field_ios.mm +++ b/ios/chrome/browser/omnibox/ui/omnibox_text_field_ios.mm
@@ -166,6 +166,14 @@ [[NSNotificationCenter defaultCenter] removeObserver:self]; } +- (void)setAllowsReturnKeyWithEmptyText:(BOOL)allowsReturnKeyWithEmptyText { + if (_allowsReturnKeyWithEmptyText == allowsReturnKeyWithEmptyText) { + return; + } + _allowsReturnKeyWithEmptyText = allowsReturnKeyWithEmptyText; + [self reloadInputViews]; +} + - (void)setText:(NSAttributedString*)text userTextLength:(size_t)userTextLength { DCHECK_LE(userTextLength, text.length);
diff --git a/ios/chrome/browser/omnibox/ui/omnibox_text_view_ios.mm b/ios/chrome/browser/omnibox/ui/omnibox_text_view_ios.mm index 61be69c..2a68c200 100644 --- a/ios/chrome/browser/omnibox/ui/omnibox_text_view_ios.mm +++ b/ios/chrome/browser/omnibox/ui/omnibox_text_view_ios.mm
@@ -13,7 +13,6 @@ #import "base/not_fatal_until.h" #import "base/notreached.h" #import "base/strings/sys_string_conversions.h" -#import "base/task/sequenced_task_runner.h" #import "components/grit/components_scaled_resources.h" #import "components/omnibox/browser/autocomplete_input.h" #import "components/open_from_clipboard/clipboard_async_wrapper_ios.h" @@ -162,18 +161,11 @@ } - (void)setAllowsReturnKeyWithEmptyText:(BOOL)allowsReturnKeyWithEmptyText { + if (_allowsReturnKeyWithEmptyText == allowsReturnKeyWithEmptyText) { + return; + } _allowsReturnKeyWithEmptyText = allowsReturnKeyWithEmptyText; - - // To make sure the keyboard is correctly taking the new value into account, - // call `-reloadInputViews`. That being said, `-reloadInputViews` can - // update the input mode, which can itself call again this method. - // `-reloadInputViews` being non-reentrant (contention on - // `+[UIKeyboardAutomatic sharedInstance]`), call this asynchronously. - __weak __typeof(self) weakSelf = self; - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(^{ - [weakSelf reloadInputViews]; - })); + [self reloadInputViews]; } - (void)setText:(NSAttributedString*)text
diff --git a/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/BUILD.gn b/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/BUILD.gn index bff0156..a173bd1d 100644 --- a/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/BUILD.gn +++ b/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/BUILD.gn
@@ -32,7 +32,6 @@ "//ios/chrome/browser/favicon/model", "//ios/chrome/browser/feature_engagement/model", "//ios/chrome/browser/first_run/public:best_features_item", - "//ios/chrome/browser/first_run/public:features", "//ios/chrome/browser/passwords/model", "//ios/chrome/browser/passwords/model:password_controller_delegate", "//ios/chrome/browser/passwords/model:store_factory", @@ -48,6 +47,7 @@ "//ios/chrome/browser/shared/ui/symbols", "//ios/chrome/browser/tips_manager/model:factory", "//ios/chrome/browser/tips_manager/model:tips_manager", + "//ios/chrome/browser/welcome_back/model:features", "//ios/chrome/common/ui/favicon:favicon_constants", "//ios/chrome/common/ui/reauthentication", "//ios/web/public",
diff --git a/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/DEPS b/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/DEPS index a260780..3038a38 100644 --- a/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/DEPS +++ b/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/DEPS
@@ -1,4 +1,4 @@ include_rules = [ "+ios/chrome/browser/first_run/public/best_features_item.h", - "+ios/chrome/browser/first_run/public/features.h", + "+ios/chrome/browser/welcome_back/model/features.h", ]
diff --git a/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/password_suggestion_bottom_sheet_coordinator.mm b/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/password_suggestion_bottom_sheet_coordinator.mm index e110084a..31561e2 100644 --- a/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/password_suggestion_bottom_sheet_coordinator.mm +++ b/ios/chrome/browser/passwords/ui_bundled/bottom_sheet/password_suggestion_bottom_sheet_coordinator.mm
@@ -14,7 +14,6 @@ #import "ios/chrome/browser/favicon/model/ios_chrome_favicon_loader_factory.h" #import "ios/chrome/browser/feature_engagement/model/tracker_factory.h" #import "ios/chrome/browser/first_run/public/best_features_item.h" -#import "ios/chrome/browser/first_run/public/features.h" #import "ios/chrome/browser/passwords/model/ios_chrome_account_password_store_factory.h" #import "ios/chrome/browser/passwords/model/ios_chrome_profile_password_store_factory.h" #import "ios/chrome/browser/passwords/model/password_controller_delegate.h" @@ -28,6 +27,7 @@ #import "ios/chrome/browser/shared/public/features/features.h" #import "ios/chrome/browser/tips_manager/model/tips_manager_ios.h" #import "ios/chrome/browser/tips_manager/model/tips_manager_ios_factory.h" +#import "ios/chrome/browser/welcome_back/model/features.h" #import "ios/chrome/common/ui/reauthentication/reauthentication_module.h" #import "ios/web/public/web_state.h" #import "services/network/public/cpp/shared_url_loader_factory.h"
diff --git a/ios/chrome/browser/policy/model/reporting/profile_report_generator_ios_unittest.mm b/ios/chrome/browser/policy/model/reporting/profile_report_generator_ios_unittest.mm index 9ce7a030..72b86d5 100644 --- a/ios/chrome/browser/policy/model/reporting/profile_report_generator_ios_unittest.mm +++ b/ios/chrome/browser/policy/model/reporting/profile_report_generator_ios_unittest.mm
@@ -264,7 +264,7 @@ EXPECT_TRUE(report->has_chrome_signed_in_user()); EXPECT_EQ(base::SysNSStringToUTF8(fake_identity.userEmail), report->chrome_signed_in_user().email()); - EXPECT_EQ(base::SysNSStringToUTF8(fake_identity.gaiaID), + EXPECT_EQ(fake_identity.gaiaId.ToString(), report->chrome_signed_in_user().obfuscated_gaia_id()); }
diff --git a/ios/chrome/browser/prerender/model/prerender_service.h b/ios/chrome/browser/prerender/model/prerender_service.h deleted file mode 100644 index 867579f..0000000 --- a/ios/chrome/browser/prerender/model/prerender_service.h +++ /dev/null
@@ -1,69 +0,0 @@ -// Copyright 2017 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef IOS_CHROME_BROWSER_PRERENDER_MODEL_PRERENDER_SERVICE_H_ -#define IOS_CHROME_BROWSER_PRERENDER_MODEL_PRERENDER_SERVICE_H_ - -#include "components/keyed_service/core/keyed_service.h" -#include "ios/web/public/navigation/referrer.h" -#include "ui/base/page_transition_types.h" -#include "url/gurl.h" - -@protocol PreloadControllerDelegate; - -namespace web { -class WebState; -} -class Browser; - -// PrerenderService manages a prerendered WebState. -class PrerenderService : public KeyedService { - public: - // Sets the delegate that will provide information to this service. - virtual void SetDelegate(id<PreloadControllerDelegate> delegate) = 0; - - // Prerenders the given `url` with the given `transition`. Normally, - // prerender requests are fulfilled after a short delay, to prevent - // unnecessary prerenders while the user is typing. If `immediately` is YES, - // this method starts prerendering immediately, with no delay. - // `web_state_to_replace` is provided so that the new prerendered web state - // can have the same session data. `immediately` should be set to YES only - // when there is a very high confidence that the user will navigate to the - // given `url`. - // TODO(crbug.com/40726702): passing `web_state_to_replace` is a workaround - // for not having prerender service per browser, remove it once - // prerenderService is a browser agent. - // - // If there is already an existing request for `url`, this method does nothing - // and does not reset the delay timer. If there is an existing request for a - // different URL, this method cancels that request and queues this request - // instead. - virtual void StartPrerender(const GURL& url, - const web::Referrer& referrer, - ui::PageTransition transition, - web::WebState* web_state_to_replace, - bool immediately) = 0; - - // If `url` is prerendered, loads the prerendered web state into - // `browser`'s WebStateList at the active index, replacing the existing active - // WebState and saving the session. If not, or if it isn't possible to replace - // the active web state, cancels the active preload. Metrics and snapshots are - // appropriately updated. Returns true if the active webstate was replaced, - // false otherwise. - virtual bool MaybeLoadPrerenderedURL(const GURL& url, - ui::PageTransition transition, - Browser* browser) = 0; - - // `true` while a prerendered webstate is being inserted into a webStateList. - virtual bool IsLoadingPrerender() = 0; - - // Cancels any outstanding prerender requests and destroys any prerendered - // pages. - virtual void CancelAllPrerenders() = 0; - - // Returns true if there is a prerender for the given `url`. - virtual bool HasPrerenderForUrl(const GURL& url) = 0; -}; - -#endif // IOS_CHROME_BROWSER_PRERENDER_MODEL_PRERENDER_SERVICE_H_
diff --git a/ios/chrome/browser/price_notifications/ui_bundled/BUILD.gn b/ios/chrome/browser/price_notifications/ui_bundled/BUILD.gn index 60c0b288..4d9b300 100644 --- a/ios/chrome/browser/price_notifications/ui_bundled/BUILD.gn +++ b/ios/chrome/browser/price_notifications/ui_bundled/BUILD.gn
@@ -25,7 +25,6 @@ "//ios/chrome/browser/bookmarks/model", "//ios/chrome/browser/commerce/model:shopping_service", "//ios/chrome/browser/first_run/public:best_features_item", - "//ios/chrome/browser/first_run/public:features", "//ios/chrome/browser/price_insights/coordinator:delegates", "//ios/chrome/browser/price_insights/ui:price_insights_ui", "//ios/chrome/browser/price_notifications/ui_bundled/cells", @@ -44,6 +43,7 @@ "//ios/chrome/browser/shared/ui/table_view", "//ios/chrome/browser/shared/ui/table_view:utils", "//ios/chrome/browser/tabs/model", + "//ios/chrome/browser/welcome_back/model:features", "//ios/web/public", "//url", ]
diff --git a/ios/chrome/browser/price_notifications/ui_bundled/DEPS b/ios/chrome/browser/price_notifications/ui_bundled/DEPS index 060a395..f3be4dc 100644 --- a/ios/chrome/browser/price_notifications/ui_bundled/DEPS +++ b/ios/chrome/browser/price_notifications/ui_bundled/DEPS
@@ -6,5 +6,5 @@ "+ios/chrome/browser/push_notification/model", "+ios/chrome/browser/tabs/model/tab_title_util.h", "+ios/chrome/browser/first_run/public/best_features_item.h", - "+ios/chrome/browser/first_run/public/features.h", + "+ios/chrome/browser/welcome_back/model/features.h", ]
diff --git a/ios/chrome/browser/price_notifications/ui_bundled/price_notifications_price_tracking_mediator.mm b/ios/chrome/browser/price_notifications/ui_bundled/price_notifications_price_tracking_mediator.mm index 4c061b03..b25c32d9 100644 --- a/ios/chrome/browser/price_notifications/ui_bundled/price_notifications_price_tracking_mediator.mm +++ b/ios/chrome/browser/price_notifications/ui_bundled/price_notifications_price_tracking_mediator.mm
@@ -19,7 +19,6 @@ #import "components/power_bookmarks/core/proto/shopping_specifics.pb.h" #import "google_apis/gaia/gaia_id.h" #import "ios/chrome/browser/first_run/public/best_features_item.h" -#import "ios/chrome/browser/first_run/public/features.h" #import "ios/chrome/browser/price_insights/coordinator/price_insights_consumer.h" #import "ios/chrome/browser/price_notifications/ui_bundled/cells/price_notifications_table_view_item.h" #import "ios/chrome/browser/price_notifications/ui_bundled/price_notifications_alert_presenter.h" @@ -30,6 +29,7 @@ #import "ios/chrome/browser/shared/public/commands/bookmarks_commands.h" #import "ios/chrome/browser/shared/public/commands/price_tracked_items_commands.h" #import "ios/chrome/browser/tabs/model/tab_title_util.h" +#import "ios/chrome/browser/welcome_back/model/features.h" #import "ios/web/public/web_state.h" #import "url/gurl.h"
diff --git a/ios/chrome/browser/promos_manager/ui_bundled/BUILD.gn b/ios/chrome/browser/promos_manager/ui_bundled/BUILD.gn index 212a03bd..c8c608c8 100644 --- a/ios/chrome/browser/promos_manager/ui_bundled/BUILD.gn +++ b/ios/chrome/browser/promos_manager/ui_bundled/BUILD.gn
@@ -101,7 +101,6 @@ "//ios/chrome/browser/default_promo/ui_bundled/promo_handler", "//ios/chrome/browser/docking_promo/ui", "//ios/chrome/browser/first_run/ui_bundled:features", - "//ios/chrome/browser/first_run/ui_bundled/welcome_back/ui", "//ios/chrome/browser/post_restore_signin/ui_bundled", "//ios/chrome/browser/promos_manager/model:constants", "//ios/chrome/browser/safari_data_import/coordinator:promo", @@ -114,6 +113,8 @@ "//ios/chrome/browser/shared/public/features", "//ios/chrome/browser/shared/public/features:system_flags", "//ios/chrome/browser/sync/model", + "//ios/chrome/browser/welcome_back/model:features", + "//ios/chrome/browser/welcome_back/ui", "//ios/chrome/browser/whats_new/coordinator:util", "//ios/chrome/browser/whats_new/coordinator/promo", "//ios/chrome/common/ui/confirmation_alert",
diff --git a/ios/chrome/browser/promos_manager/ui_bundled/DEPS b/ios/chrome/browser/promos_manager/ui_bundled/DEPS index c0c5481b..1c35b68 100644 --- a/ios/chrome/browser/promos_manager/ui_bundled/DEPS +++ b/ios/chrome/browser/promos_manager/ui_bundled/DEPS
@@ -11,11 +11,12 @@ "+ios/chrome/browser/sync/model/sync_service_factory.h", "+ios/chrome/browser/whats_new/coordinator", "+ios/chrome/browser/authentication/ui_bundled/signin/promo", - "+ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/welcome_back_display_handler.h", + "+ios/chrome/browser/welcome_back/ui/welcome_back_display_handler.h", "+ios/chrome/browser/first_run/ui_bundled/features.h", "+ios/chrome/browser/authentication/ui_bundled/signin/features.h", "+ios/chrome/browser/safari_data_import/coordinator/safari_data_import_reminder_promo_display_handler.h", "+ios/chrome/browser/safari_data_import/public/safari_data_import_entry_point.h", + "+ios/chrome/browser/welcome_back/model/features.h" ] specific_include_rules = {
diff --git a/ios/chrome/browser/promos_manager/ui_bundled/promos_manager_coordinator.mm b/ios/chrome/browser/promos_manager/ui_bundled/promos_manager_coordinator.mm index c8b8bb0..2fe4db6 100644 --- a/ios/chrome/browser/promos_manager/ui_bundled/promos_manager_coordinator.mm +++ b/ios/chrome/browser/promos_manager/ui_bundled/promos_manager_coordinator.mm
@@ -38,7 +38,6 @@ #import "ios/chrome/browser/docking_promo/ui/docking_promo_display_handler.h" #import "ios/chrome/browser/feature_engagement/model/tracker_factory.h" #import "ios/chrome/browser/first_run/ui_bundled/features.h" -#import "ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/welcome_back_display_handler.h" #import "ios/chrome/browser/post_restore_signin/ui_bundled/post_restore_signin_provider.h" #import "ios/chrome/browser/promos_manager/model/features.h" #import "ios/chrome/browser/promos_manager/model/promo_config.h" @@ -64,6 +63,8 @@ #import "ios/chrome/browser/shared/public/features/features.h" #import "ios/chrome/browser/shared/public/features/system_flags.h" #import "ios/chrome/browser/sync/model/sync_service_factory.h" +#import "ios/chrome/browser/welcome_back/model/features.h" +#import "ios/chrome/browser/welcome_back/ui/welcome_back_display_handler.h" #import "ios/chrome/browser/whats_new/coordinator/promo/whats_new_promo_display_handler.h" #import "ios/chrome/browser/whats_new/coordinator/whats_new_util.h" #import "ios/chrome/common/ui/confirmation_alert/confirmation_alert_action_handler.h" @@ -490,12 +491,6 @@ [self.banneredProvider standardPromoLearnMoreAction]; } -// Invoked when a link in the disclaimer is tapped. -- (void)didTapURLInDisclaimer:(NSURL*)URL { - // TODO(crbug.com/40238885): Complete `didTapURLInDisclaimer` to bring users - // to Settings page. -} - #pragma mark - ConfirmationAlertActionHandler - (void)confirmationAlertPrimaryAction { @@ -639,7 +634,7 @@ } // Welcome Back promo handler. - if (first_run::IsWelcomeBackInFirstRunEnabled()) { + if (IsWelcomeBackInFirstRunEnabled()) { _displayHandlerPromos[promos_manager::Promo::WelcomeBack] = [[WelcomeBackDisplayHandler alloc] init]; }
diff --git a/ios/chrome/browser/reminder_notifications/model/reminder_notification_client.mm b/ios/chrome/browser/reminder_notifications/model/reminder_notification_client.mm index c41beaa..2a070ca2 100644 --- a/ios/chrome/browser/reminder_notifications/model/reminder_notification_client.mm +++ b/ios/chrome/browser/reminder_notifications/model/reminder_notification_client.mm
@@ -77,14 +77,14 @@ NSString* url_string = user_info[@"url"]; if (!url_string || url_string.length == 0) { - // TODO(crbug.com/390432325): Consider adding UMA logs for missing URL. + // TODO(crbug.com/422449238): Consider adding UMA logs for missing URL. return false; } GURL url(base::SysNSStringToUTF8(url_string)); if (!url.is_valid()) { - // TODO(crbug.com/390432325): Consider adding UMA logs for invalid URL. + // TODO(crbug.com/422449238): Consider adding UMA logs for invalid URL. return false; }
diff --git a/ios/chrome/browser/safe_browsing/model/tailored_security/BUILD.gn b/ios/chrome/browser/safe_browsing/model/tailored_security/BUILD.gn index 33d7ac5..aadc8608 100644 --- a/ios/chrome/browser/safe_browsing/model/tailored_security/BUILD.gn +++ b/ios/chrome/browser/safe_browsing/model/tailored_security/BUILD.gn
@@ -21,13 +21,13 @@ "//components/safe_browsing/core/common:safe_browsing_prefs", "//components/signin/public/identity_manager", "//ios/chrome/browser/first_run/public:best_features_item", - "//ios/chrome/browser/first_run/public:features", "//ios/chrome/browser/infobars/model", "//ios/chrome/browser/overlays/model", "//ios/chrome/browser/shared/model/profile", "//ios/chrome/browser/shared/model/profile:profile_keyed_service_factory", "//ios/chrome/browser/signin/model", "//ios/chrome/browser/sync/model", + "//ios/chrome/browser/welcome_back/model:features", "//ios/components/security_interstitials/safe_browsing", ] } @@ -44,8 +44,8 @@ "//components/safe_browsing/core/common:safe_browsing_prefs", "//ios/chrome/app/strings", "//ios/chrome/browser/first_run/public:best_features_item", - "//ios/chrome/browser/first_run/public:features", "//ios/chrome/browser/shared/model/profile", + "//ios/chrome/browser/welcome_back/model:features", "//ios/components/security_interstitials/safe_browsing", "//ios/web/public", ]
diff --git a/ios/chrome/browser/safe_browsing/model/tailored_security/DEPS b/ios/chrome/browser/safe_browsing/model/tailored_security/DEPS index 66d16c2..79a0eb4 100644 --- a/ios/chrome/browser/safe_browsing/model/tailored_security/DEPS +++ b/ios/chrome/browser/safe_browsing/model/tailored_security/DEPS
@@ -1,4 +1,4 @@ include_rules = [ "+ios/chrome/browser/first_run/public/best_features_item.h", - "+ios/chrome/browser/first_run/public/features.h", + "+ios/chrome/browser/welcome_back/model/features.h", ]
diff --git a/ios/chrome/browser/safe_browsing/model/tailored_security/tailored_security_service_infobar_delegate.mm b/ios/chrome/browser/safe_browsing/model/tailored_security/tailored_security_service_infobar_delegate.mm index 47e395b1..5128edc2 100644 --- a/ios/chrome/browser/safe_browsing/model/tailored_security/tailored_security_service_infobar_delegate.mm +++ b/ios/chrome/browser/safe_browsing/model/tailored_security/tailored_security_service_infobar_delegate.mm
@@ -8,8 +8,8 @@ #import "components/safe_browsing/core/browser/tailored_security_service/tailored_security_outcome.h" #import "components/safe_browsing/core/common/safe_browsing_prefs.h" #import "ios/chrome/browser/first_run/public/best_features_item.h" -#import "ios/chrome/browser/first_run/public/features.h" #import "ios/chrome/browser/shared/model/profile/profile_ios.h" +#import "ios/chrome/browser/welcome_back/model/features.h" #import "ios/chrome/grit/ios_branded_strings.h" #import "ios/chrome/grit/ios_strings.h" #import "ios/components/security_interstitials/safe_browsing/safe_browsing_tab_helper.h"
diff --git a/ios/chrome/browser/safe_browsing/model/tailored_security/tailored_security_tab_helper.mm b/ios/chrome/browser/safe_browsing/model/tailored_security/tailored_security_tab_helper.mm index 1880cd2..ed5fc9e 100644 --- a/ios/chrome/browser/safe_browsing/model/tailored_security/tailored_security_tab_helper.mm +++ b/ios/chrome/browser/safe_browsing/model/tailored_security/tailored_security_tab_helper.mm
@@ -12,12 +12,12 @@ #import "components/safe_browsing/core/browser/tailored_security_service/tailored_security_service_util.h" #import "components/safe_browsing/core/common/safe_browsing_prefs.h" #import "ios/chrome/browser/first_run/public/best_features_item.h" -#import "ios/chrome/browser/first_run/public/features.h" #import "ios/chrome/browser/infobars/model/infobar_ios.h" #import "ios/chrome/browser/infobars/model/infobar_manager_impl.h" #import "ios/chrome/browser/safe_browsing/model/tailored_security/tailored_security_service_infobar_delegate.h" #import "ios/chrome/browser/shared/model/profile/profile_ios.h" #import "ios/chrome/browser/sync/model/sync_service_factory.h" +#import "ios/chrome/browser/welcome_back/model/features.h" #import "ios/components/security_interstitials/safe_browsing/safe_browsing_tab_helper.h" #import "ios/web/public/navigation/navigation_context.h"
diff --git a/ios/chrome/browser/save_to_drive/ui_bundled/save_to_drive_mediator.mm b/ios/chrome/browser/save_to_drive/ui_bundled/save_to_drive_mediator.mm index 6335d8a..276687b 100644 --- a/ios/chrome/browser/save_to_drive/ui_bundled/save_to_drive_mediator.mm +++ b/ios/chrome/browser/save_to_drive/ui_bundled/save_to_drive_mediator.mm
@@ -150,7 +150,7 @@ CHECK(identity); // Memorize the account that was picked. _prefService->SetString(prefs::kIosSaveToDriveDefaultGaiaId, - base::SysNSStringToUTF8(identity.gaiaID)); + identity.gaiaId.ToString()); // Otherwise if the selected destination is Drive, check for sufficient // storage space before any further steps. [_accountPickerConsumer startValidationSpinner];
diff --git a/ios/chrome/browser/save_to_photos/ui_bundled/save_to_photos_mediator.mm b/ios/chrome/browser/save_to_photos/ui_bundled/save_to_photos_mediator.mm index d890b8ac..d63018b6 100644 --- a/ios/chrome/browser/save_to_photos/ui_bundled/save_to_photos_mediator.mm +++ b/ios/chrome/browser/save_to_photos/ui_bundled/save_to_photos_mediator.mm
@@ -185,7 +185,7 @@ // Memorize the account that was picked and whether to ask which account to // use every time. _prefService->SetString(prefs::kIosSaveToPhotosDefaultGaiaId, - base::SysNSStringToUTF8(identity.gaiaID)); + identity.gaiaId.ToString()); _prefService->SetBoolean(prefs::kIosSaveToPhotosSkipAccountPicker, !askEveryTime); @@ -542,7 +542,7 @@ // Otherwise, open the Photos app and hide Save to Photos. NSString* recentlyAddedURLString = [kGooglePhotosRecentlyAddedURLString - stringByAppendingString:_identity.gaiaID]; + stringByAppendingString:_identity.gaiaId.ToNSString()]; NSURL* photosURL = [NSURL URLWithString:recentlyAddedURLString]; [UIApplication.sharedApplication openURL:photosURL
diff --git a/ios/chrome/browser/save_to_photos/ui_bundled/save_to_photos_mediator_unittest.mm b/ios/chrome/browser/save_to_photos/ui_bundled/save_to_photos_mediator_unittest.mm index e659d04a..27514c4 100644 --- a/ios/chrome/browser/save_to_photos/ui_bundled/save_to_photos_mediator_unittest.mm +++ b/ios/chrome/browser/save_to_photos/ui_bundled/save_to_photos_mediator_unittest.mm
@@ -303,9 +303,8 @@ // This test assumes there is a default account memorized for Save to Photos // and that the user opted-in skipping the account picker. - profile_->GetPrefs()->SetString( - prefs::kIosSaveToPhotosDefaultGaiaId, - base::SysNSStringToUTF8(fake_identity_.gaiaID).c_str()); + profile_->GetPrefs()->SetString(prefs::kIosSaveToPhotosDefaultGaiaId, + fake_identity_.gaiaId.ToString()); profile_->GetPrefs()->SetBoolean(prefs::kIosSaveToPhotosSkipAccountPicker, true); @@ -527,7 +526,7 @@ // Expect that the mediator tries to open the Photos app and switch to the // Photos account associated with `fake_identity_`. NSString* recently_added_url_string = [kGooglePhotosRecentlyAddedURLString - stringByAppendingString:fake_identity_.gaiaID]; + stringByAppendingString:fake_identity_.gaiaId.ToNSString()]; NSURL* photos_url = [NSURL URLWithString:recently_added_url_string]; OCMExpect([mock_application_ openURL:photos_url
diff --git a/ios/chrome/browser/sessions/model/legacy_session_restoration_service.h b/ios/chrome/browser/sessions/model/legacy_session_restoration_service.h index bbc22d0..b29220484 100644 --- a/ios/chrome/browser/sessions/model/legacy_session_restoration_service.h +++ b/ios/chrome/browser/sessions/model/legacy_session_restoration_service.h
@@ -26,7 +26,7 @@ // (SessionRestorationBrowserAgent and SessionServiceIOS). Used when the // feature is disabled. // -// TODO(crbug.com/40245950): Remove when the feature is fully launched. +// TODO(crbug.com/40945317): Remove when the feature is fully launched. class LegacySessionRestorationService final : public SessionRestorationService, public SessionRestorationObserver, public WebStateListObserver,
diff --git a/ios/chrome/browser/sessions/model/session_service_ios_unittest.mm b/ios/chrome/browser/sessions/model/session_service_ios_unittest.mm index dce5959..dab1aa2 100644 --- a/ios/chrome/browser/sessions/model/session_service_ios_unittest.mm +++ b/ios/chrome/browser/sessions/model/session_service_ios_unittest.mm
@@ -277,7 +277,7 @@ EXPECT_TRUE(session == nil); } -// TODO(crbug.com/41284267): remove this once M67 has shipped (i.e. once more +// TODO(crbug.com/40945317): remove this once M67 has shipped (i.e. once more // than a year has passed since the introduction of the compatibility code). TEST_F(SessionServiceTest, LoadM57Session) { NSString* session_path = @@ -288,7 +288,7 @@ EXPECT_NSNE(nil, session_window); } -// TODO(crbug.com/41284267): remove this once M68 has shipped (i.e. once more +// TODO(crbug.com/40945317): remove this once M68 has shipped (i.e. once more // than a year has passed since the introduction of the compatibility code). TEST_F(SessionServiceTest, LoadM58Session) { NSString* session_path =
diff --git a/ios/chrome/browser/settings/ui_bundled/clear_browsing_data/quick_delete_egtest.mm b/ios/chrome/browser/settings/ui_bundled/clear_browsing_data/quick_delete_egtest.mm index 2a0c464..d3a5472 100644 --- a/ios/chrome/browser/settings/ui_bundled/clear_browsing_data/quick_delete_egtest.mm +++ b/ios/chrome/browser/settings/ui_bundled/clear_browsing_data/quick_delete_egtest.mm
@@ -1589,15 +1589,8 @@ // Tests the footer search history link is opened correctly and metrics are // recorded in the corrresponding histogram bucket. -// TODO(crbug.com/443704367): Test disabled on simulator. -#if TARGET_OS_SIMULATOR -#define MAYBE_testOpenSearchHistoryMyActivityFooterLink \ - DISABLED_testOpenSearchHistoryMyActivityFooterLink -#else -#define MAYBE_testOpenSearchHistoryMyActivityFooterLink \ - testOpenSearchHistoryMyActivityFooterLink -#endif -- (void)MAYBE_testOpenSearchHistoryMyActivityFooterLink { +// TODO(crbug.com/443704367): Re-enable test. +- (void)DISABLED_testOpenSearchHistoryMyActivityFooterLink { GREYAssertTrue(self.testServer->Start(), @"Test server failed to start."); // Sign in is required to show the footer. [self signIn];
diff --git a/ios/chrome/browser/settings/ui_bundled/password/password_sharing/BUILD.gn b/ios/chrome/browser/settings/ui_bundled/password/password_sharing/BUILD.gn index 5606524..316415c 100644 --- a/ios/chrome/browser/settings/ui_bundled/password/password_sharing/BUILD.gn +++ b/ios/chrome/browser/settings/ui_bundled/password/password_sharing/BUILD.gn
@@ -50,7 +50,6 @@ "//ios/chrome/browser/authentication/ui_bundled:authentication_constants", "//ios/chrome/browser/favicon/model", "//ios/chrome/browser/first_run/public:best_features_item", - "//ios/chrome/browser/first_run/public:features", "//ios/chrome/browser/net/model:crurl", "//ios/chrome/browser/passwords/model:sharing_factory", "//ios/chrome/browser/shared/coordinator/alert", @@ -65,6 +64,7 @@ "//ios/chrome/browser/signin/model", "//ios/chrome/browser/signin/model:authentication_service", "//ios/chrome/browser/signin/model:authentication_service_factory", + "//ios/chrome/browser/welcome_back/model:features", "//ios/chrome/common", "//ios/chrome/common/ui/confirmation_alert", "//ios/chrome/common/ui/favicon:favicon_constants",
diff --git a/ios/chrome/browser/settings/ui_bundled/password/password_sharing/DEPS b/ios/chrome/browser/settings/ui_bundled/password/password_sharing/DEPS index a260780..3038a38 100644 --- a/ios/chrome/browser/settings/ui_bundled/password/password_sharing/DEPS +++ b/ios/chrome/browser/settings/ui_bundled/password/password_sharing/DEPS
@@ -1,4 +1,4 @@ include_rules = [ "+ios/chrome/browser/first_run/public/best_features_item.h", - "+ios/chrome/browser/first_run/public/features.h", + "+ios/chrome/browser/welcome_back/model/features.h", ]
diff --git a/ios/chrome/browser/settings/ui_bundled/password/password_sharing/sharing_status_coordinator.mm b/ios/chrome/browser/settings/ui_bundled/password/password_sharing/sharing_status_coordinator.mm index 30398b0..65df996 100644 --- a/ios/chrome/browser/settings/ui_bundled/password/password_sharing/sharing_status_coordinator.mm +++ b/ios/chrome/browser/settings/ui_bundled/password/password_sharing/sharing_status_coordinator.mm
@@ -6,7 +6,6 @@ #import "ios/chrome/browser/favicon/model/ios_chrome_favicon_loader_factory.h" #import "ios/chrome/browser/first_run/public/best_features_item.h" -#import "ios/chrome/browser/first_run/public/features.h" #import "ios/chrome/browser/settings/ui_bundled/password/password_sharing/password_sharing_constants.h" #import "ios/chrome/browser/settings/ui_bundled/password/password_sharing/password_sharing_metrics.h" #import "ios/chrome/browser/settings/ui_bundled/password/password_sharing/recipient_info.h" @@ -22,6 +21,7 @@ #import "ios/chrome/browser/signin/model/authentication_service.h" #import "ios/chrome/browser/signin/model/authentication_service_factory.h" #import "ios/chrome/browser/signin/model/chrome_account_manager_service_factory.h" +#import "ios/chrome/browser/welcome_back/model/features.h" #import "url/gurl.h" @interface SharingStatusCoordinator () <
diff --git a/ios/chrome/browser/settings/ui_bundled/privacy/BUILD.gn b/ios/chrome/browser/settings/ui_bundled/privacy/BUILD.gn index 9b624778..e687ee5 100644 --- a/ios/chrome/browser/settings/ui_bundled/privacy/BUILD.gn +++ b/ios/chrome/browser/settings/ui_bundled/privacy/BUILD.gn
@@ -37,7 +37,6 @@ "//ios/chrome/browser/browsing_data/model:feature_flags", "//ios/chrome/browser/feature_engagement/model", "//ios/chrome/browser/first_run/public:best_features_item", - "//ios/chrome/browser/first_run/public:features", "//ios/chrome/browser/incognito_interstitial/ui_bundled:constants", "//ios/chrome/browser/incognito_reauth/ui_bundled:features", "//ios/chrome/browser/incognito_reauth/ui_bundled:incognito_reauth_util", @@ -78,6 +77,7 @@ "//ios/chrome/browser/supervised_user/model:capabilities", "//ios/chrome/browser/sync/model", "//ios/chrome/browser/web/model:feature_flags", + "//ios/chrome/browser/welcome_back/model:features", "//ios/chrome/common:string_util", "//ios/chrome/common/ui/colors", "//ios/chrome/common/ui/reauthentication",
diff --git a/ios/chrome/browser/settings/ui_bundled/privacy/DEPS b/ios/chrome/browser/settings/ui_bundled/privacy/DEPS index 5370765..65ee41ac 100644 --- a/ios/chrome/browser/settings/ui_bundled/privacy/DEPS +++ b/ios/chrome/browser/settings/ui_bundled/privacy/DEPS
@@ -1,5 +1,5 @@ include_rules = [ "+ios/chrome/browser/incognito_interstitial/ui_bundled/incognito_interstitial_constants.h", "+ios/chrome/browser/first_run/public/best_features_item.h", - "+ios/chrome/browser/first_run/public/features.h", + "+ios/chrome/browser/welcome_back/model/features.h", ]
diff --git a/ios/chrome/browser/settings/ui_bundled/privacy/incognito/BUILD.gn b/ios/chrome/browser/settings/ui_bundled/privacy/incognito/BUILD.gn index ed02225..c80a25c 100644 --- a/ios/chrome/browser/settings/ui_bundled/privacy/incognito/BUILD.gn +++ b/ios/chrome/browser/settings/ui_bundled/privacy/incognito/BUILD.gn
@@ -17,14 +17,14 @@ "//base", "//components/prefs", "//ios/chrome/browser/first_run/public:best_features_item", - "//ios/chrome/browser/first_run/public:features", - "//ios/chrome/browser/first_run/ui_bundled/welcome_back/model", "//ios/chrome/browser/incognito_reauth/ui_bundled:incognito_reauth_util", "//ios/chrome/browser/shared/coordinator/chrome_coordinator", "//ios/chrome/browser/shared/model/application_context", "//ios/chrome/browser/shared/model/prefs", "//ios/chrome/browser/shared/model/prefs:pref_names", "//ios/chrome/browser/shared/ui/table_view:utils", + "//ios/chrome/browser/welcome_back/model", + "//ios/chrome/browser/welcome_back/model:features", "//ios/chrome/common/ui/reauthentication", ] }
diff --git a/ios/chrome/browser/settings/ui_bundled/privacy/incognito/DEPS b/ios/chrome/browser/settings/ui_bundled/privacy/incognito/DEPS index a260780..3038a38 100644 --- a/ios/chrome/browser/settings/ui_bundled/privacy/incognito/DEPS +++ b/ios/chrome/browser/settings/ui_bundled/privacy/incognito/DEPS
@@ -1,4 +1,4 @@ include_rules = [ "+ios/chrome/browser/first_run/public/best_features_item.h", - "+ios/chrome/browser/first_run/public/features.h", + "+ios/chrome/browser/welcome_back/model/features.h", ]
diff --git a/ios/chrome/browser/settings/ui_bundled/privacy/incognito/incognito_lock_mediator.mm b/ios/chrome/browser/settings/ui_bundled/privacy/incognito/incognito_lock_mediator.mm index 513383e..9235cb07f 100644 --- a/ios/chrome/browser/settings/ui_bundled/privacy/incognito/incognito_lock_mediator.mm +++ b/ios/chrome/browser/settings/ui_bundled/privacy/incognito/incognito_lock_mediator.mm
@@ -9,11 +9,11 @@ #import "base/metrics/user_metrics_action.h" #import "components/prefs/pref_service.h" #import "ios/chrome/browser/first_run/public/best_features_item.h" -#import "ios/chrome/browser/first_run/public/features.h" #import "ios/chrome/browser/incognito_reauth/ui_bundled/incognito_reauth_constants.h" #import "ios/chrome/browser/settings/ui_bundled/privacy/incognito/incognito_lock_consumer.h" #import "ios/chrome/browser/shared/model/prefs/pref_backed_boolean.h" #import "ios/chrome/browser/shared/model/prefs/pref_names.h" +#import "ios/chrome/browser/welcome_back/model/features.h" @interface IncognitoLockMediator () <BooleanObserver> @end
diff --git a/ios/chrome/browser/settings/ui_bundled/privacy/privacy_safe_browsing_mediator.mm b/ios/chrome/browser/settings/ui_bundled/privacy/privacy_safe_browsing_mediator.mm index 6c674d43..c8a4fcb 100644 --- a/ios/chrome/browser/settings/ui_bundled/privacy/privacy_safe_browsing_mediator.mm +++ b/ios/chrome/browser/settings/ui_bundled/privacy/privacy_safe_browsing_mediator.mm
@@ -15,7 +15,6 @@ #import "components/safe_browsing/core/common/hashprefix_realtime/hash_realtime_utils.h" #import "components/safe_browsing/core/common/safe_browsing_prefs.h" #import "ios/chrome/browser/first_run/public/best_features_item.h" -#import "ios/chrome/browser/first_run/public/features.h" #import "ios/chrome/browser/policy/model/policy_util.h" #import "ios/chrome/browser/settings/model/sync/utils/sync_util.h" #import "ios/chrome/browser/settings/ui_bundled/cells/settings_image_detail_text_item.h" @@ -31,6 +30,7 @@ #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_info_button_item.h" #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_info_button_item_delegate.h" #import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h" +#import "ios/chrome/browser/welcome_back/model/features.h" #import "ios/chrome/common/ui/colors/semantic_color_names.h" #import "ios/chrome/grit/ios_branded_strings.h" #import "ios/chrome/grit/ios_strings.h"
diff --git a/ios/chrome/browser/share_extension/model/share_extension_controller.mm b/ios/chrome/browser/share_extension/model/share_extension_controller.mm index 53c0529..3dd8c40d 100644 --- a/ios/chrome/browser/share_extension/model/share_extension_controller.mm +++ b/ios/chrome/browser/share_extension/model/share_extension_controller.mm
@@ -399,21 +399,6 @@ DCHECK_CALLED_ON_VALID_SEQUENCE(_sequenceChecker); AddDataToProfileByGaiaID<BookmarkAdder>( gaiaID, net::GURLWithNSURL(URL), base::SysNSStringToUTF8(bookmarkTitle)); - // std::optional<std::string> profileName = - // GetApplicationContext() - // ->GetAccountProfileMapper() - // ->FindProfileNameForGaiaID(GaiaId(gaiaID)); - // - // if (profileName.has_value()) { - // std::string title = base::SysNSStringToUTF8(bookmarkTitle); - // ProfileManagerIOS* profileManager = - // GetApplicationContext()->GetProfileManager(); - // std::unique_ptr<BookmarkAdder> adder = std::make_unique<BookmarkAdder>( - // net::GURLWithNSURL(URL), std::move(title)); - // profileManager->LoadProfileAsync( - // *profileName, - // base::BindOnce(&OnProfileLoaded<BookmarkAdder>, std::move(adder))); - // } } - (void)addReadingListToProfileByGaiaID:(NSString*)gaiaID @@ -423,23 +408,6 @@ AddDataToProfileByGaiaID<ReadingListAdder>( gaiaID, net::GURLWithNSURL(URL), base::SysNSStringToUTF8(readingListTitle)); - // std::optional<std::string> profileName = - // GetApplicationContext() - // ->GetAccountProfileMapper() - // ->FindProfileNameForGaiaID(GaiaId(gaiaID)); - // - // if (profileName.has_value()) { - // std::string title = base::SysNSStringToUTF8(readingListTitle); - // ProfileManagerIOS* profileManager = - // GetApplicationContext()->GetProfileManager(); - // std::unique_ptr<ReadingListAdder> adder = - // std::make_unique<ReadingListAdder>(net::GURLWithNSURL(URL), title, - // profileManager); - // profileManager->LoadProfileAsync( - // *profileName, - // base::BindOnce(&OnProfileLoaded<ReadingListAdder>, - // std::move(adder))); - // } } @end
diff --git a/ios/chrome/browser/share_kit/model/test_share_kit_service.mm b/ios/chrome/browser/share_kit/model/test_share_kit_service.mm index eee5cb1..78c1300 100644 --- a/ios/chrome/browser/share_kit/model/test_share_kit_service.mm +++ b/ios/chrome/browser/share_kit/model/test_share_kit_service.mm
@@ -63,7 +63,7 @@ } // Creates a group member with the given `gaia_id` and `member_role`. -data_sharing_pb::GroupMember CreateGroupMember(NSString* gaia_id, +data_sharing_pb::GroupMember CreateGroupMember(const GaiaId& gaia_id, MemberRole member_role) { GroupMember member; if (member_role == data_sharing_pb::MEMBER_ROLE_OWNER) { @@ -73,7 +73,7 @@ member.set_display_name("Member"); member.set_email("member@mail.com"); } - member.set_gaia_id(base::SysNSStringToUTF8(gaia_id)); + member.set_gaia_id(gaia_id.ToString()); member.set_avatar_url("chrome://newtab"); member.set_given_name("Given Name"); member.set_role(member_role); @@ -88,14 +88,14 @@ group_data.set_group_id(collaboration_id); group_data.set_display_name("Display Name"); *group_data.add_members() = - CreateGroupMember([FakeSystemIdentity fakeIdentity1].gaiaID, member_role); + CreateGroupMember([FakeSystemIdentity fakeIdentity1].gaiaId, member_role); MemberRole member_role2 = member_role == data_sharing_pb::MEMBER_ROLE_OWNER ? data_sharing_pb::MEMBER_ROLE_MEMBER : data_sharing_pb::MEMBER_ROLE_OWNER; *group_data.add_members() = CreateGroupMember( - [FakeSystemIdentity fakeIdentity2].gaiaID, member_role2); + [FakeSystemIdentity fakeIdentity2].gaiaId, member_role2); *group_data.add_members() = - CreateGroupMember([FakeSystemIdentity fakeIdentity3].gaiaID, + CreateGroupMember([FakeSystemIdentity fakeIdentity3].gaiaId, data_sharing_pb::MEMBER_ROLE_MEMBER); group_data.set_access_token("fake_access_token"); return group_data;
diff --git a/ios/chrome/browser/shared/model/prefs/BUILD.gn b/ios/chrome/browser/shared/model/prefs/BUILD.gn index 0c529df..00e9712 100644 --- a/ios/chrome/browser/shared/model/prefs/BUILD.gn +++ b/ios/chrome/browser/shared/model/prefs/BUILD.gn
@@ -35,7 +35,7 @@ "//components/browser_sync", "//components/browsing_data/core", "//components/collaboration/public:prefs", - "//components/commerce/core:pref_names", + "//components/commerce/core:prefs", "//components/component_updater", "//components/component_updater/installer_policies", "//components/content_settings/core/browser", @@ -122,7 +122,6 @@ "//ios/chrome/browser/download/model/auto_deletion", "//ios/chrome/browser/drive/model:policy", "//ios/chrome/browser/first_run/model", - "//ios/chrome/browser/first_run/ui_bundled/welcome_back/model", "//ios/chrome/browser/geolocation/model", "//ios/chrome/browser/incognito_reauth/ui_bundled:incognito_reauth_scene_agent", "//ios/chrome/browser/memory/model", @@ -149,6 +148,7 @@ "//ios/chrome/browser/web/model", "//ios/chrome/browser/web/model/annotations", "//ios/chrome/browser/web/model/font_size", + "//ios/chrome/browser/welcome_back/model", "//ios/components/cookie_util:constants", "//ios/web/common:features", "//ui/base",
diff --git a/ios/chrome/browser/shared/model/prefs/DEPS b/ios/chrome/browser/shared/model/prefs/DEPS index 16d4b1a..0cd9fb1 100644 --- a/ios/chrome/browser/shared/model/prefs/DEPS +++ b/ios/chrome/browser/shared/model/prefs/DEPS
@@ -33,7 +33,7 @@ "+ios/chrome/browser/content_suggestions/ui_bundled/price_tracking_promo/price_tracking_promo_prefs.h", "+ios/chrome/browser/content_suggestions/ui_bundled/safety_check/safety_check_prefs.h", "+ios/chrome/browser/content_suggestions/ui_bundled/shop_card/shop_card_prefs.h", - "+ios/chrome/browser/first_run/ui_bundled/welcome_back/model/welcome_back_prefs.h", + "+ios/chrome/browser/welcome_back/model/welcome_back_prefs.h", "+ios/chrome/browser/cross_platform_promos/model/cross_platform_promos_service.h", ]
diff --git a/ios/chrome/browser/shared/model/prefs/browser_prefs.mm b/ios/chrome/browser/shared/model/prefs/browser_prefs.mm index a8da6ce..ebb81cb 100644 --- a/ios/chrome/browser/shared/model/prefs/browser_prefs.mm +++ b/ios/chrome/browser/shared/model/prefs/browser_prefs.mm
@@ -17,7 +17,7 @@ #import "components/browser_sync/sync_to_signin_migration.h" #import "components/browsing_data/core/pref_names.h" #import "components/collaboration/public/pref_names.h" -#import "components/commerce/core/pref_names.h" +#import "components/commerce/core/prefs.h" #import "components/component_updater/component_updater_service.h" #import "components/component_updater/installer_policies/autofill_states_component_installer.h" #import "components/content_settings/core/browser/host_content_settings_map.h" @@ -110,7 +110,6 @@ #import "ios/chrome/browser/download/model/auto_deletion/auto_deletion_service.h" #import "ios/chrome/browser/drive/model/drive_policy.h" #import "ios/chrome/browser/first_run/model/first_run.h" -#import "ios/chrome/browser/first_run/ui_bundled/welcome_back/model/welcome_back_prefs.h" #import "ios/chrome/browser/incognito_reauth/ui_bundled/incognito_reauth_scene_agent.h" #import "ios/chrome/browser/memory/model/memory_debugger_manager.h" #import "ios/chrome/browser/metrics/model/constants.h" @@ -134,6 +133,7 @@ #import "ios/chrome/browser/voice/model/voice_search_prefs_registration.h" #import "ios/chrome/browser/web/model/annotations/annotations_util.h" #import "ios/chrome/browser/web/model/font_size/font_size_tab_helper.h" +#import "ios/chrome/browser/welcome_back/model/welcome_back_prefs.h" #import "ios/components/cookie_util/cookie_constants.h" #import "ios/web/common/features.h" #import "ui/base/l10n/l10n_util.h" @@ -679,7 +679,7 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) { autofill::prefs::RegisterProfilePrefs(registry); collaboration::prefs::RegisterProfilePrefs(registry); - commerce::RegisterPrefs(registry); + commerce::RegisterProfilePrefs(registry); AimEligibilityService::RegisterProfilePrefs(registry); cross_device::RegisterProfilePrefs(registry); CrossPlatformPromosService::RegisterProfilePrefs(registry); @@ -955,8 +955,7 @@ true); // Registers the Magic Stack module visibility prefs. - registry->RegisterBooleanPref(prefs::kHomeCustomizationMagicStackTipsEnabled, - true); + registry->RegisterBooleanPref(ntp_tiles::prefs::kTipsHomeModuleEnabled, true); registry->RegisterBooleanPref( ntp_tiles::prefs::kTabResumptionHomeModuleEnabled, true); @@ -1117,6 +1116,11 @@ // `ntp_tiles::prefs::kTabResumptionHomeModuleEnabled` instead. registry->RegisterBooleanPref( prefs::kHomeCustomizationMagicStackTabResumptionEnabled, true); + + // Deprecated 10/2025. Use + // `ntp_tiles::prefs::kTipsHomeModuleEnabled` instead. + registry->RegisterBooleanPref(prefs::kHomeCustomizationMagicStackTipsEnabled, + true); } // This method should be periodically pruned of year+ old migrations. @@ -1298,11 +1302,11 @@ RenameBooleanPref(safety_check::prefs::kSafetyCheckHomeModuleEnabled, prefs::kHomeCustomizationMagicStackSafetyCheckEnabled, prefs); - - // Added 10/2025. RenameBooleanPref(ntp_tiles::prefs::kTabResumptionHomeModuleEnabled, prefs::kHomeCustomizationMagicStackTabResumptionEnabled, prefs); + RenameBooleanPref(ntp_tiles::prefs::kTipsHomeModuleEnabled, + prefs::kHomeCustomizationMagicStackTipsEnabled, prefs); } void MigrateObsoleteUserDefault() {
diff --git a/ios/chrome/browser/shared/model/prefs/browser_prefs_unittest.mm b/ios/chrome/browser/shared/model/prefs/browser_prefs_unittest.mm index df45d80..0bb8fba 100644 --- a/ios/chrome/browser/shared/model/prefs/browser_prefs_unittest.mm +++ b/ios/chrome/browser/shared/model/prefs/browser_prefs_unittest.mm
@@ -81,6 +81,11 @@ pref_service_.SetBoolean( prefs::kHomeCustomizationMagicStackTabResumptionEnabled, false); + // Set the old Magic Stack Tips module pref value to test its migration to + // the new name. + pref_service_.SetBoolean(prefs::kHomeCustomizationMagicStackTipsEnabled, + false); + // Bottom omnibox position local_state()->SetBoolean(prefs::kBottomOmnibox, true); @@ -155,6 +160,12 @@ .FindPreference(ntp_tiles::prefs::kTabResumptionHomeModuleEnabled) ->IsDefaultValue()); + EXPECT_FALSE( + pref_service_.GetBoolean(prefs::kHomeCustomizationMagicStackTipsEnabled)); + EXPECT_TRUE( + pref_service_.FindPreference(ntp_tiles::prefs::kTipsHomeModuleEnabled) + ->IsDefaultValue()); + // Check bottom omnibox position. EXPECT_TRUE(local_state()->GetBoolean(prefs::kBottomOmnibox)); EXPECT_TRUE(local_state() @@ -240,6 +251,15 @@ EXPECT_FALSE(pref_service_.GetBoolean( ntp_tiles::prefs::kTabResumptionHomeModuleEnabled)); + EXPECT_TRUE( + pref_service_ + .FindPreference(prefs::kHomeCustomizationMagicStackTipsEnabled) + ->IsDefaultValue()); + // The new pref `ntp_tiles::prefs::kTipsHomeModuleEnabled` should + // now be false (the migrated value). + EXPECT_FALSE( + pref_service_.GetBoolean(ntp_tiles::prefs::kTipsHomeModuleEnabled)); + // Check bottom omnibox position. EXPECT_TRUE( local_state()->FindPreference(prefs::kBottomOmnibox)->IsDefaultValue());
diff --git a/ios/chrome/browser/shared/model/web_state_list/browser_util.mm b/ios/chrome/browser/shared/model/web_state_list/browser_util.mm index fb80028..377786e 100644 --- a/ios/chrome/browser/shared/model/web_state_list/browser_util.mm +++ b/ios/chrome/browser/shared/model/web_state_list/browser_util.mm
@@ -58,8 +58,8 @@ const SnapshotID snapshot_identifier(web_state->GetUniqueIdentifier()); MoveSnapshot(snapshot_identifier, source_browser, destination_browser); - // TODO(crbug.com/40203375): Remove this workaround when it will no longer be - // required to have an active WebState in the WebStateList. + // TODO(crbug.com/451581543): Remove this workaround when it will no longer + // be required to have an active WebState in the WebStateList. if (destination_browser->GetWebStateList()->empty()) { params.Activate(); }
diff --git a/ios/chrome/browser/shared/ui/symbols/symbol_names.h b/ios/chrome/browser/shared/ui/symbols/symbol_names.h index 62164405..c674c504 100644 --- a/ios/chrome/browser/shared/ui/symbols/symbol_names.h +++ b/ios/chrome/browser/shared/ui/symbols/symbol_names.h
@@ -317,6 +317,8 @@ extern NSString* const kBookSymbol; extern NSString* const kKeySymbol; extern NSString* const kTextDocument; +extern NSString* const kPhotoOnRectangleSymbol; +extern NSString* const kSystemCameraSymbol; // Names of the default symbol being non-monochrome by default. When using them, // you probably want to set their color to monochrome.
diff --git a/ios/chrome/browser/shared/ui/symbols/symbol_names.mm b/ios/chrome/browser/shared/ui/symbols/symbol_names.mm index 72a12a7..8a56c2b 100644 --- a/ios/chrome/browser/shared/ui/symbols/symbol_names.mm +++ b/ios/chrome/browser/shared/ui/symbols/symbol_names.mm
@@ -329,6 +329,8 @@ NSString* const kBookSymbol = @"book"; NSString* const kKeySymbol = @"key"; NSString* const kTextDocument = @"text.document"; +NSString* const kPhotoOnRectangleSymbol = @"photo.on.rectangle"; +NSString* const kSystemCameraSymbol = @"camera"; // Names of the default symbol being non-monochrome by default. When using them, // you probably want to set their color to monochrome.
diff --git a/ios/chrome/browser/shared/ui/table_view/cells/table_view_detail_text_item.mm b/ios/chrome/browser/shared/ui/table_view/cells/table_view_detail_text_item.mm index c2fc3d5b0..3957283 100644 --- a/ios/chrome/browser/shared/ui/table_view/cells/table_view_detail_text_item.mm +++ b/ios/chrome/browser/shared/ui/table_view/cells/table_view_detail_text_item.mm
@@ -11,6 +11,8 @@ #import "ios/chrome/common/ui/colors/semantic_color_names.h" #import "ios/chrome/common/ui/table_view/table_view_cells_constants.h" #import "ios/chrome/common/ui/util/constraints_ui_util.h" +#import "ios/chrome/grit/ios_strings.h" +#import "ui/base/l10n/l10n_util_mac.h" #pragma mark - TableViewDetailTextItem @@ -40,7 +42,18 @@ configuration.subtitleNumberOfLines = self.allowMultilineDetailText ? 0 : 1; cell.contentConfiguration = configuration; - cell.accessibilityLabel = configuration.accessibilityLabel; + + NSString* accessibilityLabel = configuration.accessibilityLabel; + // If the cell indicates an external link, append an accessibility hint for + // screen readers. + if (self.accessorySymbol == + TableViewDetailTextCellAccessorySymbolExternalLink) { + NSString* hint = l10n_util::GetNSString(IDS_IOS_OPENS_IN_NEW_TAB); + accessibilityLabel = + [NSString stringWithFormat:@"%@, %@", accessibilityLabel, hint]; + } + + cell.accessibilityLabel = accessibilityLabel; // Accessory symbol. switch (self.accessorySymbol) {
diff --git a/ios/chrome/browser/tab_switcher/tab_grid/base_grid/ui/base_grid_view_controller.mm b/ios/chrome/browser/tab_switcher/tab_grid/base_grid/ui/base_grid_view_controller.mm index 0511372..191f2a4 100644 --- a/ios/chrome/browser/tab_switcher/tab_grid/base_grid/ui/base_grid_view_controller.mm +++ b/ios/chrome/browser/tab_switcher/tab_grid/base_grid/ui/base_grid_view_controller.mm
@@ -1347,9 +1347,9 @@ (GridItemIdentifier*)selectedItemIdentifier snapshot:(GridSnapshot*)snapshot { CHECK(item.type == GridItemType::kTab || item.type == GridItemType::kGroup); - // TODO(crbug.com/40069795): There are crash reports that show there could be - // cases where the open tabs section is not present in the snapshot. If so, - // don't perform the update. + // There are crash reports that showed there were cases where the open tabs + // section is not present in the snapshot. If so, don't perform the update. + // See crbug.com/40069795 for more details. NSInteger section = [snapshot indexOfSectionIdentifier:kGridOpenTabsSectionIdentifier]; DUMP_WILL_BE_CHECK(section != NSNotFound)
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/BUILD.gn b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/BUILD.gn index dfe681c..10ef02d 100644 --- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/BUILD.gn +++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/BUILD.gn
@@ -43,7 +43,6 @@ "//ios/chrome/browser/drag_and_drop/model", "//ios/chrome/browser/favicon/model", "//ios/chrome/browser/first_run/public:best_features_item", - "//ios/chrome/browser/first_run/public:features", "//ios/chrome/browser/main/model", "//ios/chrome/browser/net/model:crurl", "//ios/chrome/browser/policy/model:policy_util", @@ -86,6 +85,7 @@ "//ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/toolbars:toolbars_ui", "//ios/chrome/browser/url_loading/model", "//ios/chrome/browser/url_loading/model:url_loading_params_header", + "//ios/chrome/browser/welcome_back/model:features", "//ios/chrome/common/ui/colors", "//ios/chrome/common/ui/confirmation_alert", "//ios/chrome/common/ui/favicon",
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/DEPS b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/DEPS index 2503e93..339a91dd 100644 --- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/DEPS +++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/DEPS
@@ -12,7 +12,7 @@ "+ios/chrome/browser/saved_tab_groups/ui", "+ios/chrome/browser/toolbar/ui_bundled", "+ios/chrome/browser/first_run/public/best_features_item.h", - "+ios/chrome/browser/first_run/public/features.h", + "+ios/chrome/browser/welcome_back/model/features.h", ] specific_include_rules = {
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_coordinator.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_coordinator.mm index 38b68f2..3dd77c9 100644 --- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_coordinator.mm +++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_group_coordinator.mm
@@ -20,7 +20,6 @@ #import "ios/chrome/browser/collaboration/model/messaging/messaging_backend_service_factory.h" #import "ios/chrome/browser/data_sharing/model/data_sharing_service_factory.h" #import "ios/chrome/browser/first_run/public/best_features_item.h" -#import "ios/chrome/browser/first_run/public/features.h" #import "ios/chrome/browser/saved_tab_groups/coordinator/face_pile_configuration.h" #import "ios/chrome/browser/saved_tab_groups/coordinator/face_pile_coordinator.h" #import "ios/chrome/browser/saved_tab_groups/model/ios_tab_group_sync_util.h" @@ -51,6 +50,7 @@ #import "ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_groups_constants.h" #import "ios/chrome/browser/tab_switcher/ui_bundled/tab_group_action_type.h" #import "ios/chrome/browser/tab_switcher/ui_bundled/tab_group_confirmation_coordinator.h" +#import "ios/chrome/browser/welcome_back/model/features.h" #import "ios/web/public/web_state_id.h" #import "ui/base/device_form_factor.h"
diff --git a/ios/chrome/browser/first_run/ui_bundled/welcome_back/DEPS b/ios/chrome/browser/welcome_back/DEPS similarity index 100% rename from ios/chrome/browser/first_run/ui_bundled/welcome_back/DEPS rename to ios/chrome/browser/welcome_back/DEPS
diff --git a/ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/BUILD.gn b/ios/chrome/browser/welcome_back/coordinator/BUILD.gn similarity index 88% rename from ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/BUILD.gn rename to ios/chrome/browser/welcome_back/coordinator/BUILD.gn index 149e958..7449523 100644 --- a/ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/BUILD.gn +++ b/ios/chrome/browser/welcome_back/coordinator/BUILD.gn
@@ -10,8 +10,8 @@ "welcome_back_mediator.mm", ] public_deps = [ - "//ios/chrome/browser/first_run/ui_bundled/welcome_back/ui", "//ios/chrome/browser/shared/coordinator/chrome_coordinator", + "//ios/chrome/browser/welcome_back/ui", ] deps = [ "//base", @@ -20,10 +20,8 @@ "//ios/chrome/app/strings:ios_strings_grit", "//ios/chrome/browser/feature_engagement/model", "//ios/chrome/browser/first_run/public:best_features_item", - "//ios/chrome/browser/first_run/public:features", "//ios/chrome/browser/first_run/ui_bundled:features", "//ios/chrome/browser/first_run/ui_bundled/best_features/coordinator", - "//ios/chrome/browser/first_run/ui_bundled/welcome_back/model", "//ios/chrome/browser/shared/model/application_context", "//ios/chrome/browser/shared/model/browser", "//ios/chrome/browser/shared/model/profile", @@ -33,6 +31,8 @@ "//ios/chrome/browser/signin/model:authentication_service", "//ios/chrome/browser/signin/model:authentication_service_factory", "//ios/chrome/browser/signin/model:constants", + "//ios/chrome/browser/welcome_back/model", + "//ios/chrome/browser/welcome_back/model:features", ] frameworks = [ "UIKit.framework" ] } @@ -47,9 +47,7 @@ "//ios/chrome/app/strings:ios_strings_grit", "//ios/chrome/browser/feature_engagement/model", "//ios/chrome/browser/first_run/public:best_features_item", - "//ios/chrome/browser/first_run/public:features", "//ios/chrome/browser/first_run/ui_bundled:features", - "//ios/chrome/browser/first_run/ui_bundled/welcome_back/model", "//ios/chrome/browser/shared/model/application_context", "//ios/chrome/browser/shared/model/profile/test", "//ios/chrome/browser/signin/model", @@ -58,6 +56,8 @@ "//ios/chrome/browser/signin/model:fake_system_identity", "//ios/chrome/browser/signin/model:fake_system_identity_manager", "//ios/chrome/browser/signin/model:test_support", + "//ios/chrome/browser/welcome_back/model", + "//ios/chrome/browser/welcome_back/model:features", "//ios/chrome/test:test_support", "//ios/web/public/test", "//testing/gtest",
diff --git a/ios/chrome/browser/welcome_back/coordinator/DEPS b/ios/chrome/browser/welcome_back/coordinator/DEPS new file mode 100644 index 0000000..f476486 --- /dev/null +++ b/ios/chrome/browser/welcome_back/coordinator/DEPS
@@ -0,0 +1,7 @@ +include_rules = [ + "+ios/chrome/browser/feature_engagement/model", + "+ios/chrome/browser/first_run/public", + "+ios/chrome/browser/first_run/ui_bundled/best_features", + "+ios/chrome/browser/first_run/ui_bundled/features.h", + "+ios/chrome/browser/signin/model", +]
diff --git a/ios/chrome/browser/welcome_back/coordinator/welcome_back_coordinator.h b/ios/chrome/browser/welcome_back/coordinator/welcome_back_coordinator.h new file mode 100644 index 0000000..d0d7584 --- /dev/null +++ b/ios/chrome/browser/welcome_back/coordinator/welcome_back_coordinator.h
@@ -0,0 +1,17 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IOS_CHROME_BROWSER_WELCOME_BACK_COORDINATOR_WELCOME_BACK_COORDINATOR_H_ +#define IOS_CHROME_BROWSER_WELCOME_BACK_COORDINATOR_WELCOME_BACK_COORDINATOR_H_ + +#import "ios/chrome/browser/shared/coordinator/chrome_coordinator/chrome_coordinator.h" + +@protocol PromosManagerUIHandler; + +// Coordinator to present the Welcome Back screen. +@interface WelcomeBackCoordinator : ChromeCoordinator + +@end + +#endif // IOS_CHROME_BROWSER_WELCOME_BACK_COORDINATOR_WELCOME_BACK_COORDINATOR_H_
diff --git a/ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_coordinator.mm b/ios/chrome/browser/welcome_back/coordinator/welcome_back_coordinator.mm similarity index 89% rename from ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_coordinator.mm rename to ios/chrome/browser/welcome_back/coordinator/welcome_back_coordinator.mm index 52cecaad..b805bea 100644 --- a/ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_coordinator.mm +++ b/ios/chrome/browser/welcome_back/coordinator/welcome_back_coordinator.mm
@@ -2,18 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_coordinator.h" +#import "ios/chrome/browser/welcome_back/coordinator/welcome_back_coordinator.h" #import "components/feature_engagement/public/event_constants.h" #import "components/feature_engagement/public/tracker.h" #import "components/password_manager/core/browser/password_manager_util.h" #import "ios/chrome/browser/feature_engagement/model/tracker_factory.h" #import "ios/chrome/browser/first_run/public/best_features_item.h" -#import "ios/chrome/browser/first_run/public/features.h" #import "ios/chrome/browser/first_run/ui_bundled/best_features/coordinator/best_features_screen_detail_coordinator.h" -#import "ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_mediator.h" -#import "ios/chrome/browser/first_run/ui_bundled/welcome_back/model/welcome_back_prefs.h" -#import "ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/welcome_back_action_handler.h" #import "ios/chrome/browser/shared/model/application_context/application_context.h" #import "ios/chrome/browser/shared/model/browser/browser.h" #import "ios/chrome/browser/shared/model/profile/profile_ios.h" @@ -21,6 +17,10 @@ #import "ios/chrome/browser/shared/public/commands/welcome_back_promo_commands.h" #import "ios/chrome/browser/signin/model/authentication_service_factory.h" #import "ios/chrome/browser/signin/model/chrome_account_manager_service_factory.h" +#import "ios/chrome/browser/welcome_back/coordinator/welcome_back_mediator.h" +#import "ios/chrome/browser/welcome_back/model/features.h" +#import "ios/chrome/browser/welcome_back/model/welcome_back_prefs.h" +#import "ios/chrome/browser/welcome_back/ui/welcome_back_action_handler.h" #import "ios/chrome/common/ui/confirmation_alert/confirmation_alert_action_handler.h" @interface WelcomeBackCoordinator () <ConfirmationAlertActionHandler,
diff --git a/ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_mediator.h b/ios/chrome/browser/welcome_back/coordinator/welcome_back_mediator.h similarity index 76% rename from ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_mediator.h rename to ios/chrome/browser/welcome_back/coordinator/welcome_back_mediator.h index 72c204a..04d0b02 100644 --- a/ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_mediator.h +++ b/ios/chrome/browser/welcome_back/coordinator/welcome_back_mediator.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_WELCOME_BACK_COORDINATOR_WELCOME_BACK_MEDIATOR_H_ -#define IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_WELCOME_BACK_COORDINATOR_WELCOME_BACK_MEDIATOR_H_ +#ifndef IOS_CHROME_BROWSER_WELCOME_BACK_COORDINATOR_WELCOME_BACK_MEDIATOR_H_ +#define IOS_CHROME_BROWSER_WELCOME_BACK_COORDINATOR_WELCOME_BACK_MEDIATOR_H_ #import <UIKit/UIKit.h> @@ -34,4 +34,4 @@ @end -#endif // IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_WELCOME_BACK_COORDINATOR_WELCOME_BACK_MEDIATOR_H_ +#endif // IOS_CHROME_BROWSER_WELCOME_BACK_COORDINATOR_WELCOME_BACK_MEDIATOR_H_
diff --git a/ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_mediator.mm b/ios/chrome/browser/welcome_back/coordinator/welcome_back_mediator.mm similarity index 91% rename from ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_mediator.mm rename to ios/chrome/browser/welcome_back/coordinator/welcome_back_mediator.mm index 922dcd6978..fb5ad1b 100644 --- a/ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_mediator.mm +++ b/ios/chrome/browser/welcome_back/coordinator/welcome_back_mediator.mm
@@ -2,17 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_mediator.h" +#import "ios/chrome/browser/welcome_back/coordinator/welcome_back_mediator.h" #import "base/notreached.h" #import "base/strings/sys_string_conversions.h" #import "ios/chrome/browser/first_run/public/best_features_item.h" #import "ios/chrome/browser/first_run/ui_bundled/features.h" -#import "ios/chrome/browser/first_run/ui_bundled/welcome_back/model/welcome_back_prefs.h" -#import "ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/welcome_back_screen_consumer.h" #import "ios/chrome/browser/signin/model/authentication_service.h" #import "ios/chrome/browser/signin/model/chrome_account_manager_service.h" #import "ios/chrome/browser/signin/model/constants.h" +#import "ios/chrome/browser/welcome_back/model/features.h" +#import "ios/chrome/browser/welcome_back/model/welcome_back_prefs.h" +#import "ios/chrome/browser/welcome_back/ui/welcome_back_screen_consumer.h" #import "ios/chrome/grit/ios_strings.h" #import "ui/base/l10n/l10n_util.h" @@ -111,10 +112,10 @@ // Returns the default items for each Welcome Back variation. - (std::vector<BestFeaturesItemType>)preferredItems { - using enum first_run::WelcomeBackScreenVariationType; + using enum WelcomeBackScreenVariationType; using enum BestFeaturesItemType; - switch (first_run::GetWelcomeBackScreenVariationType()) { + switch (GetWelcomeBackScreenVariationType()) { case kBasicsWithLockedIncognitoTabs: return {kLensSearch, kEnhancedSafeBrowsing, kLockedIncognitoTabs}; case kBasicsWithPasswords:
diff --git a/ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_mediator_unittest.mm b/ios/chrome/browser/welcome_back/coordinator/welcome_back_mediator_unittest.mm similarity index 92% rename from ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_mediator_unittest.mm rename to ios/chrome/browser/welcome_back/coordinator/welcome_back_mediator_unittest.mm index e35ccd5d..a231978 100644 --- a/ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_mediator_unittest.mm +++ b/ios/chrome/browser/welcome_back/coordinator/welcome_back_mediator_unittest.mm
@@ -2,17 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator/welcome_back_mediator.h" +#import "ios/chrome/browser/welcome_back/coordinator/welcome_back_mediator.h" #import <UIKit/UIKit.h> #import "base/strings/sys_string_conversions.h" #import "base/test/scoped_feature_list.h" #import "ios/chrome/browser/first_run/public/best_features_item.h" -#import "ios/chrome/browser/first_run/public/features.h" #import "ios/chrome/browser/first_run/ui_bundled/features.h" -#import "ios/chrome/browser/first_run/ui_bundled/welcome_back/model/welcome_back_prefs.h" -#import "ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/welcome_back_screen_consumer.h" #import "ios/chrome/browser/shared/model/application_context/application_context.h" #import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h" #import "ios/chrome/browser/shared/model/profile/test/test_profile_manager_ios.h" @@ -22,6 +19,9 @@ #import "ios/chrome/browser/signin/model/fake_authentication_service_delegate.h" #import "ios/chrome/browser/signin/model/fake_system_identity.h" #import "ios/chrome/browser/signin/model/fake_system_identity_manager.h" +#import "ios/chrome/browser/welcome_back/model/features.h" +#import "ios/chrome/browser/welcome_back/model/welcome_back_prefs.h" +#import "ios/chrome/browser/welcome_back/ui/welcome_back_screen_consumer.h" #import "ios/chrome/grit/ios_strings.h" #import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h" #import "ios/web/public/test/web_task_environment.h" @@ -84,8 +84,7 @@ TEST_F(WelcomeBackMediatorTest, ConfirmEligiblePreferredItemsSet) { // Enable Variant A: Bling’s Basics with Locked Incognito Tabs. scoped_feature_list_.InitAndEnableFeatureWithParameters( - first_run::kWelcomeBackInFirstRun, - {{first_run::kWelcomeBackInFirstRunParam, "1"}}); + kWelcomeBackInFirstRun, {{kWelcomeBackInFirstRunParam, "1"}}); // Expect Lens, Enhanced Safe Browsing, and Locked Incognito Tabs and the // default title. @@ -110,8 +109,7 @@ TEST_F(WelcomeBackMediatorTest, ConfirmIneligibleItemReplaced) { // Enable Variant B: Bling’s Basics with Save & Autofill Passwords. scoped_feature_list_.InitAndEnableFeatureWithParameters( - first_run::kWelcomeBackInFirstRun, - {{first_run::kWelcomeBackInFirstRunParam, "2"}}); + kWelcomeBackInFirstRun, {{kWelcomeBackInFirstRunParam, "2"}}); // Mark Lens as used. MarkWelcomeBackFeatureUsed(BestFeaturesItemType::kLensSearch); @@ -138,8 +136,7 @@ TEST_F(WelcomeBackMediatorTest, ConfirmAllPreferredItemsReplaced) { // Enable Variant C: Productivity and Shopping. scoped_feature_list_.InitAndEnableFeatureWithParameters( - first_run::kWelcomeBackInFirstRun, - {{first_run::kWelcomeBackInFirstRunParam, "3"}}); + kWelcomeBackInFirstRun, {{kWelcomeBackInFirstRunParam, "3"}}); // Mark all the preferred features as used. MarkWelcomeBackFeatureUsed(BestFeaturesItemType::kTabGroups); @@ -169,8 +166,7 @@ TEST_F(WelcomeBackMediatorTest, ConfirmLowPriorityItemReplacement) { // Enable Variant A: Bling’s Basics with Locked Incognito Tabs. scoped_feature_list_.InitAndEnableFeatureWithParameters( - first_run::kWelcomeBackInFirstRun, - {{first_run::kWelcomeBackInFirstRunParam, "1"}}); + kWelcomeBackInFirstRun, {{kWelcomeBackInFirstRunParam, "1"}}); // Mark half of the features as used. MarkWelcomeBackFeatureUsed(BestFeaturesItemType::kLockedIncognitoTabs); @@ -201,8 +197,7 @@ TEST_F(WelcomeBackMediatorTest, ConfirmOnlyTwoItemsSet) { // Enable Variant A: Bling’s Basics with Locked Incognito Tabs. scoped_feature_list_.InitAndEnableFeatureWithParameters( - first_run::kWelcomeBackInFirstRun, - {{first_run::kWelcomeBackInFirstRunParam, "1"}}); + kWelcomeBackInFirstRun, {{kWelcomeBackInFirstRunParam, "1"}}); // Mark 6 out of 8 features as used. MarkWelcomeBackFeatureUsed(BestFeaturesItemType::kEnhancedSafeBrowsing); @@ -234,8 +229,7 @@ TEST_F(WelcomeBackMediatorTest, ConfirmOnlyThreeEligibleItemsSet) { // Enable Variant A: Bling’s Basics with Locked Incognito Tabs. scoped_feature_list_.InitAndEnableFeatureWithParameters( - first_run::kWelcomeBackInFirstRun, - {{first_run::kWelcomeBackInFirstRunParam, "1"}}); + kWelcomeBackInFirstRun, {{kWelcomeBackInFirstRunParam, "1"}}); // Mark 5 out of 8 features as used. MarkWelcomeBackFeatureUsed(BestFeaturesItemType::kLensSearch); @@ -267,8 +261,7 @@ TEST_F(WelcomeBackMediatorTest, ConfirmUserInformationRetrieved) { // Enable Variant D: Sign In Benefits. scoped_feature_list_.InitAndEnableFeatureWithParameters( - first_run::kWelcomeBackInFirstRun, - {{first_run::kWelcomeBackInFirstRunParam, "4"}}); + kWelcomeBackInFirstRun, {{kWelcomeBackInFirstRunParam, "4"}}); // Sign in to a fake account. AuthenticationService* auth_service_ =
diff --git a/ios/chrome/browser/welcome_back/model/BUILD.gn b/ios/chrome/browser/welcome_back/model/BUILD.gn new file mode 100644 index 0000000..40a9ec7 --- /dev/null +++ b/ios/chrome/browser/welcome_back/model/BUILD.gn
@@ -0,0 +1,31 @@ +# Copyright 2025 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +source_set("model") { + sources = [ + "welcome_back_prefs.h", + "welcome_back_prefs.mm", + ] + public_deps = [ "//components/prefs" ] + deps = [ + "//ios/chrome/browser/first_run/public:best_features_item", + "//ios/chrome/browser/shared/model/application_context", + ] + frameworks = [ "UIKit.framework" ] +} + +source_set("features") { + sources = [ + "features.h", + "features.mm", + ] + public_deps = [ "//base" ] + deps = [ + "//components/prefs", + "//ios/chrome/browser/first_run/public:best_features_item", + "//ios/chrome/browser/first_run/ui_bundled:features", + "//ios/chrome/browser/shared/model/application_context", + "//ios/chrome/browser/welcome_back/model", + ] +}
diff --git a/ios/chrome/browser/welcome_back/model/DEPS b/ios/chrome/browser/welcome_back/model/DEPS new file mode 100644 index 0000000..f6b62b79 --- /dev/null +++ b/ios/chrome/browser/welcome_back/model/DEPS
@@ -0,0 +1,4 @@ +include_rules = [ + "+ios/chrome/browser/first_run/public", + "+ios/chrome/browser/first_run/ui_bundled/features.h", +]
diff --git a/ios/chrome/browser/welcome_back/model/features.h b/ios/chrome/browser/welcome_back/model/features.h new file mode 100644 index 0000000..7202a4e --- /dev/null +++ b/ios/chrome/browser/welcome_back/model/features.h
@@ -0,0 +1,51 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IOS_CHROME_BROWSER_WELCOME_BACK_MODEL_FEATURES_H_ +#define IOS_CHROME_BROWSER_WELCOME_BACK_MODEL_FEATURES_H_ + +#import "base/feature_list.h" + +enum class BestFeaturesItemType; + +// Enum to represent the arms of feature kWelcomeBackInFirstRun. +enum class WelcomeBackScreenVariationType { + kDisabled, + // Show the Search with Lens, Enhanced Safe Browsing, and Locked Incognito + // items. + kBasicsWithLockedIncognitoTabs, + // Show the Enhanced Safe Browsing, Search with Lens, and Save & Autofill + // Passwords items. + kBasicsWithPasswords, + // Show the Tab Groups, Locked Incognito, and Price Tracking & Insights items. + kProductivityAndShopping, + // Show the Search with Lens, Enhanced Safe Browsing, and Autofill Passwords + // in Other Apps items. If Credential Provider Extension (CPE) is already + // enabled, Autofill Passwords in Other Apps is replaced with Share Passwords. + kSignInBenefits, +}; + +// Feature to enable the Welcome Back screen. +BASE_DECLARE_FEATURE(kWelcomeBackInFirstRun); + +// Name of the param that indicates which variation of the +// kWelcomeBackInFirstRun is enabled. +extern const char kWelcomeBackInFirstRunParam[]; + +// Whether `kWelcomeBackInFirstRun` is enabled. This experiment is disabled +// when `kBestFeaturesScreenInFirstRun` is enabled. +bool IsWelcomeBackInFirstRunEnabled(); + +// Erases an item from `kWelcomeBackEligibleItems`. +void MarkWelcomeBackFeatureUsed(BestFeaturesItemType item_type); + +// Returns which variation of the kWelcomeBackInFirstRun feature is enabled or +// `kDisabled` if the feature is disabled. +WelcomeBackScreenVariationType GetWelcomeBackScreenVariationType(); + +// Whether `kWelcomeBackInFirstRun` is enabled. This experiment is disabled when +// `kBestFeaturesScreenInFirstRun` is enabled. +bool IsWelcomeBackInFirstRunEnabled(); + +#endif // IOS_CHROME_BROWSER_WELCOME_BACK_MODEL_FEATURES_H_
diff --git a/ios/chrome/browser/welcome_back/model/features.mm b/ios/chrome/browser/welcome_back/model/features.mm new file mode 100644 index 0000000..dba150b --- /dev/null +++ b/ios/chrome/browser/welcome_back/model/features.mm
@@ -0,0 +1,38 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "ios/chrome/browser/welcome_back/model/features.h" + +#import "base/metrics/field_trial_params.h" +#import "components/prefs/scoped_user_pref_update.h" +#import "ios/chrome/browser/first_run/public/best_features_item.h" +#import "ios/chrome/browser/first_run/ui_bundled/features.h" +#import "ios/chrome/browser/shared/model/application_context/application_context.h" +#import "ios/chrome/browser/welcome_back/model/welcome_back_prefs.h" + +BASE_FEATURE(kWelcomeBackInFirstRun, base::FEATURE_DISABLED_BY_DEFAULT); + +const char kWelcomeBackInFirstRunParam[] = "WelcomeBackInFirstRunParam"; + +bool IsWelcomeBackInFirstRunEnabled() { + return base::FeatureList::IsEnabled(kWelcomeBackInFirstRun) && + !base::FeatureList::IsEnabled( + first_run::kBestFeaturesScreenInFirstRun); +} + +void MarkWelcomeBackFeatureUsed(BestFeaturesItemType item_type) { + PrefService* local_state = GetApplicationContext()->GetLocalState(); + int pref = static_cast<int>(item_type); + ScopedListPrefUpdate update(local_state, kWelcomeBackEligibleItems); + update->EraseValue(base::Value(pref)); +} + +WelcomeBackScreenVariationType GetWelcomeBackScreenVariationType() { + if (!base::FeatureList::IsEnabled(kWelcomeBackInFirstRun)) { + return WelcomeBackScreenVariationType::kDisabled; + } + return static_cast<WelcomeBackScreenVariationType>( + base::GetFieldTrialParamByFeatureAsInt(kWelcomeBackInFirstRun, + kWelcomeBackInFirstRunParam, 1)); +}
diff --git a/ios/chrome/browser/first_run/ui_bundled/welcome_back/model/welcome_back_prefs.h b/ios/chrome/browser/welcome_back/model/welcome_back_prefs.h similarity index 71% rename from ios/chrome/browser/first_run/ui_bundled/welcome_back/model/welcome_back_prefs.h rename to ios/chrome/browser/welcome_back/model/welcome_back_prefs.h index 33e9b37..0d34088 100644 --- a/ios/chrome/browser/first_run/ui_bundled/welcome_back/model/welcome_back_prefs.h +++ b/ios/chrome/browser/welcome_back/model/welcome_back_prefs.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_WELCOME_BACK_MODEL_WELCOME_BACK_PREFS_H_ -#define IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_WELCOME_BACK_MODEL_WELCOME_BACK_PREFS_H_ +#ifndef IOS_CHROME_BROWSER_WELCOME_BACK_MODEL_WELCOME_BACK_PREFS_H_ +#define IOS_CHROME_BROWSER_WELCOME_BACK_MODEL_WELCOME_BACK_PREFS_H_ #import <UIKit/UIKit.h> @@ -22,4 +22,4 @@ // current eligible items. std::vector<BestFeaturesItemType> GetWelcomeBackEligibleItems(); -#endif // IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_WELCOME_BACK_MODEL_WELCOME_BACK_PREFS_H_ +#endif // IOS_CHROME_BROWSER_WELCOME_BACK_MODEL_WELCOME_BACK_PREFS_H_
diff --git a/ios/chrome/browser/first_run/ui_bundled/welcome_back/model/welcome_back_prefs.mm b/ios/chrome/browser/welcome_back/model/welcome_back_prefs.mm similarity index 97% rename from ios/chrome/browser/first_run/ui_bundled/welcome_back/model/welcome_back_prefs.mm rename to ios/chrome/browser/welcome_back/model/welcome_back_prefs.mm index 95eda0f..50345b3 100644 --- a/ios/chrome/browser/first_run/ui_bundled/welcome_back/model/welcome_back_prefs.mm +++ b/ios/chrome/browser/welcome_back/model/welcome_back_prefs.mm
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "ios/chrome/browser/first_run/ui_bundled/welcome_back/model/welcome_back_prefs.h" +#import "ios/chrome/browser/welcome_back/model/welcome_back_prefs.h" #import "components/prefs/pref_registry_simple.h" #import "components/prefs/scoped_user_pref_update.h"
diff --git a/ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/BUILD.gn b/ios/chrome/browser/welcome_back/ui/BUILD.gn similarity index 100% rename from ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/BUILD.gn rename to ios/chrome/browser/welcome_back/ui/BUILD.gn
diff --git a/ios/chrome/browser/welcome_back/ui/DEPS b/ios/chrome/browser/welcome_back/ui/DEPS new file mode 100644 index 0000000..87143a57d --- /dev/null +++ b/ios/chrome/browser/welcome_back/ui/DEPS
@@ -0,0 +1,3 @@ +include_rules = [ + "+ios/chrome/browser/promos_manager/model", +]
diff --git a/ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/welcome_back_action_handler.h b/ios/chrome/browser/welcome_back/ui/welcome_back_action_handler.h similarity index 63% rename from ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/welcome_back_action_handler.h rename to ios/chrome/browser/welcome_back/ui/welcome_back_action_handler.h index e03199d..2cfac72 100644 --- a/ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/welcome_back_action_handler.h +++ b/ios/chrome/browser/welcome_back/ui/welcome_back_action_handler.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_WELCOME_BACK_UI_WELCOME_BACK_ACTION_HANDLER_H_ -#define IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_WELCOME_BACK_UI_WELCOME_BACK_ACTION_HANDLER_H_ +#ifndef IOS_CHROME_BROWSER_WELCOME_BACK_UI_WELCOME_BACK_ACTION_HANDLER_H_ +#define IOS_CHROME_BROWSER_WELCOME_BACK_UI_WELCOME_BACK_ACTION_HANDLER_H_ #import "ios/chrome/common/ui/confirmation_alert/confirmation_alert_action_handler.h" @@ -17,4 +17,4 @@ @end -#endif // IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_WELCOME_BACK_UI_WELCOME_BACK_ACTION_HANDLER_H_ +#endif // IOS_CHROME_BROWSER_WELCOME_BACK_UI_WELCOME_BACK_ACTION_HANDLER_H_
diff --git a/ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/welcome_back_display_handler.h b/ios/chrome/browser/welcome_back/ui/welcome_back_display_handler.h similarity index 71% rename from ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/welcome_back_display_handler.h rename to ios/chrome/browser/welcome_back/ui/welcome_back_display_handler.h index a1f223e..21db22e 100644 --- a/ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/welcome_back_display_handler.h +++ b/ios/chrome/browser/welcome_back/ui/welcome_back_display_handler.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_WELCOME_BACK_UI_WELCOME_BACK_DISPLAY_HANDLER_H_ -#define IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_WELCOME_BACK_UI_WELCOME_BACK_DISPLAY_HANDLER_H_ +#ifndef IOS_CHROME_BROWSER_WELCOME_BACK_UI_WELCOME_BACK_DISPLAY_HANDLER_H_ +#define IOS_CHROME_BROWSER_WELCOME_BACK_UI_WELCOME_BACK_DISPLAY_HANDLER_H_ #import <Foundation/Foundation.h> @@ -23,4 +23,4 @@ @end -#endif // IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_WELCOME_BACK_UI_WELCOME_BACK_DISPLAY_HANDLER_H_ +#endif // IOS_CHROME_BROWSER_WELCOME_BACK_UI_WELCOME_BACK_DISPLAY_HANDLER_H_
diff --git a/ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/welcome_back_display_handler.mm b/ios/chrome/browser/welcome_back/ui/welcome_back_display_handler.mm similarity index 86% rename from ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/welcome_back_display_handler.mm rename to ios/chrome/browser/welcome_back/ui/welcome_back_display_handler.mm index a3909e2..126774e 100644 --- a/ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/welcome_back_display_handler.mm +++ b/ios/chrome/browser/welcome_back/ui/welcome_back_display_handler.mm
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/welcome_back_display_handler.h" +#import "ios/chrome/browser/welcome_back/ui/welcome_back_display_handler.h" #import "components/feature_engagement/public/feature_constants.h" #import "ios/chrome/browser/promos_manager/model/promo_config.h"
diff --git a/ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/welcome_back_screen_consumer.h b/ios/chrome/browser/welcome_back/ui/welcome_back_screen_consumer.h similarity index 67% rename from ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/welcome_back_screen_consumer.h rename to ios/chrome/browser/welcome_back/ui/welcome_back_screen_consumer.h index 8f44fe2..897f717 100644 --- a/ios/chrome/browser/first_run/ui_bundled/welcome_back/ui/welcome_back_screen_consumer.h +++ b/ios/chrome/browser/welcome_back/ui/welcome_back_screen_consumer.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_WELCOME_BACK_UI_WELCOME_BACK_SCREEN_CONSUMER_H_ -#define IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_WELCOME_BACK_UI_WELCOME_BACK_SCREEN_CONSUMER_H_ +#ifndef IOS_CHROME_BROWSER_WELCOME_BACK_UI_WELCOME_BACK_SCREEN_CONSUMER_H_ +#define IOS_CHROME_BROWSER_WELCOME_BACK_UI_WELCOME_BACK_SCREEN_CONSUMER_H_ #import <Foundation/Foundation.h> @@ -23,4 +23,4 @@ @end -#endif // IOS_CHROME_BROWSER_FIRST_RUN_UI_BUNDLED_WELCOME_BACK_UI_WELCOME_BACK_SCREEN_CONSUMER_H_ +#endif // IOS_CHROME_BROWSER_WELCOME_BACK_UI_WELCOME_BACK_SCREEN_CONSUMER_H_
diff --git a/ios/chrome/test/BUILD.gn b/ios/chrome/test/BUILD.gn index 78e3ae4..6d2b07d 100644 --- a/ios/chrome/test/BUILD.gn +++ b/ios/chrome/test/BUILD.gn
@@ -283,7 +283,6 @@ "//ios/chrome/browser/favicon/model:unit_tests", "//ios/chrome/browser/feature_engagement/model:unit_tests", "//ios/chrome/browser/first_run/ui_bundled/tos:unit_tests", - "//ios/chrome/browser/first_run/ui_bundled/welcome_back/coordinator:unit_tests", "//ios/chrome/browser/flags:unit_tests", "//ios/chrome/browser/follow/model:unit_tests", "//ios/chrome/browser/fullscreen/ui_bundled:unit_tests", @@ -538,6 +537,7 @@ "//ios/chrome/browser/web_state_list/model:unit_tests", "//ios/chrome/browser/web_state_list/model/web_usage_enabler:unit_tests", "//ios/chrome/browser/webui/model:unit_tests", + "//ios/chrome/browser/welcome_back/coordinator:unit_tests", "//ios/chrome/browser/whats_new/coordinator/promo:unit_tests", "//ios/chrome/browser/whats_new/ui/cells:unit_tests", "//ios/chrome/browser/whats_new/ui/data_source:unit_tests",
diff --git a/ios/chrome/test/earl_grey2/BUILD.gn b/ios/chrome/test/earl_grey2/BUILD.gn index 5c2b196..b42a127 100644 --- a/ios/chrome/test/earl_grey2/BUILD.gn +++ b/ios/chrome/test/earl_grey2/BUILD.gn
@@ -203,6 +203,7 @@ "//ios/chrome/browser/download/ui:eg2_tests", "//ios/chrome/browser/drive_file_picker/test:eg2_tests", "//ios/chrome/browser/explain_with_gemini/coordinator:eg2_tests", + "//ios/chrome/browser/file_upload_panel/test:eg2_tests", "//ios/chrome/browser/first_run/ui_bundled:eg2_tests", "//ios/chrome/browser/first_run/ui_bundled/best_features/ui:eg2_tests", "//ios/chrome/browser/first_run/ui_bundled/default_browser:eg2_tests",
diff --git a/ios/public/provider/chrome/browser/bwg/bwg_api.h b/ios/public/provider/chrome/browser/bwg/bwg_api.h index 335cc4b..2d79e41 100644 --- a/ios/public/provider/chrome/browser/bwg/bwg_api.h +++ b/ios/public/provider/chrome/browser/bwg/bwg_api.h
@@ -38,28 +38,6 @@ kEnterpriseDisabled, }; -// TODO(crbug.com/434662294): Remove when migration is complete. -// Enum representing the PageContext state of the BWG experience. -// This needs to stay in sync with GCRGeminiPageState (and its SDK counterpart). -enum class BWGPageContextState { - // Default state. - kUnknown, - // PageContext was successfully attached. - kSuccessfullyAttached, - // PageContext should be detached. - kShouldDetach, - // PageContext is protected. - kProtected, - // PageContext is present but likely to be blocked. - kBlocked, - // There was an error extracting the PageContext. - kError, - // PageContext should be detached due to an enterprise policy. - kEnterpriseDisabled, - // PageContext should be detached due to the user disabling it. - kUserDisabled, -}; - // Enum representing the page context computation state of the BWG experience. // This needs to stay in sync with GCRGeminiPageContextComputationState (and its // SDK counterpart).
diff --git a/ios/web/public/web_state.h b/ios/web/public/web_state.h index a55b8e7b..cb685a4 100644 --- a/ios/web/public/web_state.h +++ b/ios/web/public/web_state.h
@@ -199,7 +199,7 @@ // Creates a new WebState from a serialized representation of the session. // `session_storage` must not be nil. - // TODO(crbug.com/40245950): remove when the optimised serialisation feature + // TODO(crbug.com/40945317): remove when the optimised serialisation feature // has been fully launched. static std::unique_ptr<WebState> CreateWithStorageSession( const CreateParams& params,
diff --git a/ios/web/web_state/web_state_impl_realized_web_state.mm b/ios/web/web_state/web_state_impl_realized_web_state.mm index 2dc655c..65d07a5 100644 --- a/ios/web/web_state/web_state_impl_realized_web_state.mm +++ b/ios/web/web_state/web_state_impl_realized_web_state.mm
@@ -77,7 +77,7 @@ // The WebStateStorage is only needed to implement SerializeToProto() while // the navigation history restoration is in progress for the legacy session // serialization logic. - // TODO(crbug.com/40245950): Remove it once the feature has launched. + // TODO(crbug.com/40945317): Remove it once the feature has launched. const proto::WebStateStorage storage_; const std::u16string page_title_; const GURL page_visible_url_;
diff --git a/ios/web/web_state/web_state_impl_serialized_data.h b/ios/web/web_state/web_state_impl_serialized_data.h index 396f5a5..198697b 100644 --- a/ios/web/web_state/web_state_impl_serialized_data.h +++ b/ios/web/web_state/web_state_impl_serialized_data.h
@@ -50,7 +50,7 @@ // Getter and setter for the CRWSessionStorage; only available when the // session serialization optimisation feature is disabled. - // TODO(crbug.com/40245950): remove once the feature is fully launched. + // TODO(crbug.com/40945317): remove once the feature is fully launched. CRWSessionStorage* GetSessionStorage() const; void SetSessionStorage(CRWSessionStorage* storage); @@ -111,7 +111,7 @@ // Serialized representation of the session; only available when the // session serialization optimisation feature is disabled. - // TODO(crbug.com/40245950): remove once the feature is fully launched. + // TODO(crbug.com/40945317): remove once the feature is fully launched. __strong CRWSessionStorage* session_storage_ = nil; };
diff --git a/ios_internal b/ios_internal index 71d59af..0b03de5 160000 --- a/ios_internal +++ b/ios_internal
@@ -1 +1 @@ -Subproject commit 71d59afa95dde3450fd6b7ae73cf8478a52ea226 +Subproject commit 0b03de5e10bb21ce7ce99f72042c16a172139d41
diff --git a/media/base/decoder_buffer_side_data.cc b/media/base/decoder_buffer_side_data.cc index c43f770..04c2dd3 100644 --- a/media/base/decoder_buffer_side_data.cc +++ b/media/base/decoder_buffer_side_data.cc
@@ -29,6 +29,7 @@ bool DecoderBufferSideData::Matches(const DecoderBufferSideData& other) const { return spatial_layers == other.spatial_layers && alpha_data.as_span() == other.alpha_data.as_span() && + itu_t35_data.as_span() == other.itu_t35_data.as_span() && secure_handle == other.secure_handle && discard_padding == other.discard_padding && next_config == other.next_config; @@ -40,6 +41,9 @@ if (!alpha_data.empty()) { result->alpha_data = base::HeapArray<uint8_t>::CopiedFrom(alpha_data); } + if (!itu_t35_data.empty()) { + result->itu_t35_data = base::HeapArray<uint8_t>::CopiedFrom(itu_t35_data); + } result->secure_handle = secure_handle; result->discard_padding = discard_padding; result->next_config = next_config;
diff --git a/media/base/decoder_buffer_side_data.h b/media/base/decoder_buffer_side_data.h index d7fe80c..1b3c5102 100644 --- a/media/base/decoder_buffer_side_data.h +++ b/media/base/decoder_buffer_side_data.h
@@ -39,6 +39,9 @@ std::vector<uint32_t> spatial_layers; base::HeapArray<uint8_t> alpha_data; + // ITU-T35 metadata. + base::HeapArray<uint8_t> itu_t35_data; + // Secure buffer handle corresponding to the decrypted contents of the // associated DecoderBuffer. A non-zero value indicates this was set. //
diff --git a/media/base/decoder_buffer_unittest.cc b/media/base/decoder_buffer_unittest.cc index 74c4a9e..6e59126 100644 --- a/media/base/decoder_buffer_unittest.cc +++ b/media/base/decoder_buffer_unittest.cc
@@ -264,15 +264,20 @@ constexpr uint64_t kSecureHandle = 42; const std::vector<uint32_t> kSpatialLayers = {1, 2, 3}; const std::vector<uint8_t> kAlphaData = {9, 8, 7}; + const std::vector<uint8_t> kItuT35Data = {5, 6, 7}; buffer->WritableSideData().secure_handle = kSecureHandle; buffer->WritableSideData().spatial_layers = kSpatialLayers; buffer->WritableSideData().alpha_data = base::HeapArray<uint8_t>::CopiedFrom(kAlphaData); + buffer->WritableSideData().itu_t35_data = + base::HeapArray<uint8_t>::CopiedFrom(kItuT35Data); EXPECT_TRUE(buffer->side_data()); EXPECT_EQ(buffer->side_data()->secure_handle, kSecureHandle); EXPECT_EQ(buffer->side_data()->spatial_layers, kSpatialLayers); EXPECT_EQ(buffer->side_data()->alpha_data.as_span(), base::span(kAlphaData)); + EXPECT_EQ(buffer->side_data()->itu_t35_data.as_span(), + base::span(kItuT35Data)); auto cloned_side_data = buffer->side_data()->Clone(); @@ -282,6 +287,8 @@ cloned_side_data->spatial_layers); EXPECT_EQ(buffer->side_data()->alpha_data.as_span(), cloned_side_data->alpha_data.as_span()); + EXPECT_EQ(buffer->side_data()->itu_t35_data.as_span(), + cloned_side_data->itu_t35_data.as_span()); buffer->set_side_data(nullptr); EXPECT_FALSE(buffer->side_data());
diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc index b8a6fdb..875a991e 100644 --- a/media/filters/ffmpeg_demuxer.cc +++ b/media/filters/ffmpeg_demuxer.cc
@@ -466,6 +466,9 @@ if (side_data_id == 1) { buffer->WritableSideData().alpha_data = base::HeapArray<uint8_t>::CopiedFrom(side_data.subspan(8u)); + } else if (side_data_id == 4) { + buffer->WritableSideData().itu_t35_data = + base::HeapArray<uint8_t>::CopiedFrom(side_data.subspan(8u)); } }
diff --git a/media/filters/vpx_video_decoder.cc b/media/filters/vpx_video_decoder.cc index ad3c939..d5a8a0f 100644 --- a/media/filters/vpx_video_decoder.cc +++ b/media/filters/vpx_video_decoder.cc
@@ -21,6 +21,7 @@ #include "base/numerics/byte_conversions.h" #include "base/task/bind_post_task.h" #include "base/trace_event/trace_event.h" +#include "media/base/agtm.h" #include "media/base/decoder_buffer.h" #include "media/base/limits.h" #include "media/base/media_switches.h" @@ -29,7 +30,6 @@ #include "third_party/libvpx/source/libvpx/vpx/vp8dx.h" #include "third_party/libvpx/source/libvpx/vpx/vpx_decoder.h" #include "third_party/libvpx/source/libvpx/vpx/vpx_frame_buffer.h" - #include "third_party/libyuv/include/libyuv/convert.h" #include "third_party/libyuv/include/libyuv/planar_functions.h" @@ -332,6 +332,25 @@ return true; } + static constexpr size_t kItut35HeaderSize = 7; + if (buffer->side_data() && + buffer->side_data()->itu_t35_data.size() >= kItut35HeaderSize) { + auto side_data = buffer->side_data()->itu_t35_data.as_span(); + static constexpr uint8_t kItut35CountryCodeExtensionMarker = 0xFF; + if (side_data.data()[0] == kItut35CountryCodeExtensionMarker) { + side_data = side_data.subspan(1u); + } + auto [country_code, payload] = side_data.split_at<1u>(); + const std::optional<gfx::HdrMetadataAgtm> agtm = + GetHdrMetadataAgtmFromItutT35(country_code.data()[0], payload); + if (agtm.has_value()) { + gfx::HDRMetadata hdr_metadata = + config_.hdr_metadata().value_or(gfx::HDRMetadata()); + hdr_metadata.agtm = agtm; + config_.set_hdr_metadata(hdr_metadata); + } + } + const vpx_image_t* vpx_image_alpha = nullptr; const auto alpha_decode_status = DecodeAlphaPlane(vpx_image, &vpx_image_alpha, buffer);
diff --git a/media/filters/vpx_video_decoder_unittest.cc b/media/filters/vpx_video_decoder_unittest.cc index 4778119..82f6625 100644 --- a/media/filters/vpx_video_decoder_unittest.cc +++ b/media/filters/vpx_video_decoder_unittest.cc
@@ -15,6 +15,7 @@ #include "base/functional/bind.h" #include "base/functional/callback_helpers.h" +#include "base/numerics/byte_conversions.h" #include "base/run_loop.h" #include "base/test/task_environment.h" #include "build/build_config.h" @@ -27,6 +28,7 @@ #include "media/ffmpeg/scoped_av_packet.h" #include "media/filters/in_memory_url_protocol.h" #include "testing/gmock/include/gmock/gmock.h" +#include "third_party/skia/include/core/SkData.h" using ::testing::_; @@ -365,4 +367,65 @@ } #endif // !defined(LIBVPX_NO_HIGH_BIT_DEPTH) && !defined(ARCH_CPU_ARM_FAMILY) +TEST_F(VpxVideoDecoderTest, AgtmMetadata) { + Initialize(); + + scoped_refptr<DecoderBuffer> data = ReadTestDataFile("vp9-agtm.webm"); + InMemoryUrlProtocol protocol(*data, false); + FFmpegGlue glue(&protocol); + ASSERT_TRUE(glue.OpenContext()); + + auto packet = ScopedAVPacket::Allocate(); + ASSERT_GE(av_read_frame(glue.format_context(), packet.get()), 0); + ASSERT_EQ(packet->side_data_elems, 1); + auto buffer = DecoderBuffer::CopyFrom(AVPacketData(*packet)); + // SAFETY: The best we can do here is trust the size reported by ffmpeg. + auto side_data = UNSAFE_BUFFERS( + base::span(packet->side_data[0].data, packet->side_data[0].size)); + ASSERT_EQ(base::U64FromBigEndian(side_data.first<8u>()), 4u); + buffer->WritableSideData().itu_t35_data = + base::HeapArray<uint8_t>::CopiedFrom(side_data.subspan(8u)); + DecoderStatus decode_status = Decode(buffer); + av_packet_unref(packet.get()); + ASSERT_TRUE(decode_status.is_ok()); + + const auto& frame = output_frames_.front(); + ASSERT_TRUE(frame->hdr_metadata().has_value()); + ASSERT_TRUE(frame->hdr_metadata()->agtm.has_value()); + EXPECT_EQ(frame->hdr_metadata()->agtm->payload->size(), 533u); + + Destroy(); +} + +TEST_F(VpxVideoDecoderTest, AgtmMetadataWithItut35CountryCodeExtension) { + Initialize(); + + scoped_refptr<DecoderBuffer> data = + ReadTestDataFile("vp9-agtm-country-code-extension.webm"); + InMemoryUrlProtocol protocol(*data, false); + FFmpegGlue glue(&protocol); + ASSERT_TRUE(glue.OpenContext()); + + auto packet = ScopedAVPacket::Allocate(); + ASSERT_GE(av_read_frame(glue.format_context(), packet.get()), 0); + ASSERT_EQ(packet->side_data_elems, 1); + auto buffer = DecoderBuffer::CopyFrom(AVPacketData(*packet)); + // SAFETY: The best we can do here is trust the size reported by ffmpeg. + auto side_data = UNSAFE_BUFFERS( + base::span(packet->side_data[0].data, packet->side_data[0].size)); + ASSERT_EQ(base::U64FromBigEndian(side_data.first<8u>()), 4u); + buffer->WritableSideData().itu_t35_data = + base::HeapArray<uint8_t>::CopiedFrom(side_data.subspan(8u)); + DecoderStatus decode_status = Decode(buffer); + av_packet_unref(packet.get()); + ASSERT_TRUE(decode_status.is_ok()); + + const auto& frame = output_frames_.front(); + ASSERT_TRUE(frame->hdr_metadata().has_value()); + ASSERT_TRUE(frame->hdr_metadata()->agtm.has_value()); + EXPECT_EQ(frame->hdr_metadata()->agtm->payload->size(), 533u); + + Destroy(); +} + } // namespace media
diff --git a/media/formats/webm/webm_cluster_parser.cc b/media/formats/webm/webm_cluster_parser.cc index 897325c..3f58c69 100644 --- a/media/formats/webm/webm_cluster_parser.cc +++ b/media/formats/webm/webm_cluster_parser.cc
@@ -529,6 +529,9 @@ if (side_data_id == 1) { buffer->WritableSideData().alpha_data = base::HeapArray<uint8_t>::CopiedFrom(side_data.subspan(8u)); + } else if (side_data_id == 4) { + buffer->WritableSideData().itu_t35_data = + base::HeapArray<uint8_t>::CopiedFrom(side_data.subspan(8u)); } }
diff --git a/media/test/data/README.md b/media/test/data/README.md index e431f39..216c2438 100644 --- a/media/test/data/README.md +++ b/media/test/data/README.md
@@ -868,15 +868,6 @@ #### av1-monochrome-I-frame-320x240-[8,10,12]bpp Same as av1-I-frame-320x240 with --monochrome and -b=[8,10,12] aomenc options. -#### av1-I-frame-320x240-agtm -Same as av1-I-frame-320x240 but with an AGTM ITU_T35 metadata OBU added. - -#### av1-I-frame-320x240-agtm.ivf -Created by converting av1-I-frame-320x240-agtm (raw OBU) to IVF: -``` -ffmpeg -i av1-I-frame-320x240-agtm -c:v copy av1-I-frame-320x240-agtm.ivf -``` - #### bear-av1-cenc.mp4 Encrypted version of bear-av1.mp4. Encrypted by [Shaka Packager] built locally at commit 53aa775ea488c0ffd3a2e1cb78ad000154e414e1 using key ID [1] and key [2]. @@ -931,6 +922,24 @@ shaka/packager/tools/pssh/pssh-box.py --common-system-id --key-id 30313233343536373839303132333435 --hex ``` +### AGTM + +#### av1-I-frame-320x240-agtm +Same as av1-I-frame-320x240 but with an AGTM ITU_T35 metadata OBU added. + +#### av1-I-frame-320x240-agtm.ivf +Created by converting av1-I-frame-320x240-agtm (raw OBU) to IVF: +``` +ffmpeg -i av1-I-frame-320x240-agtm -c:v copy av1-I-frame-320x240-agtm.ivf +``` + +#### vp9-agtm.webm +VP9 video with a single frame that contains agtm metadata. + +#### vp9-agtm-country-code-extension.webm +Same as vp9-agtm.webm but the ITU_T35 message contains a country code extension +byte. + ### HLS #### bear-1280x720-hls-clear-mpl.m3u8
diff --git a/media/test/data/vp9-agtm-country-code-extension.webm b/media/test/data/vp9-agtm-country-code-extension.webm new file mode 100644 index 0000000..03238a0 --- /dev/null +++ b/media/test/data/vp9-agtm-country-code-extension.webm Binary files differ
diff --git a/media/test/data/vp9-agtm.webm b/media/test/data/vp9-agtm.webm new file mode 100644 index 0000000..bdbd7fc7 --- /dev/null +++ b/media/test/data/vp9-agtm.webm Binary files differ
diff --git a/media/test/media_bundle_data.filelist b/media/test/media_bundle_data.filelist index c17f25f..6e03cfc6 100644 --- a/media/test/media_bundle_data.filelist +++ b/media/test/media_bundle_data.filelist
@@ -512,6 +512,8 @@ data/vp8-I-frame-640x240 data/vp8-P-frame-320x240 data/vp8-corrupt-I-frame +data/vp9-agtm.webm +data/vp9-agtm-country-code-extension.webm data/vp9-I-frame-1280x720 data/vp9-I-frame-320x240 data/vp9-duplicate-frame.webm
diff --git a/media/unit_tests_bundle_data.filelist b/media/unit_tests_bundle_data.filelist index ff3bc09c..d9b7221 100644 --- a/media/unit_tests_bundle_data.filelist +++ b/media/unit_tests_bundle_data.filelist
@@ -524,6 +524,8 @@ //media/test/data/vp8-I-frame-640x240 //media/test/data/vp8-P-frame-320x240 //media/test/data/vp8-corrupt-I-frame +//media/test/data/vp9-agtm.webm +//media/test/data/vp9-agtm-country-code-extension.webm //media/test/data/vp9-I-frame-1280x720 //media/test/data/vp9-I-frame-320x240 //media/test/data/vp9-duplicate-frame.webm
diff --git a/mojo/core/channel.h b/mojo/core/channel.h index 276f6c8..108c2ed 100644 --- a/mojo/core/channel.h +++ b/mojo/core/channel.h
@@ -360,11 +360,6 @@ Channel(const Channel&) = delete; Channel& operator=(const Channel&) = delete; -#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(MOJO_USE_APPLE_CHANNEL) - // At this point only ChannelPosix needs InitFeatures. - static void set_posix_use_writev(bool use_writev); -#endif - static void set_use_trivial_messages(bool use_trivial_messages); bool is_for_ipcz() const { return is_for_ipcz_; }
diff --git a/mojo/core/channel_posix.cc b/mojo/core/channel_posix.cc index 324b1bb..f3fbaa7 100644 --- a/mojo/core/channel_posix.cc +++ b/mojo/core/channel_posix.cc
@@ -41,7 +41,6 @@ namespace mojo::core { namespace { -std::atomic<bool> g_use_writev{false}; const size_t kMaxBatchReadCapacity = 256 * 1024; } // namespace @@ -435,9 +434,6 @@ } bool ChannelPosix::FlushOutgoingMessagesNoLock() { - if (g_use_writev) - return FlushOutgoingMessagesWritevNoLock(); - base::circular_deque<MessageView> messages; std::swap(outgoing_messages_, messages); @@ -498,105 +494,7 @@ OnError(error); } -bool ChannelPosix::WriteOutgoingMessagesWithWritev() { - if (outgoing_messages_.empty()) - return true; - // If all goes well we can submit a writev(2) with a iovec of size - // outgoing_messages_.size() but never more than the kernel allows. - size_t num_messages_to_send = - std::min<size_t>(IOV_MAX, outgoing_messages_.size()); - base::FixedArray<iovec> iov(num_messages_to_send); - - // Populate the iov. - size_t num_iovs_set = 0; - for (auto it = outgoing_messages_.begin(); - num_iovs_set < num_messages_to_send; ++it) { - if (it->num_handles_remaining() > 0) { - // We can't send handles with writev(2) so stop at this message. - break; - } - - iov[num_iovs_set].iov_base = const_cast<void*>(it->data()); - iov[num_iovs_set].iov_len = it->data_num_bytes(); - num_iovs_set++; - } - - size_t iov_offset = 0; - while (iov_offset < num_iovs_set) { - ssize_t bytes_written = SocketWritev(socket_.get(), &iov[iov_offset], - num_iovs_set - iov_offset); - if (bytes_written < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - WaitForWriteOnIOThreadNoLock(); - return true; - } - return false; - } - - // Let's walk our outgoing_messages_ popping off outgoing_messages_ - // that were fully written. - size_t bytes_remaining = bytes_written; - while (bytes_remaining > 0) { - if (bytes_remaining >= outgoing_messages_.front().data_num_bytes()) { - // This message was fully written. - bytes_remaining -= outgoing_messages_.front().data_num_bytes(); - outgoing_messages_.pop_front(); - iov_offset++; - } else { - // This message was partially written, account for what was - // already written. - outgoing_messages_.front().advance_data_offset(bytes_remaining); - bytes_remaining = 0; - - // Update the iov too as we will call writev again. - iov[iov_offset].iov_base = - const_cast<void*>(outgoing_messages_.front().data()); - iov[iov_offset].iov_len = outgoing_messages_.front().data_num_bytes(); - } - } - } - - return true; -} - -// FlushOutgoingMessagesWritevNoLock is equivalent to -// FlushOutgoingMessagesNoLock except it looks for opportunities to make only -// a single write syscall by using writev(2) instead of write(2). In most -// situations this is very straight forward; however, when a handle needs to -// be transferred we cannot use writev(2) and instead will fall back to the -// standard write. -bool ChannelPosix::FlushOutgoingMessagesWritevNoLock() { - do { - // If the first message contains a handle we will flush it first using a - // standard write, we will also use the standard write if we only have a - // single message. - while (!outgoing_messages_.empty() && - (outgoing_messages_.front().num_handles_remaining() > 0 || - outgoing_messages_.size() == 1)) { - MessageView message = std::move(outgoing_messages_.front()); - - outgoing_messages_.pop_front(); - size_t messages_before_write = outgoing_messages_.size(); - if (!WriteNoLock(std::move(message))) - return false; - - if (outgoing_messages_.size() > messages_before_write) { - // It was re-queued by WriteNoLock. - return true; - } - } - - if (!WriteOutgoingMessagesWithWritev()) - return false; - - // At this point if we have more messages then it's either because we - // exceeded IOV_MAX OR it's because we ran into a FileHandle. Either way - // we just start the process all over again and it will flush any - // FileHandles before attempting writev(2) again. - } while (!outgoing_messages_.empty()); - return true; -} bool ChannelPosix::OnControlMessage(Message::MessageType message_type, const void* payload, @@ -673,11 +571,6 @@ #endif // BUILDFLAG(IS_IOS) // static -void Channel::set_posix_use_writev(bool use_writev) { - g_use_writev = use_writev; -} - -// static scoped_refptr<Channel> Channel::Create( Delegate* delegate, ConnectionParams connection_params,
diff --git a/mojo/core/channel_posix.h b/mojo/core/channel_posix.h index 0591ecc..f9ff872 100644 --- a/mojo/core/channel_posix.h +++ b/mojo/core/channel_posix.h
@@ -91,17 +91,6 @@ ; bool FlushOutgoingMessagesNoLock() EXCLUSIVE_LOCKS_REQUIRED(write_lock_); - bool WriteOutgoingMessagesWithWritev() EXCLUSIVE_LOCKS_REQUIRED(write_lock_); - - // FlushOutgoingMessagesWritevNoLock is equivalent to - // FlushOutgoingMessagesNoLock except it looks for opportunities to make - // only a single write syscall by using writev(2) instead of write(2). In - // most situations this is very straight forward; however, when a handle - // needs to be transferred we cannot use writev(2) and instead will fall - // back to the standard write. - bool FlushOutgoingMessagesWritevNoLock() - EXCLUSIVE_LOCKS_REQUIRED(write_lock_); - #if BUILDFLAG(IS_IOS) bool CloseHandles(const int* fds, size_t num_fds) LOCKS_EXCLUDED(fds_to_close_lock_);
diff --git a/mojo/core/embedder/embedder.cc b/mojo/core/embedder/embedder.cc index 4edf6f8..8b03ef6d 100644 --- a/mojo/core/embedder/embedder.cc +++ b/mojo/core/embedder/embedder.cc
@@ -79,9 +79,6 @@ CHECK(base::FeatureList::GetInstance()); #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(MOJO_USE_APPLE_CHANNEL) - Channel::set_posix_use_writev( - base::FeatureList::IsEnabled(kMojoPosixUseWritev)); - #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) bool shared_mem_enabled = base::FeatureList::IsEnabled(kMojoLinuxChannelSharedMem);
diff --git a/mojo/core/embedder/features.cc b/mojo/core/embedder/features.cc index 50451f8..885bbcb 100644 --- a/mojo/core/embedder/features.cc +++ b/mojo/core/embedder/features.cc
@@ -9,7 +9,6 @@ namespace mojo { namespace core { -#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(MOJO_USE_APPLE_CHANNEL) #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) BASE_FEATURE(kMojoLinuxChannelSharedMem, base::FEATURE_DISABLED_BY_DEFAULT); const base::FeatureParam<int> kMojoLinuxChannelSharedMemPages{ @@ -17,9 +16,6 @@ #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || // BUILDFLAG(IS_ANDROID) -BASE_FEATURE(kMojoPosixUseWritev, base::FEATURE_DISABLED_BY_DEFAULT); -#endif // BUILDFLAG(IS_POSIX) && !BUILDFLAG(MOJO_USE_APPLE_CHANNEL) - BASE_FEATURE(kMojoInlineMessagePayloads, base::FEATURE_DISABLED_BY_DEFAULT); #if BUILDFLAG(MOJO_SUPPORT_LEGACY_CORE)
diff --git a/mojo/core/embedder/features.h b/mojo/core/embedder/features.h index a72ebb7e..87f9df9 100644 --- a/mojo/core/embedder/features.h +++ b/mojo/core/embedder/features.h
@@ -14,7 +14,6 @@ namespace mojo { namespace core { -#if BUILDFLAG(IS_POSIX) && !BUILDFLAG(MOJO_USE_APPLE_CHANNEL) #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) COMPONENT_EXPORT(MOJO_CORE_EMBEDDER_FEATURES) BASE_DECLARE_FEATURE(kMojoLinuxChannelSharedMem); @@ -25,11 +24,6 @@ // BUILDFLAG(IS_ANDROID) COMPONENT_EXPORT(MOJO_CORE_EMBEDDER_FEATURES) -BASE_DECLARE_FEATURE(kMojoPosixUseWritev); - -#endif // BUILDFLAG(IS_POSIX) && !BUILDFLAG(MOJO_USE_APPLE_CHANNEL) - -COMPONENT_EXPORT(MOJO_CORE_EMBEDDER_FEATURES) BASE_DECLARE_FEATURE(kMojoInlineMessagePayloads); #if BUILDFLAG(MOJO_SUPPORT_LEGACY_CORE)
diff --git a/mojo/public/cpp/platform/socket_utils_posix.cc b/mojo/public/cpp/platform/socket_utils_posix.cc index 2a87752..7657fbe 100644 --- a/mojo/public/cpp/platform/socket_utils_posix.cc +++ b/mojo/public/cpp/platform/socket_utils_posix.cc
@@ -73,15 +73,6 @@ return send(socket, bytes, num_bytes, MSG_NOSIGNAL); } -ssize_t SocketWritev(base::PlatformFile socket, - struct iovec* iov, - size_t num_iov) { - struct msghdr msg = {}; - msg.msg_iov = iov; - msg.msg_iovlen = num_iov; - return HANDLE_EINTR(sendmsg(socket, &msg, MSG_NOSIGNAL)); -} - ssize_t SendmsgWithHandles(base::PlatformFile socket, struct iovec* iov, size_t num_iov,
diff --git a/mojo/public/cpp/platform/socket_utils_posix.h b/mojo/public/cpp/platform/socket_utils_posix.h index 87e3457..e059b31 100644 --- a/mojo/public/cpp/platform/socket_utils_posix.h +++ b/mojo/public/cpp/platform/socket_utils_posix.h
@@ -34,12 +34,6 @@ const void* bytes, size_t num_bytes); -// Like |writev()| but handles |EINTR| and never raises |SIGPIPE|. -COMPONENT_EXPORT(MOJO_CPP_PLATFORM) -ssize_t SocketWritev(base::PlatformFile socket, - struct iovec* iov, - size_t num_iov); - // Wrapper around |sendmsg()| which makes it convenient to send attached file // descriptors. All entries in |descriptors| must be valid and |descriptors| // must be non-empty.
diff --git a/mojo/public/rust/mojom_parser/parse_messages.rs b/mojo/public/rust/mojom_parser/parse_messages.rs index 2e294ce1..44b9f3c5 100644 --- a/mojo/public/rust/mojom_parser/parse_messages.rs +++ b/mojo/public/rust/mojom_parser/parse_messages.rs
@@ -64,7 +64,7 @@ let mut data = ParserData::new(data_slice); let _ = parse_header(&mut data)?; match ty { - MojomType::Struct { ref fields } => { + MojomType::Struct { fields } => { let ret = crate::parse_values::parse_struct(&mut data, fields)?; if data.remaining_bytes() != 0 { // We don't support the interface ID struct yet
diff --git a/net/http/transport_security_state_static.pins b/net/http/transport_security_state_static.pins index c717638..8eb8c44 100644 --- a/net/http/transport_security_state_static.pins +++ b/net/http/transport_security_state_static.pins
@@ -43,9 +43,9 @@ # hash function for preloaded entries again (we have already done so once). # -# Last updated: 2025-10-12 12:54 UTC +# Last updated: 2025-10-13 12:54 UTC PinsListTimestamp -1760273644 +1760360046 TestSPKI sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
diff --git a/net/http/transport_security_state_static_pins.json b/net/http/transport_security_state_static_pins.json index 366ed58..3eaddb3 100644 --- a/net/http/transport_security_state_static_pins.json +++ b/net/http/transport_security_state_static_pins.json
@@ -31,7 +31,7 @@ // the 'static_spki_hashes' and 'bad_static_spki_hashes' fields in 'pinsets' // refer to, and the timestamp at which the pins list was last updated. // -// Last updated: 2025-10-12 12:54 UTC +// Last updated: 2025-10-13 12:54 UTC // { "pinsets": [
diff --git a/printing/common/metafile_utils.cc b/printing/common/metafile_utils.cc index 04015b4..6d76cfe5 100644 --- a/printing/common/metafile_utils.cc +++ b/printing/common/metafile_utils.cc
@@ -56,6 +56,8 @@ namespace { +// TODO(crbug.com/451536362) Share these constants with PDF. + // Table 364 in PDF 32000-2:2020 spec, section 14.8.4.3 const char kPDFStructureTypeDocument[] = "Document"; @@ -101,6 +103,13 @@ const char kPDFTableHeaderScopeColumn[] = "Column"; const char kPDFTableHeaderScopeRow[] = "Row"; +// Table 333 in PDF 32000-1:2008 spec, section 14.8.4.2 +const char kPDFStructureTypeArticle[] = "Art"; +const char kPDFStructureTypeBlockQuote[] = "BlockQuote"; + +// Table 338 in PDF 32000-1:2008 spec, section 14.8.4.4.1 +const char kPDFStructureTypeCode[] = "Code"; + SkString GetHeadingStructureType(int heading_level) { // From Table 366 in PDF 32000-2:2020 spec, section 14.8.4.5, // "H1"..."H6" are valid structure types. @@ -152,6 +161,15 @@ case ax::mojom::Role::kGenericContainer: tag->fTypeString = kPDFStructureTypeDiv; break; + case ax::mojom::Role::kArticle: + tag->fTypeString = kPDFStructureTypeArticle; + break; + case ax::mojom::Role::kBlockquote: + tag->fTypeString = kPDFStructureTypeBlockQuote; + break; + case ax::mojom::Role::kCode: + tag->fTypeString = kPDFStructureTypeCode; + break; case ax::mojom::Role::kComplementary: tag->fTypeString = kPDFStructureTypeAside; break;
diff --git a/skia/BUILD.gn b/skia/BUILD.gn index 6b2fc74..68a712e 100644 --- a/skia/BUILD.gn +++ b/skia/BUILD.gn
@@ -645,6 +645,7 @@ rust_static_library("rust_png_ffi") { visibility = [ ":*" ] + edition = "2021" allow_unsafe = true # For FFI crate_root = skia_codec_rust_png_ffi_crate_root sources = skia_codec_rust_png_ffi_rs_srcs
diff --git a/testing/rust_gtest_interop/gtest_attribute.rs b/testing/rust_gtest_interop/gtest_attribute.rs index d3792be..751c9f0d 100644 --- a/testing/rust_gtest_interop/gtest_attribute.rs +++ b/testing/rust_gtest_interop/gtest_attribute.rs
@@ -284,7 +284,7 @@ // of `run_test_fn`. We can not use `pub` to resolve this unfortunately. When `#[used]` // is fixed in https://github.com/rust-lang/rust/issues/47384, this may also be // resolved as well. - #[no_mangle] + #[unsafe(no_mangle)] extern "C" fn #run_test_fn( suite: std::pin::Pin<&mut ::rust_gtest_interop::OpaqueTestingTest> ) {
diff --git a/testing/rust_gtest_interop/rust_gtest_interop.rs b/testing/rust_gtest_interop/rust_gtest_interop.rs index 35b0515..bc649e0 100644 --- a/testing/rust_gtest_interop/rust_gtest_interop.rs +++ b/testing/rust_gtest_interop/rust_gtest_interop.rs
@@ -117,7 +117,7 @@ let null_term_file = std::ffi::CString::new(make_canonical_file_path(file)).unwrap(); let null_term_message = std::ffi::CString::new(message).unwrap(); - extern "C" { + unsafe extern "C" { fn rust_gtest_add_failure_at( file: *const std::ffi::c_char, line: i32, @@ -178,16 +178,14 @@ .to_string() } - extern "C" { + unsafe extern "C" { /// extern for C++'s rust_gtest_default_factory(). /// TODO(danakj): We do this by hand because cxx doesn't support passing /// raw function pointers: https://github.com/dtolnay/cxx/issues/1011. pub fn rust_gtest_default_factory( f: extern "C" fn(Pin<&mut OpaqueTestingTest>), ) -> Pin<&'static mut OpaqueTestingTest>; - } - extern "C" { /// extern for C++'s rust_gtest_add_test(). /// /// Note that the `factory` parameter is actually a C++ function
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index 213de26..81adf02 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json
@@ -25045,52 +25045,6 @@ ] } ], - "ThreadCacheMinCachedMemoryForPurging": [ - { - "platforms": [ - "android", - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "size_500kb_20230909", - "params": { - "ThreadCacheMinCachedMemoryForPurgingBytes": "512000" - }, - "enable_features": [ - "EnableConfigurableThreadCacheMinCachedMemoryForPurging" - ] - } - ] - } - ], - "ThreadCachePurgeInterval": [ - { - "platforms": [ - "android", - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "interval_1_2_60_20230909", - "params": { - "ThreadCacheDefaultPurgeInterval": "2s", - "ThreadCacheMaxPurgeInterval": "60s", - "ThreadCacheMinPurgeInterval": "1s" - }, - "enable_features": [ - "EnableConfigurableThreadCachePurgeInterval" - ] - } - ] - } - ], "ThreeButtonPasswordSaveDialog": [ { "platforms": [
diff --git a/third_party/blink/renderer/bindings/idl_in_core.gni b/third_party/blink/renderer/bindings/idl_in_core.gni index 9f183cde..5d12d2c 100644 --- a/third_party/blink/renderer/bindings/idl_in_core.gni +++ b/third_party/blink/renderer/bindings/idl_in_core.gni
@@ -789,6 +789,7 @@ "//third_party/blink/renderer/core/trustedtypes/trusted_type_policy.idl", "//third_party/blink/renderer/core/trustedtypes/trusted_type_policy_factory.idl", "//third_party/blink/renderer/core/trustedtypes/trusted_type_policy_options.idl", + "//third_party/blink/renderer/core/trustedtypes/window_or_worker_global_scope_trusted_types.idl", "//third_party/blink/renderer/core/preferences/preference_manager.idl", "//third_party/blink/renderer/core/preferences/preference_object.idl", "//third_party/blink/renderer/core/preferences/navigator_preferences.idl",
diff --git a/third_party/blink/renderer/core/css/css_gradient_value.cc b/third_party/blink/renderer/core/css/css_gradient_value.cc index aa221fe..39bf9aa 100644 --- a/third_party/blink/renderer/core/css/css_gradient_value.cc +++ b/third_party/blink/renderer/core/css/css_gradient_value.cc
@@ -975,6 +975,65 @@ return value; } +static const CSSValue* ComputedPositionOrigin(const CSSValue* value) { + if (IsA<CSSIdentifierValue>(value)) { + auto* identifier_value = To<CSSIdentifierValue>(value); + switch (identifier_value->GetValueID()) { + case CSSValueID::kCenter: + return CSSNumericLiteralValue::Create( + 50, CSSNumericLiteralValue::UnitType::kPercentage); + case CSSValueID::kLeft: + case CSSValueID::kTop: + return CSSNumericLiteralValue::Create( + 0, CSSNumericLiteralValue::UnitType::kPercentage); + case CSSValueID::kRight: + case CSSValueID::kBottom: + return CSSNumericLiteralValue::Create( + 100, CSSNumericLiteralValue::UnitType::kPercentage); + default: + break; + } + } + return value; +} + +// This method resolve the 'at <position>' component of a gradient. +// https://www.w3.org/TR/css-values-5/#typedef-position +static const CSSValue* ResolvePosition( + const CSSValue* value, + const CSSToLengthConversionData& conversion_data) { + if (IsA<CSSIdentifierValue>(value)) { + return ComputedPositionOrigin(value); + } + const CSSValue* result = value; + if (IsA<CSSValuePair>(value)) { + auto* pair = To<CSSValuePair>(value); + auto* origin = DynamicTo<CSSIdentifierValue>(pair->First()); + auto* offset = DynamicTo<CSSPrimitiveValue>(pair->Second()); + if (origin && offset) { + switch (origin->GetValueID()) { + case CSSValueID::kTop: + case CSSValueID::kLeft: + result = offset; + break; + case CSSValueID::kBottom: + case CSSValueID::kRight: { + Length length = offset->ConvertToLength(conversion_data) + .SubtractFromOneHundredPercent(); + result = CSSPrimitiveValue::CreateFromLength(length, + conversion_data.Zoom()); + break; + } + case CSSValueID::kCenter: + NOTREACHED(); + default: + break; + } + } + } + return ResolveLength(result, conversion_data); +} + const CSSGradientValue* CSSGradientValue::ResolveValuesIfNeeded( const CSSToLengthConversionData& conversion_data) const { switch (GetClassType()) { @@ -1913,10 +1972,10 @@ const CSSRadialGradientValue* CSSRadialGradientValue::ResolveValuesIfNeeded( const CSSToLengthConversionData& conversion_data) const { - const CSSValue* first_x = ResolveLength(first_x_, conversion_data); - const CSSValue* first_y = ResolveLength(first_y_, conversion_data); - const CSSValue* second_x = ResolveLength(second_x_, conversion_data); - const CSSValue* second_y = ResolveLength(second_y_, conversion_data); + const CSSValue* first_x = ResolvePosition(first_x_, conversion_data); + const CSSValue* first_y = ResolvePosition(first_y_, conversion_data); + const CSSValue* second_x = ResolvePosition(second_x_, conversion_data); + const CSSValue* second_y = ResolvePosition(second_y_, conversion_data); const CSSPrimitiveValue* first_radius = DynamicTo<CSSPrimitiveValue>( ResolveLength(first_radius_, conversion_data)); const CSSPrimitiveValue* second_radius = DynamicTo<CSSPrimitiveValue>( @@ -2088,8 +2147,8 @@ const CSSConicGradientValue* CSSConicGradientValue::ResolveValuesIfNeeded( const CSSToLengthConversionData& conversion_data) const { - const CSSValue* x = ResolveLength(x_, conversion_data); - const CSSValue* y = ResolveLength(y_, conversion_data); + const CSSValue* x = ResolvePosition(x_, conversion_data); + const CSSValue* y = ResolvePosition(y_, conversion_data); // TODO(crbug.com/40620723): We may need a new Length category for degrees, // so it's better to skip the resolution for now. const CSSPrimitiveValue* from_angle =
diff --git a/third_party/blink/renderer/core/css/style_element.cc b/third_party/blink/renderer/core/css/style_element.cc index cb62f7f1..287c336 100644 --- a/third_party/blink/renderer/core/css/style_element.cc +++ b/third_party/blink/renderer/core/css/style_element.cc
@@ -40,10 +40,16 @@ namespace blink { -static bool IsCSS(const AtomicString& type) { - return type.empty() || EqualIgnoringASCIICase(type, "text/css"); +namespace { +bool IsCSS(const AtomicString& type) { + return type.empty() || EqualIgnoringASCIICase(type, keywords::kTextCss); } +bool IsCSSModule(const AtomicString& type) { + return EqualIgnoringASCIICase(type, keywords::kModule); +} +} // namespace + StyleElement::StyleElement(Document* document, bool created_by_parser) : has_finished_parsing_children_(!created_by_parser), loading_(false), @@ -68,12 +74,34 @@ TRACE_EVENT0("blink", "StyleElement::processStyleSheet"); DCHECK(element.isConnected()); - registered_as_candidate_ = true; - document.GetStyleEngine().AddStyleSheetCandidateNode(element); + // Module type is static based upon when it's first connected. + // TODO(crbug.com/448174611): Confirm this with the WHATWG and update behavior + // according to WHATWG resolutions. + if (RuntimeEnabledFeatures::DeclarativeCSSModulesEnabled()) { + if ((element_type_ == StyleType::kPending) && element.isConnected()) { + // TODO(crbug.com/448174611): For consistency with Import Maps, should we + // mimic passing "Already Started" state when cloneNode is called? This + // would involve passing `element_type_` to the clone. + if (IsCSSModule(this->type())) { + element_type_ = StyleType::kModule; + } else { + element_type_ = StyleType::kClassic; + } + } + + // Sheet should always be empty for modules. + DCHECK(!IsModule() || !sheet_); + } + // Classic <style> tags may have an associated stylesheet and need to added as + // a candidate node. + if (!IsModule()) { + DCHECK(!sheet_); + registered_as_candidate_ = true; + document.GetStyleEngine().AddStyleSheetCandidateNode(element); + } if (!has_finished_parsing_children_) { return kProcessingSuccessful; } - return Process(element); } @@ -114,7 +142,7 @@ if (!element.isConnected()) { return kProcessingSuccessful; } - return CreateSheet(element, element.TextFromChildren()); + return CreateSheetOrModule(element, element.TextFromChildren()); } void StyleElement::ClearSheet(Element& owner_element) { @@ -137,8 +165,9 @@ return root && root->IsUserAgent(); } -StyleElement::ProcessingResult StyleElement::CreateSheet(Element& element, - const String& text) { +StyleElement::ProcessingResult StyleElement::CreateSheetOrModule( + Element& element, + const String& text) { DCHECK(element.isConnected()); DCHECK(IsSameObject(element)); Document& document = element.GetDocument(); @@ -155,6 +184,13 @@ (csp && csp->AllowInline(ContentSecurityPolicy::InlineType::kStyle, &element, text, element.nonce(), document.Url(), start_position_.line_)); + if (RuntimeEnabledFeatures::DeclarativeCSSModulesEnabled() && IsModule()) { + // Return early, since we explicitly *don't* want to create a CSSStyleSheet + // for CSS modules. + // TODO(crbug.com/448174611): Create Import Map entry using `text`. + DCHECK(!sheet_); + return kProcessingSuccessful; + } // Use a strong reference to keep the cache entry (which is a weak reference) // alive after ClearSheet(). @@ -207,13 +243,22 @@ } bool StyleElement::IsLoading() const { + DCHECK(!IsModule()); if (loading_) { return true; } return sheet_ && sheet_->IsLoading(); } +bool StyleElement::IsModule() const { + // It's only possible to set the type to module when the flag is enabled. + DCHECK(element_type_ != StyleType::kModule || + RuntimeEnabledFeatures::DeclarativeCSSModulesEnabled()); + return element_type_ == StyleType::kModule; +} + bool StyleElement::SheetLoaded(Document& document) { + DCHECK(!IsModule()); if (IsLoading()) { return false; } @@ -230,6 +275,7 @@ } void StyleElement::SetToPendingState(Document& document, Element& element) { + DCHECK(!IsModule()); DCHECK(IsSameObject(element)); DCHECK_LT(pending_sheet_type_, PendingSheetType::kBlocking); pending_sheet_type_ = PendingSheetType::kBlocking;
diff --git a/third_party/blink/renderer/core/css/style_element.h b/third_party/blink/renderer/core/css/style_element.h index 87ed914..869dbf3 100644 --- a/third_party/blink/renderer/core/css/style_element.h +++ b/third_party/blink/renderer/core/css/style_element.h
@@ -40,6 +40,8 @@ virtual ~StyleElement(); void Trace(Visitor*) const override; + bool IsModule() const; + protected: enum ProcessingResult { kProcessingSuccessful, kProcessingFatalError }; @@ -69,14 +71,26 @@ bool CreatedByParser() const { return created_by_parser_; } private: - ProcessingResult CreateSheet(Element&, const String& text = String()); + ProcessingResult CreateSheetOrModule(Element&, const String& text = String()); ProcessingResult Process(Element&); void ClearSheet(Element& owner_element); + // We want CSS Modules to behave similar to the "already started" flag Import + // Maps, essentially making it a one-shot operation when the <style> element + // is first connected. This behavior is subject to change based on WHATWG + // feedback. Once set on a given element, these types cannot change. + // TODO(crbug.com/448174611): Update this behavior based on WHATWG feedback. + enum class StyleType { + kPending, // Still unknown. + kClassic, // Definitely a classic style tag. + kModule // Definitely a declarative CSS module. + }; + bool has_finished_parsing_children_ : 1; bool loading_ : 1; bool registered_as_candidate_ : 1; bool created_by_parser_ : 1; + StyleType element_type_{StyleType::kPending}; TextPosition start_position_; PendingSheetType pending_sheet_type_; RenderBlockingBehavior render_blocking_behavior_;
diff --git a/third_party/blink/renderer/core/css/style_element_test.cc b/third_party/blink/renderer/core/css/style_element_test.cc index 15d689c1..d9b3510 100644 --- a/third_party/blink/renderer/core/css/style_element_test.cc +++ b/third_party/blink/renderer/core/css/style_element_test.cc
@@ -27,6 +27,7 @@ auto& style_element = To<HTMLStyleElement>(*document.getElementById(AtomicString("style"))); + EXPECT_FALSE(style_element.IsModule()); StyleSheetContents* sheet = style_element.sheet()->Contents(); Comment* comment = document.createComment("hello!"); @@ -37,4 +38,136 @@ EXPECT_EQ(style_element.sheet()->Contents(), sheet); } +TEST(StyleElementTest, CSSModule) { + test::TaskEnvironment task_environment; + auto dummy_page_holder = + std::make_unique<DummyPageHolder>(gfx::Size(800, 600)); + Document& document = dummy_page_holder->GetDocument(); + + document.documentElement()->SetInnerHTMLWithoutTrustedTypes( + "<style id='style' type='module'>a { top: 0; }</style>"); + + auto& style_element_module = + To<HTMLStyleElement>(*document.getElementById(AtomicString("style"))); + + EXPECT_TRUE(style_element_module.IsModule()); + + // Modules do not have an associated CSSStyleSheet. + EXPECT_EQ(style_element_module.sheet(), nullptr); + + // Once a CSS module is created, it cannot be changed to a non-module by + // changing the "type" value. + style_element_module.setAttribute(html_names::kTypeAttr, + AtomicString("text/css")); + EXPECT_TRUE(style_element_module.IsModule()); + EXPECT_EQ(style_element_module.sheet(), nullptr); + + // Likewise, a classic <style> tag cannot be converted to a module. + document.documentElement()->SetInnerHTMLWithoutTrustedTypes( + "<style id='style'>a { top: 0; }</style>"); + auto& style_element_classic = + To<HTMLStyleElement>(*document.getElementById(AtomicString("style"))); + EXPECT_FALSE(style_element_classic.IsModule()); + EXPECT_NE(style_element_classic.sheet(), nullptr); + + // Attempting to convert a classic <style> tag to a module won't be a module, + // but the sheet will be empty because only an empty type or "text/css" is + // allowed. + style_element_classic.setAttribute(html_names::kTypeAttr, + AtomicString("module")); + EXPECT_FALSE(style_element_classic.IsModule()); + EXPECT_EQ(style_element_classic.sheet(), nullptr); + + // Switching back to a valid type will create a stylesheet. + style_element_classic.setAttribute(html_names::kTypeAttr, + AtomicString("text/css")); + EXPECT_FALSE(style_element_classic.IsModule()); + EXPECT_NE(style_element_classic.sheet(), nullptr); + + // Test dynamically creating and inserting style elements. + auto* style_element_dynamic_module = + MakeGarbageCollected<HTMLStyleElement>(document); + style_element_dynamic_module->SetInnerHTMLWithoutTrustedTypes( + "a { top: 0; }"); + + style_element_dynamic_module->setAttribute(html_names::kTypeAttr, + AtomicString("module")); + + // Module-ness doesn't get computed until an element is connected. + EXPECT_FALSE(style_element_dynamic_module->IsModule()); + EXPECT_EQ(style_element_dynamic_module->sheet(), nullptr); + + document.body()->AppendChild(style_element_dynamic_module); + EXPECT_TRUE(style_element_dynamic_module->IsModule()); + EXPECT_EQ(style_element_dynamic_module->sheet(), nullptr); + + // Once connected, module-ness is fixed. + style_element_dynamic_module->setAttribute(html_names::kTypeAttr, + AtomicString("text/css")); + EXPECT_TRUE(style_element_dynamic_module->IsModule()); + EXPECT_EQ(style_element_dynamic_module->sheet(), nullptr); + + // This behavior persists, even after being removed and re-inserted. + document.body()->RemoveChild(style_element_dynamic_module); + EXPECT_TRUE(style_element_dynamic_module->IsModule()); + EXPECT_EQ(style_element_dynamic_module->sheet(), nullptr); + + // Module type is not passed along to clones. + // TODO(kschmi): For consistency with Import Maps, should we mimic passing + // "Already Started" state when cloneNode is called? This would involve + // passing `element_type_` to the clone. + HTMLStyleElement* module_clone = static_cast<HTMLStyleElement*>( + style_element_dynamic_module->cloneNode(/*deep=*/true)); + EXPECT_EQ(module_clone->sheet(), nullptr); + EXPECT_FALSE(module_clone->IsModule()); + EXPECT_EQ(module_clone->getAttribute(html_names::kTypeAttr), + AtomicString("text/css")); + document.body()->AppendChild(module_clone); + EXPECT_FALSE(module_clone->IsModule()); + EXPECT_NE(module_clone->sheet(), nullptr); + + // Test a dynamic classic style element. + auto* style_element_dynamic_classic = + MakeGarbageCollected<HTMLStyleElement>(document); + style_element_dynamic_classic->SetInnerHTMLWithoutTrustedTypes( + "a { top: 0; }"); + + EXPECT_FALSE(style_element_dynamic_classic->IsModule()); + EXPECT_EQ(style_element_dynamic_classic->sheet(), nullptr); + + document.body()->AppendChild(style_element_dynamic_classic); + EXPECT_FALSE(style_element_dynamic_classic->IsModule()); + EXPECT_NE(style_element_dynamic_classic->sheet(), nullptr); + + style_element_dynamic_classic->setAttribute(html_names::kTypeAttr, + AtomicString("module")); + EXPECT_FALSE(style_element_dynamic_classic->IsModule()); + + // Setting the "type" to anything but "text/css" clears the sheet immediately. + EXPECT_EQ(style_element_dynamic_classic->sheet(), nullptr); + + // Removing a classic module with type later set to "module" and re-inserting + // it will not convert it to a module, as it is fixed at first insertion time. + document.body()->RemoveChild(style_element_dynamic_classic); + document.body()->AppendChild(style_element_dynamic_classic); + EXPECT_FALSE(style_element_dynamic_classic->IsModule()); + EXPECT_EQ(style_element_dynamic_classic->sheet(), nullptr); + + // Module type is not passed along to clones. + // TODO(kschmi): For consistency with Import Maps, should we mimic passing + // "Already Started" state when cloneNode is called? This would involve + // passing `element_type_` to the clone. + HTMLStyleElement* classic_clone = static_cast<HTMLStyleElement*>( + style_element_dynamic_classic->cloneNode(/*deep=*/true)); + EXPECT_EQ(classic_clone->getAttribute(html_names::kTypeAttr), + AtomicString("module")); + document.body()->AppendChild(classic_clone); + EXPECT_TRUE(classic_clone->IsModule()); + EXPECT_EQ(classic_clone->sheet(), nullptr); + + // TODO(kschmi) - Updating the child contents shouldn't create a new Module + // Map entry, but this can't be tested until Module Map functionality is + // added. +} + } // namespace blink
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.cc b/third_party/blink/renderer/core/frame/local_dom_window.cc index a8f76819..a496053 100644 --- a/third_party/blink/renderer/core/frame/local_dom_window.cc +++ b/third_party/blink/renderer/core/frame/local_dom_window.cc
@@ -363,11 +363,6 @@ .stored_value->value; } -TrustedTypePolicyFactory* LocalDOMWindow::trustedTypes( - ScriptState* script_state) const { - return GetTrustedTypesForWorld(script_state->World()); -} - bool LocalDOMWindow::IsCrossSiteSubframe() const { if (!GetFrame()) return false;
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.h b/third_party/blink/renderer/core/frame/local_dom_window.h index 6bda020..8b22256 100644 --- a/third_party/blink/renderer/core/frame/local_dom_window.h +++ b/third_party/blink/renderer/core/frame/local_dom_window.h
@@ -188,6 +188,8 @@ const BrowserInterfaceBrokerProxy& GetBrowserInterfaceBroker() const final; FrameOrWorkerScheduler* GetScheduler() final; scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner(TaskType) final; + // TODO(crbug.com/451479061): Consider moving the following function + // under trustedTypes/ TrustedTypePolicyFactory* GetTrustedTypes() const final { return GetTrustedTypesForWorld(*GetCurrentWorld()); } @@ -453,7 +455,6 @@ Event* CurrentEvent() const; void SetCurrentEvent(Event*); - TrustedTypePolicyFactory* trustedTypes(ScriptState*) const; TrustedTypePolicyFactory* GetTrustedTypesForWorld( const DOMWrapperWorld&) const;
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc index 06053c5..2e74cfd 100644 --- a/third_party/blink/renderer/core/frame/local_frame.cc +++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -3465,13 +3465,12 @@ } #if BUILDFLAG(IS_MAC) -void LocalFrame::GetCharacterIndexAtPoint(const gfx::Point& point) { +uint32_t LocalFrame::GetCharacterIndexAtPoint(const gfx::Point& point) { HitTestLocation location(View()->ViewportToFrame(gfx::Point(point))); HitTestResult result = GetEventHandler().HitTestResultAtLocation( location, HitTestRequest::kReadOnly | HitTestRequest::kActive); - uint32_t index = - Selection().CharacterIndexForPoint(result.RoundedPointInInnerNodeFrame()); - mojo_handler_->TextInputHost().GotCharacterIndexAtPoint(index); + return Selection().CharacterIndexForPoint( + result.RoundedPointInInnerNodeFrame()); } #endif @@ -3927,16 +3926,6 @@ return false; } -#if BUILDFLAG(IS_MAC) -void LocalFrame::ResetTextInputHostForTesting() { - mojo_handler_->ResetTextInputHostForTesting(); -} - -void LocalFrame::RebindTextInputHostForTesting() { - mojo_handler_->RebindTextInputHostForTesting(); -} -#endif - Frame* LocalFrame::GetProvisionalOwnerFrame() { DCHECK(IsProvisional()); if (Owner()) {
diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h index 6ea33fee..11018c22 100644 --- a/third_party/blink/renderer/core/frame/local_frame.h +++ b/third_party/blink/renderer/core/frame/local_frame.h
@@ -782,7 +782,7 @@ void SetInitialFocus(bool reverse); #if BUILDFLAG(IS_MAC) - void GetCharacterIndexAtPoint(const gfx::Point& point); + uint32_t GetCharacterIndexAtPoint(const gfx::Point& point); #endif void UpdateWindowControlsOverlay(const gfx::Rect& bounding_rect_in_dips); @@ -857,11 +857,6 @@ // Invoked on first contentful paint on this frame. void OnFirstContentfulPaint(const base::TimeTicks& first_paint_time); -#if BUILDFLAG(IS_MAC) - void ResetTextInputHostForTesting(); - void RebindTextInputHostForTesting(); -#endif - void WriteIntoTrace(perfetto::TracedValue ctx) const; bool AncestorOrSelfHasCSPEE() const { return ancestor_or_self_has_cspee_; }
diff --git a/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc b/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc index 3a1da2e..b315b65 100644 --- a/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc +++ b/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc
@@ -326,8 +326,8 @@ back_forward_cache_controller_host_remote_.BindNewEndpointAndPassReceiver( frame.GetTaskRunner(TaskType::kInternalDefault))); #if BUILDFLAG(IS_MAC) - // It should be bound before accessing TextInputHost which is the interface to - // respond to GetCharacterIndexAtPoint. + // It should be bound before accessing text_input_host_ which is the interface + // to respond to GetCharacterIndexAtPoint and GetFirstRectForRange. frame.GetBrowserInterfaceBroker().GetInterface( text_input_host_.BindNewPipeAndPassReceiver( frame.GetTaskRunner(TaskType::kInternalDefault))); @@ -389,23 +389,6 @@ return *back_forward_cache_controller_host_remote_.get(); } -#if BUILDFLAG(IS_MAC) -mojom::blink::TextInputHost& LocalFrameMojoHandler::TextInputHost() { - DCHECK(text_input_host_.is_bound()); - return *text_input_host_.get(); -} - -void LocalFrameMojoHandler::ResetTextInputHostForTesting() { - text_input_host_.reset(); -} - -void LocalFrameMojoHandler::RebindTextInputHostForTesting() { - frame_->GetBrowserInterfaceBroker().GetInterface( - text_input_host_.BindNewPipeAndPassReceiver( - frame_->GetTaskRunner(TaskType::kInternalDefault))); -} -#endif - mojom::blink::ReportingServiceProxy* LocalFrameMojoHandler::ReportingService() { if (!reporting_service_.is_bound()) { frame_->GetBrowserInterfaceBroker().GetInterface( @@ -1007,7 +990,8 @@ #if BUILDFLAG(IS_MAC) void LocalFrameMojoHandler::GetCharacterIndexAtPoint(const gfx::Point& point) { - frame_->GetCharacterIndexAtPoint(point); + text_input_host_->GotCharacterIndexAtPoint( + frame_->GetCharacterIndexAtPoint(point)); } void LocalFrameMojoHandler::GetFirstRectForRange(const gfx::Range& range) { @@ -1031,7 +1015,7 @@ base::checked_cast<uint32_t>(range.length()), rect); } - TextInputHost().GotFirstRectForRange(rect); + text_input_host_->GotFirstRectForRange(rect); } void LocalFrameMojoHandler::GetStringForRange(
diff --git a/third_party/blink/renderer/core/frame/local_frame_mojo_handler.h b/third_party/blink/renderer/core/frame/local_frame_mojo_handler.h index c5a2c15..cbfe54a 100644 --- a/third_party/blink/renderer/core/frame/local_frame_mojo_handler.h +++ b/third_party/blink/renderer/core/frame/local_frame_mojo_handler.h
@@ -75,12 +75,6 @@ mojom::blink::BackForwardCacheControllerHost& BackForwardCacheControllerHostRemote(); -#if BUILDFLAG(IS_MAC) - mojom::blink::TextInputHost& TextInputHost(); - void ResetTextInputHostForTesting(); - void RebindTextInputHostForTesting(); -#endif - mojom::blink::DevicePostureType GetDevicePosture(); void OverrideDevicePostureForEmulation( mojom::blink::DevicePostureType device_posture_param);
diff --git a/third_party/blink/renderer/core/frame/local_frame_test.cc b/third_party/blink/renderer/core/frame/local_frame_test.cc index 1709330..c773eed 100644 --- a/third_party/blink/renderer/core/frame/local_frame_test.cc +++ b/third_party/blink/renderer/core/frame/local_frame_test.cc
@@ -28,42 +28,6 @@ WebString::FromUTF8(base_url), test::CoreTestDataPath(), WebString::FromUTF8(file_name)); } - -class TestTextInputHostWaiter : public mojom::blink::TextInputHost { - public: - TestTextInputHostWaiter() = default; - ~TestTextInputHostWaiter() override = default; - - void Init(base::OnceClosure callback, - const blink::BrowserInterfaceBrokerProxy& provider) { - callback_ = std::move(callback); - provider.SetBinderForTesting( - mojom::blink::TextInputHost::Name_, - BindRepeating(&TestTextInputHostWaiter::BindTextInputHostReceiver, - Unretained(this))); - } - - void GotCharacterIndexAtPoint(uint32_t index) override { - index_ = index; - if (callback_) - std::move(callback_).Run(); - } - - void GotFirstRectForRange(const gfx::Rect& rect) override {} - - void BindTextInputHostReceiver( - mojo::ScopedMessagePipeHandle message_pipe_handle) { - receiver_.Bind(mojo::PendingReceiver<mojom::blink::TextInputHost>( - std::move(message_pipe_handle))); - } - - uint32_t index() { return index_; } - - private: - mojo::Receiver<mojom::blink::TextInputHost> receiver_{this}; - uint32_t index_; - base::OnceClosure callback_; -}; #endif } // namespace @@ -167,17 +131,11 @@ Page* page = web_view_helper.GetWebView()->GetPage(); LocalFrame* main_frame = DynamicTo<LocalFrame>(page->MainFrame()); - main_frame->ResetTextInputHostForTesting(); - base::RunLoop run_loop; - TestTextInputHostWaiter waiter; - waiter.Init(run_loop.QuitClosure(), main_frame->GetBrowserInterfaceBroker()); - main_frame->RebindTextInputHostForTesting(); // Since we're zoomed in to 2X, each char of Ahem is 20px wide/tall in // viewport space. We expect to hit the fifth char on the first line. - main_frame->GetCharacterIndexAtPoint(gfx::Point(100, 15)); - run_loop.Run(); - EXPECT_EQ(waiter.index(), 5ul); + uint32_t index = main_frame->GetCharacterIndexAtPoint(gfx::Point(100, 15)); + EXPECT_EQ(index, 5ul); } #endif } // namespace blink
diff --git a/third_party/blink/renderer/core/frame/window.idl b/third_party/blink/renderer/core/frame/window.idl index f8202cc..273c40c9 100644 --- a/third_party/blink/renderer/core/frame/window.idl +++ b/third_party/blink/renderer/core/frame/window.idl
@@ -206,9 +206,6 @@ // Event handler attributes attribute EventHandler onsearch; - // TrustedTypes API: http://github.com/w3c/trusted-types - [CallWith=ScriptState] readonly attribute TrustedTypePolicyFactory trustedTypes; - // Anonymous iframe: // https://github.com/WICG/anonymous-iframe [RuntimeEnabled=AnonymousIframe] readonly attribute boolean credentialless;
diff --git a/third_party/blink/renderer/core/html/html_style_element.h b/third_party/blink/renderer/core/html/html_style_element.h index ef55a02..2efa96b 100644 --- a/third_party/blink/renderer/core/html/html_style_element.h +++ b/third_party/blink/renderer/core/html/html_style_element.h
@@ -41,6 +41,10 @@ const CreateElementFlags = CreateElementFlags()); ~HTMLStyleElement() override; + // TODO(crbug.com/448174611): Remove this and make IsModule private once we + // can test Declarative CSS Modules with WPT's. + using StyleElement::IsModule; + using StyleElement::sheet; bool disabled() const;
diff --git a/third_party/blink/renderer/core/html/keywords.json5 b/third_party/blink/renderer/core/html/keywords.json5 index d804e19..88f575d 100644 --- a/third_party/blink/renderer/core/html/keywords.json5 +++ b/third_party/blink/renderer/core/html/keywords.json5
@@ -248,5 +248,13 @@ "button", "reset", "submit", + + // <style> type attribute + // Note that "text/css" is obsolete but not going away any time soon and "module" + // is still being specified. + // https://html.spec.whatwg.org/#obsolete-but-conforming-features:attr-style-type + // https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/ShadowDOM/explainer.md + "text/css", + "module", ], }
diff --git a/third_party/blink/renderer/core/trustedtypes/build.gni b/third_party/blink/renderer/core/trustedtypes/build.gni index 40e959e8..0545993 100644 --- a/third_party/blink/renderer/core/trustedtypes/build.gni +++ b/third_party/blink/renderer/core/trustedtypes/build.gni
@@ -3,6 +3,8 @@ # found in the LICENSE file. blink_core_sources_trustedtypes = [ + "global_trusted_types.cc", + "global_trusted_types.h", "trusted_html.cc", "trusted_html.h", "trusted_script.cc",
diff --git a/third_party/blink/renderer/core/trustedtypes/global_trusted_types.cc b/third_party/blink/renderer/core/trustedtypes/global_trusted_types.cc new file mode 100644 index 0000000..e0bc8bbf --- /dev/null +++ b/third_party/blink/renderer/core/trustedtypes/global_trusted_types.cc
@@ -0,0 +1,26 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/trustedtypes/global_trusted_types.h" + +#include "third_party/blink/renderer/core/frame/local_dom_window.h" +#include "third_party/blink/renderer/core/workers/worker_global_scope.h" + +namespace blink { + +// static +TrustedTypePolicyFactory* GlobalTrustedTypes::trustedTypes( + ScriptState* script_state, + LocalDOMWindow& window) { + return window.GetTrustedTypesForWorld(script_state->World()); +} + +// static +TrustedTypePolicyFactory* GlobalTrustedTypes::trustedTypes( + ScriptState* script_state, + WorkerGlobalScope& worker) { + return worker.GetTrustedTypes(); +} + +} // namespace blink
diff --git a/third_party/blink/renderer/core/trustedtypes/global_trusted_types.h b/third_party/blink/renderer/core/trustedtypes/global_trusted_types.h new file mode 100644 index 0000000..ff79551 --- /dev/null +++ b/third_party/blink/renderer/core/trustedtypes/global_trusted_types.h
@@ -0,0 +1,29 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_TRUSTEDTYPES_GLOBAL_TRUSTED_TYPES_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_TRUSTEDTYPES_GLOBAL_TRUSTED_TYPES_H_ + +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h" + +namespace blink { + +class LocalDOMWindow; +class ScriptState; +class TrustedTypePolicyFactory; +class WorkerGlobalScope; + +class CORE_EXPORT GlobalTrustedTypes final { + STATIC_ONLY(GlobalTrustedTypes); + + public: + static TrustedTypePolicyFactory* trustedTypes(ScriptState*, LocalDOMWindow&); + static TrustedTypePolicyFactory* trustedTypes(ScriptState*, + WorkerGlobalScope&); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_TRUSTEDTYPES_GLOBAL_TRUSTED_TYPES_H_
diff --git a/third_party/blink/renderer/core/trustedtypes/window_or_worker_global_scope_trusted_types.idl b/third_party/blink/renderer/core/trustedtypes/window_or_worker_global_scope_trusted_types.idl new file mode 100644 index 0000000..57de838 --- /dev/null +++ b/third_party/blink/renderer/core/trustedtypes/window_or_worker_global_scope_trusted_types.idl
@@ -0,0 +1,11 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// https://www.w3.org/TR/trusted-types/#extensions-to-the-windoworworkerglobalscope-interface + +[ + ImplementedAs=GlobalTrustedTypes +] partial interface mixin WindowOrWorkerGlobalScope { + [CallWith=ScriptState] readonly attribute TrustedTypePolicyFactory trustedTypes; +}; \ No newline at end of file
diff --git a/third_party/blink/renderer/core/workers/worker_global_scope.h b/third_party/blink/renderer/core/workers/worker_global_scope.h index e04e13ce..989689d 100644 --- a/third_party/blink/renderer/core/workers/worker_global_scope.h +++ b/third_party/blink/renderer/core/workers/worker_global_scope.h
@@ -238,8 +238,9 @@ FontFaceSet* fonts(); + // TODO(crbug.com/451479061): Consider moving the following function + // under trustedTypes/ TrustedTypePolicyFactory* GetTrustedTypes() const override; - TrustedTypePolicyFactory* trustedTypes() const { return GetTrustedTypes(); } // TODO(https://crbug.com/835717): Remove this function after dedicated // workers support off-the-main-thread script fetch by default.
diff --git a/third_party/blink/renderer/core/workers/worker_global_scope.idl b/third_party/blink/renderer/core/workers/worker_global_scope.idl index f8f33e7..4b6eceea 100644 --- a/third_party/blink/renderer/core/workers/worker_global_scope.idl +++ b/third_party/blink/renderer/core/workers/worker_global_scope.idl
@@ -57,9 +57,6 @@ // WindowOrWorkerGlobalScope mixin // https://html.spec.whatwg.org/C/#windoworworkerglobalscope-mixin [Replaceable] readonly attribute DOMString origin; - - // TrustedTypes API: http://github.com/w3c/trusted-types - readonly attribute TrustedTypePolicyFactory trustedTypes; }; WorkerGlobalScope includes UniversalGlobalScope;
diff --git a/third_party/blink/renderer/modules/peerconnection/adapters/web_rtc_cross_thread_copier.h b/third_party/blink/renderer/modules/peerconnection/adapters/web_rtc_cross_thread_copier.h index 7871c45..d275dda 100644 --- a/third_party/blink/renderer/modules/peerconnection/adapters/web_rtc_cross_thread_copier.h +++ b/third_party/blink/renderer/modules/peerconnection/adapters/web_rtc_cross_thread_copier.h
@@ -177,6 +177,12 @@ STATIC_ONLY(CrossThreadCopier); }; +template <> +struct CrossThreadCopier<webrtc::Timestamp> + : public CrossThreadCopierPassThrough<webrtc::Timestamp> { + STATIC_ONLY(CrossThreadCopier); +}; + } // namespace blink #endif // THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_ADAPTERS_WEB_RTC_CROSS_THREAD_COPIER_H_
diff --git a/third_party/blink/renderer/modules/peerconnection/peer_connection_util.cc b/third_party/blink/renderer/modules/peerconnection/peer_connection_util.cc index 9cf8167a..5ea6a2c 100644 --- a/third_party/blink/renderer/modules/peerconnection/peer_connection_util.cc +++ b/third_party/blink/renderer/modules/peerconnection/peer_connection_util.cc
@@ -43,9 +43,8 @@ } // namespace -DOMHighResTimeStamp RTCEncodedFrameTimestampFromTimeTicks( - ExecutionContext* context, - base::TimeTicks timestamp) { +DOMHighResTimeStamp RTCTimeStampFromTimeTicks(ExecutionContext* context, + base::TimeTicks timestamp) { Performance* performance = GetPerformanceFromExecutionContext(context); return Performance::MonotonicTimeToDOMHighResTimeStamp( performance->GetTimeOriginInternal(), timestamp, @@ -58,7 +57,7 @@ CaptureTimeInfo capture_time_info) { switch (capture_time_info.clock_type) { case CaptureTimeInfo::ClockType::kTimeTicks: - return RTCEncodedFrameTimestampFromTimeTicks( + return RTCTimeStampFromTimeTicks( context, base::TimeTicks() + capture_time_info.capture_time); case CaptureTimeInfo::ClockType::kNtpRealClock: base::TimeDelta time_since_unix_epoch =
diff --git a/third_party/blink/renderer/modules/peerconnection/peer_connection_util.h b/third_party/blink/renderer/modules/peerconnection/peer_connection_util.h index 563c0a6..d099cc6 100644 --- a/third_party/blink/renderer/modules/peerconnection/peer_connection_util.h +++ b/third_party/blink/renderer/modules/peerconnection/peer_connection_util.h
@@ -36,8 +36,8 @@ }; // Returns a DOMHighResTimeStamp relative to Performance.timeOrigin. -MODULES_EXPORT DOMHighResTimeStamp -RTCEncodedFrameTimestampFromTimeTicks(ExecutionContext*, base::TimeTicks); +MODULES_EXPORT DOMHighResTimeStamp RTCTimeStampFromTimeTicks(ExecutionContext*, + base::TimeTicks); // Returns a DOMHighResTimeStamp relative to Performance.timeOrigin. MODULES_EXPORT DOMHighResTimeStamp
diff --git a/third_party/blink/renderer/modules/peerconnection/peer_connection_util_test.cc b/third_party/blink/renderer/modules/peerconnection/peer_connection_util_test.cc index 5f6b5c5e..ade249ef 100644 --- a/third_party/blink/renderer/modules/peerconnection/peer_connection_util_test.cc +++ b/third_party/blink/renderer/modules/peerconnection/peer_connection_util_test.cc
@@ -32,14 +32,14 @@ } // namespace -TEST(PeerConnectionUtilTest, RTCEncodedFrameTimestampFromTimeTicks) { +TEST(PeerConnectionUtilTest, RTCTimeStampFromTimeTicks) { test::TaskEnvironment task_environment; V8TestingScope v8_scope; // Use timestamps precise to 0.1ms, since that is the precision of // DOMHighResTimeStamp without cross-origin isolation. std::vector<double> timestamps_ms = {123.4, -123.4}; for (double timestamp_ms : timestamps_ms) { - DOMHighResTimeStamp timestamp = RTCEncodedFrameTimestampFromTimeTicks( + DOMHighResTimeStamp timestamp = RTCTimeStampFromTimeTicks( v8_scope.GetExecutionContext(), GetTimeOriginTimeTicks(v8_scope) + base::Milliseconds(timestamp_ms)); // Use 0.2ms as tolerance to account for the 0.1ms precision.
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.cc index 7ac5a0ed..50de4e5 100644 --- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.cc +++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.cc
@@ -171,8 +171,8 @@ if (RuntimeEnabledFeatures::RTCEncodedFrameTimestampsEnabled()) { if (std::optional<base::TimeTicks> receive_time = delegate_->ReceiveTime()) { - metadata->setReceiveTime(RTCEncodedFrameTimestampFromTimeTicks( - execution_context, *receive_time)); + metadata->setReceiveTime( + RTCTimeStampFromTimeTicks(execution_context, *receive_time)); } if (std::optional<CaptureTimeInfo> capture_time_info = delegate_->CaptureTime()) {
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.cc index 26e7cc21..88666f55 100644 --- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.cc +++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.cc
@@ -213,7 +213,7 @@ if (std::optional<base::TimeTicks> receive_time = delegate_->ReceiveTime()) { metadata->setReceiveTime( - RTCEncodedFrameTimestampFromTimeTicks(context, *receive_time)); + RTCTimeStampFromTimeTicks(context, *receive_time)); } if (std::optional<CaptureTimeInfo> capture_time_info = delegate_->CaptureTime()) {
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_received_packet.h b/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_received_packet.h index 9ce1d59..d75a0ff 100644 --- a/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_received_packet.h +++ b/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_received_packet.h
@@ -6,24 +6,29 @@ #define THIRD_PARTY_BLINK_RENDERER_MODULES_PEERCONNECTION_RTC_TRANSPORT_RTC_RECEIVED_PACKET_H_ #include "third_party/blink/renderer/bindings/core/v8/idl_types.h" +#include "third_party/blink/renderer/core/dom/dom_high_res_time_stamp.h" #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h" #include "third_party/blink/renderer/modules/modules_export.h" #include "third_party/blink/renderer/platform/bindings/script_wrappable.h" namespace blink { + class MODULES_EXPORT RtcReceivedPacket final : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); public: - RtcReceivedPacket(DOMArrayBuffer* data) : data_(data) {} + RtcReceivedPacket(DOMArrayBuffer* data, DOMHighResTimeStamp receive_time) + : data_(data), receive_time_(receive_time) {} DOMArrayBuffer* data(); + DOMHighResTimeStamp receiveTime() { return receive_time_; } // ScriptWrappable impl void Trace(Visitor* visitor) const override; private: Member<DOMArrayBuffer> data_; + DOMHighResTimeStamp receive_time_; }; } // namespace blink
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_received_packet.idl b/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_received_packet.idl index 74957a7..0cffbca 100644 --- a/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_received_packet.idl +++ b/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_received_packet.idl
@@ -8,4 +8,5 @@ ] interface RtcReceivedPacket { // TODO(crbug.com/443019066): Change to support BYOB. readonly attribute ArrayBuffer data; + readonly attribute DOMHighResTimeStamp receiveTime; };
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_transport.cc b/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_transport.cc index 05870b4..6516bc2c 100644 --- a/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_transport.cc +++ b/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_transport.cc
@@ -19,13 +19,16 @@ #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_transport_config.h" #include "third_party/blink/renderer/core/execution_context/execution_context.h" #include "third_party/blink/renderer/core/frame/local_dom_window.h" +#include "third_party/blink/renderer/core/timing/performance.h" #include "third_party/blink/renderer/modules/peerconnection/adapters/web_rtc_cross_thread_copier.h" +#include "third_party/blink/renderer/modules/peerconnection/peer_connection_util.h" #include "third_party/blink/renderer/modules/peerconnection/rtc_ice_candidate.h" #include "third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_transport_dependencies.h" #include "third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_transport_ice_event.h" #include "third_party/blink/renderer/platform/bindings/exception_code.h" #include "third_party/blink/renderer/platform/heap/cross_thread_handle.h" #include "third_party/blink/renderer/platform/peerconnection/rtc_scoped_refptr_cross_thread_copier.h" +#include "third_party/blink/renderer/platform/peerconnection/webrtc_util.h" #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h" #include "third_party/blink/renderer/platform/wtf/cross_thread_copier_base.h" #include "third_party/blink/renderer/platform/wtf/cross_thread_copier_std.h" @@ -103,13 +106,14 @@ candidate_copy)); } - void OnPacketReceived(webrtc::ArrayView<const uint8_t> data) override { + void OnPacketReceived(webrtc::ArrayView<const uint8_t> data, + PacketMetadata metadata) override { Vector<uint8_t> data_vec(data); PostCrossThreadTask( *main_task_runner_, FROM_HERE, CrossThreadBindOnce(&RtcTransport::OnPacketReceivedOnMainThread, MakeUnwrappingCrossThreadWeakHandle(transport_), - std::move(data_vec))); + std::move(data_vec), metadata.receive_time)); } // TODO(crbug.com/443019066): Hook up this with JS events once the API design @@ -381,9 +385,13 @@ candidate.address().port(), IceCandidateTypeFrom(candidate.type())))); } -void RtcTransport::OnPacketReceivedOnMainThread(Vector<uint8_t> data) { - received_packets_.push_back( - MakeGarbageCollected<RtcReceivedPacket>(DOMArrayBuffer::Create(data))); +void RtcTransport::OnPacketReceivedOnMainThread( + Vector<uint8_t> data, + webrtc::Timestamp receive_time) { + received_packets_.push_back(MakeGarbageCollected<RtcReceivedPacket>( + DOMArrayBuffer::Create(data), + RTCTimeStampFromTimeTicks(GetExecutionContext(), + ConvertToBaseTimeTicks(receive_time)))); } void RtcTransport::addRemoteCandidate(RtcTransportICECandidateInit* init,
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_transport.h b/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_transport.h index 7f7cee9a..dccea2e 100644 --- a/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_transport.h +++ b/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_transport.h
@@ -98,7 +98,8 @@ DEFINE_ATTRIBUTE_EVENT_LISTENER(icecandidate, kIcecandidate) - void OnPacketReceivedOnMainThread(Vector<uint8_t> data); + void OnPacketReceivedOnMainThread(Vector<uint8_t> data, + webrtc::Timestamp receive_time); void OnCandidateGatheredOnMainThread(webrtc::Candidate candidate); // ScriptWrappable implementation
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_transport_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_transport_test.cc index 374ce85..a25b12d 100644 --- a/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_transport_test.cc +++ b/third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_transport_test.cc
@@ -14,6 +14,7 @@ #include "third_party/blink/renderer/core/event_target_names.h" #include "third_party/blink/renderer/core/testing/page_test_base.h" #include "third_party/blink/renderer/core/testing/wait_for_event.h" +#include "third_party/blink/renderer/core/timing/dom_window_performance.h" #include "third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_transport_ice_candidate.h" #include "third_party/blink/renderer/modules/peerconnection/rtc_transport/rtc_transport_ice_event.h" #include "third_party/blink/renderer/platform/bindings/script_state.h" @@ -23,6 +24,12 @@ namespace blink { using testing::_; +webrtc::Timestamp GetWebRTCTimeOrigin(LocalDOMWindow* window) { + return webrtc::Timestamp::Micros( + (WindowPerformance::GetTimeOrigin(window) - base::TimeTicks()) + .InMicroseconds()); +} + RtcTransportConfig* CreateRtcTransportConfig() { auto* ice_server = RTCIceServer::Create(); ice_server->setUrls(MakeGarbageCollected<V8UnionStringOrStringSequence>( @@ -176,12 +183,19 @@ CreateInitializedTransport(); Vector<uint8_t> data; data.Append("packet", 6); - transport_->OnPacketReceivedOnMainThread(data); + int kReceiveTimeMillis = 12345; + + transport_->OnPacketReceivedOnMainThread( + data, GetWebRTCTimeOrigin(GetDocument().domWindow()) + + webrtc::TimeDelta::Millis(kReceiveTimeMillis)); HeapVector<Member<RtcReceivedPacket>> packets = transport_->getReceivedPackets(); EXPECT_EQ(packets.size(), 1u); EXPECT_EQ(packets[0]->data()->ByteSpan(), String("packet").RawByteSpan()); + // The precision for DOMHighResTimestamp is 0.1ms. Test equality by making + // sure the difference between expected and received is less than 0.2ms. + EXPECT_LT(std::abs(packets[0]->receiveTime() - kReceiveTimeMillis), 0.2); } TEST_F(RtcTransportTest, Writable) {
diff --git a/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl.py b/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl.py index 1276f80..04cf5cf 100644 --- a/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl.py +++ b/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl.py
@@ -248,7 +248,7 @@ def selected_try_bots(self): if self._builders: return set(self._builders) - return self._tool.builders.builders_for_rebaselining() + return set(self._tool.builders.all_try_builder_names()) def _fetch_results(self, build_statuses: BuildStatuses) -> ResultsBySuite: """Fetches results for all of the given builds.
diff --git a/third_party/blink/tools/blinkpy/w3c/test_importer.py b/third_party/blink/tools/blinkpy/w3c/test_importer.py index 19b3f24..ba4b2c8f 100644 --- a/third_party/blink/tools/blinkpy/w3c/test_importer.py +++ b/third_party/blink/tools/blinkpy/w3c/test_importer.py
@@ -69,7 +69,8 @@ host, github=None, wpt_manifests=None, - buganizer_client: Optional[BuganizerClient] = None): + buganizer_client: Optional[BuganizerClient] = None, + builders: list[str] | None = None): self.host = host self.github = github @@ -87,6 +88,7 @@ self.verbose = False self.wpt_manifests = wpt_manifests self._buganizer_client = buganizer_client or BuganizerClient() + self._builders = builders or WPTExpectationsUpdater.DEFAULT_BUILDERS self._cleanup = contextlib.ExitStack() def __enter__(self): @@ -294,11 +296,10 @@ return True def _trigger_try_jobs(self): - builders = self.host.builders.builders_for_rebaselining() _log.info('Triggering try jobs for updating expectations:') - for builder in sorted(builders): + for builder in sorted(self._builders): _log.info(f' {builder}') - self.git_cl.trigger_try_jobs(builders) + self.git_cl.trigger_try_jobs(self._builders) def run_commit_queue_for_cl(self): """Triggers CQ and either commits or aborts; returns True on success."""
diff --git a/third_party/blink/tools/blinkpy/w3c/test_importer_unittest.py b/third_party/blink/tools/blinkpy/w3c/test_importer_unittest.py index 5d3a704..f863c73 100644 --- a/third_party/blink/tools/blinkpy/w3c/test_importer_unittest.py +++ b/third_party/blink/tools/blinkpy/w3c/test_importer_unittest.py
@@ -100,7 +100,8 @@ return TestImporter(host, github=github, wpt_manifests=[manifest], - buganizer_client=self.buganizer_client) + buganizer_client=self.buganizer_client, + builders=host.builders.all_try_builder_names()) def test_update_expectations_for_cl_no_results(self): host = self.mock_host()
diff --git a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py index a9dfdffa..b00a0fc 100644 --- a/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py +++ b/third_party/blink/tools/blinkpy/w3c/wpt_expectations_updater.py
@@ -43,6 +43,17 @@ class WPTExpectationsUpdater: MARKER_COMMENT = '# ====== New tests from wpt-importer added here ======' + DEFAULT_BUILDERS: list[str] = [ + # TODO(crbug.com/433830466): Rebaseline with macOS and Windows on ARM. + # Architecture can sometimes affect results: crbug.com/450592015 + 'mac-rel', + 'win-rel', + # Use these `*-blink-rel` builders instead of `android-*-rel` and + # `linux-rel`, respectively, to update `*webdriver_wpt_tests` + # expectations. + 'android-15-chrome-blink-rel', + 'linux-blink-rel', + ] def __init__(self, host, args=None, wpt_manifests=None): self.host = host @@ -59,15 +70,7 @@ parser = argparse.ArgumentParser(description=__doc__) self.add_arguments(parser) self.options = parser.parse_args(args or []) - self.options.builders = self.options.builders or [ - 'mac-rel', - 'win-rel', - # Use these `*-blink-rel` builders instead of `android-*-rel` and - # `linux-rel`, respectively, to update `*webdriver_wpt_tests` - # expectations. - 'android-15-chrome-blink-rel', - 'linux-blink-rel', - ] + self.options.builders = self.options.builders or self.DEFAULT_BUILDERS if not (self.options.clean_up_test_expectations or self.options.clean_up_test_expectations_only): assert not self.options.clean_up_affected_tests_only, (
diff --git a/third_party/blink/tools/blinkpy/web_tests/builder_list.py b/third_party/blink/tools/blinkpy/web_tests/builder_list.py index 021ee69..465d176 100644 --- a/third_party/blink/tools/blinkpy/web_tests/builder_list.py +++ b/third_party/blink/tools/blinkpy/web_tests/builder_list.py
@@ -103,13 +103,6 @@ def all_flag_specific_try_builder_names(self, flag_specific): return self.filter_builders(is_try=True, flag_specific=flag_specific) - def builders_for_rebaselining(self) -> Set[str]: - try_builders = { - builder - for builder in self.filter_builders(is_try=True) - } - return try_builders - def all_continuous_builder_names(self): return self.filter_builders(is_try=False)
diff --git a/third_party/blink/tools/blinkpy/web_tests/builder_list_unittest.py b/third_party/blink/tools/blinkpy/web_tests/builder_list_unittest.py index 85da49d..d129d26e7 100644 --- a/third_party/blink/tools/blinkpy/web_tests/builder_list_unittest.py +++ b/third_party/blink/tools/blinkpy/web_tests/builder_list_unittest.py
@@ -175,14 +175,6 @@ ['CQ Try C', 'Flag Specific C'], builders.all_flag_specific_try_builder_names(flag_specific="*")) - def test_builders_for_rebaselining(self): - builders = self.sample_builder_list() - self.assertEqual( - { - 'Try A', 'Try B', 'Flag Specific C', 'CQ Try A', 'CQ Try B', - 'CQ Try C', 'some-wpt-bot' - }, builders.builders_for_rebaselining()) - def test_all_port_names(self): builders = self.sample_builder_list() self.assertEqual(['port-a', 'port-b', 'port-c'],
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations index c2a33d1..c544b61 100644 --- a/third_party/blink/web_tests/TestExpectations +++ b/third_party/blink/web_tests/TestExpectations
@@ -2387,7 +2387,8 @@ crbug.com/825270 external/wpt/payment-request/delegate-request.https.sub.html [ Failure ] -# css-borders-4 corner shaping +# external/wpt/css/css-borders/ +crbug.com/451531616 external/wpt/css/css-borders/outline-offset-rounding.tentative.html [ Failure ] crbug.com/330758166 external/wpt/css/css-borders/tentative/border-radius-side-shorthands/border-radius-side-shorthands-001.html [ Failure ] crbug.com/330758166 external/wpt/css/css-borders/tentative/border-radius-side-shorthands/border-radius-side-shorthands-002.html [ Failure ] @@ -8526,6 +8527,7 @@ crbug.com/356930166 [ Mac15 ] external/wpt/webrtc/simplecall-no-ssrcs.https.html [ Crash Timeout ] crbug.com/356930166 [ Mac14 ] external/wpt/webrtc/simplecall.https.html [ Timeout ] crbug.com/356930166 [ Mac15 ] external/wpt/webrtc/simplecall.https.html [ Crash Timeout ] +crbug.com/356930166 [ Mac15 ] external/wpt/webcodecs/videoFrame-metadata-rtpTimestamp.https.html [ Timeout ] crbug.com/356930166 [ Mac15-arm64 ] external/wpt/webrtc-encoded-transform/tentative/RTCPeerConnection-insertable-streams-errors.https.html [ Timeout ] crbug.com/356930166 [ Mac15-arm64 ] external/wpt/webrtc-extensions/RTCRtpSynchronizationSource-captureTimestamp.html [ Skip Timeout ] crbug.com/356930166 [ Mac15-arm64 ] external/wpt/webrtc-extensions/RTCRtpSynchronizationSource-senderCaptureTimeOffset.html [ Skip Timeout ] @@ -8583,6 +8585,7 @@ crbug.com/356930166 [ Release Win11-arm64 ] external/wpt/webrtc/RTCSctpTransport-maxChannels.html [ Timeout ] crbug.com/356930166 [ Mac15-arm64 ] external/wpt/webrtc/simplecall-no-ssrcs.https.html [ Crash Timeout ] crbug.com/356930166 [ Mac15-arm64 ] external/wpt/webrtc/simplecall.https.html [ Crash Timeout ] +crbug.com/356930166 [ Mac15-arm64 ] external/wpt/webcodecs/videoFrame-metadata-rtpTimestamp.https.html [ Timeout ] # Mac15/arm64 - Other timeouts [ Mac15 ] external/wpt/fetch/api/abort/serviceworker-intercepted.https.html [ Timeout ]
diff --git a/third_party/blink/web_tests/external/wpt/css/css-borders/outline-offset-rounding.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-borders/outline-offset-rounding.tentative-expected.txt deleted file mode 100644 index d72eb4a6..0000000 --- a/third_party/blink/web_tests/external/wpt/css/css-borders/outline-offset-rounding.tentative-expected.txt +++ /dev/null
@@ -1,31 +0,0 @@ -This is a testharness.js-based test. -[FAIL] 0.1px - assert_equals: expected "1px" but got "0.09375px" -[FAIL] -0.1px - assert_equals: expected "-1px" but got "-0.109375px" -[FAIL] 0.25px - assert_equals: expected "1px" but got "0.25px" -[FAIL] -0.25px - assert_equals: expected "-1px" but got "-0.25px" -[FAIL] 0.5px - assert_equals: expected "1px" but got "0.5px" -[FAIL] -0.5px - assert_equals: expected "-1px" but got "-0.5px" -[FAIL] 0.9px - assert_equals: expected "1px" but got "0.890625px" -[FAIL] -0.9px - assert_equals: expected "-1px" but got "-0.90625px" -[FAIL] 1.25px - assert_equals: expected "1px" but got "1.25px" -[FAIL] -1.25px - assert_equals: expected "-1px" but got "-1.25px" -[FAIL] 1.5px - assert_equals: expected "1px" but got "1.5px" -[FAIL] -1.5px - assert_equals: expected "-1px" but got "-1.5px" -[FAIL] 2.75px - assert_equals: expected "2px" but got "2.75px" -[FAIL] -2.75px - assert_equals: expected "-2px" but got "-2.75px" -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/parsing/gradient-interpolation-method-computed-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-images/parsing/gradient-interpolation-method-computed-expected.txt deleted file mode 100644 index 7ec19d18..0000000 --- a/third_party/blink/web_tests/external/wpt/css/css-images/parsing/gradient-interpolation-method-computed-expected.txt +++ /dev/null
@@ -1,444 +0,0 @@ -This is a testharness.js-based test. -Found 220 FAIL, 0 TIMEOUT, 0 NOTRUN. -[FAIL] Property background-image value 'radial-gradient(at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50%, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50%, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in lab, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lab, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lab, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in lab at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lab, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lab, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in lab, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lab, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lab, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in lab at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lab, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lab, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in oklab, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklab, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklab, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in oklab at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklab, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklab, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in oklab, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50%, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in oklab at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50%, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in srgb, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50%, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in srgb at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50%, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in srgb, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in srgb, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in srgb, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in srgb at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in srgb, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in srgb, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in srgb-linear, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in srgb-linear, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in srgb-linear, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in srgb-linear at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in srgb-linear, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in srgb-linear, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in srgb-linear, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in srgb-linear, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in srgb-linear, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in srgb-linear at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in srgb-linear, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in srgb-linear, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in xyz, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in xyz-d65, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in xyz-d65, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in xyz at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in xyz-d65, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in xyz-d65, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in xyz, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in xyz-d65, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in xyz-d65, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in xyz at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in xyz-d65, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in xyz-d65, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in xyz-d50, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in xyz-d50, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in xyz-d50, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in xyz-d50 at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in xyz-d50, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in xyz-d50, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in xyz-d50, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in xyz-d50, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in xyz-d50, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in xyz-d50 at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in xyz-d50, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in xyz-d50, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in xyz-d65, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in xyz-d65, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in xyz-d65, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in xyz-d65 at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in xyz-d65, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in xyz-d65, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in xyz-d65, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in xyz-d65, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in xyz-d65, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in xyz-d65 at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in xyz-d65, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in xyz-d65, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hsl, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hsl at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hsl, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hsl at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hsl shorter hue, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hsl shorter hue at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hsl shorter hue, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hsl shorter hue at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hsl longer hue, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hsl longer hue at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hsl longer hue, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hsl longer hue at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hsl increasing hue, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hsl increasing hue at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hsl increasing hue, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hsl increasing hue at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hsl decreasing hue, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hsl decreasing hue at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hsl decreasing hue, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hsl decreasing hue at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hsl decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hsl decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hwb, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hwb at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hwb, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hwb at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hwb shorter hue, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hwb shorter hue at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hwb shorter hue, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hwb shorter hue at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hwb longer hue, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hwb longer hue at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hwb longer hue, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hwb longer hue at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hwb increasing hue, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hwb increasing hue at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hwb increasing hue, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hwb increasing hue at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hwb decreasing hue, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hwb decreasing hue at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in hwb decreasing hue, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in hwb decreasing hue at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in hwb decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in hwb decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in lch, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in lch at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in lch, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in lch at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in lch shorter hue, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in lch shorter hue at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in lch shorter hue, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in lch shorter hue at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in lch longer hue, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in lch longer hue at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in lch longer hue, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in lch longer hue at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in lch increasing hue, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in lch increasing hue at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in lch increasing hue, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in lch increasing hue at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in lch decreasing hue, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in lch decreasing hue at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in lch decreasing hue, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in lch decreasing hue at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in lch decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in lch decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in oklch, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in oklch at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in oklch, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in oklch at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in oklch shorter hue, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in oklch shorter hue at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in oklch shorter hue, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in oklch shorter hue at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in oklch longer hue, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in oklch longer hue at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in oklch longer hue, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in oklch longer hue at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in oklch increasing hue, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in oklch increasing hue at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in oklch increasing hue, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in oklch increasing hue at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in oklch decreasing hue, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in oklch decreasing hue at right center, red, blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(at right center in oklch decreasing hue, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'radial-gradient(in oklch decreasing hue at right center, color(srgb 1 0 0), blue)' - assert_equals: expected "radial-gradient(at 100% 50% in oklch decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "radial-gradient(at right center in oklch decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in lab, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lab, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lab, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in lab at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lab, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lab, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in lab, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lab, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lab, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in lab at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lab, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lab, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in oklab, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklab, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklab, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in oklab at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklab, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklab, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in oklab, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in oklab at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in srgb, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in srgb at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in srgb, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in srgb, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in srgb, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in srgb at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in srgb, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in srgb, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in srgb-linear, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in srgb-linear, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in srgb-linear, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in srgb-linear at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in srgb-linear, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in srgb-linear, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in srgb-linear, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in srgb-linear, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in srgb-linear, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in srgb-linear at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in srgb-linear, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in srgb-linear, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in xyz, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in xyz-d65, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in xyz-d65, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in xyz at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in xyz-d65, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in xyz-d65, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in xyz, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in xyz-d65, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in xyz-d65, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in xyz at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in xyz-d65, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in xyz-d65, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in xyz-d50, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in xyz-d50, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in xyz-d50, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in xyz-d50 at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in xyz-d50, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in xyz-d50, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in xyz-d50, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in xyz-d50, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in xyz-d50, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in xyz-d50 at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in xyz-d50, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in xyz-d50, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in xyz-d65, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in xyz-d65, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in xyz-d65, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in xyz-d65 at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in xyz-d65, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in xyz-d65, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in xyz-d65, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in xyz-d65, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in xyz-d65, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in xyz-d65 at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in xyz-d65, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in xyz-d65, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hsl, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hsl at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hsl, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hsl at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hsl shorter hue, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hsl shorter hue at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hsl shorter hue, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hsl shorter hue at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hsl longer hue, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hsl longer hue at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hsl longer hue, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hsl longer hue at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hsl increasing hue, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hsl increasing hue at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hsl increasing hue, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hsl increasing hue at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hsl decreasing hue, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hsl decreasing hue at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hsl decreasing hue, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hsl decreasing hue at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hsl decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hsl decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hwb, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hwb at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hwb, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hwb at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hwb shorter hue, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hwb shorter hue at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hwb shorter hue, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hwb shorter hue at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hwb longer hue, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hwb longer hue at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hwb longer hue, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hwb longer hue at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hwb increasing hue, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hwb increasing hue at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hwb increasing hue, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hwb increasing hue at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hwb decreasing hue, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hwb decreasing hue at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in hwb decreasing hue, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in hwb decreasing hue at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in hwb decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in hwb decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in lch, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in lch at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in lch, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in lch at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in lch shorter hue, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in lch shorter hue at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in lch shorter hue, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in lch shorter hue at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in lch longer hue, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in lch longer hue at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in lch longer hue, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in lch longer hue at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in lch increasing hue, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in lch increasing hue at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in lch increasing hue, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in lch increasing hue at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in lch decreasing hue, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in lch decreasing hue at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in lch decreasing hue, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in lch decreasing hue at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in lch decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in lch decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in oklch, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in oklch at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in oklch, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in oklch at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in oklch shorter hue, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in oklch shorter hue at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in oklch shorter hue, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in oklch shorter hue at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in oklch longer hue, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in oklch longer hue at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch longer hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in oklch longer hue, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in oklch longer hue at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch longer hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in oklch increasing hue, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in oklch increasing hue at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch increasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in oklch increasing hue, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in oklch increasing hue at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch increasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in oklch decreasing hue, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in oklch decreasing hue at left 10px top 50em, red, blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch decreasing hue, rgb(255, 0, 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(at left 10px top 50em in oklch decreasing hue, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -[FAIL] Property background-image value 'conic-gradient(in oklch decreasing hue at left 10px top 50em, color(srgb 1 0 0), blue)' - assert_equals: expected "conic-gradient(at 10px 800px in oklch decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" but got "conic-gradient(at left 10px top 50em in oklch decreasing hue, color(srgb 1 0 0), rgb(0, 0, 255))" -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/parsing/gradient-position-computed.html b/third_party/blink/web_tests/external/wpt/css/css-images/parsing/gradient-position-computed.html new file mode 100644 index 0000000..1fc2ce3 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-images/parsing/gradient-position-computed.html
@@ -0,0 +1,79 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>CSS Images Module Level 4: computed styke of gradients focusing on the position values</title> +<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com"> +<link rel="help" href="https://drafts.csswg.org/css-images-4/#radial-gradients"> +<link rel="help" href="https://drafts.csswg.org/css-images-4/#conic-gradients"> +<link rel="help" href="https://drafts.csswg.org/css-values-4/#typedef-position"> +<meta name="assert" content="gradient positions computed style is correctly serialized."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/css/support/computed-testcommon.js"></script> +<style> + #target { + font-size: 40px; + } +</style> +</head> +<body> +<div id="target"></div> +<script> +test_computed_value("background-image", "radial-gradient(at 10%, red, blue)", "radial-gradient(at 10% 50%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "radial-gradient(at 20% 30px, red, blue)", "radial-gradient(at 20% 30px, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "radial-gradient(at 30px center, red, blue)", "radial-gradient(at 30px 50%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "radial-gradient(at 40px top, red, blue)", "radial-gradient(at 40px 0%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "radial-gradient(at bottom 10% right 20%, red, blue)", "radial-gradient(at 80% 90%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "radial-gradient(at bottom right, red, blue)", "radial-gradient(at 100% 100%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "radial-gradient(at center, red, blue)", "radial-gradient(rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "radial-gradient(at center 50px, red, blue)", "radial-gradient(at 50% 50px, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "radial-gradient(at center bottom, red, blue)", "radial-gradient(at 50% 100%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "radial-gradient(at center center, red, blue)", "radial-gradient(rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "radial-gradient(at center left, red, blue)", "radial-gradient(at 0% 50%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "radial-gradient(at left, red, blue)", "radial-gradient(at 0% 50%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "radial-gradient(at left bottom, red, blue)", "radial-gradient(at 0% 100%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "radial-gradient(at left center, red, blue)", "radial-gradient(at 0% 50%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "radial-gradient(at right 40%, red, blue)", "radial-gradient(at 100% 40%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "radial-gradient(at right 30% top 60px, red, blue)", "radial-gradient(at 70% 60px, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "radial-gradient(at top, red, blue)", "radial-gradient(at 50% 0%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "radial-gradient(at top center, red, blue)", "radial-gradient(at 50% 0%, rgb(255, 0, 0), rgb(0, 0, 255))"); + +// Single keyword positions +test_computed_value("background-image", "conic-gradient(at top, red, blue)", "conic-gradient(at 50% 0%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "conic-gradient(at bottom, red, blue)", "conic-gradient(at 50% 100%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "conic-gradient(at left, red, blue)", "conic-gradient(at 0% 50%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "conic-gradient(at right, red, blue)", "conic-gradient(at 100% 50%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "conic-gradient(at center, red, blue)", "conic-gradient(rgb(255, 0, 0), rgb(0, 0, 255))"); + +// Two keyword combinations +test_computed_value("background-image", "conic-gradient(at top left, red, blue)", "conic-gradient(at 0% 0%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "conic-gradient(at left top, red, blue)", "conic-gradient(at 0% 0%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "conic-gradient(at bottom right, red, blue)", "conic-gradient(at 100% 100%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "conic-gradient(at right bottom, red, blue)", "conic-gradient(at 100% 100%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "conic-gradient(at left center, red, blue)", "conic-gradient(at 0% 50%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "conic-gradient(at center right, red, blue)", "conic-gradient(at 100% 50%, rgb(255, 0, 0), rgb(0, 0, 255))"); + +// Two offsets (lengths or percentages) +test_computed_value("background-image", "conic-gradient(at 10% 20%, red, blue)", "conic-gradient(at 10% 20%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "conic-gradient(at 0% 100%, red, blue)", "conic-gradient(at 0% 100%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "conic-gradient(at 50% 50%, red, blue)", "conic-gradient(rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "conic-gradient(at 20px 40px, red, blue)", "conic-gradient(at 20px 40px, rgb(255, 0, 0), rgb(0, 0, 255))"); + +// Mixed keyword + offset +test_computed_value("background-image", "conic-gradient(at right 10px top 20px, red, blue)", "conic-gradient(at calc(100% - 10px) 20px, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "conic-gradient(at left 5% bottom 10px, red, blue)", "conic-gradient(at 5% calc(100% - 10px), rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "conic-gradient(at top 5px right 10px, red, blue)", "conic-gradient(at calc(100% - 10px) 5px, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "conic-gradient(at bottom 20px left 30px, red, blue)", "conic-gradient(at 30px calc(100% - 20px), rgb(255, 0, 0), rgb(0, 0, 255))"); + +// Single offset (interpreted as x position) +test_computed_value("background-image", "conic-gradient(at 30%, red, blue)", "conic-gradient(at 30% 50%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "conic-gradient(at 70px, red, blue)", "conic-gradient(at 70px 50%, rgb(255, 0, 0), rgb(0, 0, 255))"); + +// Out-of-range or negative offsets (still valid syntax) +test_computed_value("background-image", "conic-gradient(at -10% -20%, red, blue)", "conic-gradient(at -10% -20%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "conic-gradient(at 150% 200%, red, blue)", "conic-gradient(at 150% 200%, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "conic-gradient(at -10px 50px, red, blue)", "conic-gradient(at -10px 50px, rgb(255, 0, 0), rgb(0, 0, 255))"); +test_computed_value("background-image", "conic-gradient(at 1000% 1000%, red, blue)", "conic-gradient(at 1000% 1000%, rgb(255, 0, 0), rgb(0, 0, 255))"); + +</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-shapes/shape-outside/values/shape-outside-gradient-computed-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-shapes/shape-outside/values/shape-outside-gradient-computed-expected.txt deleted file mode 100644 index 82d39d2..0000000 --- a/third_party/blink/web_tests/external/wpt/css/css-shapes/shape-outside/values/shape-outside-gradient-computed-expected.txt +++ /dev/null
@@ -1,5 +0,0 @@ -This is a testharness.js-based test. -[FAIL] Property shape-outside value 'radial-gradient(circle at calc(10px + 5%), red, blue)' - assert_equals: expected "radial-gradient(circle at calc(5% + 10px) 50%, rgb(255, 0, 0), rgb(0, 0, 255))" but got "radial-gradient(circle at calc(5% + 10px) center, rgb(255, 0, 0), rgb(0, 0, 255))" -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/external/wpt/webcodecs/videoFrame-metadata-rtpTimestamp.https.html b/third_party/blink/web_tests/external/wpt/webcodecs/videoFrame-metadata-rtpTimestamp.https.html new file mode 100644 index 0000000..171e3d6 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/webcodecs/videoFrame-metadata-rtpTimestamp.https.html
@@ -0,0 +1,75 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>VideoFrame.metadata.rtpTimestamp exposed via MediaStreamTrackProcessor over WebRTC</title> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/webrtc/RTCPeerConnection-helper.js"></script> + +<script> + promise_test(async t => { + if (!("MediaStreamTrackProcessor" in self) || + !("VideoFrame" in self)) { + assert_implements_optional(true, "MediaStreamTrackProcessor/VideoFrame APIs not supported. Skipping test."); + return; + } + + const stream = await getNoiseStream({ video: true }); + const track = stream.getVideoTracks()[0]; + + // This test validates the RTP timestamps on the encoded video frames + // sent by a WebRTC peer (sender) are preserved when received by the + // WebRTC receiver which in turn uses the MediaStreamTrackProcessor + // API to render the video frames. We expect the RTP timestamps to + // be available in the metadata() object exposed by the VideoFrame + // in MediaStreamTrackProcessor if the VideoFrameMetadataRtpTimestamp + // feature is enabled. + + const senderPc = new RTCPeerConnection(); + const receiverPc = new RTCPeerConnection(); + + senderPc.onicecandidate = e => e.candidate && receiverPc.addIceCandidate(e.candidate); + receiverPc.onicecandidate = e => e.candidate && senderPc.addIceCandidate(e.candidate); + + const receiverTrackPromise = new Promise(resolve => { + receiverPc.ontrack = e => resolve(e.track); + }); + + const sender = senderPc.addTrack(track, stream); + + // Offer/Answer exchange + const offer = await senderPc.createOffer(); + await senderPc.setLocalDescription(offer); + await receiverPc.setRemoteDescription(offer); + + const answer = await receiverPc.createAnswer(); + await receiverPc.setLocalDescription(answer); + await senderPc.setRemoteDescription(answer); + + let receiverTrack = await receiverTrackPromise; + + // Send the received track to MediaStreamTrackProcessor for rendering. + const processor = new MediaStreamTrackProcessor({ track: receiverTrack }); + const reader = processor.readable.getReader(); + + const result = await reader.read(); + const frame = result.value; + const metadata = frame.metadata?.(); + + // If the VideoFrameMetadataRtpTimestamp feature is enabled we expect that + // the rtpTimestamp will be available in the metadata. + if (metadata && 'rtpTimestamp' in metadata) { + assert_equals(typeof metadata.rtpTimestamp, "number", "rtpTimestamp should be a number"); + assert_greater_than(metadata.rtpTimestamp, 0, "rtpTimestamp should be non-zero"); + assert_true(true, "rtpTimestamp present in VideoFrame metadata") + } else { + assert_true(true, "rtpTimestamp not present in VideoFrame metadata. Skipping validation because feature may be disabled."); + } + + frame.close(); + receiverTrack.stop(); + track.stop(); + senderPc.close(); + receiverPc.close(); + }, "Check VideoFrame.metadata.rtpTimestamp only when feature is enabled"); +</script>
diff --git a/third_party/blink/web_tests/fast/gradients/conic-gradient-parsing.html b/third_party/blink/web_tests/fast/gradients/conic-gradient-parsing.html index 3a510b4..1270fe2 100644 --- a/third_party/blink/web_tests/fast/gradients/conic-gradient-parsing.html +++ b/third_party/blink/web_tests/fast/gradients/conic-gradient-parsing.html
@@ -66,8 +66,8 @@ { style: "conic-gradient(blue 10%, blue 20% 30%, white)" , computed: "conic-gradient(rgb(0, 0, 255) 10%, rgb(0, 0, 255) 20%, rgb(0, 0, 255) 30%, rgb(255, 255, 255))" }, { style: "conic-gradient(blue, blue, blue, white)" , computed: "conic-gradient(rgb(0, 0, 255), rgb(0, 0, 255), rgb(0, 0, 255), rgb(255, 255, 255))" }, - { style: "conic-gradient(at top left, black, white)" , computed: "conic-gradient(at left top, rgb(0, 0, 0), rgb(255, 255, 255))" }, - { style: "conic-gradient(at bottom right, black, white)" , computed: "conic-gradient(at right bottom, rgb(0, 0, 0), rgb(255, 255, 255))" }, + { style: "conic-gradient(at top left, black, white)" , computed: "conic-gradient(at 0% 0%, rgb(0, 0, 0), rgb(255, 255, 255))" }, + { style: "conic-gradient(at bottom right, black, white)" , computed: "conic-gradient(at 100% 100%, rgb(0, 0, 0), rgb(255, 255, 255))" }, { style: "conic-gradient(at center, black, white)" , computed: "conic-gradient(rgb(0, 0, 0), rgb(255, 255, 255))" }, { style: "conic-gradient(at center center, black, white)", computed: "conic-gradient(rgb(0, 0, 0), rgb(255, 255, 255))" },
diff --git a/third_party/blink/web_tests/fast/gradients/css3-gradient-parsing.html b/third_party/blink/web_tests/fast/gradients/css3-gradient-parsing.html index 3434318..05de565 100644 --- a/third_party/blink/web_tests/fast/gradients/css3-gradient-parsing.html +++ b/third_party/blink/web_tests/fast/gradients/css3-gradient-parsing.html
@@ -49,13 +49,13 @@ test(() => { assert_equals(testGradient("background-image: radial-gradient(white, black)", "background-image"), 'radial-gradient(rgb(255, 255, 255), rgb(0, 0, 0))'); - assert_equals(testGradient("background-image: radial-gradient(at bottom right, white, black)", "background-image"), 'radial-gradient(at right bottom, rgb(255, 255, 255), rgb(0, 0, 0))'); + assert_equals(testGradient("background-image: radial-gradient(at bottom right, white, black)", "background-image"), 'radial-gradient(at 100% 100%, rgb(255, 255, 255), rgb(0, 0, 0))'); assert_equals(testGradient("background-image: radial-gradient(ellipse farthest-corner, white, black)", "background-image"), 'radial-gradient(rgb(255, 255, 255), rgb(0, 0, 0))'); assert_equals(testGradient("background-image: radial-gradient(circle closest-corner, white, black)", "background-image"), 'radial-gradient(circle closest-corner, rgb(255, 255, 255), rgb(0, 0, 0))'); assert_equals(testGradient("background-image: radial-gradient(circle, white, black)", "background-image"), 'radial-gradient(circle, rgb(255, 255, 255), rgb(0, 0, 0))'); assert_equals(testGradient("background-image: radial-gradient(circle closest-side, white, black)", "background-image"), 'radial-gradient(circle closest-side, rgb(255, 255, 255), rgb(0, 0, 0))'); - assert_equals(testGradient("background-image: radial-gradient(circle closest-side at top, white, black)", "background-image"), 'radial-gradient(circle closest-side at center top, rgb(255, 255, 255), rgb(0, 0, 0))'); - assert_equals(testGradient("background-image: radial-gradient(circle closest-side at top left, white, black)", "background-image"), 'radial-gradient(circle closest-side at left top, rgb(255, 255, 255), rgb(0, 0, 0))'); + assert_equals(testGradient("background-image: radial-gradient(circle closest-side at top, white, black)", "background-image"), 'radial-gradient(circle closest-side at 50% 0%, rgb(255, 255, 255), rgb(0, 0, 0))'); + assert_equals(testGradient("background-image: radial-gradient(circle closest-side at top left, white, black)", "background-image"), 'radial-gradient(circle closest-side at 0% 0%, rgb(255, 255, 255), rgb(0, 0, 0))'); assert_equals(testGradient("background-image: radial-gradient(circle closest-side at 10px 20%, white, black)", "background-image"), 'radial-gradient(circle closest-side at 10px 20%, rgb(255, 255, 255), rgb(0, 0, 0))'); assert_equals(testGradient("background-image: radial-gradient(10px, 20%, circle closest-side, white, black)", "background-image"), 'none'); assert_equals(testGradient("background-image: radial-gradient(circle 10px 20%, circle closest-side, white, black)", "background-image"), 'none');
diff --git a/third_party/blink/web_tests/fast/gradients/unprefixed-gradient-parsing.html b/third_party/blink/web_tests/fast/gradients/unprefixed-gradient-parsing.html index c59edf7..47ee8cf 100644 --- a/third_party/blink/web_tests/fast/gradients/unprefixed-gradient-parsing.html +++ b/third_party/blink/web_tests/fast/gradients/unprefixed-gradient-parsing.html
@@ -73,7 +73,7 @@ test(() => { assert_equals(testGradient("background-image: radial-gradient(white, black)"), 'radial-gradient(rgb(255, 255, 255), rgb(0, 0, 0))'); - assert_equals(testGradient("background-image: radial-gradient(at bottom right, white, black)"), 'radial-gradient(at right bottom, rgb(255, 255, 255), rgb(0, 0, 0))'); + assert_equals(testGradient("background-image: radial-gradient(at bottom right, white, black)"), 'radial-gradient(at 100% 100%, rgb(255, 255, 255), rgb(0, 0, 0))'); assert_equals(testGradient("background-image: radial-gradient(farthest-corner, white, black)"), 'radial-gradient(rgb(255, 255, 255), rgb(0, 0, 0))'); assert_equals(testGradient("background-image: radial-gradient(farthest-corner, white, 35%, black)"), 'radial-gradient(rgb(255, 255, 255), 35%, rgb(0, 0, 0))'); assert_equals(testGradient("background-image: radial-gradient(closest-side, white, black)"), 'radial-gradient(closest-side, rgb(255, 255, 255), rgb(0, 0, 0))'); @@ -83,8 +83,8 @@ assert_equals(testGradient("background-image: radial-gradient(circle, white, black)"), 'radial-gradient(circle, rgb(255, 255, 255), rgb(0, 0, 0))'); assert_equals(testGradient("background-image: radial-gradient(circle farthest-corner, white, black)"), 'radial-gradient(circle, rgb(255, 255, 255), rgb(0, 0, 0))'); assert_equals(testGradient("background-image: radial-gradient(circle closest-side, white, black)"), 'radial-gradient(circle closest-side, rgb(255, 255, 255), rgb(0, 0, 0))'); - assert_equals(testGradient("background-image: radial-gradient(circle closest-side at top, white, black)"), 'radial-gradient(circle closest-side at center top, rgb(255, 255, 255), rgb(0, 0, 0))'); - assert_equals(testGradient("background-image: radial-gradient(circle closest-side at top left, white, black)"), 'radial-gradient(circle closest-side at left top, rgb(255, 255, 255), rgb(0, 0, 0))'); + assert_equals(testGradient("background-image: radial-gradient(circle closest-side at top, white, black)"), 'radial-gradient(circle closest-side at 50% 0%, rgb(255, 255, 255), rgb(0, 0, 0))'); + assert_equals(testGradient("background-image: radial-gradient(circle closest-side at top left, white, black)"), 'radial-gradient(circle closest-side at 0% 0%, rgb(255, 255, 255), rgb(0, 0, 0))'); assert_equals(testGradient("background-image: radial-gradient(circle closest-side at 10px 20%, white, black)"), 'radial-gradient(circle closest-side at 10px 20%, rgb(255, 255, 255), rgb(0, 0, 0))'); assert_equals(testGradient("background-image: radial-gradient(at 10px 20% circle closest-side, white, black)"), 'none'); assert_equals(testGradient("background-image: radial-gradient(circle at 10px 20% circle, white, black)"), 'none');
diff --git a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt index 1477bf95..33738159 100644 --- a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt +++ b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -1805,6 +1805,7 @@ interface RtcReceivedPacket attribute @@toStringTag getter data + getter receiveTime method constructor interface RtcTransport attribute @@toStringTag
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt index 62e964a..4cb130f 100644 --- a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt +++ b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -1948,6 +1948,7 @@ [Worker] interface RtcReceivedPacket [Worker] attribute @@toStringTag [Worker] getter data +[Worker] getter receiveTime [Worker] method constructor [Worker] interface RtcTransport [Worker] attribute @@toStringTag
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt index 9dbe44f..eb62692 100644 --- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt +++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -8342,6 +8342,7 @@ interface RtcReceivedPacket attribute @@toStringTag getter data + getter receiveTime method constructor interface RtcTransport attribute @@toStringTag
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt index 807c317..0a9e3220 100644 --- a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt +++ b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -1721,6 +1721,7 @@ [Worker] interface RtcReceivedPacket [Worker] attribute @@toStringTag [Worker] getter data +[Worker] getter receiveTime [Worker] method constructor [Worker] interface RtcTransport [Worker] attribute @@toStringTag
diff --git a/third_party/boringssl/src b/third_party/boringssl/src index eae76e0..bc81f389 160000 --- a/third_party/boringssl/src +++ b/third_party/boringssl/src
@@ -1 +1 @@ -Subproject commit eae76e0715de794f4fe0a189fe8c8146cbc9990c +Subproject commit bc81f389c45e0192251c4b29622cfd734ae7baf2
diff --git a/third_party/chromite b/third_party/chromite index 8048c5f..eee11b0 160000 --- a/third_party/chromite +++ b/third_party/chromite
@@ -1 +1 @@ -Subproject commit 8048c5f57f9d7a21da839b452f4b864a5ca5bc83 +Subproject commit eee11b00b6754f040c86aee8d731d8e9d9e53bbf
diff --git a/third_party/cloud_authenticator/cbor/BUILD.gn b/third_party/cloud_authenticator/cbor/BUILD.gn index 9aba749d..9d5f1e0 100644 --- a/third_party/cloud_authenticator/cbor/BUILD.gn +++ b/third_party/cloud_authenticator/cbor/BUILD.gn
@@ -6,6 +6,7 @@ rust_static_library("cbor") { crate_name = "cbor" + edition = "2021" sources = [ "src/lib.rs" ] deps = [ "//third_party/rust/bytes/v1:lib" ] no_chromium_prelude = true
diff --git a/third_party/cloud_authenticator/crypto/BUILD.gn b/third_party/cloud_authenticator/crypto/BUILD.gn index 91e58c51..ee69b26 100644 --- a/third_party/cloud_authenticator/crypto/BUILD.gn +++ b/third_party/cloud_authenticator/crypto/BUILD.gn
@@ -6,6 +6,7 @@ rust_static_library("crypto") { crate_name = "crypto" + edition = "2021" sources = [ "src/lib.rs" ] deps = [ "//third_party/boringssl:bssl_crypto" ] features = [ "bssl" ]
diff --git a/third_party/compiler-rt/src b/third_party/compiler-rt/src index bc66085..3a23fdd 160000 --- a/third_party/compiler-rt/src +++ b/third_party/compiler-rt/src
@@ -1 +1 @@ -Subproject commit bc66085be3ed8f3c2a3cb9a938cbc57edb86934d +Subproject commit 3a23fdd90573a881f5077eed539502702e467357
diff --git a/third_party/crabbyavif/BUILD.gn b/third_party/crabbyavif/BUILD.gn index 38b2055f..73baa63e 100644 --- a/third_party/crabbyavif/BUILD.gn +++ b/third_party/crabbyavif/BUILD.gn
@@ -144,6 +144,7 @@ rust_static_library("crabbyavif") { crate_root = "src/src/lib.rs" + edition = "2021" # This library exposes a C API and uses a couple of C/C++ libraries. So unsafe # has to be allowed in order to allow those. The core library itself does not
diff --git a/third_party/crashpad/README.chromium b/third_party/crashpad/README.chromium index 19e96e99..312d438 100644 --- a/third_party/crashpad/README.chromium +++ b/third_party/crashpad/README.chromium
@@ -2,7 +2,7 @@ Short Name: crashpad URL: https://chromium.googlesource.com/crashpad/crashpad Version: N/A -Revision: 3472da65aa2b3f6ad178a90ec8aae56a72b9e022 +Revision: ad8e3e906ca996977020a2cbf89668e737559f8f Update Mechanism: Manual License: Apache-2.0 License File: crashpad/LICENSE
diff --git a/third_party/crashpad/crashpad/snapshot/minidump/module_snapshot_minidump.cc b/third_party/crashpad/crashpad/snapshot/minidump/module_snapshot_minidump.cc index 039ac59b..36b00521 100644 --- a/third_party/crashpad/crashpad/snapshot/minidump/module_snapshot_minidump.cc +++ b/third_party/crashpad/crashpad/snapshot/minidump/module_snapshot_minidump.cc
@@ -17,6 +17,8 @@ #include <stddef.h> #include <string.h> +#include <iterator> + #include "base/check_op.h" #include "base/logging.h" #include "base/notreached.h"
diff --git a/third_party/crashpad/crashpad/third_party/cpp-httplib/README.crashpad b/third_party/crashpad/crashpad/third_party/cpp-httplib/README.crashpad index 7564e12..cabad95 100644 --- a/third_party/crashpad/crashpad/third_party/cpp-httplib/README.crashpad +++ b/third_party/crashpad/crashpad/third_party/cpp-httplib/README.crashpad
@@ -1,8 +1,8 @@ Name: cpp-httplib Short Name: cpp-httplib URL: https://github.com/yhirose/cpp-httplib -Version: 0.19.0 (+22 commits) -Revision: 71ba7e7b1b328fe0de6cfbd3e94e5e0ddd4b4073 +Version: 0.25.0 +Revision: 3f44c80fd345e859d6f61b2dda566aa3329ce35e Update Mechanism: Manual License: MIT License File: cpp-httplib/LICENSE @@ -14,8 +14,14 @@ A C++11 single-file header-only cross-platform HTTP/HTTPS library. Local Modifications: - - Exclude benchmark/, docker/, example/, and test/ subdirectories, and - build-related files. + - Exclude all subdirectories and build-related files. - Patch httplib.h to use #include "third_party/zlib/zlib_crashpad.h" instead of <zlib.h>. - #define CPPHTTPLIB_NO_EXCEPTIONS 1. + - Revert upstream patches that drop support for 32-bit Windows: + - 52163ed9823c Fix #2148 (#2173) + - 9dbaed75efff Fix #2175 (#2177) + - 1f110b54d8b3 Chang #error to #warning for the 32-bit environment check + except 32-bit Windows + - 0b3758ec36be Fix problem with Windows version check + - fbee136dca54 Fix #2193. Allow _WIN32
diff --git a/third_party/crashpad/crashpad/third_party/cpp-httplib/cpp-httplib/README.md b/third_party/crashpad/crashpad/third_party/cpp-httplib/cpp-httplib/README.md index aed19ca6..ff07694 100644 --- a/third_party/crashpad/crashpad/third_party/cpp-httplib/cpp-httplib/README.md +++ b/third_party/crashpad/crashpad/third_party/cpp-httplib/cpp-httplib/README.md
@@ -5,7 +5,7 @@ A C++11 single-file header-only cross platform HTTP/HTTPS library. -It's extremely easy to setup. Just include the **httplib.h** file in your code! +It's extremely easy to set up. Just include the **httplib.h** file in your code! > [!IMPORTANT] > This library uses 'blocking' socket I/O. If you are looking for a library with 'non-blocking' socket I/O, this is not the one that you want. @@ -85,6 +85,49 @@ > [!NOTE] > When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself. +### SSL Error Handling + +When SSL operations fail, cpp-httplib provides detailed error information through two separate error fields: + +```c++ +#define CPPHTTPLIB_OPENSSL_SUPPORT +#include "path/to/httplib.h" + +httplib::Client cli("https://example.com"); + +auto res = cli.Get("/"); +if (!res) { + // Check the error type + auto err = res.error(); + + switch (err) { + case httplib::Error::SSLConnection: + std::cout << "SSL connection failed, SSL error: " + << res->ssl_error() << std::endl; + break; + + case httplib::Error::SSLLoadingCerts: + std::cout << "SSL cert loading failed, OpenSSL error: " + << std::hex << res->ssl_openssl_error() << std::endl; + break; + + case httplib::Error::SSLServerVerification: + std::cout << "SSL verification failed, X509 error: " + << res->ssl_openssl_error() << std::endl; + break; + + case httplib::Error::SSLServerHostnameVerification: + std::cout << "SSL hostname verification failed, X509 error: " + << res->ssl_openssl_error() << std::endl; + break; + + default: + std::cout << "HTTP error: " << httplib::to_string(err) << std::endl; + } + } +} +``` + Server ------ @@ -187,7 +230,7 @@ svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h"); ``` -The followings are built-in mappings: +The following are built-in mappings: | Extension | MIME Type | Extension | MIME Type | | :--------- | :-------------------------- | :--------- | :-------------------------- | @@ -201,7 +244,7 @@ | bmp | image/bmp | 7z | application/x-7z-compressed | | gif | image/gif | atom | application/atom+xml | | png | image/png | pdf | application/pdf | -| svg | image/svg+xml | mjs, js | application/javascript | +| svg | image/svg+xml | mjs, js | text/javascript | | webp | image/webp | json | application/json | | ico | image/x-icon | rss | application/rss+xml | | tif | image/tiff | tar | application/x-tar | @@ -226,9 +269,45 @@ ### Logging +cpp-httplib provides separate logging capabilities for access logs and error logs, similar to web servers like Nginx and Apache. + +#### Access Logging + +Access loggers capture successful HTTP requests and responses: + ```cpp -svr.set_logger([](const auto& req, const auto& res) { - your_logger(req, res); +svr.set_logger([](const httplib::Request& req, const httplib::Response& res) { + std::cout << req.method << " " << req.path << " -> " << res.status << std::endl; +}); +``` + +#### Pre-compression Logging + +You can also set a pre-compression logger to capture request/response data before compression is applied: + +```cpp +svr.set_pre_compression_logger([](const httplib::Request& req, const httplib::Response& res) { + // Log before compression - res.body contains uncompressed content + // Content-Encoding header is not yet set + your_pre_compression_logger(req, res); +}); +``` + +The pre-compression logger is only called when compression would be applied. For responses without compression, only the access logger is called. + +#### Error Logging + +Error loggers capture failed requests and connection issues. Unlike access loggers, error loggers only receive the Error and Request information, as errors typically occur before a meaningful Response can be generated. + +```cpp +svr.set_error_logger([](const httplib::Error& err, const httplib::Request* req) { + std::cerr << httplib::to_string(err) << " while processing request"; + if (req) { + std::cerr << ", client: " << req->get_header_value("X-Forwarded-For") + << ", request: '" << req->method << " " << req->path << " " << req->version << "'" + << ", host: " << req->get_header_value("Host"); + } + std::cerr << std::endl; }); ``` @@ -285,16 +364,96 @@ }); ``` -### 'multipart/form-data' POST data +### Pre request handler ```cpp -svr.Post("/multipart", [&](const auto& req, auto& res) { - auto size = req.files.size(); - auto ret = req.has_file("name1"); - const auto& file = req.get_file_value("name1"); - // file.filename; - // file.content_type; - // file.content; +svr.set_pre_request_handler([](const auto& req, auto& res) { + if (req.matched_route == "/user/:user") { + auto user = req.path_params.at("user"); + if (user != "john") { + res.status = StatusCode::Forbidden_403; + res.set_content("error", "text/html"); + return Server::HandlerResponse::Handled; + } + } + return Server::HandlerResponse::Unhandled; +}); +``` + +### Form data handling + +#### URL-encoded form data ('application/x-www-form-urlencoded') + +```cpp +svr.Post("/form", [&](const auto& req, auto& res) { + // URL query parameters and form-encoded data are accessible via req.params + std::string username = req.get_param_value("username"); + std::string password = req.get_param_value("password"); + + // Handle multiple values with same name + auto interests = req.get_param_values("interests"); + + // Check existence + if (req.has_param("newsletter")) { + // Handle newsletter subscription + } +}); +``` + +#### 'multipart/form-data' POST data + +```cpp +svr.Post("/multipart", [&](const Request& req, Response& res) { + // Access text fields (from form inputs without files) + std::string username = req.form.get_field("username"); + std::string bio = req.form.get_field("bio"); + + // Access uploaded files + if (req.form.has_file("avatar")) { + const auto& file = req.form.get_file("avatar"); + std::cout << "Uploaded file: " << file.filename + << " (" << file.content_type << ") - " + << file.content.size() << " bytes" << std::endl; + + // Access additional headers if needed + for (const auto& header : file.headers) { + std::cout << "Header: " << header.first << " = " << header.second << std::endl; + } + + // Save to disk + std::ofstream ofs(file.filename, std::ios::binary); + ofs << file.content; + } + + // Handle multiple values with same name + auto tags = req.form.get_fields("tags"); // e.g., multiple checkboxes + for (const auto& tag : tags) { + std::cout << "Tag: " << tag << std::endl; + } + + auto documents = req.form.get_files("documents"); // multiple file upload + for (const auto& doc : documents) { + std::cout << "Document: " << doc.filename + << " (" << doc.content.size() << " bytes)" << std::endl; + } + + // Check existence before accessing + if (req.form.has_field("newsletter")) { + std::cout << "Newsletter subscription: " << req.form.get_field("newsletter") << std::endl; + } + + // Get counts for validation + if (req.form.get_field_count("tags") > 5) { + res.status = StatusCode::BadRequest_400; + res.set_content("Too many tags", "text/plain"); + return; + } + + // Summary + std::cout << "Received " << req.form.fields.size() << " text fields and " + << req.form.files.size() << " files" << std::endl; + + res.set_content("Upload successful", "text/plain"); }); ``` @@ -305,16 +464,29 @@ [&](const Request &req, Response &res, const ContentReader &content_reader) { if (req.is_multipart_form_data()) { // NOTE: `content_reader` is blocking until every form data field is read - MultipartFormDataItems files; + // This approach allows streaming processing of large files + std::vector<FormData> items; content_reader( - [&](const MultipartFormData &file) { - files.push_back(file); + [&](const FormData &item) { + items.push_back(item); return true; }, [&](const char *data, size_t data_length) { - files.back().content.append(data, data_length); + items.back().content.append(data, data_length); return true; }); + + // Process the received items + for (const auto& item : items) { + if (item.filename.empty()) { + // Text field + std::cout << "Field: " << item.name << " = " << item.content << std::endl; + } else { + // File + std::cout << "File: " << item.name << " (" << item.filename << ") - " + << item.content.size() << " bytes" << std::endl; + } + } } else { std::string body; content_reader([&](const char *data, size_t data_length) { @@ -435,7 +607,7 @@ ### Keep-Alive connection ```cpp -svr.set_keep_alive_max_count(2); // Default is 5 +svr.set_keep_alive_max_count(2); // Default is 100 svr.set_keep_alive_timeout(10); // Default is 5 ``` @@ -562,12 +734,59 @@ SSLConnection, SSLLoadingCerts, SSLServerVerification, + SSLServerHostnameVerification, UnsupportedMultipartBoundaryChars, Compression, ConnectionTimeout, + ProxyConnection, }; ``` +### Client Logging + +#### Access Logging + +```cpp +cli.set_logger([](const httplib::Request& req, const httplib::Response& res) { + auto duration = std::chrono::duration_cast<std::chrono::milliseconds>( + std::chrono::steady_clock::now() - start_time).count(); + std::cout << "✓ " << req.method << " " << req.path + << " -> " << res.status << " (" << res.body.size() << " bytes, " + << duration << "ms)" << std::endl; +}); +``` + +#### Error Logging + +```cpp +cli.set_error_logger([](const httplib::Error& err, const httplib::Request* req) { + std::cerr << "✗ "; + if (req) { + std::cerr << req->method << " " << req->path << " "; + } + std::cerr << "failed: " << httplib::to_string(err); + + // Add specific guidance based on error type + switch (err) { + case httplib::Error::Connection: + std::cerr << " (verify server is running and reachable)"; + break; + case httplib::Error::SSLConnection: + std::cerr << " (check SSL certificate and TLS configuration)"; + break; + case httplib::Error::ConnectionTimeout: + std::cerr << " (increase timeout or check network latency)"; + break; + case httplib::Error::Read: + std::cerr << " (server may have closed connection prematurely)"; + break; + default: + break; + } + std::cerr << std::endl; +}); +``` + ### GET with HTTP headers ```c++ @@ -618,7 +837,7 @@ ### POST with Multipart Form Data ```c++ -httplib::MultipartFormDataItems items = { +httplib::UploadFormDataItems items = { { "text1", "text default", "", "" }, { "text2", "aωb", "", "" }, { "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" }, @@ -655,8 +874,8 @@ cli.set_read_timeout(5, 0); // 5 seconds cli.set_write_timeout(5, 0); // 5 seconds -// This method works the same as curl's `--max-timeout` option -svr.set_max_timeout(5000); // 5 seconds +// This method works the same as curl's `--max-time` option +cli.set_max_timeout(5000); // 5 seconds ``` ### Receive content with a content receiver @@ -721,7 +940,7 @@ httplib::Client cli(url, port); // prints: 0 / 000 bytes => 50% complete -auto res = cli.Get("/", [](uint64_t len, uint64_t total) { +auto res = cli.Get("/", [](size_t len, size_t total) { printf("%lld / %lld bytes => %d%% complete\n", len, total, (int)(len*100/total)); @@ -820,6 +1039,45 @@ cli.set_interface("eth0"); // Interface name, IP address or host name ``` +### Automatic Path Encoding + +The client automatically encodes special characters in URL paths by default: + +```cpp +httplib::Client cli("https://example.com"); + +// Automatic path encoding (default behavior) +cli.set_path_encode(true); +auto res = cli.Get("/path with spaces/file.txt"); // Automatically encodes spaces + +// Disable automatic path encoding +cli.set_path_encode(false); +auto res = cli.Get("/already%20encoded/path"); // Use pre-encoded paths +``` + +- `set_path_encode(bool on)` - Controls automatic encoding of special characters in URL paths + - `true` (default): Automatically encodes spaces, plus signs, newlines, and other special characters + - `false`: Sends paths as-is without encoding (useful for pre-encoded URLs) + +### Performance Note for Local Connections + +> [!WARNING] +> On Windows systems with improperly configured IPv6 settings, using "localhost" as the hostname may cause significant connection delays (up to 2 seconds per request) due to DNS resolution issues. This affects both client and server operations. For better performance when connecting to local services, use "127.0.0.1" instead of "localhost". +> +> See: https://github.com/yhirose/cpp-httplib/issues/366#issuecomment-593004264 + +```cpp +// May be slower on Windows due to DNS resolution delays +httplib::Client cli("localhost", 8080); +httplib::Server svr; +svr.listen("localhost", 8080); + +// Faster alternative for local connections +httplib::Client cli("127.0.0.1", 8080); +httplib::Server svr; +svr.listen("127.0.0.1", 8080); +``` + Compression ----------- @@ -830,6 +1088,7 @@ * application/javascript * application/json * application/xml + * application/protobuf * application/xhtml+xml ### Zlib Support @@ -841,13 +1100,18 @@ Brotli compression is available with `CPPHTTPLIB_BROTLI_SUPPORT`. Necessary libraries should be linked. Please see https://github.com/google/brotli for more detail. +### Zstd Support + +Zstd compression is available with `CPPHTTPLIB_ZSTD_SUPPORT`. Necessary libraries should be linked. +Please see https://github.com/facebook/zstd for more detail. + ### Default `Accept-Encoding` value The default `Accept-Encoding` value contains all possible compression types. So, the following two examples are same. ```c++ res = cli.Get("/resource/foo"); -res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}}); +res = cli.Get("/resource/foo", {{"Accept-Encoding", "br, gzip, deflate, zstd"}}); ``` If we don't want a response without compression, we have to set `Accept-Encoding` to an empty string. This behavior is similar to curl. @@ -887,7 +1151,36 @@ cli.set_address_family(AF_UNIX); ``` -"my-socket.sock" can be a relative path or an absolute path. You application must have the appropriate permissions for the path. You can also use an abstract socket address on Linux. To use an abstract socket address, prepend a null byte ('\x00') to the path. +"my-socket.sock" can be a relative path or an absolute path. Your application must have the appropriate permissions for the path. You can also use an abstract socket address on Linux. To use an abstract socket address, prepend a null byte ('\x00') to the path. + +This library automatically sets the Host header to "localhost" for Unix socket connections, similar to curl's behavior: + + +URI Encoding/Decoding Utilities +------------------------------- + +cpp-httplib provides utility functions for URI encoding and decoding: + +```cpp +#include <httplib.h> + +std::string url = "https://example.com/search?q=hello world"; +std::string encoded = httplib::encode_uri(url); +std::string decoded = httplib::decode_uri(encoded); + +std::string param = "hello world"; +std::string encoded_component = httplib::encode_uri_component(param); +std::string decoded_component = httplib::decode_uri_component(encoded_component); +``` + +### Functions + +- `encode_uri(const std::string &value)` - Encodes a full URI, preserving reserved characters like `://`, `?`, `&`, `=` +- `decode_uri(const std::string &value)` - Decodes a URI-encoded string +- `encode_uri_component(const std::string &value)` - Encodes a URI component (query parameter, path segment), encoding all reserved characters +- `decode_uri_component(const std::string &value)` - Decodes a URI component + +Use `encode_uri()` for full URLs and `encode_uri_component()` for individual query parameters or path segments. Split httplib.h into .h and .cc @@ -938,6 +1231,24 @@ NOTE ---- +### Regular Expression Stack Overflow + +> [!CAUTION] +> When using complex regex patterns in route handlers, be aware that certain patterns may cause stack overflow during pattern matching. This is a known issue with `std::regex` implementations and affects the `dispatch_request()` method. +> +> ```cpp +> // This pattern can cause stack overflow with large input +> svr.Get(".*", handler); +> ``` +> +> Consider using simpler patterns or path parameters to avoid this issue: +> +> ```cpp +> // Safer alternatives +> svr.Get("/users/:id", handler); // Path parameters +> svr.Get(R"(/api/v\d+/.*)", handler); // More specific patterns +> ``` + ### g++ g++ 4.8 and below cannot build this library since `<regex>` in the versions are [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions).
diff --git a/third_party/crashpad/crashpad/third_party/cpp-httplib/cpp-httplib/httplib.h b/third_party/crashpad/crashpad/third_party/cpp-httplib/cpp-httplib/httplib.h index b1a09d24..7cd665f 100644 --- a/third_party/crashpad/crashpad/third_party/cpp-httplib/cpp-httplib/httplib.h +++ b/third_party/crashpad/crashpad/third_party/cpp-httplib/cpp-httplib/httplib.h
@@ -8,7 +8,8 @@ #ifndef CPPHTTPLIB_HTTPLIB_H #define CPPHTTPLIB_HTTPLIB_H -#define CPPHTTPLIB_VERSION "0.19.0" +#define CPPHTTPLIB_VERSION "0.25.0" +#define CPPHTTPLIB_VERSION_NUM "0x001900" /* * Configuration @@ -76,7 +77,7 @@ #ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND #ifdef _WIN32 -#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000 +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 1000 #else #define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 #endif @@ -90,6 +91,10 @@ #define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 #endif +#ifndef CPPHTTPLIB_HEADER_MAX_COUNT +#define CPPHTTPLIB_HEADER_MAX_COUNT 100 +#endif + #ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT #define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 #endif @@ -122,6 +127,10 @@ #define CPPHTTPLIB_RECV_BUFSIZ size_t(16384u) #endif +#ifndef CPPHTTPLIB_SEND_BUFSIZ +#define CPPHTTPLIB_SEND_BUFSIZ size_t(16384u) +#endif + #ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ #define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) #endif @@ -145,6 +154,10 @@ #define CPPHTTPLIB_LISTEN_BACKLOG 5 #endif +#ifndef CPPHTTPLIB_MAX_LINE_LENGTH +#define CPPHTTPLIB_MAX_LINE_LENGTH 32768 +#endif + #define CPPHTTPLIB_NO_EXCEPTIONS 1 /* @@ -190,13 +203,21 @@ #include <winsock2.h> #include <ws2tcpip.h> +#if defined(__has_include) +#if __has_include(<afunix.h>) +// afunix.h uses types declared in winsock2.h, so has to be included after it. +#include <afunix.h> +#define CPPHTTPLIB_HAVE_AFUNIX_H 1 +#endif +#endif + #ifndef WSA_FLAG_NO_HANDLE_INHERIT #define WSA_FLAG_NO_HANDLE_INHERIT 0x80 #endif +using nfds_t = unsigned long; using socket_t = SOCKET; using socklen_t = int; -#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) #else // not _WIN32 @@ -231,6 +252,10 @@ #endif #endif //_WIN32 +#if defined(__APPLE__) +#include <TargetConditionals.h> +#endif + #include <algorithm> #include <array> #include <atomic> @@ -242,7 +267,6 @@ #include <errno.h> #include <exception> #include <fcntl.h> -#include <fstream> #include <functional> #include <iomanip> #include <iostream> @@ -261,6 +285,15 @@ #include <unordered_set> #include <utility> +#if defined(CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO) || \ + defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) +#if TARGET_OS_OSX +#include <CFNetwork/CFHost.h> +#include <CoreFoundation/CoreFoundation.h> +#endif +#endif // CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO or + // CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN + #ifdef CPPHTTPLIB_OPENSSL_SUPPORT #ifdef _WIN32 #include <wincrypt.h> @@ -275,14 +308,14 @@ #ifdef _MSC_VER #pragma comment(lib, "crypt32.lib") #endif -#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) -#include <TargetConditionals.h> -#if TARGET_OS_OSX -#include <CoreFoundation/CoreFoundation.h> -#include <Security/Security.h> -#endif // TARGET_OS_OSX #endif // _WIN32 +#if defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) +#if TARGET_OS_OSX +#include <Security/Security.h> +#endif +#endif // CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO + #include <openssl/err.h> #include <openssl/evp.h> #include <openssl/ssl.h> @@ -304,7 +337,7 @@ #error Sorry, OpenSSL versions prior to 3.0.0 are not supported #endif -#endif +#endif // CPPHTTPLIB_OPENSSL_SUPPORT #ifdef CPPHTTPLIB_ZLIB_SUPPORT #include "third_party/zlib/zlib_crashpad.h" @@ -315,6 +348,10 @@ #include <brotli/encode.h> #endif +#ifdef CPPHTTPLIB_ZSTD_SUPPORT +#include <zstd.h> +#endif + /* * Declaration */ @@ -398,6 +435,10 @@ } }; +template <typename T> +using unordered_set = std::unordered_set<T, detail::case_ignore::hash, + detail::case_ignore::equal_to>; + } // namespace case_ignore // This is based on @@ -521,19 +562,53 @@ using Params = std::multimap<std::string, std::string>; using Match = std::smatch; -using Progress = std::function<bool(uint64_t current, uint64_t total)>; +using DownloadProgress = std::function<bool(size_t current, size_t total)>; +using UploadProgress = std::function<bool(size_t current, size_t total)>; struct Response; using ResponseHandler = std::function<bool(const Response &response)>; +struct FormData { + std::string name; + std::string content; + std::string filename; + std::string content_type; + Headers headers; +}; + +struct FormField { + std::string name; + std::string content; + Headers headers; +}; +using FormFields = std::multimap<std::string, FormField>; + +using FormFiles = std::multimap<std::string, FormData>; + struct MultipartFormData { + FormFields fields; // Text fields from multipart + FormFiles files; // Files from multipart + + // Text field access + std::string get_field(const std::string &key, size_t id = 0) const; + std::vector<std::string> get_fields(const std::string &key) const; + bool has_field(const std::string &key) const; + size_t get_field_count(const std::string &key) const; + + // File access + FormData get_file(const std::string &key, size_t id = 0) const; + std::vector<FormData> get_files(const std::string &key) const; + bool has_file(const std::string &key) const; + size_t get_file_count(const std::string &key) const; +}; + +struct UploadFormData { std::string name; std::string content; std::string filename; std::string content_type; }; -using MultipartFormDataItems = std::vector<MultipartFormData>; -using MultipartFormDataMap = std::multimap<std::string, MultipartFormData>; +using UploadFormDataItems = std::vector<UploadFormData>; class DataSink { public: @@ -576,37 +651,34 @@ using ContentProviderResourceReleaser = std::function<void(bool success)>; -struct MultipartFormDataProvider { +struct FormDataProvider { std::string name; ContentProviderWithoutLength provider; std::string filename; std::string content_type; }; -using MultipartFormDataProviderItems = std::vector<MultipartFormDataProvider>; +using FormDataProviderItems = std::vector<FormDataProvider>; -using ContentReceiverWithProgress = - std::function<bool(const char *data, size_t data_length, uint64_t offset, - uint64_t total_length)>; +using ContentReceiverWithProgress = std::function<bool( + const char *data, size_t data_length, size_t offset, size_t total_length)>; using ContentReceiver = std::function<bool(const char *data, size_t data_length)>; -using MultipartContentHeader = - std::function<bool(const MultipartFormData &file)>; +using FormDataHeader = std::function<bool(const FormData &file)>; class ContentReader { public: using Reader = std::function<bool(ContentReceiver receiver)>; - using MultipartReader = std::function<bool(MultipartContentHeader header, - ContentReceiver receiver)>; + using FormDataReader = + std::function<bool(FormDataHeader header, ContentReceiver receiver)>; - ContentReader(Reader reader, MultipartReader multipart_reader) + ContentReader(Reader reader, FormDataReader multipart_reader) : reader_(std::move(reader)), - multipart_reader_(std::move(multipart_reader)) {} + formdata_reader_(std::move(multipart_reader)) {} - bool operator()(MultipartContentHeader header, - ContentReceiver receiver) const { - return multipart_reader_(std::move(header), std::move(receiver)); + bool operator()(FormDataHeader header, ContentReceiver receiver) const { + return formdata_reader_(std::move(header), std::move(receiver)); } bool operator()(ContentReceiver receiver) const { @@ -614,7 +686,7 @@ } Reader reader_; - MultipartReader multipart_reader_; + FormDataReader formdata_reader_; }; using Range = std::pair<ssize_t, ssize_t>; @@ -623,8 +695,10 @@ struct Request { std::string method; std::string path; + std::string matched_route; Params params; Headers headers; + Headers trailers; std::string body; std::string remote_addr; @@ -635,16 +709,18 @@ // for server std::string version; std::string target; - MultipartFormDataMap files; + MultipartFormData form; Ranges ranges; Match matches; std::unordered_map<std::string, std::string> path_params; std::function<bool()> is_connection_closed = []() { return true; }; // for client + std::vector<std::string> accept_content_types; ResponseHandler response_handler; ContentReceiverWithProgress content_receiver; - Progress progress; + DownloadProgress download_progress; + UploadProgress upload_progress; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT const SSL *ssl = nullptr; #endif @@ -652,21 +728,21 @@ bool has_header(const std::string &key) const; std::string get_header_value(const std::string &key, const char *def = "", size_t id = 0) const; - uint64_t get_header_value_u64(const std::string &key, uint64_t def = 0, - size_t id = 0) const; + size_t get_header_value_u64(const std::string &key, size_t def = 0, + size_t id = 0) const; size_t get_header_value_count(const std::string &key) const; void set_header(const std::string &key, const std::string &val); + bool has_trailer(const std::string &key) const; + std::string get_trailer_value(const std::string &key, size_t id = 0) const; + size_t get_trailer_value_count(const std::string &key) const; + bool has_param(const std::string &key) const; std::string get_param_value(const std::string &key, size_t id = 0) const; size_t get_param_value_count(const std::string &key) const; bool is_multipart_form_data() const; - bool has_file(const std::string &key) const; - MultipartFormData get_file_value(const std::string &key) const; - std::vector<MultipartFormData> get_file_values(const std::string &key) const; - // private members... size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; size_t content_length_ = 0; @@ -682,17 +758,22 @@ int status = -1; std::string reason; Headers headers; + Headers trailers; std::string body; std::string location; // Redirect location bool has_header(const std::string &key) const; std::string get_header_value(const std::string &key, const char *def = "", size_t id = 0) const; - uint64_t get_header_value_u64(const std::string &key, uint64_t def = 0, - size_t id = 0) const; + size_t get_header_value_u64(const std::string &key, size_t def = 0, + size_t id = 0) const; size_t get_header_value_count(const std::string &key) const; void set_header(const std::string &key, const std::string &val); + bool has_trailer(const std::string &key) const; + std::string get_trailer_value(const std::string &key, size_t id = 0) const; + size_t get_trailer_value_count(const std::string &key) const; + void set_redirect(const std::string &url, int status = StatusCode::Found_302); void set_content(const char *s, size_t n, const std::string &content_type); void set_content(const std::string &s, const std::string &content_type); @@ -852,6 +933,10 @@ using Logger = std::function<void(const Request &, const Response &)>; +// Forward declaration for Error type +enum class Error; +using ErrorLogger = std::function<void(const Error &, const Request *)>; + using SocketOptions = std::function<void(socket_t sock)>; namespace detail { @@ -874,10 +959,16 @@ class MatcherBase { public: + MatcherBase(std::string pattern) : pattern_(pattern) {} virtual ~MatcherBase() = default; + const std::string &pattern() const { return pattern_; } + // Match request path and populate its matches and virtual bool match(Request &request) const = 0; + +private: + std::string pattern_; }; /** @@ -929,7 +1020,8 @@ */ class RegexMatcher final : public MatcherBase { public: - RegexMatcher(const std::string &pattern) : regex_(pattern) {} + RegexMatcher(const std::string &pattern) + : MatcherBase(pattern), regex_(pattern) {} bool match(Request &request) const override; @@ -996,11 +1088,16 @@ } Server &set_exception_handler(ExceptionHandler handler); + Server &set_pre_routing_handler(HandlerWithResponse handler); Server &set_post_routing_handler(Handler handler); + Server &set_pre_request_handler(HandlerWithResponse handler); + Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); Server &set_logger(Logger logger); + Server &set_pre_compression_logger(Logger logger); + Server &set_error_logger(ErrorLogger error_logger); Server &set_address_family(int family); Server &set_tcp_nodelay(bool on); @@ -1079,8 +1176,7 @@ bool listen_internal(); bool routing(Request &req, Response &res, Stream &strm); - bool handle_file_request(const Request &req, Response &res, - bool head = false); + bool handle_file_request(const Request &req, Response &res); bool dispatch_request(Request &req, Response &res, const Handlers &handlers) const; bool dispatch_request_for_content_reader( @@ -1101,18 +1197,23 @@ Response &res, const std::string &boundary, const std::string &content_type); bool read_content(Stream &strm, Request &req, Response &res); - bool - read_content_with_content_receiver(Stream &strm, Request &req, Response &res, - ContentReceiver receiver, - MultipartContentHeader multipart_header, - ContentReceiver multipart_receiver); + bool read_content_with_content_receiver(Stream &strm, Request &req, + Response &res, + ContentReceiver receiver, + FormDataHeader multipart_header, + ContentReceiver multipart_receiver); bool read_content_core(Stream &strm, Request &req, Response &res, ContentReceiver receiver, - MultipartContentHeader multipart_header, + FormDataHeader multipart_header, ContentReceiver multipart_receiver) const; virtual bool process_and_close_socket(socket_t sock); + void output_log(const Request &req, const Response &res) const; + void output_pre_compression_log(const Request &req, + const Response &res) const; + void output_error_log(const Error &err, const Request *req) const; + std::atomic<bool> is_running_{false}; std::atomic<bool> is_decommissioned{false}; @@ -1141,9 +1242,13 @@ ExceptionHandler exception_handler_; HandlerWithResponse pre_routing_handler_; Handler post_routing_handler_; + HandlerWithResponse pre_request_handler_; Expect100ContinueHandler expect_100_continue_handler_; + mutable std::mutex logger_mutex_; Logger logger_; + Logger pre_compression_logger_; + ErrorLogger error_logger_; int address_family_ = AF_UNSPEC; bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; @@ -1172,6 +1277,22 @@ Compression, ConnectionTimeout, ProxyConnection, + ResourceExhaustion, + TooManyFormDataFiles, + ExceedMaxPayloadSize, + ExceedUriMaxLength, + ExceedMaxSocketDescriptorCount, + InvalidRequestLine, + InvalidHTTPMethod, + InvalidHTTPVersion, + InvalidHeaders, + MultipartParsing, + OpenFile, + Listen, + GetSockName, + UnsupportedAddressFamily, + HTTPParsing, + InvalidRangeHeader, // For internal use only SSLPeerCouldBeClosed_, @@ -1188,6 +1309,17 @@ Headers &&request_headers = Headers{}) : res_(std::move(res)), err_(err), request_headers_(std::move(request_headers)) {} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + Result(std::unique_ptr<Response> &&res, Error err, Headers &&request_headers, + int ssl_error) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)), ssl_error_(ssl_error) {} + Result(std::unique_ptr<Response> &&res, Error err, Headers &&request_headers, + int ssl_error, unsigned long ssl_openssl_error) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)), ssl_error_(ssl_error), + ssl_openssl_error_(ssl_openssl_error) {} +#endif // Response operator bool() const { return res_ != nullptr; } bool operator==(std::nullptr_t) const { return res_ == nullptr; } @@ -1202,19 +1334,30 @@ // Error Error error() const { return err_; } +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // SSL Error + int ssl_error() const { return ssl_error_; } + // OpenSSL Error + unsigned long ssl_openssl_error() const { return ssl_openssl_error_; } +#endif + // Request Headers bool has_request_header(const std::string &key) const; std::string get_request_header_value(const std::string &key, const char *def = "", size_t id = 0) const; - uint64_t get_request_header_value_u64(const std::string &key, - uint64_t def = 0, size_t id = 0) const; + size_t get_request_header_value_u64(const std::string &key, size_t def = 0, + size_t id = 0) const; size_t get_request_header_value_count(const std::string &key) const; private: std::unique_ptr<Response> res_; Error err_ = Error::Unknown; Headers request_headers_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + int ssl_error_ = 0; + unsigned long ssl_openssl_error_ = 0; +#endif }; class ClientImpl { @@ -1231,185 +1374,86 @@ virtual bool is_valid() const; - Result Get(const std::string &path); - Result Get(const std::string &path, const Headers &headers); - Result Get(const std::string &path, Progress progress); - Result Get(const std::string &path, const Headers &headers, - Progress progress); - Result Get(const std::string &path, ContentReceiver content_receiver); - Result Get(const std::string &path, const Headers &headers, - ContentReceiver content_receiver); - Result Get(const std::string &path, ContentReceiver content_receiver, - Progress progress); - Result Get(const std::string &path, const Headers &headers, - ContentReceiver content_receiver, Progress progress); - Result Get(const std::string &path, ResponseHandler response_handler, - ContentReceiver content_receiver); - Result Get(const std::string &path, const Headers &headers, - ResponseHandler response_handler, - ContentReceiver content_receiver); - Result Get(const std::string &path, ResponseHandler response_handler, - ContentReceiver content_receiver, Progress progress); - Result Get(const std::string &path, const Headers &headers, - ResponseHandler response_handler, ContentReceiver content_receiver, - Progress progress); - - Result Get(const std::string &path, const Params ¶ms, - const Headers &headers, Progress progress = nullptr); - Result Get(const std::string &path, const Params ¶ms, - const Headers &headers, ContentReceiver content_receiver, - Progress progress = nullptr); - Result Get(const std::string &path, const Params ¶ms, - const Headers &headers, ResponseHandler response_handler, - ContentReceiver content_receiver, Progress progress = nullptr); + // clang-format off + Result Get(const std::string &path, DownloadProgress progress = nullptr); + Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Head(const std::string &path); Result Head(const std::string &path, const Headers &headers); Result Post(const std::string &path); - Result Post(const std::string &path, const Headers &headers); - Result Post(const std::string &path, const char *body, size_t content_length, - const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, const char *body, - size_t content_length, const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, const char *body, - size_t content_length, const std::string &content_type, - Progress progress); - Result Post(const std::string &path, const std::string &body, - const std::string &content_type); - Result Post(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress); - Result Post(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type, - Progress progress); - Result Post(const std::string &path, size_t content_length, - ContentProvider content_provider, - const std::string &content_type); - Result Post(const std::string &path, - ContentProviderWithoutLength content_provider, - const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, - size_t content_length, ContentProvider content_provider, - const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, - const std::string &content_type); + Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, const Params ¶ms); - Result Post(const std::string &path, const Headers &headers, - const Params ¶ms); - Result Post(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress); - Result Post(const std::string &path, const MultipartFormDataItems &items); - Result Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items); - Result Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, const std::string &boundary); - Result Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items); + Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Put(const std::string &path); - Result Put(const std::string &path, const char *body, size_t content_length, - const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, const char *body, - size_t content_length, const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, const char *body, - size_t content_length, const std::string &content_type, - Progress progress); - Result Put(const std::string &path, const std::string &body, - const std::string &content_type); - Result Put(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress); - Result Put(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type, - Progress progress); - Result Put(const std::string &path, size_t content_length, - ContentProvider content_provider, const std::string &content_type); - Result Put(const std::string &path, - ContentProviderWithoutLength content_provider, - const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, - size_t content_length, ContentProvider content_provider, - const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, - const std::string &content_type); + Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, const Params ¶ms); - Result Put(const std::string &path, const Headers &headers, - const Params ¶ms); - Result Put(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress); - Result Put(const std::string &path, const MultipartFormDataItems &items); - Result Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items); - Result Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, const std::string &boundary); - Result Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items); + Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers); + Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Patch(const std::string &path); - Result Patch(const std::string &path, const char *body, size_t content_length, - const std::string &content_type); - Result Patch(const std::string &path, const char *body, size_t content_length, - const std::string &content_type, Progress progress); - Result Patch(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type, Progress progress); - Result Patch(const std::string &path, const std::string &body, - const std::string &content_type); - Result Patch(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress); - Result Patch(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type, - Progress progress); - Result Patch(const std::string &path, size_t content_length, - ContentProvider content_provider, - const std::string &content_type); - Result Patch(const std::string &path, - ContentProviderWithoutLength content_provider, - const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, - size_t content_length, ContentProvider content_provider, - const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, - const std::string &content_type); + Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Params ¶ms); + Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const Params ¶ms); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); - Result Delete(const std::string &path); - Result Delete(const std::string &path, const Headers &headers); - Result Delete(const std::string &path, const char *body, - size_t content_length, const std::string &content_type); - Result Delete(const std::string &path, const char *body, - size_t content_length, const std::string &content_type, - Progress progress); - Result Delete(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type); - Result Delete(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type, Progress progress); - Result Delete(const std::string &path, const std::string &body, - const std::string &content_type); - Result Delete(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress); - Result Delete(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type); - Result Delete(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type, - Progress progress); + Result Delete(const std::string &path, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Params ¶ms, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const Params ¶ms, DownloadProgress progress = nullptr); Result Options(const std::string &path); Result Options(const std::string &path, const Headers &headers); + // clang-format on bool send(Request &req, Response &res, Error &error); Result send(const Request &req); @@ -1461,7 +1505,7 @@ void set_keep_alive(bool on); void set_follow_location(bool on); - void set_url_encode(bool on); + void set_path_encode(bool on); void set_compress(bool on); @@ -1493,6 +1537,7 @@ #endif void set_logger(Logger logger); + void set_error_logger(ErrorLogger error_logger); protected: struct Socket { @@ -1525,6 +1570,9 @@ void copy_settings(const ClientImpl &rhs); + void output_log(const Request &req, const Response &res) const; + void output_error_log(const Error &err, const Request *req) const; + // Socket endpoint information const std::string host_; const int port_; @@ -1573,7 +1621,7 @@ bool keep_alive_ = false; bool follow_location_ = false; - bool url_encode_ = true; + bool path_encode_ = true; int address_family_ = AF_UNSPEC; bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; @@ -1609,7 +1657,14 @@ std::function<SSLVerifierResponse(SSL *ssl)> server_certificate_verifier_; #endif + mutable std::mutex logger_mutex_; Logger logger_; + ErrorLogger error_logger_; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + int last_ssl_error_ = 0; + unsigned long last_openssl_error_ = 0; +#endif private: bool send_(Request &req, Response &res, Error &error); @@ -1621,6 +1676,11 @@ bool write_request(Stream &strm, Request &req, bool close_connection, Error &error); bool redirect(Request &req, Response &res, Error &error); + bool create_redirect_client(const std::string &scheme, + const std::string &host, int port, Request &req, + Response &res, const std::string &path, + const std::string &location, Error &error); + template <typename ClientType> void setup_redirect_client(ClientType &client); bool handle_request(Stream &strm, Request &req, Response &res, bool close_connection, Error &error); std::unique_ptr<Response> send_with_content_provider( @@ -1633,10 +1693,10 @@ const Headers &headers, const char *body, size_t content_length, ContentProvider content_provider, ContentProviderWithoutLength content_provider_without_length, - const std::string &content_type, Progress progress); + const std::string &content_type, UploadProgress progress); ContentProviderWithoutLength get_multipart_content_provider( - const std::string &boundary, const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items) const; + const std::string &boundary, const UploadFormDataItems &items, + const FormDataProviderItems &provider_items) const; std::string adjust_host_string(const std::string &host) const; @@ -1670,185 +1730,86 @@ bool is_valid() const; - Result Get(const std::string &path); - Result Get(const std::string &path, const Headers &headers); - Result Get(const std::string &path, Progress progress); - Result Get(const std::string &path, const Headers &headers, - Progress progress); - Result Get(const std::string &path, ContentReceiver content_receiver); - Result Get(const std::string &path, const Headers &headers, - ContentReceiver content_receiver); - Result Get(const std::string &path, ContentReceiver content_receiver, - Progress progress); - Result Get(const std::string &path, const Headers &headers, - ContentReceiver content_receiver, Progress progress); - Result Get(const std::string &path, ResponseHandler response_handler, - ContentReceiver content_receiver); - Result Get(const std::string &path, const Headers &headers, - ResponseHandler response_handler, - ContentReceiver content_receiver); - Result Get(const std::string &path, const Headers &headers, - ResponseHandler response_handler, ContentReceiver content_receiver, - Progress progress); - Result Get(const std::string &path, ResponseHandler response_handler, - ContentReceiver content_receiver, Progress progress); - - Result Get(const std::string &path, const Params ¶ms, - const Headers &headers, Progress progress = nullptr); - Result Get(const std::string &path, const Params ¶ms, - const Headers &headers, ContentReceiver content_receiver, - Progress progress = nullptr); - Result Get(const std::string &path, const Params ¶ms, - const Headers &headers, ResponseHandler response_handler, - ContentReceiver content_receiver, Progress progress = nullptr); + // clang-format off + Result Get(const std::string &path, DownloadProgress progress = nullptr); + Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Head(const std::string &path); Result Head(const std::string &path, const Headers &headers); Result Post(const std::string &path); - Result Post(const std::string &path, const Headers &headers); - Result Post(const std::string &path, const char *body, size_t content_length, - const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, const char *body, - size_t content_length, const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, const char *body, - size_t content_length, const std::string &content_type, - Progress progress); - Result Post(const std::string &path, const std::string &body, - const std::string &content_type); - Result Post(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress); - Result Post(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type, - Progress progress); - Result Post(const std::string &path, size_t content_length, - ContentProvider content_provider, - const std::string &content_type); - Result Post(const std::string &path, - ContentProviderWithoutLength content_provider, - const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, - size_t content_length, ContentProvider content_provider, - const std::string &content_type); - Result Post(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, - const std::string &content_type); + Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Post(const std::string &path, const Params ¶ms); - Result Post(const std::string &path, const Headers &headers, - const Params ¶ms); - Result Post(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress); - Result Post(const std::string &path, const MultipartFormDataItems &items); - Result Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items); - Result Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, const std::string &boundary); - Result Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items); + Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Put(const std::string &path); - Result Put(const std::string &path, const char *body, size_t content_length, - const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, const char *body, - size_t content_length, const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, const char *body, - size_t content_length, const std::string &content_type, - Progress progress); - Result Put(const std::string &path, const std::string &body, - const std::string &content_type); - Result Put(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress); - Result Put(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type, - Progress progress); - Result Put(const std::string &path, size_t content_length, - ContentProvider content_provider, const std::string &content_type); - Result Put(const std::string &path, - ContentProviderWithoutLength content_provider, - const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, - size_t content_length, ContentProvider content_provider, - const std::string &content_type); - Result Put(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, - const std::string &content_type); + Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, const Params ¶ms); - Result Put(const std::string &path, const Headers &headers, - const Params ¶ms); - Result Put(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress); - Result Put(const std::string &path, const MultipartFormDataItems &items); - Result Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items); - Result Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, const std::string &boundary); - Result Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items); + Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers); + Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); Result Patch(const std::string &path); - Result Patch(const std::string &path, const char *body, size_t content_length, - const std::string &content_type); - Result Patch(const std::string &path, const char *body, size_t content_length, - const std::string &content_type, Progress progress); - Result Patch(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type, Progress progress); - Result Patch(const std::string &path, const std::string &body, - const std::string &content_type); - Result Patch(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress); - Result Patch(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type, - Progress progress); - Result Patch(const std::string &path, size_t content_length, - ContentProvider content_provider, - const std::string &content_type); - Result Patch(const std::string &path, - ContentProviderWithoutLength content_provider, - const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, - size_t content_length, ContentProvider content_provider, - const std::string &content_type); - Result Patch(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, - const std::string &content_type); + Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Params ¶ms); + Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers); + Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const Params ¶ms); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr); + Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr); - Result Delete(const std::string &path); - Result Delete(const std::string &path, const Headers &headers); - Result Delete(const std::string &path, const char *body, - size_t content_length, const std::string &content_type); - Result Delete(const std::string &path, const char *body, - size_t content_length, const std::string &content_type, - Progress progress); - Result Delete(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type); - Result Delete(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type, Progress progress); - Result Delete(const std::string &path, const std::string &body, - const std::string &content_type); - Result Delete(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress); - Result Delete(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type); - Result Delete(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type, - Progress progress); + Result Delete(const std::string &path, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Params ¶ms, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr); + Result Delete(const std::string &path, const Headers &headers, const Params ¶ms, DownloadProgress progress = nullptr); Result Options(const std::string &path); Result Options(const std::string &path, const Headers &headers); + // clang-format on bool send(Request &req, Response &res, Error &error); Result send(const Request &req); @@ -1899,6 +1860,7 @@ void set_keep_alive(bool on); void set_follow_location(bool on); + void set_path_encode(bool on); void set_url_encode(bool on); void set_compress(bool on); @@ -1924,6 +1886,7 @@ #endif void set_logger(Logger logger); + void set_error_logger(ErrorLogger error_logger); // SSL #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -1974,6 +1937,9 @@ SSL_CTX *ctx_; std::mutex ctx_mutex_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + int last_ssl_error_ = 0; +#endif }; class SSLClient final : public ClientImpl { @@ -2053,13 +2019,19 @@ callback(static_cast<time_t>(sec), static_cast<time_t>(usec)); } -inline bool is_numeric(const std::string &str) { - return !str.empty() && std::all_of(str.begin(), str.end(), ::isdigit); +template <size_t N> inline constexpr size_t str_len(const char (&)[N]) { + return N - 1; } -inline uint64_t get_header_value_u64(const Headers &headers, - const std::string &key, uint64_t def, - size_t id, bool &is_invalid_value) { +inline bool is_numeric(const std::string &str) { + return !str.empty() && + std::all_of(str.cbegin(), str.cend(), + [](unsigned char c) { return std::isdigit(c); }); +} + +inline size_t get_header_value_u64(const Headers &headers, + const std::string &key, size_t def, + size_t id, bool &is_invalid_value) { is_invalid_value = false; auto rng = headers.equal_range(key); auto it = rng.first; @@ -2074,22 +2046,22 @@ return def; } -inline uint64_t get_header_value_u64(const Headers &headers, - const std::string &key, uint64_t def, - size_t id) { - bool dummy = false; +inline size_t get_header_value_u64(const Headers &headers, + const std::string &key, size_t def, + size_t id) { + auto dummy = false; return get_header_value_u64(headers, key, def, id, dummy); } } // namespace detail -inline uint64_t Request::get_header_value_u64(const std::string &key, - uint64_t def, size_t id) const { +inline size_t Request::get_header_value_u64(const std::string &key, size_t def, + size_t id) const { return detail::get_header_value_u64(headers, key, def, id); } -inline uint64_t Response::get_header_value_u64(const std::string &key, - uint64_t def, size_t id) const { +inline size_t Response::get_header_value_u64(const std::string &key, size_t def, + size_t id) const { return detail::get_header_value_u64(headers, key, def, id); } @@ -2212,9 +2184,9 @@ inline std::string get_bearer_token_auth(const Request &req) { if (req.has_header("Authorization")) { - static std::string BearerHeaderPrefix = "Bearer "; + constexpr auto bearer_header_prefix_len = detail::str_len("Bearer "); return req.get_header_value("Authorization") - .substr(BearerHeaderPrefix.length()); + .substr(bearer_header_prefix_len); } return ""; } @@ -2246,6 +2218,7 @@ inline std::string to_string(const Error error) { switch (error) { case Error::Success: return "Success (no error)"; + case Error::Unknown: return "Unknown"; case Error::Connection: return "Could not establish connection"; case Error::BindIPAddress: return "Failed to bind IP address"; case Error::Read: return "Failed to read connection"; @@ -2262,7 +2235,23 @@ case Error::Compression: return "Compression failed"; case Error::ConnectionTimeout: return "Connection timed out"; case Error::ProxyConnection: return "Proxy connection failed"; - case Error::Unknown: return "Unknown"; + case Error::ResourceExhaustion: return "Resource exhaustion"; + case Error::TooManyFormDataFiles: return "Too many form data files"; + case Error::ExceedMaxPayloadSize: return "Exceeded maximum payload size"; + case Error::ExceedUriMaxLength: return "Exceeded maximum URI length"; + case Error::ExceedMaxSocketDescriptorCount: + return "Exceeded maximum socket descriptor count"; + case Error::InvalidRequestLine: return "Invalid request line"; + case Error::InvalidHTTPMethod: return "Invalid HTTP method"; + case Error::InvalidHTTPVersion: return "Invalid HTTP version"; + case Error::InvalidHeaders: return "Invalid headers"; + case Error::MultipartParsing: return "Multipart parsing failed"; + case Error::OpenFile: return "Failed to open file"; + case Error::Listen: return "Failed to listen on socket"; + case Error::GetSockName: return "Failed to get socket name"; + case Error::UnsupportedAddressFamily: return "Unsupported address family"; + case Error::HTTPParsing: return "HTTP parsing failed"; + case Error::InvalidRangeHeader: return "Invalid Range header"; default: break; } @@ -2275,9 +2264,9 @@ return os; } -inline uint64_t Result::get_request_header_value_u64(const std::string &key, - uint64_t def, - size_t id) const { +inline size_t Result::get_request_header_value_u64(const std::string &key, + size_t def, + size_t id) const { return detail::get_header_value_u64(request_headers_, key, def, id); } @@ -2329,6 +2318,10 @@ cli_->set_write_timeout(duration); } +inline void Client::set_max_timeout(time_t msec) { + cli_->set_max_timeout(msec); +} + template <class Rep, class Period> inline void Client::set_max_timeout(const std::chrono::duration<Rep, Period> &duration) { @@ -2344,6 +2337,20 @@ void hosted_at(const std::string &hostname, std::vector<std::string> &addrs); +// JavaScript-style URL encoding/decoding functions +std::string encode_uri_component(const std::string &value); +std::string encode_uri(const std::string &value); +std::string decode_uri_component(const std::string &value); +std::string decode_uri(const std::string &value); + +// RFC 3986 compliant URL component encoding/decoding functions +std::string encode_path_component(const std::string &component); +std::string decode_path_component(const std::string &component); +std::string encode_query_component(const std::string &component, + bool space_as_plus = true); +std::string decode_query_component(const std::string &component, + bool plus_as_space = true); + std::string append_query_params(const std::string &path, const Params ¶ms); std::pair<std::string, std::string> make_range_header(const Ranges &ranges); @@ -2385,12 +2392,6 @@ int ret_ = -1; }; -std::string encode_query_param(const std::string &value); - -std::string decode_url(const std::string &s, bool convert_plus_to_space); - -void read_file(const std::string &path, std::string &out); - std::string trim_copy(const std::string &s); void divide( @@ -2440,13 +2441,16 @@ bool parse_range_header(const std::string &s, Ranges &ranges); +bool parse_accept_header(const std::string &s, + std::vector<std::string> &content_types); + int close_socket(socket_t sock); ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags); ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags); -enum class EncodingType { None = 0, Gzip, Brotli }; +enum class EncodingType { None = 0, Gzip, Brotli, Zstd }; EncodingType encoding_type(const Request &req, const Response &res); @@ -2559,6 +2563,34 @@ }; #endif +#ifdef CPPHTTPLIB_ZSTD_SUPPORT +class zstd_compressor : public compressor { +public: + zstd_compressor(); + ~zstd_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + ZSTD_CCtx *ctx_ = nullptr; +}; + +class zstd_decompressor : public decompressor { +public: + zstd_decompressor(); + ~zstd_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + ZSTD_DCtx *ctx_ = nullptr; +}; +#endif + // NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` // to store data. The call can set memory on stack for performance. class stream_line_reader { @@ -2828,28 +2860,7 @@ return ret_ >= 0 && S_ISDIR(st_.st_mode); } -inline std::string encode_query_param(const std::string &value) { - std::ostringstream escaped; - escaped.fill('0'); - escaped << std::hex; - - for (auto c : value) { - if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' || - c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || - c == ')') { - escaped << c; - } else { - escaped << std::uppercase; - escaped << '%' << std::setw(2) - << static_cast<int>(static_cast<unsigned char>(c)); - escaped << std::nouppercase; - } - } - - return escaped.str(); -} - -inline std::string encode_url(const std::string &s) { +inline std::string encode_path(const std::string &s) { std::string result; result.reserve(s.size()); @@ -2881,55 +2892,9 @@ return result; } -inline std::string decode_url(const std::string &s, - bool convert_plus_to_space) { - std::string result; - - for (size_t i = 0; i < s.size(); i++) { - if (s[i] == '%' && i + 1 < s.size()) { - if (s[i + 1] == 'u') { - auto val = 0; - if (from_hex_to_i(s, i + 2, 4, val)) { - // 4 digits Unicode codes - char buff[4]; - size_t len = to_utf8(val, buff); - if (len > 0) { result.append(buff, len); } - i += 5; // 'u0000' - } else { - result += s[i]; - } - } else { - auto val = 0; - if (from_hex_to_i(s, i + 1, 2, val)) { - // 2 digits hex codes - result += static_cast<char>(val); - i += 2; // '00' - } else { - result += s[i]; - } - } - } else if (convert_plus_to_space && s[i] == '+') { - result += ' '; - } else { - result += s[i]; - } - } - - return result; -} - -inline void read_file(const std::string &path, std::string &out) { - std::ifstream fs(path, std::ios_base::binary); - fs.seekg(0, std::ios_base::end); - auto size = fs.tellg(); - fs.seekg(0); - out.resize(static_cast<size_t>(size)); - fs.read(&out[0], static_cast<std::streamsize>(size)); -} - inline std::string file_extension(const std::string &path) { std::smatch m; - static auto re = std::regex("\\.([a-zA-Z0-9]+)$"); + thread_local auto re = std::regex("\\.([a-zA-Z0-9]+)$"); if (std::regex_search(path, m, re)) { return m[1].str(); } return std::string(); } @@ -3042,6 +3007,11 @@ #endif for (size_t i = 0;; i++) { + if (size() >= CPPHTTPLIB_MAX_LINE_LENGTH) { + // Treat exceptionally long lines as an error to + // prevent infinite loops/memory exhaustion + return false; + } char byte; auto n = strm_.read(&byte, 1); @@ -3254,15 +3224,41 @@ }); } +inline int poll_wrapper(struct pollfd *fds, nfds_t nfds, int timeout) { +#ifdef _WIN32 + return ::WSAPoll(fds, nfds, timeout); +#else + return ::poll(fds, nfds, timeout); +#endif +} + template <bool Read> inline ssize_t select_impl(socket_t sock, time_t sec, time_t usec) { +#ifdef __APPLE__ + if (sock >= FD_SETSIZE) { return -1; } + + fd_set fds, *rfds, *wfds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + rfds = (Read ? &fds : nullptr); + wfds = (Read ? nullptr : &fds); + + timeval tv; + tv.tv_sec = static_cast<long>(sec); + tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec); + + return handle_EINTR([&]() { + return select(static_cast<int>(sock + 1), rfds, wfds, nullptr, &tv); + }); +#else struct pollfd pfd; pfd.fd = sock; pfd.events = (Read ? POLLIN : POLLOUT); auto timeout = static_cast<int>(sec * 1000 + usec / 1000); - return handle_EINTR([&]() { return poll(&pfd, 1, timeout); }); + return handle_EINTR([&]() { return poll_wrapper(&pfd, 1, timeout); }); +#endif } inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { @@ -3275,13 +3271,44 @@ inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, time_t usec) { +#ifdef __APPLE__ + if (sock >= FD_SETSIZE) { return Error::Connection; } + + fd_set fdsr, fdsw; + FD_ZERO(&fdsr); + FD_ZERO(&fdsw); + FD_SET(sock, &fdsr); + FD_SET(sock, &fdsw); + + timeval tv; + tv.tv_sec = static_cast<long>(sec); + tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec); + + auto ret = handle_EINTR([&]() { + return select(static_cast<int>(sock + 1), &fdsr, &fdsw, nullptr, &tv); + }); + + if (ret == 0) { return Error::ConnectionTimeout; } + + if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { + auto error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast<char *>(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + + return Error::Connection; +#else struct pollfd pfd_read; pfd_read.fd = sock; pfd_read.events = POLLIN | POLLOUT; auto timeout = static_cast<int>(sec * 1000 + usec / 1000); - auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); + auto poll_res = + handle_EINTR([&]() { return poll_wrapper(&pfd_read, 1, timeout); }); if (poll_res == 0) { return Error::ConnectionTimeout; } @@ -3295,6 +3322,7 @@ } return Error::Connection; +#endif } inline bool is_socket_alive(socket_t sock) { @@ -3334,7 +3362,7 @@ time_t write_timeout_sec_; time_t write_timeout_usec_; time_t max_timeout_msec_; - const std::chrono::time_point<std::chrono::steady_clock> start_time; + const std::chrono::time_point<std::chrono::steady_clock> start_time_; std::vector<char> read_buff_; size_t read_buff_off_ = 0; @@ -3372,7 +3400,7 @@ time_t write_timeout_sec_; time_t write_timeout_usec_; time_t max_timeout_msec_; - const std::chrono::time_point<std::chrono::steady_clock> start_time; + const std::chrono::time_point<std::chrono::steady_clock> start_time_; }; #endif @@ -3482,11 +3510,328 @@ return s; } +inline int getaddrinfo_with_timeout(const char *node, const char *service, + const struct addrinfo *hints, + struct addrinfo **res, time_t timeout_sec) { +#ifdef CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO + if (timeout_sec <= 0) { + // No timeout specified, use standard getaddrinfo + return getaddrinfo(node, service, hints, res); + } + +#ifdef _WIN32 + // Windows-specific implementation using GetAddrInfoEx with overlapped I/O + OVERLAPPED overlapped = {0}; + HANDLE event = CreateEventW(nullptr, TRUE, FALSE, nullptr); + if (!event) { return EAI_FAIL; } + + overlapped.hEvent = event; + + PADDRINFOEXW result_addrinfo = nullptr; + HANDLE cancel_handle = nullptr; + + ADDRINFOEXW hints_ex = {0}; + if (hints) { + hints_ex.ai_flags = hints->ai_flags; + hints_ex.ai_family = hints->ai_family; + hints_ex.ai_socktype = hints->ai_socktype; + hints_ex.ai_protocol = hints->ai_protocol; + } + + auto wnode = u8string_to_wstring(node); + auto wservice = u8string_to_wstring(service); + + auto ret = ::GetAddrInfoExW(wnode.data(), wservice.data(), NS_DNS, nullptr, + hints ? &hints_ex : nullptr, &result_addrinfo, + nullptr, &overlapped, nullptr, &cancel_handle); + + if (ret == WSA_IO_PENDING) { + auto wait_result = + ::WaitForSingleObject(event, static_cast<DWORD>(timeout_sec * 1000)); + if (wait_result == WAIT_TIMEOUT) { + if (cancel_handle) { ::GetAddrInfoExCancel(&cancel_handle); } + ::CloseHandle(event); + return EAI_AGAIN; + } + + DWORD bytes_returned; + if (!::GetOverlappedResult((HANDLE)INVALID_SOCKET, &overlapped, + &bytes_returned, FALSE)) { + ::CloseHandle(event); + return ::WSAGetLastError(); + } + } + + ::CloseHandle(event); + + if (ret == NO_ERROR || ret == WSA_IO_PENDING) { + *res = reinterpret_cast<struct addrinfo *>(result_addrinfo); + return 0; + } + + return ret; +#elif defined(TARGET_OS_OSX) + // macOS implementation using CFHost API for asynchronous DNS resolution + CFStringRef hostname_ref = CFStringCreateWithCString( + kCFAllocatorDefault, node, kCFStringEncodingUTF8); + if (!hostname_ref) { return EAI_MEMORY; } + + CFHostRef host_ref = CFHostCreateWithName(kCFAllocatorDefault, hostname_ref); + CFRelease(hostname_ref); + if (!host_ref) { return EAI_MEMORY; } + + // Set up context for callback + struct CFHostContext { + bool completed = false; + bool success = false; + CFArrayRef addresses = nullptr; + std::mutex mutex; + std::condition_variable cv; + } context; + + CFHostClientContext client_context; + memset(&client_context, 0, sizeof(client_context)); + client_context.info = &context; + + // Set callback + auto callback = [](CFHostRef theHost, CFHostInfoType /*typeInfo*/, + const CFStreamError *error, void *info) { + auto ctx = static_cast<CFHostContext *>(info); + std::lock_guard<std::mutex> lock(ctx->mutex); + + if (error && error->error != 0) { + ctx->success = false; + } else { + Boolean hasBeenResolved; + ctx->addresses = CFHostGetAddressing(theHost, &hasBeenResolved); + if (ctx->addresses && hasBeenResolved) { + CFRetain(ctx->addresses); + ctx->success = true; + } else { + ctx->success = false; + } + } + ctx->completed = true; + ctx->cv.notify_one(); + }; + + if (!CFHostSetClient(host_ref, callback, &client_context)) { + CFRelease(host_ref); + return EAI_SYSTEM; + } + + // Schedule on run loop + CFRunLoopRef run_loop = CFRunLoopGetCurrent(); + CFHostScheduleWithRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode); + + // Start resolution + CFStreamError stream_error; + if (!CFHostStartInfoResolution(host_ref, kCFHostAddresses, &stream_error)) { + CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode); + CFRelease(host_ref); + return EAI_FAIL; + } + + // Wait for completion with timeout + auto timeout_time = + std::chrono::steady_clock::now() + std::chrono::seconds(timeout_sec); + bool timed_out = false; + + { + std::unique_lock<std::mutex> lock(context.mutex); + + while (!context.completed) { + auto now = std::chrono::steady_clock::now(); + if (now >= timeout_time) { + timed_out = true; + break; + } + + // Run the runloop for a short time + lock.unlock(); + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true); + lock.lock(); + } + } + + // Clean up + CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode); + CFHostSetClient(host_ref, nullptr, nullptr); + + if (timed_out || !context.completed) { + CFHostCancelInfoResolution(host_ref, kCFHostAddresses); + CFRelease(host_ref); + return EAI_AGAIN; + } + + if (!context.success || !context.addresses) { + CFRelease(host_ref); + return EAI_NODATA; + } + + // Convert CFArray to addrinfo + CFIndex count = CFArrayGetCount(context.addresses); + if (count == 0) { + CFRelease(context.addresses); + CFRelease(host_ref); + return EAI_NODATA; + } + + struct addrinfo *result_addrinfo = nullptr; + struct addrinfo **current = &result_addrinfo; + + for (CFIndex i = 0; i < count; i++) { + CFDataRef addr_data = + static_cast<CFDataRef>(CFArrayGetValueAtIndex(context.addresses, i)); + if (!addr_data) continue; + + const struct sockaddr *sockaddr_ptr = + reinterpret_cast<const struct sockaddr *>(CFDataGetBytePtr(addr_data)); + socklen_t sockaddr_len = static_cast<socklen_t>(CFDataGetLength(addr_data)); + + // Allocate addrinfo structure + *current = static_cast<struct addrinfo *>(malloc(sizeof(struct addrinfo))); + if (!*current) { + freeaddrinfo(result_addrinfo); + CFRelease(context.addresses); + CFRelease(host_ref); + return EAI_MEMORY; + } + + memset(*current, 0, sizeof(struct addrinfo)); + + // Set up addrinfo fields + (*current)->ai_family = sockaddr_ptr->sa_family; + (*current)->ai_socktype = hints ? hints->ai_socktype : SOCK_STREAM; + (*current)->ai_protocol = hints ? hints->ai_protocol : IPPROTO_TCP; + (*current)->ai_addrlen = sockaddr_len; + + // Copy sockaddr + (*current)->ai_addr = static_cast<struct sockaddr *>(malloc(sockaddr_len)); + if (!(*current)->ai_addr) { + freeaddrinfo(result_addrinfo); + CFRelease(context.addresses); + CFRelease(host_ref); + return EAI_MEMORY; + } + memcpy((*current)->ai_addr, sockaddr_ptr, sockaddr_len); + + // Set port if service is specified + if (service && strlen(service) > 0) { + int port = atoi(service); + if (port > 0) { + if (sockaddr_ptr->sa_family == AF_INET) { + reinterpret_cast<struct sockaddr_in *>((*current)->ai_addr) + ->sin_port = htons(static_cast<uint16_t>(port)); + } else if (sockaddr_ptr->sa_family == AF_INET6) { + reinterpret_cast<struct sockaddr_in6 *>((*current)->ai_addr) + ->sin6_port = htons(static_cast<uint16_t>(port)); + } + } + } + + current = &((*current)->ai_next); + } + + CFRelease(context.addresses); + CFRelease(host_ref); + + *res = result_addrinfo; + return 0; +#elif defined(_GNU_SOURCE) && defined(__GLIBC__) && \ + (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 2)) + // Linux implementation using getaddrinfo_a for asynchronous DNS resolution + struct gaicb request; + struct gaicb *requests[1] = {&request}; + struct sigevent sevp; + struct timespec timeout; + + // Initialize the request structure + memset(&request, 0, sizeof(request)); + request.ar_name = node; + request.ar_service = service; + request.ar_request = hints; + + // Set up timeout + timeout.tv_sec = timeout_sec; + timeout.tv_nsec = 0; + + // Initialize sigevent structure (not used, but required) + memset(&sevp, 0, sizeof(sevp)); + sevp.sigev_notify = SIGEV_NONE; + + // Start asynchronous resolution + int start_result = getaddrinfo_a(GAI_NOWAIT, requests, 1, &sevp); + if (start_result != 0) { return start_result; } + + // Wait for completion with timeout + int wait_result = + gai_suspend((const struct gaicb *const *)requests, 1, &timeout); + + if (wait_result == 0) { + // Completed successfully, get the result + int gai_result = gai_error(&request); + if (gai_result == 0) { + *res = request.ar_result; + return 0; + } else { + // Clean up on error + if (request.ar_result) { freeaddrinfo(request.ar_result); } + return gai_result; + } + } else if (wait_result == EAI_AGAIN) { + // Timeout occurred, cancel the request + gai_cancel(&request); + return EAI_AGAIN; + } else { + // Other error occurred + gai_cancel(&request); + return wait_result; + } +#else + // Fallback implementation using thread-based timeout for other Unix systems + std::mutex result_mutex; + std::condition_variable result_cv; + auto completed = false; + auto result = EAI_SYSTEM; + struct addrinfo *result_addrinfo = nullptr; + + std::thread resolve_thread([&]() { + auto thread_result = getaddrinfo(node, service, hints, &result_addrinfo); + + std::lock_guard<std::mutex> lock(result_mutex); + result = thread_result; + completed = true; + result_cv.notify_one(); + }); + + // Wait for completion or timeout + std::unique_lock<std::mutex> lock(result_mutex); + auto finished = result_cv.wait_for(lock, std::chrono::seconds(timeout_sec), + [&] { return completed; }); + + if (finished) { + // Operation completed within timeout + resolve_thread.join(); + *res = result_addrinfo; + return result; + } else { + // Timeout occurred + resolve_thread.detach(); // Let the thread finish in background + return EAI_AGAIN; // Return timeout error + } +#endif +#else + (void)(timeout_sec); // Unused parameter for non-blocking getaddrinfo + return getaddrinfo(node, service, hints, res); +#endif +} + template <typename BindOrConnect> socket_t create_socket(const std::string &host, const std::string &ip, int port, int address_family, int socket_flags, bool tcp_nodelay, bool ipv6_v6only, SocketOptions socket_options, - BindOrConnect bind_or_connect) { + BindOrConnect bind_or_connect, time_t timeout_sec = 0) { // Get address info const char *node = nullptr; struct addrinfo hints; @@ -3507,7 +3852,7 @@ hints.ai_flags = socket_flags; } -#ifndef _WIN32 +#if !defined(_WIN32) || defined(CPPHTTPLIB_HAVE_AFUNIX_H) if (hints.ai_family == AF_UNIX) { const auto addrlen = host.length(); if (addrlen > sizeof(sockaddr_un::sun_path)) { return INVALID_SOCKET; } @@ -3531,11 +3876,19 @@ sizeof(addr) - sizeof(addr.sun_path) + addrlen); #ifndef SOCK_CLOEXEC +#ifndef _WIN32 fcntl(sock, F_SETFD, FD_CLOEXEC); #endif +#endif if (socket_options) { socket_options(sock); } +#ifdef _WIN32 + // Setting SO_REUSEADDR seems not to work well with AF_UNIX on windows, so + // remove the option. + detail::set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 0); +#endif + bool dummy; if (!bind_or_connect(sock, hints, dummy)) { close_socket(sock); @@ -3548,7 +3901,8 @@ auto service = std::to_string(port); - if (getaddrinfo(node, service.c_str(), &hints, &result)) { + if (getaddrinfo_with_timeout(node, service.c_str(), &hints, &result, + timeout_sec)) { #if defined __linux__ && !defined __ANDROID__ res_init(); #endif @@ -3646,7 +4000,10 @@ hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; - if (getaddrinfo(host.c_str(), "0", &hints, &result)) { return false; } + if (getaddrinfo_with_timeout(host.c_str(), "0", &hints, &result, 0)) { + return false; + } + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); auto ret = false; @@ -3751,7 +4108,8 @@ error = Error::Success; return true; - }); + }, + connection_timeout_sec); // Pass DNS timeout if (sock != INVALID_SOCKET) { error = Error::Success; @@ -3807,7 +4165,7 @@ if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) { port = ucred.pid; } -#elif defined(SOL_LOCAL) && defined(SO_PEERPID) // __APPLE__ +#elif defined(SOL_LOCAL) && defined(SO_PEERPID) pid_t pid; socklen_t len = sizeof(pid); if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) { @@ -3950,6 +4308,12 @@ if (ret) { return EncodingType::Gzip; } #endif +#ifdef CPPHTTPLIB_ZSTD_SUPPORT + // TODO: 'Accept-Encoding' has zstd, not zstd;q=0 + ret = s.find("zstd") != std::string::npos; + if (ret) { return EncodingType::Zstd; } +#endif + return EncodingType::None; } @@ -4158,6 +4522,61 @@ } #endif +#ifdef CPPHTTPLIB_ZSTD_SUPPORT +inline zstd_compressor::zstd_compressor() { + ctx_ = ZSTD_createCCtx(); + ZSTD_CCtx_setParameter(ctx_, ZSTD_c_compressionLevel, ZSTD_fast); +} + +inline zstd_compressor::~zstd_compressor() { ZSTD_freeCCtx(ctx_); } + +inline bool zstd_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{}; + + ZSTD_EndDirective mode = last ? ZSTD_e_end : ZSTD_e_continue; + ZSTD_inBuffer input = {data, data_length, 0}; + + bool finished; + do { + ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0}; + size_t const remaining = ZSTD_compressStream2(ctx_, &output, &input, mode); + + if (ZSTD_isError(remaining)) { return false; } + + if (!callback(buff.data(), output.pos)) { return false; } + + finished = last ? (remaining == 0) : (input.pos == input.size); + + } while (!finished); + + return true; +} + +inline zstd_decompressor::zstd_decompressor() { ctx_ = ZSTD_createDCtx(); } + +inline zstd_decompressor::~zstd_decompressor() { ZSTD_freeDCtx(ctx_); } + +inline bool zstd_decompressor::is_valid() const { return ctx_ != nullptr; } + +inline bool zstd_decompressor::decompress(const char *data, size_t data_length, + Callback callback) { + std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{}; + ZSTD_inBuffer input = {data, data_length, 0}; + + while (input.pos < input.size) { + ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0}; + size_t const remaining = ZSTD_decompressStream(ctx_, &output, &input); + + if (ZSTD_isError(remaining)) { return false; } + + if (!callback(buff.data(), output.pos)) { return false; } + } + + return true; +} +#endif + inline bool has_header(const Headers &headers, const std::string &key) { return headers.find(key) != headers.end(); } @@ -4184,6 +4603,9 @@ p++; } + auto name = std::string(beg, p); + if (!detail::fields::is_field_name(name)) { return false; } + if (p == end) { return false; } auto key_end = p; @@ -4207,7 +4629,7 @@ case_ignore::equal(key, "Referer")) { fn(key, val); } else { - fn(key, decode_url(val, false)); + fn(key, decode_path_component(val)); } return true; @@ -4221,6 +4643,8 @@ char buf[bufsiz]; stream_line_reader line_reader(strm, buf, bufsiz); + size_t header_count = 0; + for (;;) { if (!line_reader.getline()) { return false; } @@ -4241,6 +4665,9 @@ if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + // Check header count limit + if (header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; } + // Exclude line terminator auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; @@ -4250,24 +4677,26 @@ })) { return false; } + + header_count++; } return true; } -inline bool read_content_with_length(Stream &strm, uint64_t len, - Progress progress, +inline bool read_content_with_length(Stream &strm, size_t len, + DownloadProgress progress, ContentReceiverWithProgress out) { char buf[CPPHTTPLIB_RECV_BUFSIZ]; - uint64_t r = 0; + size_t r = 0; while (r < len) { auto read_len = static_cast<size_t>(len - r); auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); if (n <= 0) { return false; } if (!out(buf, static_cast<size_t>(n), r, len)) { return false; } - r += static_cast<uint64_t>(n); + r += static_cast<size_t>(n); if (progress) { if (!progress(r, len)) { return false; } @@ -4277,62 +4706,90 @@ return true; } -inline void skip_content_with_length(Stream &strm, uint64_t len) { +inline void skip_content_with_length(Stream &strm, size_t len) { char buf[CPPHTTPLIB_RECV_BUFSIZ]; - uint64_t r = 0; + size_t r = 0; while (r < len) { auto read_len = static_cast<size_t>(len - r); auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); if (n <= 0) { return; } - r += static_cast<uint64_t>(n); + r += static_cast<size_t>(n); } } -inline bool read_content_without_length(Stream &strm, - ContentReceiverWithProgress out) { +enum class ReadContentResult { + Success, // Successfully read the content + PayloadTooLarge, // The content exceeds the specified payload limit + Error // An error occurred while reading the content +}; + +inline ReadContentResult +read_content_without_length(Stream &strm, size_t payload_max_length, + ContentReceiverWithProgress out) { char buf[CPPHTTPLIB_RECV_BUFSIZ]; - uint64_t r = 0; + size_t r = 0; for (;;) { auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); - if (n <= 0) { return false; } + if (n == 0) { return ReadContentResult::Success; } + if (n < 0) { return ReadContentResult::Error; } - if (!out(buf, static_cast<size_t>(n), r, 0)) { return false; } - r += static_cast<uint64_t>(n); + // Check if adding this data would exceed the payload limit + if (r > payload_max_length || + payload_max_length - r < static_cast<size_t>(n)) { + return ReadContentResult::PayloadTooLarge; + } + + if (!out(buf, static_cast<size_t>(n), r, 0)) { + return ReadContentResult::Error; + } + r += static_cast<size_t>(n); } - return true; + return ReadContentResult::Success; } template <typename T> -inline bool read_content_chunked(Stream &strm, T &x, - ContentReceiverWithProgress out) { +inline ReadContentResult read_content_chunked(Stream &strm, T &x, + size_t payload_max_length, + ContentReceiverWithProgress out) { const auto bufsiz = 16; char buf[bufsiz]; stream_line_reader line_reader(strm, buf, bufsiz); - if (!line_reader.getline()) { return false; } + if (!line_reader.getline()) { return ReadContentResult::Error; } unsigned long chunk_len; + size_t total_len = 0; while (true) { char *end_ptr; chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); - if (end_ptr == line_reader.ptr()) { return false; } - if (chunk_len == ULONG_MAX) { return false; } + if (end_ptr == line_reader.ptr()) { return ReadContentResult::Error; } + if (chunk_len == ULONG_MAX) { return ReadContentResult::Error; } if (chunk_len == 0) { break; } - if (!read_content_with_length(strm, chunk_len, nullptr, out)) { - return false; + // Check if adding this chunk would exceed the payload limit + if (total_len > payload_max_length || + payload_max_length - total_len < chunk_len) { + return ReadContentResult::PayloadTooLarge; } - if (!line_reader.getline()) { return false; } + total_len += chunk_len; - if (strcmp(line_reader.ptr(), "\r\n") != 0) { return false; } + if (!read_content_with_length(strm, chunk_len, nullptr, out)) { + return ReadContentResult::Error; + } - if (!line_reader.getline()) { return false; } + if (!line_reader.getline()) { return ReadContentResult::Error; } + + if (strcmp(line_reader.ptr(), "\r\n") != 0) { + return ReadContentResult::Error; + } + + if (!line_reader.getline()) { return ReadContentResult::Error; } } assert(chunk_len == 0); @@ -4349,10 +4806,54 @@ // // According to the reference code in RFC 9112, cpp-httplib now allows // chunked transfer coding data without the final CRLF. - if (!line_reader.getline()) { return true; } + if (!line_reader.getline()) { return ReadContentResult::Success; } + // RFC 7230 Section 4.1.2 - Headers prohibited in trailers + thread_local case_ignore::unordered_set<std::string> prohibited_trailers = { + // Message framing + "transfer-encoding", "content-length", + + // Routing + "host", + + // Authentication + "authorization", "www-authenticate", "proxy-authenticate", + "proxy-authorization", "cookie", "set-cookie", + + // Request modifiers + "cache-control", "expect", "max-forwards", "pragma", "range", "te", + + // Response control + "age", "expires", "date", "location", "retry-after", "vary", "warning", + + // Payload processing + "content-encoding", "content-type", "content-range", "trailer"}; + + // Parse declared trailer headers once for performance + case_ignore::unordered_set<std::string> declared_trailers; + if (has_header(x.headers, "Trailer")) { + auto trailer_header = get_header_value(x.headers, "Trailer", "", 0); + auto len = std::strlen(trailer_header); + + split(trailer_header, trailer_header + len, ',', + [&](const char *b, const char *e) { + std::string key(b, e); + if (prohibited_trailers.find(key) == prohibited_trailers.end()) { + declared_trailers.insert(key); + } + }); + } + + size_t trailer_header_count = 0; while (strcmp(line_reader.ptr(), "\r\n") != 0) { - if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { + return ReadContentResult::Error; + } + + // Check trailer header count limit + if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { + return ReadContentResult::Error; + } // Exclude line terminator constexpr auto line_terminator_len = 2; @@ -4360,13 +4861,16 @@ parse_header(line_reader.ptr(), end, [&](const std::string &key, const std::string &val) { - x.headers.emplace(key, val); + if (declared_trailers.find(key) != declared_trailers.end()) { + x.trailers.emplace(key, val); + trailer_header_count++; + } }); - if (!line_reader.getline()) { return false; } + if (!line_reader.getline()) { return ReadContentResult::Error; } } - return true; + return ReadContentResult::Success; } inline bool is_chunked_transfer_encoding(const Headers &headers) { @@ -4396,12 +4900,19 @@ status = StatusCode::UnsupportedMediaType_415; return false; #endif + } else if (encoding == "zstd") { +#ifdef CPPHTTPLIB_ZSTD_SUPPORT + decompressor = detail::make_unique<zstd_decompressor>(); +#else + status = StatusCode::UnsupportedMediaType_415; + return false; +#endif } if (decompressor) { if (decompressor->is_valid()) { ContentReceiverWithProgress out = [&](const char *buf, size_t n, - uint64_t off, uint64_t len) { + size_t off, size_t len) { return decompressor->decompress(buf, n, [&](const char *buf2, size_t n2) { return receiver(buf2, n2, off, len); @@ -4415,8 +4926,8 @@ } } - ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, - uint64_t len) { + ContentReceiverWithProgress out = [&](const char *buf, size_t n, size_t off, + size_t len) { return receiver(buf, n, off, len); }; return callback(std::move(out)); @@ -4424,8 +4935,8 @@ template <typename T> bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, - Progress progress, ContentReceiverWithProgress receiver, - bool decompress) { + DownloadProgress progress, + ContentReceiverWithProgress receiver, bool decompress) { return prepare_content_receiver( x, status, std::move(receiver), decompress, [&](const ContentReceiverWithProgress &out) { @@ -4433,14 +4944,31 @@ auto exceed_payload_max_length = false; if (is_chunked_transfer_encoding(x.headers)) { - ret = read_content_chunked(strm, x, out); + auto result = read_content_chunked(strm, x, payload_max_length, out); + if (result == ReadContentResult::Success) { + ret = true; + } else if (result == ReadContentResult::PayloadTooLarge) { + exceed_payload_max_length = true; + ret = false; + } else { + ret = false; + } } else if (!has_header(x.headers, "Content-Length")) { - ret = read_content_without_length(strm, out); + auto result = + read_content_without_length(strm, payload_max_length, out); + if (result == ReadContentResult::Success) { + ret = true; + } else if (result == ReadContentResult::PayloadTooLarge) { + exceed_payload_max_length = true; + ret = false; + } else { + ret = false; + } } else { auto is_invalid_value = false; - auto len = get_header_value_u64( - x.headers, "Content-Length", - (std::numeric_limits<uint64_t>::max)(), 0, is_invalid_value); + auto len = get_header_value_u64(x.headers, "Content-Length", + (std::numeric_limits<size_t>::max)(), + 0, is_invalid_value); if (is_invalid_value) { ret = false; @@ -4509,10 +5037,14 @@ } template <typename T> -inline bool write_content(Stream &strm, const ContentProvider &content_provider, - size_t offset, size_t length, T is_shutting_down, - Error &error) { +inline bool write_content_with_progress(Stream &strm, + const ContentProvider &content_provider, + size_t offset, size_t length, + T is_shutting_down, + const UploadProgress &upload_progress, + Error &error) { size_t end_offset = offset + length; + size_t start_offset = offset; auto ok = true; DataSink data_sink; @@ -4520,6 +5052,14 @@ if (ok) { if (write_data(strm, d, l)) { offset += l; + + if (upload_progress && length > 0) { + size_t current_written = offset - start_offset; + if (!upload_progress(current_written, length)) { + ok = false; + return false; + } + } } else { ok = false; } @@ -4548,6 +5088,14 @@ template <typename T> inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, T is_shutting_down, + Error &error) { + return write_content_with_progress<T>(strm, content_provider, offset, length, + is_shutting_down, nullptr, error); +} + +template <typename T> +inline bool write_content(Stream &strm, const ContentProvider &content_provider, size_t offset, size_t length, const T &is_shutting_down) { auto error = Error::Success; @@ -4648,10 +5196,8 @@ } } - static const std::string done_marker("0\r\n"); - if (!write_data(strm, done_marker.data(), done_marker.size())) { - ok = false; - } + constexpr const char done_marker[] = "0\r\n"; + if (!write_data(strm, done_marker, str_len(done_marker))) { ok = false; } // Trailer if (trailer) { @@ -4663,8 +5209,8 @@ } } - static const std::string crlf("\r\n"); - if (!write_data(strm, crlf.data(), crlf.size())) { ok = false; } + constexpr const char crlf[] = "\r\n"; + if (!write_data(strm, crlf, str_len(crlf))) { ok = false; } }; data_sink.done = [&](void) { done_with_trailer(nullptr); }; @@ -4731,9 +5277,9 @@ for (auto it = params.begin(); it != params.end(); ++it) { if (it != params.begin()) { query += "&"; } - query += it->first; + query += encode_query_component(it->first); query += "="; - query += encode_query_param(it->second); + query += encode_query_component(it->second); } return query; } @@ -4756,7 +5302,7 @@ }); if (!key.empty()) { - params.emplace(decode_url(key, true), decode_url(val, true)); + params.emplace(decode_query_component(key), decode_query_component(val)); } }); } @@ -4851,9 +5397,139 @@ } catch (...) { return false; } #endif -class MultipartFormDataParser { +inline bool parse_accept_header(const std::string &s, + std::vector<std::string> &content_types) { + content_types.clear(); + + // Empty string is considered valid (no preference) + if (s.empty()) { return true; } + + // Check for invalid patterns: leading/trailing commas or consecutive commas + if (s.front() == ',' || s.back() == ',' || + s.find(",,") != std::string::npos) { + return false; + } + + struct AcceptEntry { + std::string media_type; + double quality; + int order; // Original order in header + }; + + std::vector<AcceptEntry> entries; + int order = 0; + bool has_invalid_entry = false; + + // Split by comma and parse each entry + split(s.data(), s.data() + s.size(), ',', [&](const char *b, const char *e) { + std::string entry(b, e); + entry = trim_copy(entry); + + if (entry.empty()) { + has_invalid_entry = true; + return; + } + + AcceptEntry accept_entry; + accept_entry.quality = 1.0; // Default quality + accept_entry.order = order++; + + // Find q= parameter + auto q_pos = entry.find(";q="); + if (q_pos == std::string::npos) { q_pos = entry.find("; q="); } + + if (q_pos != std::string::npos) { + // Extract media type (before q parameter) + accept_entry.media_type = trim_copy(entry.substr(0, q_pos)); + + // Extract quality value + auto q_start = entry.find('=', q_pos) + 1; + auto q_end = entry.find(';', q_start); + if (q_end == std::string::npos) { q_end = entry.length(); } + + std::string quality_str = + trim_copy(entry.substr(q_start, q_end - q_start)); + if (quality_str.empty()) { + has_invalid_entry = true; + return; + } + +#ifdef CPPHTTPLIB_NO_EXCEPTIONS + { + std::istringstream iss(quality_str); + iss >> accept_entry.quality; + + // Check if conversion was successful and entire string was consumed + if (iss.fail() || !iss.eof()) { + has_invalid_entry = true; + return; + } + } +#else + try { + accept_entry.quality = std::stod(quality_str); + } catch (...) { + has_invalid_entry = true; + return; + } +#endif + // Check if quality is in valid range [0.0, 1.0] + if (accept_entry.quality < 0.0 || accept_entry.quality > 1.0) { + has_invalid_entry = true; + return; + } + } else { + // No quality parameter, use entire entry as media type + accept_entry.media_type = entry; + } + + // Remove additional parameters from media type + auto param_pos = accept_entry.media_type.find(';'); + if (param_pos != std::string::npos) { + accept_entry.media_type = + trim_copy(accept_entry.media_type.substr(0, param_pos)); + } + + // Basic validation of media type format + if (accept_entry.media_type.empty()) { + has_invalid_entry = true; + return; + } + + // Check for basic media type format (should contain '/' or be '*') + if (accept_entry.media_type != "*" && + accept_entry.media_type.find('/') == std::string::npos) { + has_invalid_entry = true; + return; + } + + entries.push_back(accept_entry); + }); + + // Return false if any invalid entry was found + if (has_invalid_entry) { return false; } + + // Sort by quality (descending), then by original order (ascending) + std::sort(entries.begin(), entries.end(), + [](const AcceptEntry &a, const AcceptEntry &b) { + if (a.quality != b.quality) { + return a.quality > b.quality; // Higher quality first + } + return a.order < b.order; // Earlier order first for same quality + }); + + // Extract sorted media types + content_types.reserve(entries.size()); + for (const auto &entry : entries) { + content_types.push_back(entry.media_type); + } + + return true; +} + +class FormDataParser { public: - MultipartFormDataParser() = default; + FormDataParser() = default; void set_boundary(std::string &&boundary) { boundary_ = boundary; @@ -4863,18 +5539,17 @@ bool is_valid() const { return is_valid_; } - bool parse(const char *buf, size_t n, const ContentReceiver &content_callback, - const MultipartContentHeader &header_callback) { + bool parse(const char *buf, size_t n, const FormDataHeader &header_callback, + const ContentReceiver &content_callback) { buf_append(buf, n); while (buf_size() > 0) { switch (state_) { case 0: { // Initial boundary - buf_erase(buf_find(dash_boundary_crlf_)); - if (dash_boundary_crlf_.size() > buf_size()) { return true; } - if (!buf_start_with(dash_boundary_crlf_)) { return false; } - buf_erase(dash_boundary_crlf_.size()); + auto pos = buf_find(dash_boundary_crlf_); + if (pos == buf_size()) { return true; } + buf_erase(pos + dash_boundary_crlf_.size()); state_ = 1; break; } @@ -4906,13 +5581,23 @@ return false; } - static const std::string header_content_type = "Content-Type:"; + // Parse and emplace space trimmed headers into a map + if (!parse_header( + header.data(), header.data() + header.size(), + [&](const std::string &key, const std::string &val) { + file_.headers.emplace(key, val); + })) { + is_valid_ = false; + return false; + } + + constexpr const char header_content_type[] = "Content-Type:"; if (start_with_case_ignore(header, header_content_type)) { file_.content_type = - trim_copy(header.substr(header_content_type.size())); + trim_copy(header.substr(str_len(header_content_type))); } else { - static const std::regex re_content_disposition( + thread_local const std::regex re_content_disposition( R"~(^Content-Disposition:\s*form-data;\s*(.*)$)~", std::regex_constants::icase); @@ -4935,12 +5620,12 @@ it = params.find("filename*"); if (it != params.end()) { // Only allow UTF-8 encoding... - static const std::regex re_rfc5987_encoding( + thread_local const std::regex re_rfc5987_encoding( R"~(^UTF-8''(.+?)$)~", std::regex_constants::icase); std::smatch m2; if (std::regex_match(it->second, m2, re_rfc5987_encoding)) { - file_.filename = decode_url(m2[1], false); // override... + file_.filename = decode_path_component(m2[1]); // override... } else { is_valid_ = false; return false; @@ -5005,12 +5690,13 @@ file_.name.clear(); file_.filename.clear(); file_.content_type.clear(); + file_.headers.clear(); } - bool start_with_case_ignore(const std::string &a, - const std::string &b) const { - if (a.size() < b.size()) { return false; } - for (size_t i = 0; i < b.size(); i++) { + bool start_with_case_ignore(const std::string &a, const char *b) const { + const auto b_len = strlen(b); + if (a.size() < b_len) { return false; } + for (size_t i = 0; i < b_len; i++) { if (case_ignore::to_lower(a[i]) != case_ignore::to_lower(b[i])) { return false; } @@ -5026,7 +5712,7 @@ size_t state_ = 0; bool is_valid_ = false; - MultipartFormData file_; + FormData file_; // Buffer bool start_with(const std::string &a, size_t spos, size_t epos, @@ -5097,19 +5783,18 @@ }; inline std::string random_string(size_t length) { - static const char data[] = + constexpr const char data[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - // std::random_device might actually be deterministic on some - // platforms, but due to lack of support in the c++ standard library, - // doing better requires either some ugly hacks or breaking portability. - static std::random_device seed_gen; - - // Request 128 bits of entropy for initialization - static std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), - seed_gen()}; - - static std::mt19937 engine(seed_sequence); + thread_local auto engine([]() { + // std::random_device might actually be deterministic on some + // platforms, but due to lack of support in the c++ standard library, + // doing better requires either some ugly hacks or breaking portability. + std::random_device seed_gen; + // Request 128 bits of entropy for initialization + std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()}; + return std::mt19937(seed_sequence); + }()); std::string result; for (size_t i = 0; i < length; i++) { @@ -5165,7 +5850,7 @@ } inline std::string -serialize_multipart_formdata(const MultipartFormDataItems &items, +serialize_multipart_formdata(const UploadFormDataItems &items, const std::string &boundary, bool finish = true) { std::string body; @@ -5179,13 +5864,68 @@ return body; } +inline void coalesce_ranges(Ranges &ranges, size_t content_length) { + if (ranges.size() <= 1) return; + + // Sort ranges by start position + std::sort(ranges.begin(), ranges.end(), + [](const Range &a, const Range &b) { return a.first < b.first; }); + + Ranges coalesced; + coalesced.reserve(ranges.size()); + + for (auto &r : ranges) { + auto first_pos = r.first; + auto last_pos = r.second; + + // Handle special cases like in range_error + if (first_pos == -1 && last_pos == -1) { + first_pos = 0; + last_pos = static_cast<ssize_t>(content_length); + } + + if (first_pos == -1) { + first_pos = static_cast<ssize_t>(content_length) - last_pos; + last_pos = static_cast<ssize_t>(content_length) - 1; + } + + if (last_pos == -1 || last_pos >= static_cast<ssize_t>(content_length)) { + last_pos = static_cast<ssize_t>(content_length) - 1; + } + + // Skip invalid ranges + if (!(0 <= first_pos && first_pos <= last_pos && + last_pos < static_cast<ssize_t>(content_length))) { + continue; + } + + // Coalesce with previous range if overlapping or adjacent (but not + // identical) + if (!coalesced.empty()) { + auto &prev = coalesced.back(); + // Check if current range overlaps or is adjacent to previous range + // but don't coalesce identical ranges (allow duplicates) + if (first_pos <= prev.second + 1 && + !(first_pos == prev.first && last_pos == prev.second)) { + // Extend the previous range + prev.second = (std::max)(prev.second, last_pos); + continue; + } + } + + // Add new range + coalesced.emplace_back(first_pos, last_pos); + } + + ranges = std::move(coalesced); +} + inline bool range_error(Request &req, Response &res) { if (!req.ranges.empty() && 200 <= res.status && res.status < 300) { ssize_t content_len = static_cast<ssize_t>( res.content_length_ ? res.content_length_ : res.body.size()); - ssize_t prev_first_pos = -1; - ssize_t prev_last_pos = -1; + std::vector<std::pair<ssize_t, ssize_t>> processed_ranges; size_t overwrapping_count = 0; // NOTE: The following Range check is based on '14.2. Range' in RFC 9110 @@ -5228,18 +5968,21 @@ return true; } - // Ranges must be in ascending order - if (first_pos <= prev_first_pos) { return true; } - // Request must not have more than two overlapping ranges - if (first_pos <= prev_last_pos) { - overwrapping_count++; - if (overwrapping_count > 2) { return true; } + for (const auto &processed_range : processed_ranges) { + if (!(last_pos < processed_range.first || + first_pos > processed_range.second)) { + overwrapping_count++; + if (overwrapping_count > 2) { return true; } + break; // Only count once per range + } } - prev_first_pos = (std::max)(prev_first_pos, first_pos); - prev_last_pos = (std::max)(prev_last_pos, last_pos); + processed_ranges.emplace_back(first_pos, last_pos); } + + // After validation, coalesce overlapping ranges as per RFC 9110 + coalesce_ranges(req.ranges, static_cast<size_t>(content_len)); } return false; @@ -5507,8 +6250,8 @@ return result; } -#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) -#if TARGET_OS_OSX +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && \ + defined(TARGET_OS_OSX) template <typename T> using CFObjectPtr = std::unique_ptr<typename std::remove_pointer<T>::type, void (*)(CFTypeRef)>; @@ -5596,7 +6339,6 @@ return result; } -#endif // TARGET_OS_OSX #endif // _WIN32 #endif // CPPHTTPLIB_OPENSSL_SUPPORT @@ -5623,7 +6365,8 @@ bool is_proxy) { auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; if (res.has_header(auth_key)) { - static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); + thread_local auto re = + std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); auto s = res.get_header_value(auth_key); auto pos = s.find(' '); if (pos != std::string::npos) { @@ -5684,7 +6427,8 @@ hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; - if (getaddrinfo(hostname.c_str(), nullptr, &hints, &result)) { + if (detail::getaddrinfo_with_timeout(hostname.c_str(), nullptr, &hints, + &result, 0)) { #if defined __linux__ && !defined __ANDROID__ res_init(); #endif @@ -5704,10 +6448,243 @@ } } +inline std::string encode_uri_component(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')') { + escaped << c; + } else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast<int>(static_cast<unsigned char>(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); +} + +inline std::string encode_uri(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')' || c == ';' || c == '/' || c == '?' || c == ':' || c == '@' || + c == '&' || c == '=' || c == '+' || c == '$' || c == ',' || c == '#') { + escaped << c; + } else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast<int>(static_cast<unsigned char>(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); +} + +inline std::string decode_uri_component(const std::string &value) { + std::string result; + + for (size_t i = 0; i < value.size(); i++) { + if (value[i] == '%' && i + 2 < value.size()) { + auto val = 0; + if (detail::from_hex_to_i(value, i + 1, 2, val)) { + result += static_cast<char>(val); + i += 2; + } else { + result += value[i]; + } + } else { + result += value[i]; + } + } + + return result; +} + +inline std::string decode_uri(const std::string &value) { + std::string result; + + for (size_t i = 0; i < value.size(); i++) { + if (value[i] == '%' && i + 2 < value.size()) { + auto val = 0; + if (detail::from_hex_to_i(value, i + 1, 2, val)) { + result += static_cast<char>(val); + i += 2; + } else { + result += value[i]; + } + } else { + result += value[i]; + } + } + + return result; +} + +inline std::string encode_path_component(const std::string &component) { + std::string result; + result.reserve(component.size() * 3); + + for (size_t i = 0; i < component.size(); i++) { + auto c = static_cast<unsigned char>(component[i]); + + // Unreserved characters per RFC 3986: ALPHA / DIGIT / "-" / "." / "_" / "~" + if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') { + result += static_cast<char>(c); + } + // Path-safe sub-delimiters: "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / + // "," / ";" / "=" + else if (c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' || + c == ')' || c == '*' || c == '+' || c == ',' || c == ';' || + c == '=') { + result += static_cast<char>(c); + } + // Colon is allowed in path segments except first segment + else if (c == ':') { + result += static_cast<char>(c); + } + // @ is allowed in path + else if (c == '@') { + result += static_cast<char>(c); + } else { + result += '%'; + char hex[3]; + snprintf(hex, sizeof(hex), "%02X", c); + result.append(hex, 2); + } + } + return result; +} + +inline std::string decode_path_component(const std::string &component) { + std::string result; + result.reserve(component.size()); + + for (size_t i = 0; i < component.size(); i++) { + if (component[i] == '%' && i + 1 < component.size()) { + if (component[i + 1] == 'u') { + // Unicode %uXXXX encoding + auto val = 0; + if (detail::from_hex_to_i(component, i + 2, 4, val)) { + // 4 digits Unicode codes + char buff[4]; + size_t len = detail::to_utf8(val, buff); + if (len > 0) { result.append(buff, len); } + i += 5; // 'u0000' + } else { + result += component[i]; + } + } else { + // Standard %XX encoding + auto val = 0; + if (detail::from_hex_to_i(component, i + 1, 2, val)) { + // 2 digits hex codes + result += static_cast<char>(val); + i += 2; // 'XX' + } else { + result += component[i]; + } + } + } else { + result += component[i]; + } + } + return result; +} + +inline std::string encode_query_component(const std::string &component, + bool space_as_plus) { + std::string result; + result.reserve(component.size() * 3); + + for (size_t i = 0; i < component.size(); i++) { + auto c = static_cast<unsigned char>(component[i]); + + // Unreserved characters per RFC 3986 + if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') { + result += static_cast<char>(c); + } + // Space handling + else if (c == ' ') { + if (space_as_plus) { + result += '+'; + } else { + result += "%20"; + } + } + // Plus sign handling + else if (c == '+') { + if (space_as_plus) { + result += "%2B"; + } else { + result += static_cast<char>(c); + } + } + // Query-safe sub-delimiters (excluding & and = which are query delimiters) + else if (c == '!' || c == '$' || c == '\'' || c == '(' || c == ')' || + c == '*' || c == ',' || c == ';') { + result += static_cast<char>(c); + } + // Colon and @ are allowed in query + else if (c == ':' || c == '@') { + result += static_cast<char>(c); + } + // Forward slash is allowed in query values + else if (c == '/') { + result += static_cast<char>(c); + } + // Question mark is allowed in query values (after first ?) + else if (c == '?') { + result += static_cast<char>(c); + } else { + result += '%'; + char hex[3]; + snprintf(hex, sizeof(hex), "%02X", c); + result.append(hex, 2); + } + } + return result; +} + +inline std::string decode_query_component(const std::string &component, + bool plus_as_space) { + std::string result; + result.reserve(component.size()); + + for (size_t i = 0; i < component.size(); i++) { + if (component[i] == '%' && i + 2 < component.size()) { + std::string hex = component.substr(i + 1, 2); + char *end; + unsigned long value = std::strtoul(hex.c_str(), &end, 16); + if (end == hex.c_str() + 2) { + result += static_cast<char>(value); + i += 2; + } else { + result += component[i]; + } + } else if (component[i] == '+' && plus_as_space) { + result += ' '; // + becomes space in form-urlencoded + } else { + result += component[i]; + } + } + return result; +} + inline std::string append_query_params(const std::string &path, const Params ¶ms) { std::string path_with_query = path; - const static std::regex re("[^?]+\\?.*"); + thread_local const std::regex re("[^?]+\\?.*"); auto delm = std::regex_match(path, re) ? '&' : '?'; path_with_query += delm + detail::params_to_query_str(params); return path_with_query; @@ -5767,6 +6744,24 @@ } } +inline bool Request::has_trailer(const std::string &key) const { + return trailers.find(key) != trailers.end(); +} + +inline std::string Request::get_trailer_value(const std::string &key, + size_t id) const { + auto rng = trailers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast<ssize_t>(id)); + if (it != rng.second) { return it->second; } + return std::string(); +} + +inline size_t Request::get_trailer_value_count(const std::string &key) const { + auto r = trailers.equal_range(key); + return static_cast<size_t>(std::distance(r.first, r.second)); +} + inline bool Request::has_param(const std::string &key) const { return params.find(key) != params.end(); } @@ -5790,19 +6785,47 @@ return !content_type.rfind("multipart/form-data", 0); } -inline bool Request::has_file(const std::string &key) const { - return files.find(key) != files.end(); +// Multipart FormData implementation +inline std::string MultipartFormData::get_field(const std::string &key, + size_t id) const { + auto rng = fields.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast<ssize_t>(id)); + if (it != rng.second) { return it->second.content; } + return std::string(); } -inline MultipartFormData Request::get_file_value(const std::string &key) const { - auto it = files.find(key); - if (it != files.end()) { return it->second; } - return MultipartFormData(); +inline std::vector<std::string> +MultipartFormData::get_fields(const std::string &key) const { + std::vector<std::string> values; + auto rng = fields.equal_range(key); + for (auto it = rng.first; it != rng.second; it++) { + values.push_back(it->second.content); + } + return values; } -inline std::vector<MultipartFormData> -Request::get_file_values(const std::string &key) const { - std::vector<MultipartFormData> values; +inline bool MultipartFormData::has_field(const std::string &key) const { + return fields.find(key) != fields.end(); +} + +inline size_t MultipartFormData::get_field_count(const std::string &key) const { + auto r = fields.equal_range(key); + return static_cast<size_t>(std::distance(r.first, r.second)); +} + +inline FormData MultipartFormData::get_file(const std::string &key, + size_t id) const { + auto rng = files.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast<ssize_t>(id)); + if (it != rng.second) { return it->second; } + return FormData(); +} + +inline std::vector<FormData> +MultipartFormData::get_files(const std::string &key) const { + std::vector<FormData> values; auto rng = files.equal_range(key); for (auto it = rng.first; it != rng.second; it++) { values.push_back(it->second); @@ -5810,6 +6833,15 @@ return values; } +inline bool MultipartFormData::has_file(const std::string &key) const { + return files.find(key) != files.end(); +} + +inline size_t MultipartFormData::get_file_count(const std::string &key) const { + auto r = files.equal_range(key); + return static_cast<size_t>(std::distance(r.first, r.second)); +} + // Response implementation inline bool Response::has_header(const std::string &key) const { return headers.find(key) != headers.end(); @@ -5833,6 +6865,23 @@ headers.emplace(key, val); } } +inline bool Response::has_trailer(const std::string &key) const { + return trailers.find(key) != trailers.end(); +} + +inline std::string Response::get_trailer_value(const std::string &key, + size_t id) const { + auto rng = trailers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast<ssize_t>(id)); + if (it != rng.second) { return it->second; } + return std::string(); +} + +inline size_t Response::get_trailer_value_count(const std::string &key) const { + auto r = trailers.equal_range(key); + return static_cast<size_t>(std::distance(r.first, r.second)); +} inline void Response::set_redirect(const std::string &url, int stat) { if (detail::fields::is_field_value(url)) { @@ -5945,6 +6994,8 @@ auto actual_timeout_msec = (std::min)(max_timeout_msec - duration_msec, timeout_msec); + if (actual_timeout_msec < 0) { actual_timeout_msec = 0; } + actual_timeout_sec = actual_timeout_msec / 1000; actual_timeout_usec = (actual_timeout_msec % 1000) * 1000; } @@ -5959,7 +7010,7 @@ read_timeout_usec_(read_timeout_usec), write_timeout_sec_(write_timeout_sec), write_timeout_usec_(write_timeout_usec), - max_timeout_msec_(max_timeout_msec), start_time(start_time), + max_timeout_msec_(max_timeout_msec), start_time_(start_time), read_buff_(read_buff_size_, 0) {} inline SocketStream::~SocketStream() = default; @@ -6057,7 +7108,7 @@ inline time_t SocketStream::duration() const { return std::chrono::duration_cast<std::chrono::milliseconds>( - std::chrono::steady_clock::now() - start_time) + std::chrono::steady_clock::now() - start_time_) .count(); } @@ -6095,8 +7146,9 @@ inline const std::string &BufferStream::get_buffer() const { return buffer; } -inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) { - static constexpr char marker[] = "/:"; +inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) + : MatcherBase(pattern) { + constexpr const char marker[] = "/:"; // One past the last ending position of a path param substring std::size_t last_param_end = 0; @@ -6117,7 +7169,7 @@ static_fragments_.push_back( pattern.substr(last_param_end, marker_pos - last_param_end + 1)); - const auto param_name_start = marker_pos + 2; + const auto param_name_start = marker_pos + str_len(marker); auto sep_pos = pattern.find(separator, param_name_start); if (sep_pos == std::string::npos) { sep_pos = pattern.length(); } @@ -6346,11 +7398,26 @@ return *this; } +inline Server &Server::set_pre_request_handler(HandlerWithResponse handler) { + pre_request_handler_ = std::move(handler); + return *this; +} + inline Server &Server::set_logger(Logger logger) { logger_ = std::move(logger); return *this; } +inline Server &Server::set_error_logger(ErrorLogger error_logger) { + error_logger_ = std::move(error_logger); + return *this; +} + +inline Server &Server::set_pre_compression_logger(Logger logger) { + pre_compression_logger_ = std::move(logger); + return *this; +} + inline Server & Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { expect_100_continue_handler_ = std::move(handler); @@ -6481,13 +7548,19 @@ if (count != 3) { return false; } } - static const std::set<std::string> methods{ + thread_local const std::set<std::string> methods{ "GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"}; - if (methods.find(req.method) == methods.end()) { return false; } + if (methods.find(req.method) == methods.end()) { + output_error_log(Error::InvalidHTTPMethod, &req); + return false; + } - if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { return false; } + if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { + output_error_log(Error::InvalidHTTPVersion, &req); + return false; + } { // Skip URL fragment @@ -6501,8 +7574,8 @@ detail::divide(req.target, '?', [&](const char *lhs_data, std::size_t lhs_size, const char *rhs_data, std::size_t rhs_size) { - req.path = detail::decode_url( - std::string(lhs_data, lhs_size), false); + req.path = + decode_path_component(std::string(lhs_data, lhs_size)); detail::parse_query_text(rhs_data, rhs_size, req.params); }); } @@ -6594,7 +7667,7 @@ } // Log - if (logger_) { logger_(req, res); } + output_log(req, res); return ret; } @@ -6636,6 +7709,10 @@ #ifdef CPPHTTPLIB_BROTLI_SUPPORT compressor = detail::make_unique<detail::brotli_compressor>(); #endif + } else if (type == detail::EncodingType::Zstd) { +#ifdef CPPHTTPLIB_ZSTD_SUPPORT + compressor = detail::make_unique<detail::zstd_compressor>(); +#endif } else { compressor = detail::make_unique<detail::nocompressor>(); } @@ -6651,8 +7728,10 @@ } inline bool Server::read_content(Stream &strm, Request &req, Response &res) { - MultipartFormDataMap::iterator cur; - auto file_count = 0; + FormFields::iterator cur_field; + FormFiles::iterator cur_file; + auto is_text_field = false; + size_t count = 0; if (read_content_core( strm, req, res, // Regular @@ -6661,24 +7740,40 @@ req.body.append(buf, n); return true; }, - // Multipart - [&](const MultipartFormData &file) { - if (file_count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) { + // Multipart FormData + [&](const FormData &file) { + if (count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) { + output_error_log(Error::TooManyFormDataFiles, &req); return false; } - cur = req.files.emplace(file.name, file); + + if (file.filename.empty()) { + cur_field = req.form.fields.emplace( + file.name, FormField{file.name, file.content, file.headers}); + is_text_field = true; + } else { + cur_file = req.form.files.emplace(file.name, file); + is_text_field = false; + } return true; }, [&](const char *buf, size_t n) { - auto &content = cur->second.content; - if (content.size() + n > content.max_size()) { return false; } - content.append(buf, n); + if (is_text_field) { + auto &content = cur_field->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + } else { + auto &content = cur_file->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + } return true; })) { const auto &content_type = req.get_header_value("Content-Type"); if (!content_type.find("application/x-www-form-urlencoded")) { if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) { res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414? + output_error_log(Error::ExceedMaxPayloadSize, &req); return false; } detail::parse_query_text(req.body, req.params); @@ -6690,19 +7785,16 @@ inline bool Server::read_content_with_content_receiver( Stream &strm, Request &req, Response &res, ContentReceiver receiver, - MultipartContentHeader multipart_header, - ContentReceiver multipart_receiver) { + FormDataHeader multipart_header, ContentReceiver multipart_receiver) { return read_content_core(strm, req, res, std::move(receiver), std::move(multipart_header), std::move(multipart_receiver)); } -inline bool -Server::read_content_core(Stream &strm, Request &req, Response &res, - ContentReceiver receiver, - MultipartContentHeader multipart_header, - ContentReceiver multipart_receiver) const { - detail::MultipartFormDataParser multipart_form_data_parser; +inline bool Server::read_content_core( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + FormDataHeader multipart_header, ContentReceiver multipart_receiver) const { + detail::FormDataParser multipart_form_data_parser; ContentReceiverWithProgress out; if (req.is_multipart_form_data()) { @@ -6710,28 +7802,18 @@ std::string boundary; if (!detail::parse_multipart_boundary(content_type, boundary)) { res.status = StatusCode::BadRequest_400; + output_error_log(Error::MultipartParsing, &req); return false; } multipart_form_data_parser.set_boundary(std::move(boundary)); - out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { - /* For debug - size_t pos = 0; - while (pos < n) { - auto read_size = (std::min)<size_t>(1, n - pos); - auto ret = multipart_form_data_parser.parse( - buf + pos, read_size, multipart_receiver, multipart_header); - if (!ret) { return false; } - pos += read_size; - } - return true; - */ - return multipart_form_data_parser.parse(buf, n, multipart_receiver, - multipart_header); + out = [&](const char *buf, size_t n, size_t /*off*/, size_t /*len*/) { + return multipart_form_data_parser.parse(buf, n, multipart_header, + multipart_receiver); }; } else { - out = [receiver](const char *buf, size_t n, uint64_t /*off*/, - uint64_t /*len*/) { return receiver(buf, n); }; + out = [receiver](const char *buf, size_t n, size_t /*off*/, + size_t /*len*/) { return receiver(buf, n); }; } if (req.method == "DELETE" && !req.has_header("Content-Length")) { @@ -6746,6 +7828,7 @@ if (req.is_multipart_form_data()) { if (!multipart_form_data_parser.is_valid()) { res.status = StatusCode::BadRequest_400; + output_error_log(Error::MultipartParsing, &req); return false; } } @@ -6753,8 +7836,7 @@ return true; } -inline bool Server::handle_file_request(const Request &req, Response &res, - bool head) { +inline bool Server::handle_file_request(const Request &req, Response &res) { for (const auto &entry : base_dirs_) { // Prefix match if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { @@ -6776,7 +7858,10 @@ } auto mm = std::make_shared<detail::mmap>(path.c_str()); - if (!mm->is_open()) { return false; } + if (!mm->is_open()) { + output_error_log(Error::OpenFile, &req); + return false; + } res.set_content_provider( mm->size(), @@ -6787,11 +7872,13 @@ return true; }); - if (!head && file_request_handler_) { + if (req.method != "HEAD" && file_request_handler_) { file_request_handler_(req, res); } return true; + } else { + output_error_log(Error::OpenFile, &req); } } } @@ -6806,11 +7893,15 @@ return detail::create_socket( host, std::string(), port, address_family_, socket_flags, tcp_nodelay_, ipv6_v6only_, std::move(socket_options), - [](socket_t sock, struct addrinfo &ai, bool & /*quit*/) -> bool { + [&](socket_t sock, struct addrinfo &ai, bool & /*quit*/) -> bool { if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) { + output_error_log(Error::BindIPAddress, nullptr); return false; } - if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { return false; } + if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { + output_error_log(Error::Listen, nullptr); + return false; + } return true; }); } @@ -6829,6 +7920,7 @@ socklen_t addr_len = sizeof(addr); if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&addr), &addr_len) == -1) { + output_error_log(Error::GetSockName, nullptr); return -1; } if (addr.ss_family == AF_INET) { @@ -6836,6 +7928,7 @@ } else if (addr.ss_family == AF_INET6) { return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_port); } else { + output_error_log(Error::UnsupportedAddressFamily, nullptr); return -1; } } else { @@ -6889,6 +7982,7 @@ if (svr_sock_ != INVALID_SOCKET) { detail::close_socket(svr_sock_); ret = false; + output_error_log(Error::Connection, nullptr); } else { ; // The server socket was closed by user. } @@ -6902,6 +7996,7 @@ if (!task_queue->enqueue( [this, sock]() { process_and_close_socket(sock); })) { + output_error_log(Error::ResourceExhaustion, nullptr); detail::shutdown_socket(sock); detail::close_socket(sock); } @@ -6921,9 +8016,8 @@ } // File handler - auto is_head_request = req.method == "HEAD"; - if ((req.method == "GET" || is_head_request) && - handle_file_request(req, res, is_head_request)) { + if ((req.method == "GET" || req.method == "HEAD") && + handle_file_request(req, res)) { return true; } @@ -6932,13 +8026,17 @@ { ContentReader reader( [&](ContentReceiver receiver) { - return read_content_with_content_receiver( + auto result = read_content_with_content_receiver( strm, req, res, std::move(receiver), nullptr, nullptr); + if (!result) { output_error_log(Error::Read, &req); } + return result; }, - [&](MultipartContentHeader header, ContentReceiver receiver) { - return read_content_with_content_receiver(strm, req, res, nullptr, - std::move(header), - std::move(receiver)); + [&](FormDataHeader header, ContentReceiver receiver) { + auto result = read_content_with_content_receiver( + strm, req, res, nullptr, std::move(header), + std::move(receiver)); + if (!result) { output_error_log(Error::Read, &req); } + return result; }); if (req.method == "POST") { @@ -6969,7 +8067,10 @@ } // Read content into `req.body` - if (!read_content(strm, req, res)) { return false; } + if (!read_content(strm, req, res)) { + output_error_log(Error::Read, &req); + return false; + } } // Regular handler @@ -6998,7 +8099,11 @@ const auto &handler = x.second; if (matcher->match(req)) { - handler(req, res); + req.matched_route = matcher->pattern(); + if (!pre_request_handler_ || + pre_request_handler_(req, res) != HandlerResponse::Handled) { + handler(req, res); + } return true; } } @@ -7050,6 +8155,8 @@ res.set_header("Content-Encoding", "gzip"); } else if (type == detail::EncodingType::Brotli) { res.set_header("Content-Encoding", "br"); + } else if (type == detail::EncodingType::Zstd) { + res.set_header("Content-Encoding", "zstd"); } } } @@ -7077,6 +8184,8 @@ } if (type != detail::EncodingType::None) { + output_pre_compression_log(req, res); + std::unique_ptr<detail::compressor> compressor; std::string content_encoding; @@ -7090,6 +8199,11 @@ compressor = detail::make_unique<detail::brotli_compressor>(); content_encoding = "br"; #endif + } else if (type == detail::EncodingType::Zstd) { +#ifdef CPPHTTPLIB_ZSTD_SUPPORT + compressor = detail::make_unique<detail::zstd_compressor>(); + content_encoding = "zstd"; +#endif } if (compressor) { @@ -7118,7 +8232,11 @@ const auto &handler = x.second; if (matcher->match(req)) { - handler(req, res, content_reader); + req.matched_route = matcher->pattern(); + if (!pre_request_handler_ || + pre_request_handler_(req, res) != HandlerResponse::Handled) { + handler(req, res, content_reader); + } return true; } } @@ -7144,10 +8262,28 @@ res.version = "HTTP/1.1"; res.headers = default_headers_; +#ifdef __APPLE__ + // Socket file descriptor exceeded FD_SETSIZE... + if (strm.socket() >= FD_SETSIZE) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = StatusCode::InternalServerError_500; + output_error_log(Error::ExceedMaxSocketDescriptorCount, &req); + return write_response(strm, close_connection, req, res); + } +#endif + // Request line and headers - if (!parse_request_line(line_reader.ptr(), req) || - !detail::read_headers(strm, req.headers)) { + if (!parse_request_line(line_reader.ptr(), req)) { res.status = StatusCode::BadRequest_400; + output_error_log(Error::InvalidRequestLine, &req); + return write_response(strm, close_connection, req, res); + } + + // Request headers + if (!detail::read_headers(strm, req.headers)) { + res.status = StatusCode::BadRequest_400; + output_error_log(Error::InvalidHeaders, &req); return write_response(strm, close_connection, req, res); } @@ -7156,6 +8292,7 @@ Headers dummy; detail::read_headers(strm, dummy); res.status = StatusCode::UriTooLong_414; + output_error_log(Error::ExceedUriMaxLength, &req); return write_response(strm, close_connection, req, res); } @@ -7178,10 +8315,20 @@ req.set_header("LOCAL_ADDR", req.local_addr); req.set_header("LOCAL_PORT", std::to_string(req.local_port)); + if (req.has_header("Accept")) { + const auto &accept_header = req.get_header_value("Accept"); + if (!detail::parse_accept_header(accept_header, req.accept_content_types)) { + res.status = StatusCode::BadRequest_400; + output_error_log(Error::HTTPParsing, &req); + return write_response(strm, close_connection, req, res); + } + } + if (req.has_header("Range")) { const auto &range_header_value = req.get_header_value("Range"); if (!detail::parse_range_header(range_header_value, req.ranges)) { res.status = StatusCode::RangeNotSatisfiable_416; + output_error_log(Error::InvalidRangeHeader, &req); return write_response(strm, close_connection, req, res); } } @@ -7206,8 +8353,9 @@ } // Setup `is_connection_closed` method - req.is_connection_closed = [&]() { - return !detail::is_socket_alive(strm.socket()); + auto sock = strm.socket(); + req.is_connection_closed = [sock]() { + return !detail::is_socket_alive(sock); }; // Routing @@ -7261,6 +8409,7 @@ res.content_length_ = 0; res.content_provider_ = nullptr; res.status = StatusCode::NotFound_404; + output_error_log(Error::OpenFile, &req); return write_response(strm, close_connection, req, res); } @@ -7320,6 +8469,29 @@ return ret; } +inline void Server::output_log(const Request &req, const Response &res) const { + if (logger_) { + std::lock_guard<std::mutex> guard(logger_mutex_); + logger_(req, res); + } +} + +inline void Server::output_pre_compression_log(const Request &req, + const Response &res) const { + if (pre_compression_logger_) { + std::lock_guard<std::mutex> guard(logger_mutex_); + pre_compression_logger_(req, res); + } +} + +inline void Server::output_error_log(const Error &err, + const Request *req) const { + if (error_logger_) { + std::lock_guard<std::mutex> guard(logger_mutex_); + error_logger_(err, req); + } +} + // HTTP client implementation inline ClientImpl::ClientImpl(const std::string &host) : ClientImpl(host, 80, std::string(), std::string()) {} @@ -7370,7 +8542,7 @@ #endif keep_alive_ = rhs.keep_alive_; follow_location_ = rhs.follow_location_; - url_encode_ = rhs.url_encode_; + path_encode_ = rhs.path_encode_; address_family_ = rhs.address_family_; tcp_nodelay_ = rhs.tcp_nodelay_; ipv6_v6only_ = rhs.ipv6_v6only_; @@ -7398,6 +8570,7 @@ server_certificate_verifier_ = rhs.server_certificate_verifier_; #endif logger_ = rhs.logger_; + error_logger_ = rhs.error_logger_; } inline socket_t ClientImpl::create_client_socket(Error &error) const { @@ -7470,9 +8643,9 @@ if (!line_reader.getline()) { return false; } #ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR - const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); + thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); #else - const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); + thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); #endif std::cmatch m; @@ -7540,7 +8713,10 @@ } if (!is_alive) { - if (!create_and_connect_socket(socket_, error)) { return false; } + if (!create_and_connect_socket(socket_, error)) { + output_error_log(error, &req); + return false; + } #ifdef CPPHTTPLIB_OPENSSL_SUPPORT // TODO: refactoring @@ -7550,11 +8726,15 @@ auto success = false; if (!scli.connect_with_proxy(socket_, req.start_time_, res, success, error)) { + if (!success) { output_error_log(error, &req); } return success; } } - if (!scli.initialize_ssl(socket_, error)) { return false; } + if (!scli.initialize_ssl(socket_, error)) { + output_error_log(error, &req); + return false; + } } #endif } @@ -7600,7 +8780,10 @@ }); if (!ret) { - if (error == Error::Success) { error = Error::Unknown; } + if (error == Error::Success) { + error = Error::Unknown; + output_error_log(error, &req); + } } return ret; @@ -7615,7 +8798,12 @@ auto res = detail::make_unique<Response>(); auto error = Error::Success; auto ret = send(req, *res, error); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers), + last_ssl_error_, last_openssl_error_}; +#else return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; +#endif } inline bool ClientImpl::handle_request(Stream &strm, Request &req, @@ -7623,6 +8811,7 @@ Error &error) { if (req.path.empty()) { error = Error::Connection; + output_error_log(error, &req); return false; } @@ -7698,13 +8887,14 @@ inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) { if (req.redirect_count_ == 0) { error = Error::ExceedRedirectCount; + output_error_log(error, &req); return false; } auto location = res.get_header_value("location"); if (location.empty()) { return false; } - const static std::regex re( + thread_local const std::regex re( R"((?:(https?):)?(?://(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)"); std::smatch m; @@ -7730,26 +8920,154 @@ if (next_host.empty()) { next_host = host_; } if (next_path.empty()) { next_path = "/"; } - auto path = detail::decode_url(next_path, true) + next_query; + auto path = decode_query_component(next_path, true) + next_query; + // Same host redirect - use current client if (next_scheme == scheme && next_host == host_ && next_port == port_) { return detail::redirect(*this, req, res, path, location, error); - } else { - if (next_scheme == "https") { -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT - SSLClient cli(next_host, next_port); - cli.copy_settings(*this); - if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); } - return detail::redirect(cli, req, res, path, location, error); -#else - return false; -#endif - } else { - ClientImpl cli(next_host, next_port); - cli.copy_settings(*this); - return detail::redirect(cli, req, res, path, location, error); + } + + // Cross-host/scheme redirect - create new client with robust setup + return create_redirect_client(next_scheme, next_host, next_port, req, res, + path, location, error); +} + +// New method for robust redirect client creation +inline bool ClientImpl::create_redirect_client( + const std::string &scheme, const std::string &host, int port, Request &req, + Response &res, const std::string &path, const std::string &location, + Error &error) { + // Determine if we need SSL + auto need_ssl = (scheme == "https"); + + // Clean up request headers that are host/client specific + // Remove headers that should not be carried over to new host + auto headers_to_remove = + std::vector<std::string>{"Host", "Proxy-Authorization", "Authorization"}; + + for (const auto &header_name : headers_to_remove) { + auto it = req.headers.find(header_name); + while (it != req.headers.end()) { + it = req.headers.erase(it); + it = req.headers.find(header_name); } } + + // Create appropriate client type and handle redirect + if (need_ssl) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // Create SSL client for HTTPS redirect + SSLClient redirect_client(host, port); + + // Setup basic client configuration first + setup_redirect_client(redirect_client); + + // SSL-specific configuration for proxy environments + if (!proxy_host_.empty() && proxy_port_ != -1) { + // Critical: Disable SSL verification for proxy environments + redirect_client.enable_server_certificate_verification(false); + redirect_client.enable_server_hostname_verification(false); + } else { + // For direct SSL connections, copy SSL verification settings + redirect_client.enable_server_certificate_verification( + server_certificate_verification_); + redirect_client.enable_server_hostname_verification( + server_hostname_verification_); + } + + // Handle CA certificate store and paths if available + if (ca_cert_store_) { redirect_client.set_ca_cert_store(ca_cert_store_); } + if (!ca_cert_file_path_.empty()) { + redirect_client.set_ca_cert_path(ca_cert_file_path_, ca_cert_dir_path_); + } + + // Client certificates are set through constructor for SSLClient + // NOTE: SSLClient constructor already takes client_cert_path and + // client_key_path so we need to create it properly if client certs are + // needed + + // Execute the redirect + return detail::redirect(redirect_client, req, res, path, location, error); +#else + // SSL not supported - set appropriate error + error = Error::SSLConnection; + output_error_log(error, &req); + return false; +#endif + } else { + // HTTP redirect + ClientImpl redirect_client(host, port); + + // Setup client with robust configuration + setup_redirect_client(redirect_client); + + // Execute the redirect + return detail::redirect(redirect_client, req, res, path, location, error); + } +} + +// New method for robust client setup (based on basic_manual_redirect.cpp logic) +template <typename ClientType> +inline void ClientImpl::setup_redirect_client(ClientType &client) { + // Copy basic settings first + client.set_connection_timeout(connection_timeout_sec_); + client.set_read_timeout(read_timeout_sec_, read_timeout_usec_); + client.set_write_timeout(write_timeout_sec_, write_timeout_usec_); + client.set_keep_alive(keep_alive_); + client.set_follow_location( + true); // Enable redirects to handle multi-step redirects + client.set_path_encode(path_encode_); + client.set_compress(compress_); + client.set_decompress(decompress_); + + // Copy authentication settings BEFORE proxy setup + if (!basic_auth_username_.empty()) { + client.set_basic_auth(basic_auth_username_, basic_auth_password_); + } + if (!bearer_token_auth_token_.empty()) { + client.set_bearer_token_auth(bearer_token_auth_token_); + } +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!digest_auth_username_.empty()) { + client.set_digest_auth(digest_auth_username_, digest_auth_password_); + } +#endif + + // Setup proxy configuration (CRITICAL ORDER - proxy must be set + // before proxy auth) + if (!proxy_host_.empty() && proxy_port_ != -1) { + // First set proxy host and port + client.set_proxy(proxy_host_, proxy_port_); + + // Then set proxy authentication (order matters!) + if (!proxy_basic_auth_username_.empty()) { + client.set_proxy_basic_auth(proxy_basic_auth_username_, + proxy_basic_auth_password_); + } + if (!proxy_bearer_token_auth_token_.empty()) { + client.set_proxy_bearer_token_auth(proxy_bearer_token_auth_token_); + } +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!proxy_digest_auth_username_.empty()) { + client.set_proxy_digest_auth(proxy_digest_auth_username_, + proxy_digest_auth_password_); + } +#endif + } + + // Copy network and socket settings + client.set_address_family(address_family_); + client.set_tcp_nodelay(tcp_nodelay_); + client.set_ipv6_v6only(ipv6_v6only_); + if (socket_options_) { client.set_socket_options(socket_options_); } + if (!interface_.empty()) { client.set_interface(interface_); } + + // Copy logging and headers + if (logger_) { client.set_logger(logger_); } + if (error_logger_) { client.set_error_logger(error_logger_); } + + // NOTE: DO NOT copy default_headers_ as they may contain stale Host headers + // Each new client should generate its own headers based on its target host } inline bool ClientImpl::write_content_with_provider(Stream &strm, @@ -7772,8 +9090,9 @@ return detail::write_content_chunked(strm, req.content_provider_, is_shutting_down, *compressor, error); } else { - return detail::write_content(strm, req.content_provider_, 0, - req.content_length_, is_shutting_down, error); + return detail::write_content_with_progress( + strm, req.content_provider_, 0, req.content_length_, is_shutting_down, + req.upload_progress, error); } } @@ -7787,7 +9106,11 @@ } if (!req.has_header("Host")) { - if (is_ssl()) { + // For Unix socket connections, use "localhost" as Host header (similar to + // curl behavior) + if (address_family_ == AF_UNIX) { + req.set_header("Host", "localhost"); + } else if (is_ssl()) { if (port_ == 443) { req.set_header("Host", host_); } else { @@ -7814,6 +9137,10 @@ if (!accept_encoding.empty()) { accept_encoding += ", "; } accept_encoding += "gzip, deflate"; #endif +#ifdef CPPHTTPLIB_ZSTD_SUPPORT + if (!accept_encoding.empty()) { accept_encoding += ", "; } + accept_encoding += "zstd"; +#endif req.set_header("Accept-Encoding", accept_encoding); } @@ -7883,21 +9210,35 @@ { detail::BufferStream bstrm; - const auto &path_with_query = - req.params.empty() ? req.path - : append_query_params(req.path, req.params); + // Extract path and query from req.path + std::string path_part, query_part; + auto query_pos = req.path.find('?'); + if (query_pos != std::string::npos) { + path_part = req.path.substr(0, query_pos); + query_part = req.path.substr(query_pos + 1); + } else { + path_part = req.path; + query_part = ""; + } - const auto &path = - url_encode_ ? detail::encode_url(path_with_query) : path_with_query; + // Encode path and query + auto path_with_query = + path_encode_ ? detail::encode_path(path_part) : path_part; - detail::write_request_line(bstrm, req.method, path); + detail::parse_query_text(query_part, req.params); + if (!req.params.empty()) { + path_with_query = append_query_params(path_with_query, req.params); + } + // Write request line and headers + detail::write_request_line(bstrm, req.method, path_with_query); header_writer_(bstrm, req.headers); // Flush buffer auto &data = bstrm.get_buffer(); if (!detail::write_data(strm, data.data(), data.size())) { error = Error::Write; + output_error_log(error, &req); return false; } } @@ -7907,9 +9248,32 @@ return write_content_with_provider(strm, req, error); } - if (!detail::write_data(strm, req.body.data(), req.body.size())) { - error = Error::Write; - return false; + if (req.upload_progress) { + auto body_size = req.body.size(); + size_t written = 0; + auto data = req.body.data(); + + while (written < body_size) { + size_t to_write = (std::min)(CPPHTTPLIB_SEND_BUFSIZ, body_size - written); + if (!detail::write_data(strm, data + written, to_write)) { + error = Error::Write; + output_error_log(error, &req); + return false; + } + written += to_write; + + if (!req.upload_progress(written, body_size)) { + error = Error::Canceled; + output_error_log(error, &req); + return false; + } + } + } else { + if (!detail::write_data(strm, req.body.data(), req.body.size())) { + error = Error::Write; + output_error_log(error, &req); + return false; + } } return true; @@ -7959,6 +9323,7 @@ while (ok && offset < content_length) { if (!content_provider(offset, content_length - offset, data_sink)) { error = Error::Canceled; + output_error_log(error, &req); return nullptr; } } @@ -7969,6 +9334,7 @@ return true; })) { error = Error::Compression; + output_error_log(error, &req); return nullptr; } } @@ -7998,12 +9364,12 @@ const std::string &method, const std::string &path, const Headers &headers, const char *body, size_t content_length, ContentProvider content_provider, ContentProviderWithoutLength content_provider_without_length, - const std::string &content_type, Progress progress) { + const std::string &content_type, UploadProgress progress) { Request req; req.method = method; req.headers = headers; req.path = path; - req.progress = progress; + req.upload_progress = std::move(progress); if (max_timeout_msec_ > 0) { req.start_time_ = std::chrono::steady_clock::now(); } @@ -8014,7 +9380,12 @@ req, body, content_length, std::move(content_provider), std::move(content_provider_without_length), content_type, error); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + return Result{std::move(res), error, std::move(req.headers), last_ssl_error_, + last_openssl_error_}; +#else return Result{std::move(res), error, std::move(req.headers)}; +#endif } inline std::string @@ -8023,6 +9394,22 @@ return host; } +inline void ClientImpl::output_log(const Request &req, + const Response &res) const { + if (logger_) { + std::lock_guard<std::mutex> guard(logger_mutex_); + logger_(req, res); + } +} + +inline void ClientImpl::output_error_log(const Error &err, + const Request *req) const { + if (error_logger_) { + std::lock_guard<std::mutex> guard(logger_mutex_); + error_logger_(err, req); + } +} + inline bool ClientImpl::process_request(Stream &strm, Request &req, Response &res, bool close_connection, Error &error) { @@ -8035,6 +9422,7 @@ if (!is_proxy_enabled) { if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) { error = Error::SSLPeerCouldBeClosed_; + output_error_log(error, &req); return false; } } @@ -8045,6 +9433,7 @@ if (!read_response_line(strm, req, res) || !detail::read_headers(strm, res.headers)) { error = Error::Read; + output_error_log(error, &req); return false; } @@ -8058,6 +9447,7 @@ if (req.response_handler && !redirect) { if (!req.response_handler(res)) { error = Error::Canceled; + output_error_log(error, &req); return false; } } @@ -8065,24 +9455,30 @@ auto out = req.content_receiver ? static_cast<ContentReceiverWithProgress>( - [&](const char *buf, size_t n, uint64_t off, uint64_t len) { + [&](const char *buf, size_t n, size_t off, size_t len) { if (redirect) { return true; } auto ret = req.content_receiver(buf, n, off, len); - if (!ret) { error = Error::Canceled; } + if (!ret) { + error = Error::Canceled; + output_error_log(error, &req); + } return ret; }) : static_cast<ContentReceiverWithProgress>( - [&](const char *buf, size_t n, uint64_t /*off*/, - uint64_t /*len*/) { + [&](const char *buf, size_t n, size_t /*off*/, + size_t /*len*/) { assert(res.body.size() + n <= res.body.max_size()); res.body.append(buf, n); return true; }); - auto progress = [&](uint64_t current, uint64_t total) { - if (!req.progress || redirect) { return true; } - auto ret = req.progress(current, total); - if (!ret) { error = Error::Canceled; } + auto progress = [&](size_t current, size_t total) { + if (!req.download_progress || redirect) { return true; } + auto ret = req.download_progress(current, total); + if (!ret) { + error = Error::Canceled; + output_error_log(error, &req); + } return ret; }; @@ -8091,6 +9487,7 @@ auto len = res.get_header_value_u64("Content-Length"); if (len > res.body.max_size()) { error = Error::Read; + output_error_log(error, &req); return false; } res.body.reserve(static_cast<size_t>(len)); @@ -8103,20 +9500,21 @@ dummy_status, std::move(progress), std::move(out), decompress_)) { if (error != Error::Canceled) { error = Error::Read; } + output_error_log(error, &req); return false; } } } // Log - if (logger_) { logger_(req, res); } + output_log(req, res); return true; } inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( - const std::string &boundary, const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items) const { + const std::string &boundary, const UploadFormDataItems &items, + const FormDataProviderItems &provider_items) const { size_t cur_item = 0; size_t cur_start = 0; // cur_item and cur_start are copied to within the std::function and maintain @@ -8169,25 +9567,27 @@ inline bool ClientImpl::is_ssl() const { return false; } -inline Result ClientImpl::Get(const std::string &path) { - return Get(path, Headers(), Progress()); -} - -inline Result ClientImpl::Get(const std::string &path, Progress progress) { +inline Result ClientImpl::Get(const std::string &path, + DownloadProgress progress) { return Get(path, Headers(), std::move(progress)); } -inline Result ClientImpl::Get(const std::string &path, const Headers &headers) { - return Get(path, headers, Progress()); +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + DownloadProgress progress) { + if (params.empty()) { return Get(path, headers); } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query, headers, std::move(progress)); } inline Result ClientImpl::Get(const std::string &path, const Headers &headers, - Progress progress) { + DownloadProgress progress) { Request req; req.method = "GET"; req.path = path; req.headers = headers; - req.progress = std::move(progress); + req.download_progress = std::move(progress); if (max_timeout_msec_ > 0) { req.start_time_ = std::chrono::steady_clock::now(); } @@ -8196,47 +9596,23 @@ } inline Result ClientImpl::Get(const std::string &path, - ContentReceiver content_receiver) { - return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr); -} - -inline Result ClientImpl::Get(const std::string &path, ContentReceiver content_receiver, - Progress progress) { + DownloadProgress progress) { return Get(path, Headers(), nullptr, std::move(content_receiver), std::move(progress)); } inline Result ClientImpl::Get(const std::string &path, const Headers &headers, - ContentReceiver content_receiver) { - return Get(path, headers, nullptr, std::move(content_receiver), nullptr); -} - -inline Result ClientImpl::Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, - Progress progress) { + DownloadProgress progress) { return Get(path, headers, nullptr, std::move(content_receiver), std::move(progress)); } inline Result ClientImpl::Get(const std::string &path, ResponseHandler response_handler, - ContentReceiver content_receiver) { - return Get(path, Headers(), std::move(response_handler), - std::move(content_receiver), nullptr); -} - -inline Result ClientImpl::Get(const std::string &path, const Headers &headers, - ResponseHandler response_handler, - ContentReceiver content_receiver) { - return Get(path, headers, std::move(response_handler), - std::move(content_receiver), nullptr); -} - -inline Result ClientImpl::Get(const std::string &path, - ResponseHandler response_handler, ContentReceiver content_receiver, - Progress progress) { + DownloadProgress progress) { return Get(path, Headers(), std::move(response_handler), std::move(content_receiver), std::move(progress)); } @@ -8244,7 +9620,7 @@ inline Result ClientImpl::Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, - Progress progress) { + DownloadProgress progress) { Request req; req.method = "GET"; req.path = path; @@ -8252,10 +9628,10 @@ req.response_handler = std::move(response_handler); req.content_receiver = [content_receiver](const char *data, size_t data_length, - uint64_t /*offset*/, uint64_t /*total_length*/) { + size_t /*offset*/, size_t /*total_length*/) { return content_receiver(data, data_length); }; - req.progress = std::move(progress); + req.download_progress = std::move(progress); if (max_timeout_msec_ > 0) { req.start_time_ = std::chrono::steady_clock::now(); } @@ -8264,17 +9640,9 @@ } inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, - const Headers &headers, Progress progress) { - if (params.empty()) { return Get(path, headers); } - - std::string path_with_query = append_query_params(path, params); - return Get(path_with_query, headers, std::move(progress)); -} - -inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, const Headers &headers, ContentReceiver content_receiver, - Progress progress) { + DownloadProgress progress) { return Get(path, params, headers, nullptr, std::move(content_receiver), std::move(progress)); } @@ -8283,7 +9651,7 @@ const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, - Progress progress) { + DownloadProgress progress) { if (params.empty()) { return Get(path, headers, std::move(response_handler), std::move(content_receiver), std::move(progress)); @@ -8322,85 +9690,35 @@ inline Result ClientImpl::Post(const std::string &path, const char *body, size_t content_length, - const std::string &content_type) { - return Post(path, Headers(), body, content_length, content_type, nullptr); -} - -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type) { - return send_with_content_provider("POST", path, headers, body, content_length, - nullptr, nullptr, content_type, nullptr); -} - -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - const char *body, size_t content_length, const std::string &content_type, - Progress progress) { - return send_with_content_provider("POST", path, headers, body, content_length, - nullptr, nullptr, content_type, progress); -} - -inline Result ClientImpl::Post(const std::string &path, const std::string &body, - const std::string &content_type) { - return Post(path, Headers(), body, content_type); + UploadProgress progress) { + return Post(path, Headers(), body, content_length, content_type, progress); } inline Result ClientImpl::Post(const std::string &path, const std::string &body, const std::string &content_type, - Progress progress) { + UploadProgress progress) { return Post(path, Headers(), body, content_type, progress); } -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - const std::string &body, - const std::string &content_type) { - return send_with_content_provider("POST", path, headers, body.data(), - body.size(), nullptr, nullptr, content_type, - nullptr); -} - -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - const std::string &body, - const std::string &content_type, - Progress progress) { - return send_with_content_provider("POST", path, headers, body.data(), - body.size(), nullptr, nullptr, content_type, - progress); -} - inline Result ClientImpl::Post(const std::string &path, const Params ¶ms) { return Post(path, Headers(), params); } inline Result ClientImpl::Post(const std::string &path, size_t content_length, ContentProvider content_provider, - const std::string &content_type) { + const std::string &content_type, + UploadProgress progress) { return Post(path, Headers(), content_length, std::move(content_provider), - content_type); + content_type, progress); } inline Result ClientImpl::Post(const std::string &path, ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return Post(path, Headers(), std::move(content_provider), content_type); -} - -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - size_t content_length, - ContentProvider content_provider, - const std::string &content_type) { - return send_with_content_provider("POST", path, headers, nullptr, - content_length, std::move(content_provider), - nullptr, content_type, nullptr); -} - -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, - std::move(content_provider), content_type, - nullptr); + const std::string &content_type, + UploadProgress progress) { + return Post(path, Headers(), std::move(content_provider), content_type, + progress); } inline Result ClientImpl::Post(const std::string &path, const Headers &headers, @@ -8409,30 +9727,26 @@ return Post(path, headers, query, "application/x-www-form-urlencoded"); } -inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress) { - auto query = detail::params_to_query_str(params); - return Post(path, headers, query, "application/x-www-form-urlencoded", - progress); -} - inline Result ClientImpl::Post(const std::string &path, - const MultipartFormDataItems &items) { - return Post(path, Headers(), items); + const UploadFormDataItems &items, + UploadProgress progress) { + return Post(path, Headers(), items, progress); } inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items) { + const UploadFormDataItems &items, + UploadProgress progress) { const auto &boundary = detail::make_multipart_data_boundary(); const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); const auto &body = detail::serialize_multipart_formdata(items, boundary); - return Post(path, headers, body, content_type); + return Post(path, headers, body, content_type, progress); } inline Result ClientImpl::Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const std::string &boundary) { + const UploadFormDataItems &items, + const std::string &boundary, + UploadProgress progress) { if (!detail::is_multipart_boundary_chars_valid(boundary)) { return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; } @@ -8440,139 +9754,151 @@ const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); const auto &body = detail::serialize_multipart_formdata(items, boundary); - return Post(path, headers, body, content_type); + return Post(path, headers, body, content_type, progress); } -inline Result -ClientImpl::Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items) { +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type, progress); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + progress); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("POST", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type, progress); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type, + progress); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) { const auto &boundary = detail::make_multipart_data_boundary(); const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); return send_with_content_provider( "POST", path, headers, nullptr, 0, nullptr, get_multipart_content_provider(boundary, items, provider_items), - content_type, nullptr); + content_type, progress); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) { + Request req; + req.method = "POST"; + req.path = path; + req.headers = headers; + req.body = body; + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + size_t /*offset*/, size_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.download_progress = std::move(progress); + + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + + return send_(std::move(req)); } inline Result ClientImpl::Put(const std::string &path) { return Put(path, std::string(), std::string()); } +inline Result ClientImpl::Put(const std::string &path, const Headers &headers) { + return Put(path, headers, nullptr, 0, std::string()); +} + inline Result ClientImpl::Put(const std::string &path, const char *body, size_t content_length, - const std::string &content_type) { - return Put(path, Headers(), body, content_length, content_type); -} - -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type) { - return send_with_content_provider("PUT", path, headers, body, content_length, - nullptr, nullptr, content_type, nullptr); -} - -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, - const char *body, size_t content_length, const std::string &content_type, - Progress progress) { - return send_with_content_provider("PUT", path, headers, body, content_length, - nullptr, nullptr, content_type, progress); -} - -inline Result ClientImpl::Put(const std::string &path, const std::string &body, - const std::string &content_type) { - return Put(path, Headers(), body, content_type); + UploadProgress progress) { + return Put(path, Headers(), body, content_length, content_type, progress); } inline Result ClientImpl::Put(const std::string &path, const std::string &body, const std::string &content_type, - Progress progress) { + UploadProgress progress) { return Put(path, Headers(), body, content_type, progress); } -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, - const std::string &body, - const std::string &content_type) { - return send_with_content_provider("PUT", path, headers, body.data(), - body.size(), nullptr, nullptr, content_type, - nullptr); -} - -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, - const std::string &body, - const std::string &content_type, - Progress progress) { - return send_with_content_provider("PUT", path, headers, body.data(), - body.size(), nullptr, nullptr, content_type, - progress); -} - -inline Result ClientImpl::Put(const std::string &path, size_t content_length, - ContentProvider content_provider, - const std::string &content_type) { - return Put(path, Headers(), content_length, std::move(content_provider), - content_type); -} - -inline Result ClientImpl::Put(const std::string &path, - ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return Put(path, Headers(), std::move(content_provider), content_type); -} - -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, - size_t content_length, - ContentProvider content_provider, - const std::string &content_type) { - return send_with_content_provider("PUT", path, headers, nullptr, - content_length, std::move(content_provider), - nullptr, content_type, nullptr); -} - -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, - ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, - std::move(content_provider), content_type, - nullptr); -} - inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) { return Put(path, Headers(), params); } +inline Result ClientImpl::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return Put(path, Headers(), content_length, std::move(content_provider), + content_type, progress); +} + +inline Result ClientImpl::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return Put(path, Headers(), std::move(content_provider), content_type, + progress); +} + inline Result ClientImpl::Put(const std::string &path, const Headers &headers, const Params ¶ms) { auto query = detail::params_to_query_str(params); return Put(path, headers, query, "application/x-www-form-urlencoded"); } -inline Result ClientImpl::Put(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress) { - auto query = detail::params_to_query_str(params); - return Put(path, headers, query, "application/x-www-form-urlencoded", - progress); -} - inline Result ClientImpl::Put(const std::string &path, - const MultipartFormDataItems &items) { - return Put(path, Headers(), items); + const UploadFormDataItems &items, + UploadProgress progress) { + return Put(path, Headers(), items, progress); } inline Result ClientImpl::Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items) { + const UploadFormDataItems &items, + UploadProgress progress) { const auto &boundary = detail::make_multipart_data_boundary(); const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); const auto &body = detail::serialize_multipart_formdata(items, boundary); - return Put(path, headers, body, content_type); + return Put(path, headers, body, content_type, progress); } inline Result ClientImpl::Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const std::string &boundary) { + const UploadFormDataItems &items, + const std::string &boundary, + UploadProgress progress) { if (!detail::is_multipart_boundary_chars_valid(boundary)) { return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; } @@ -8580,150 +9906,297 @@ const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); const auto &body = detail::serialize_multipart_formdata(items, boundary); - return Put(path, headers, body, content_type); + return Put(path, headers, body, content_type, progress); } -inline Result -ClientImpl::Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items) { +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type, progress); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, content_type, + progress); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("PUT", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type, progress); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type, + progress); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) { const auto &boundary = detail::make_multipart_data_boundary(); const auto &content_type = detail::serialize_multipart_formdata_get_content_type(boundary); return send_with_content_provider( "PUT", path, headers, nullptr, 0, nullptr, get_multipart_content_provider(boundary, items, provider_items), - content_type, nullptr); + content_type, progress); } + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) { + Request req; + req.method = "PUT"; + req.path = path; + req.headers = headers; + req.body = body; + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + size_t /*offset*/, size_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.download_progress = std::move(progress); + + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + + return send_(std::move(req)); +} + inline Result ClientImpl::Patch(const std::string &path) { return Patch(path, std::string(), std::string()); } -inline Result ClientImpl::Patch(const std::string &path, const char *body, - size_t content_length, - const std::string &content_type) { - return Patch(path, Headers(), body, content_length, content_type); +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + UploadProgress progress) { + return Patch(path, headers, nullptr, 0, std::string(), progress); } inline Result ClientImpl::Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, - Progress progress) { + UploadProgress progress) { return Patch(path, Headers(), body, content_length, content_type, progress); } +inline Result ClientImpl::Patch(const std::string &path, + const std::string &body, + const std::string &content_type, + UploadProgress progress) { + return Patch(path, Headers(), body, content_type, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, const Params ¶ms) { + return Patch(path, Headers(), params); +} + +inline Result ClientImpl::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type, + UploadProgress progress) { + return Patch(path, Headers(), content_length, std::move(content_provider), + content_type, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type, + UploadProgress progress) { + return Patch(path, Headers(), std::move(content_provider), content_type, + progress); +} + inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type) { - return Patch(path, headers, body, content_length, content_type, nullptr); + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Patch(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Patch(const std::string &path, + const UploadFormDataItems &items, + UploadProgress progress) { + return Patch(path, Headers(), items, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + UploadProgress progress) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Patch(path, headers, body, content_type, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const std::string &boundary, + UploadProgress progress) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Patch(path, headers, body, content_type, progress); } inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, - Progress progress) { + UploadProgress progress) { return send_with_content_provider("PATCH", path, headers, body, content_length, nullptr, nullptr, content_type, progress); } -inline Result ClientImpl::Patch(const std::string &path, - const std::string &body, - const std::string &content_type) { - return Patch(path, Headers(), body, content_type); -} - -inline Result ClientImpl::Patch(const std::string &path, - const std::string &body, - const std::string &content_type, - Progress progress) { - return Patch(path, Headers(), body, content_type, progress); -} - -inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, - const std::string &body, - const std::string &content_type) { - return Patch(path, headers, body, content_type, nullptr); -} - inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, - Progress progress) { + UploadProgress progress) { return send_with_content_provider("PATCH", path, headers, body.data(), body.size(), nullptr, nullptr, content_type, progress); } -inline Result ClientImpl::Patch(const std::string &path, size_t content_length, - ContentProvider content_provider, - const std::string &content_type) { - return Patch(path, Headers(), content_length, std::move(content_provider), - content_type); -} - -inline Result ClientImpl::Patch(const std::string &path, - ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return Patch(path, Headers(), std::move(content_provider), content_type); -} - inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, - const std::string &content_type) { + const std::string &content_type, + UploadProgress progress) { return send_with_content_provider("PATCH", path, headers, nullptr, content_length, std::move(content_provider), - nullptr, content_type, nullptr); + nullptr, content_type, progress); } inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, - const std::string &content_type) { + const std::string &content_type, + UploadProgress progress) { return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr, std::move(content_provider), content_type, - nullptr); + progress); } -inline Result ClientImpl::Delete(const std::string &path) { - return Delete(path, Headers(), std::string(), std::string()); +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "PATCH", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type, progress); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) { + Request req; + req.method = "PATCH"; + req.path = path; + req.headers = headers; + req.body = body; + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + size_t /*offset*/, size_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.download_progress = std::move(progress); + + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } + + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + + return send_(std::move(req)); } inline Result ClientImpl::Delete(const std::string &path, - const Headers &headers) { - return Delete(path, headers, std::string(), std::string()); + DownloadProgress progress) { + return Delete(path, Headers(), std::string(), std::string(), progress); } -inline Result ClientImpl::Delete(const std::string &path, const char *body, - size_t content_length, - const std::string &content_type) { - return Delete(path, Headers(), body, content_length, content_type); +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, + DownloadProgress progress) { + return Delete(path, headers, std::string(), std::string(), progress); } inline Result ClientImpl::Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, - Progress progress) { + DownloadProgress progress) { return Delete(path, Headers(), body, content_length, content_type, progress); } inline Result ClientImpl::Delete(const std::string &path, - const Headers &headers, const char *body, - size_t content_length, - const std::string &content_type) { - return Delete(path, headers, body, content_length, content_type, nullptr); + const std::string &body, + const std::string &content_type, + DownloadProgress progress) { + return Delete(path, Headers(), body.data(), body.size(), content_type, + progress); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type, + DownloadProgress progress) { + return Delete(path, headers, body.data(), body.size(), content_type, + progress); +} + +inline Result ClientImpl::Delete(const std::string &path, const Params ¶ms, + DownloadProgress progress) { + return Delete(path, Headers(), params, progress); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, const Params ¶ms, + DownloadProgress progress) { + auto query = detail::params_to_query_str(params); + return Delete(path, headers, query, "application/x-www-form-urlencoded", + progress); } inline Result ClientImpl::Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, - Progress progress) { + DownloadProgress progress) { Request req; req.method = "DELETE"; req.headers = headers; req.path = path; - req.progress = progress; + req.download_progress = std::move(progress); if (max_timeout_msec_ > 0) { req.start_time_ = std::chrono::steady_clock::now(); } @@ -8734,36 +10207,6 @@ return send_(std::move(req)); } -inline Result ClientImpl::Delete(const std::string &path, - const std::string &body, - const std::string &content_type) { - return Delete(path, Headers(), body.data(), body.size(), content_type); -} - -inline Result ClientImpl::Delete(const std::string &path, - const std::string &body, - const std::string &content_type, - Progress progress) { - return Delete(path, Headers(), body.data(), body.size(), content_type, - progress); -} - -inline Result ClientImpl::Delete(const std::string &path, - const Headers &headers, - const std::string &body, - const std::string &content_type) { - return Delete(path, headers, body.data(), body.size(), content_type); -} - -inline Result ClientImpl::Delete(const std::string &path, - const Headers &headers, - const std::string &body, - const std::string &content_type, - Progress progress) { - return Delete(path, headers, body.data(), body.size(), content_type, - progress); -} - inline Result ClientImpl::Options(const std::string &path) { return Options(path, Headers()); } @@ -8856,7 +10299,7 @@ inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } -inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; } +inline void ClientImpl::set_path_encode(bool on) { path_encode_ = on; } inline void ClientImpl::set_hostname_addr_map(std::map<std::string, std::string> addr_map) { @@ -8968,6 +10411,10 @@ logger_ = std::move(logger); } +inline void ClientImpl::set_error_logger(ErrorLogger error_logger) { + error_logger_ = std::move(error_logger); +} + /* * SSL Implementation */ @@ -9028,8 +10475,8 @@ template <typename U> bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, U ssl_connect_or_accept, - time_t timeout_sec, - time_t timeout_usec) { + time_t timeout_sec, time_t timeout_usec, + int *ssl_error) { auto res = 0; while ((res = ssl_connect_or_accept(ssl)) != 1) { auto err = SSL_get_error(ssl, res); @@ -9042,6 +10489,7 @@ break; default: break; } + if (ssl_error) { *ssl_error = err; } return false; } return true; @@ -9074,14 +10522,6 @@ return callback(strm); } -class SSLInit { -public: - SSLInit() { - OPENSSL_init_ssl( - OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); - } -}; - // SSL socket stream implementation inline SSLSocketStream::SSLSocketStream( socket_t sock, SSL *ssl, time_t read_timeout_sec, time_t read_timeout_usec, @@ -9092,7 +10532,7 @@ read_timeout_usec_(read_timeout_usec), write_timeout_sec_(write_timeout_sec), write_timeout_usec_(write_timeout_usec), - max_timeout_msec_(max_timeout_msec), start_time(start_time) { + max_timeout_msec_(max_timeout_msec), start_time_(start_time) { SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); } @@ -9143,9 +10583,10 @@ if (ret >= 0) { return ret; } err = SSL_get_error(ssl_, ret); } else { - return -1; + break; } } + assert(ret < 0); } return ret; } else { @@ -9175,9 +10616,10 @@ if (ret >= 0) { return ret; } err = SSL_get_error(ssl_, ret); } else { - return -1; + break; } } + assert(ret < 0); } return ret; } @@ -9198,12 +10640,10 @@ inline time_t SSLSocketStream::duration() const { return std::chrono::duration_cast<std::chrono::milliseconds>( - std::chrono::steady_clock::now() - start_time) + std::chrono::steady_clock::now() - start_time_) .count(); } -static SSLInit sslinit_; - } // namespace detail // SSL HTTP server implementation @@ -9230,6 +10670,7 @@ SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != 1 || SSL_CTX_check_private_key(ctx_) != 1) { + last_ssl_error_ = static_cast<int>(ERR_get_error()); SSL_CTX_free(ctx_); ctx_ = nullptr; } else if (client_ca_cert_file_path || client_ca_cert_dir_path) { @@ -9303,7 +10744,8 @@ sock, ctx_, ctx_mutex_, [&](SSL *ssl2) { return detail::ssl_connect_or_accept_nonblocking( - sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_); + sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_, + &last_ssl_error_); }, [](SSL * /*ssl2*/) { return true; }); @@ -9371,6 +10813,7 @@ SSL_FILETYPE_PEM) != 1 || SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), SSL_FILETYPE_PEM) != 1) { + last_openssl_error_ = ERR_get_error(); SSL_CTX_free(ctx_); ctx_ = nullptr; } @@ -9397,6 +10840,7 @@ if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { + last_openssl_error_ = ERR_get_error(); SSL_CTX_free(ctx_); ctx_ = nullptr; } @@ -9474,6 +10918,19 @@ !proxy_digest_auth_password_.empty()) { std::map<std::string, std::string> auth; if (detail::parse_www_authenticate(proxy_res, auth, true)) { + // Close the current socket and create a new one for the authenticated + // request + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + + // Create a new socket for the authenticated CONNECT request + if (!create_and_connect_socket(socket, error)) { + success = false; + output_error_log(error, nullptr); + return false; + } + proxy_res = Response(); if (!detail::process_client_socket( socket.sock, read_timeout_sec_, read_timeout_usec_, @@ -9508,6 +10965,7 @@ // as the response of the request if (proxy_res.status != StatusCode::OK_200) { error = Error::ProxyConnection; + output_error_log(error, nullptr); res = std::move(proxy_res); // Thread-safe to close everything because we are assuming there are // no requests in flight @@ -9528,11 +10986,13 @@ if (!ca_cert_file_path_.empty()) { if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), nullptr)) { + last_openssl_error_ = ERR_get_error(); ret = false; } } else if (!ca_cert_dir_path_.empty()) { if (!SSL_CTX_load_verify_locations(ctx_, nullptr, ca_cert_dir_path_.c_str())) { + last_openssl_error_ = ERR_get_error(); ret = false; } } else { @@ -9540,10 +11000,9 @@ #ifdef _WIN32 loaded = detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); -#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) -#if TARGET_OS_OSX +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && \ + defined(TARGET_OS_OSX) loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_)); -#endif // TARGET_OS_OSX #endif // _WIN32 if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); } } @@ -9559,6 +11018,7 @@ if (server_certificate_verification_) { if (!load_certs()) { error = Error::SSLLoadingCerts; + output_error_log(error, nullptr); return false; } SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr); @@ -9566,8 +11026,9 @@ if (!detail::ssl_connect_or_accept_nonblocking( socket.sock, ssl2, SSL_connect, connection_timeout_sec_, - connection_timeout_usec_)) { + connection_timeout_usec_, &last_ssl_error_)) { error = Error::SSLConnection; + output_error_log(error, nullptr); return false; } @@ -9579,7 +11040,9 @@ } if (verification_status == SSLVerifierResponse::CertificateRejected) { + last_openssl_error_ = ERR_get_error(); error = Error::SSLServerVerification; + output_error_log(error, nullptr); return false; } @@ -9587,7 +11050,9 @@ verify_result_ = SSL_get_verify_result(ssl2); if (verify_result_ != X509_V_OK) { + last_openssl_error_ = static_cast<unsigned long>(verify_result_); error = Error::SSLServerVerification; + output_error_log(error, nullptr); return false; } @@ -9595,13 +11060,17 @@ auto se = detail::scope_exit([&] { X509_free(server_cert); }); if (server_cert == nullptr) { + last_openssl_error_ = ERR_get_error(); error = Error::SSLServerVerification; + output_error_log(error, nullptr); return false; } if (server_hostname_verification_) { if (!verify_host(server_cert)) { + last_openssl_error_ = X509_V_ERR_HOSTNAME_MISMATCH; error = Error::SSLServerHostnameVerification; + output_error_log(error, nullptr); return false; } } @@ -9858,72 +11327,54 @@ return cli_ != nullptr && cli_->is_valid(); } -inline Result Client::Get(const std::string &path) { return cli_->Get(path); } -inline Result Client::Get(const std::string &path, const Headers &headers) { - return cli_->Get(path, headers); -} -inline Result Client::Get(const std::string &path, Progress progress) { +inline Result Client::Get(const std::string &path, DownloadProgress progress) { return cli_->Get(path, std::move(progress)); } inline Result Client::Get(const std::string &path, const Headers &headers, - Progress progress) { + DownloadProgress progress) { return cli_->Get(path, headers, std::move(progress)); } inline Result Client::Get(const std::string &path, - ContentReceiver content_receiver) { - return cli_->Get(path, std::move(content_receiver)); -} -inline Result Client::Get(const std::string &path, const Headers &headers, - ContentReceiver content_receiver) { - return cli_->Get(path, headers, std::move(content_receiver)); -} -inline Result Client::Get(const std::string &path, - ContentReceiver content_receiver, Progress progress) { + ContentReceiver content_receiver, + DownloadProgress progress) { return cli_->Get(path, std::move(content_receiver), std::move(progress)); } inline Result Client::Get(const std::string &path, const Headers &headers, - ContentReceiver content_receiver, Progress progress) { + ContentReceiver content_receiver, + DownloadProgress progress) { return cli_->Get(path, headers, std::move(content_receiver), std::move(progress)); } inline Result Client::Get(const std::string &path, ResponseHandler response_handler, - ContentReceiver content_receiver) { - return cli_->Get(path, std::move(response_handler), - std::move(content_receiver)); -} -inline Result Client::Get(const std::string &path, const Headers &headers, - ResponseHandler response_handler, - ContentReceiver content_receiver) { - return cli_->Get(path, headers, std::move(response_handler), - std::move(content_receiver)); -} -inline Result Client::Get(const std::string &path, - ResponseHandler response_handler, - ContentReceiver content_receiver, Progress progress) { + ContentReceiver content_receiver, + DownloadProgress progress) { return cli_->Get(path, std::move(response_handler), std::move(content_receiver), std::move(progress)); } inline Result Client::Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, - ContentReceiver content_receiver, Progress progress) { + ContentReceiver content_receiver, + DownloadProgress progress) { return cli_->Get(path, headers, std::move(response_handler), std::move(content_receiver), std::move(progress)); } inline Result Client::Get(const std::string &path, const Params ¶ms, - const Headers &headers, Progress progress) { + const Headers &headers, DownloadProgress progress) { return cli_->Get(path, params, headers, std::move(progress)); } inline Result Client::Get(const std::string &path, const Params ¶ms, const Headers &headers, - ContentReceiver content_receiver, Progress progress) { + ContentReceiver content_receiver, + DownloadProgress progress) { return cli_->Get(path, params, headers, std::move(content_receiver), std::move(progress)); } inline Result Client::Get(const std::string &path, const Params ¶ms, const Headers &headers, ResponseHandler response_handler, - ContentReceiver content_receiver, Progress progress) { + ContentReceiver content_receiver, + DownloadProgress progress) { return cli_->Get(path, params, headers, std::move(response_handler), std::move(content_receiver), std::move(progress)); } @@ -9939,60 +11390,55 @@ } inline Result Client::Post(const std::string &path, const char *body, size_t content_length, - const std::string &content_type) { - return cli_->Post(path, body, content_length, content_type); + const std::string &content_type, + UploadProgress progress) { + return cli_->Post(path, body, content_length, content_type, progress); } inline Result Client::Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, - const std::string &content_type) { - return cli_->Post(path, headers, body, content_length, content_type); -} -inline Result Client::Post(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type, Progress progress) { + const std::string &content_type, + UploadProgress progress) { return cli_->Post(path, headers, body, content_length, content_type, progress); } inline Result Client::Post(const std::string &path, const std::string &body, - const std::string &content_type) { - return cli_->Post(path, body, content_type); -} -inline Result Client::Post(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress) { + const std::string &content_type, + UploadProgress progress) { return cli_->Post(path, body, content_type, progress); } inline Result Client::Post(const std::string &path, const Headers &headers, const std::string &body, - const std::string &content_type) { - return cli_->Post(path, headers, body, content_type); -} -inline Result Client::Post(const std::string &path, const Headers &headers, - const std::string &body, - const std::string &content_type, Progress progress) { + const std::string &content_type, + UploadProgress progress) { return cli_->Post(path, headers, body, content_type, progress); } inline Result Client::Post(const std::string &path, size_t content_length, ContentProvider content_provider, - const std::string &content_type) { + const std::string &content_type, + UploadProgress progress) { return cli_->Post(path, content_length, std::move(content_provider), - content_type); + content_type, progress); } inline Result Client::Post(const std::string &path, ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return cli_->Post(path, std::move(content_provider), content_type); + const std::string &content_type, + UploadProgress progress) { + return cli_->Post(path, std::move(content_provider), content_type, progress); } inline Result Client::Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, - const std::string &content_type) { + const std::string &content_type, + UploadProgress progress) { return cli_->Post(path, headers, content_length, std::move(content_provider), - content_type); + content_type, progress); } inline Result Client::Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return cli_->Post(path, headers, std::move(content_provider), content_type); + const std::string &content_type, + UploadProgress progress) { + return cli_->Post(path, headers, std::move(content_provider), content_type, + progress); } inline Result Client::Post(const std::string &path, const Params ¶ms) { return cli_->Post(path, params); @@ -10001,85 +11447,91 @@ const Params ¶ms) { return cli_->Post(path, headers, params); } -inline Result Client::Post(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress) { - return cli_->Post(path, headers, params, progress); -} inline Result Client::Post(const std::string &path, - const MultipartFormDataItems &items) { - return cli_->Post(path, items); + const UploadFormDataItems &items, + UploadProgress progress) { + return cli_->Post(path, items, progress); } inline Result Client::Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items) { - return cli_->Post(path, headers, items); + const UploadFormDataItems &items, + UploadProgress progress) { + return cli_->Post(path, headers, items, progress); } inline Result Client::Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const std::string &boundary) { - return cli_->Post(path, headers, items, boundary); + const UploadFormDataItems &items, + const std::string &boundary, + UploadProgress progress) { + return cli_->Post(path, headers, items, boundary, progress); } -inline Result -Client::Post(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items) { - return cli_->Post(path, headers, items, provider_items); +inline Result Client::Post(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) { + return cli_->Post(path, headers, items, provider_items, progress); } +inline Result Client::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) { + return cli_->Post(path, headers, body, content_type, content_receiver, + progress); +} + inline Result Client::Put(const std::string &path) { return cli_->Put(path); } +inline Result Client::Put(const std::string &path, const Headers &headers) { + return cli_->Put(path, headers); +} inline Result Client::Put(const std::string &path, const char *body, size_t content_length, - const std::string &content_type) { - return cli_->Put(path, body, content_length, content_type); + const std::string &content_type, + UploadProgress progress) { + return cli_->Put(path, body, content_length, content_type, progress); } inline Result Client::Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, - const std::string &content_type) { - return cli_->Put(path, headers, body, content_length, content_type); -} -inline Result Client::Put(const std::string &path, const Headers &headers, - const char *body, size_t content_length, - const std::string &content_type, Progress progress) { + const std::string &content_type, + UploadProgress progress) { return cli_->Put(path, headers, body, content_length, content_type, progress); } inline Result Client::Put(const std::string &path, const std::string &body, - const std::string &content_type) { - return cli_->Put(path, body, content_type); -} -inline Result Client::Put(const std::string &path, const std::string &body, - const std::string &content_type, Progress progress) { + const std::string &content_type, + UploadProgress progress) { return cli_->Put(path, body, content_type, progress); } inline Result Client::Put(const std::string &path, const Headers &headers, const std::string &body, - const std::string &content_type) { - return cli_->Put(path, headers, body, content_type); -} -inline Result Client::Put(const std::string &path, const Headers &headers, - const std::string &body, - const std::string &content_type, Progress progress) { + const std::string &content_type, + UploadProgress progress) { return cli_->Put(path, headers, body, content_type, progress); } inline Result Client::Put(const std::string &path, size_t content_length, ContentProvider content_provider, - const std::string &content_type) { + const std::string &content_type, + UploadProgress progress) { return cli_->Put(path, content_length, std::move(content_provider), - content_type); + content_type, progress); } inline Result Client::Put(const std::string &path, ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return cli_->Put(path, std::move(content_provider), content_type); + const std::string &content_type, + UploadProgress progress) { + return cli_->Put(path, std::move(content_provider), content_type, progress); } inline Result Client::Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, - const std::string &content_type) { + const std::string &content_type, + UploadProgress progress) { return cli_->Put(path, headers, content_length, std::move(content_provider), - content_type); + content_type, progress); } inline Result Client::Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return cli_->Put(path, headers, std::move(content_provider), content_type); + const std::string &content_type, + UploadProgress progress) { + return cli_->Put(path, headers, std::move(content_provider), content_type, + progress); } inline Result Client::Put(const std::string &path, const Params ¶ms) { return cli_->Put(path, params); @@ -10088,147 +11540,174 @@ const Params ¶ms) { return cli_->Put(path, headers, params); } -inline Result Client::Put(const std::string &path, const Headers &headers, - const Params ¶ms, Progress progress) { - return cli_->Put(path, headers, params, progress); -} inline Result Client::Put(const std::string &path, - const MultipartFormDataItems &items) { - return cli_->Put(path, items); + const UploadFormDataItems &items, + UploadProgress progress) { + return cli_->Put(path, items, progress); } inline Result Client::Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items) { - return cli_->Put(path, headers, items); + const UploadFormDataItems &items, + UploadProgress progress) { + return cli_->Put(path, headers, items, progress); } inline Result Client::Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const std::string &boundary) { - return cli_->Put(path, headers, items, boundary); + const UploadFormDataItems &items, + const std::string &boundary, + UploadProgress progress) { + return cli_->Put(path, headers, items, boundary, progress); } -inline Result -Client::Put(const std::string &path, const Headers &headers, - const MultipartFormDataItems &items, - const MultipartFormDataProviderItems &provider_items) { - return cli_->Put(path, headers, items, provider_items); +inline Result Client::Put(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) { + return cli_->Put(path, headers, items, provider_items, progress); } +inline Result Client::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) { + return cli_->Put(path, headers, body, content_type, content_receiver, + progress); +} + inline Result Client::Patch(const std::string &path) { return cli_->Patch(path); } -inline Result Client::Patch(const std::string &path, const char *body, - size_t content_length, - const std::string &content_type) { - return cli_->Patch(path, body, content_length, content_type); +inline Result Client::Patch(const std::string &path, const Headers &headers) { + return cli_->Patch(path, headers); } inline Result Client::Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, - Progress progress) { + UploadProgress progress) { return cli_->Patch(path, body, content_length, content_type, progress); } inline Result Client::Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, - const std::string &content_type) { - return cli_->Patch(path, headers, body, content_length, content_type); -} -inline Result Client::Patch(const std::string &path, const Headers &headers, - const char *body, size_t content_length, const std::string &content_type, - Progress progress) { + UploadProgress progress) { return cli_->Patch(path, headers, body, content_length, content_type, progress); } inline Result Client::Patch(const std::string &path, const std::string &body, - const std::string &content_type) { - return cli_->Patch(path, body, content_type); -} -inline Result Client::Patch(const std::string &path, const std::string &body, const std::string &content_type, - Progress progress) { + UploadProgress progress) { return cli_->Patch(path, body, content_type, progress); } inline Result Client::Patch(const std::string &path, const Headers &headers, const std::string &body, - const std::string &content_type) { - return cli_->Patch(path, headers, body, content_type); -} -inline Result Client::Patch(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type, - Progress progress) { + UploadProgress progress) { return cli_->Patch(path, headers, body, content_type, progress); } inline Result Client::Patch(const std::string &path, size_t content_length, ContentProvider content_provider, - const std::string &content_type) { + const std::string &content_type, + UploadProgress progress) { return cli_->Patch(path, content_length, std::move(content_provider), - content_type); + content_type, progress); } inline Result Client::Patch(const std::string &path, ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return cli_->Patch(path, std::move(content_provider), content_type); + const std::string &content_type, + UploadProgress progress) { + return cli_->Patch(path, std::move(content_provider), content_type, progress); } inline Result Client::Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, - const std::string &content_type) { + const std::string &content_type, + UploadProgress progress) { return cli_->Patch(path, headers, content_length, std::move(content_provider), - content_type); + content_type, progress); } inline Result Client::Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, - const std::string &content_type) { - return cli_->Patch(path, headers, std::move(content_provider), content_type); + const std::string &content_type, + UploadProgress progress) { + return cli_->Patch(path, headers, std::move(content_provider), content_type, + progress); } -inline Result Client::Delete(const std::string &path) { - return cli_->Delete(path); +inline Result Client::Patch(const std::string &path, const Params ¶ms) { + return cli_->Patch(path, params); } -inline Result Client::Delete(const std::string &path, const Headers &headers) { - return cli_->Delete(path, headers); +inline Result Client::Patch(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Patch(path, headers, params); } -inline Result Client::Delete(const std::string &path, const char *body, - size_t content_length, - const std::string &content_type) { - return cli_->Delete(path, body, content_length, content_type); +inline Result Client::Patch(const std::string &path, + const UploadFormDataItems &items, + UploadProgress progress) { + return cli_->Patch(path, items, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + UploadProgress progress) { + return cli_->Patch(path, headers, items, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const std::string &boundary, + UploadProgress progress) { + return cli_->Patch(path, headers, items, boundary, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const UploadFormDataItems &items, + const FormDataProviderItems &provider_items, + UploadProgress progress) { + return cli_->Patch(path, headers, items, provider_items, progress); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type, + ContentReceiver content_receiver, + DownloadProgress progress) { + return cli_->Patch(path, headers, body, content_type, content_receiver, + progress); +} + +inline Result Client::Delete(const std::string &path, + DownloadProgress progress) { + return cli_->Delete(path, progress); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + DownloadProgress progress) { + return cli_->Delete(path, headers, progress); } inline Result Client::Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, - Progress progress) { + DownloadProgress progress) { return cli_->Delete(path, body, content_length, content_type, progress); } inline Result Client::Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, - const std::string &content_type) { - return cli_->Delete(path, headers, body, content_length, content_type); -} -inline Result Client::Delete(const std::string &path, const Headers &headers, - const char *body, size_t content_length, const std::string &content_type, - Progress progress) { + DownloadProgress progress) { return cli_->Delete(path, headers, body, content_length, content_type, progress); } inline Result Client::Delete(const std::string &path, const std::string &body, - const std::string &content_type) { - return cli_->Delete(path, body, content_type); -} -inline Result Client::Delete(const std::string &path, const std::string &body, const std::string &content_type, - Progress progress) { + DownloadProgress progress) { return cli_->Delete(path, body, content_type, progress); } inline Result Client::Delete(const std::string &path, const Headers &headers, const std::string &body, - const std::string &content_type) { - return cli_->Delete(path, headers, body, content_type); -} -inline Result Client::Delete(const std::string &path, const Headers &headers, - const std::string &body, const std::string &content_type, - Progress progress) { + DownloadProgress progress) { return cli_->Delete(path, headers, body, content_type, progress); } +inline Result Client::Delete(const std::string &path, const Params ¶ms, + DownloadProgress progress) { + return cli_->Delete(path, params, progress); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const Params ¶ms, DownloadProgress progress) { + return cli_->Delete(path, headers, params, progress); +} + inline Result Client::Options(const std::string &path) { return cli_->Options(path); } @@ -10307,7 +11786,12 @@ cli_->set_follow_location(on); } -inline void Client::set_url_encode(bool on) { cli_->set_url_encode(on); } +inline void Client::set_path_encode(bool on) { cli_->set_path_encode(on); } + +[[deprecated("Use set_path_encode instead")]] +inline void Client::set_url_encode(bool on) { + cli_->set_path_encode(on); +} inline void Client::set_compress(bool on) { cli_->set_compress(on); } @@ -10353,6 +11837,10 @@ cli_->set_logger(std::move(logger)); } +inline void Client::set_error_logger(ErrorLogger error_logger) { + cli_->set_error_logger(std::move(error_logger)); +} + #ifdef CPPHTTPLIB_OPENSSL_SUPPORT inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path, const std::string &ca_cert_dir_path) { @@ -10388,8 +11876,4 @@ } // namespace httplib -#ifdef _WIN32 -#undef poll -#endif - #endif // CPPHTTPLIB_HTTPLIB_H
diff --git a/third_party/crashpad/crashpad/util/mach/notify_server_test.cc b/third_party/crashpad/crashpad/util/mach/notify_server_test.cc index 2c9e7e6..e068e0e 100644 --- a/third_party/crashpad/crashpad/util/mach/notify_server_test.cc +++ b/third_party/crashpad/crashpad/util/mach/notify_server_test.cc
@@ -33,7 +33,6 @@ using testing::AllOf; using testing::DoAll; using testing::Eq; -using testing::Invoke; using testing::Pointee; using testing::ResultOf; using testing::Return; @@ -528,7 +527,7 @@ ResultOf(DeadNameRightRefCount, 2)), ResultOf(AuditPIDFromMachMessageTrailer, 0))) .WillOnce( - DoAll(WithArg<1>(Invoke(MachPortDeallocate)), Return(MIG_NO_REPLY))) + DoAll(WithArg<1>(MachPortDeallocate), Return(MIG_NO_REPLY))) .RetiresOnSaturation(); receive_right.reset();
diff --git a/third_party/crashpad/crashpad/util/net/http_transport_test_server.cc b/third_party/crashpad/crashpad/util/net/http_transport_test_server.cc index b392d88b..e4df221b 100644 --- a/third_party/crashpad/crashpad/util/net/http_transport_test_server.cc +++ b/third_party/crashpad/crashpad/util/net/http_transport_test_server.cc
@@ -96,7 +96,7 @@ std::string boundary; httplib::detail::parse_multipart_boundary( req.get_header_value("Content-Type"), boundary); - for (const auto& part : req.files) { + for (const auto& part : req.form.fields) { to_stdout += "--" + boundary + "\r\n"; to_stdout += "Content-Disposition: form-data; name=\"" + part.first + "\"\r\n\r\n";
diff --git a/third_party/crossbench b/third_party/crossbench index 5cedfa8..0dc6211 160000 --- a/third_party/crossbench +++ b/third_party/crossbench
@@ -1 +1 @@ -Subproject commit 5cedfa8fd09b3fc8aa4d0396d2794d08708998f1 +Subproject commit 0dc6211158fa20bde2b712e87fe292b610e75afa
diff --git a/third_party/dawn b/third_party/dawn index 571a08a..49b39c0 160000 --- a/third_party/dawn +++ b/third_party/dawn
@@ -1 +1 @@ -Subproject commit 571a08a06de340c8b41d96b07043aa42a11e32ac +Subproject commit 49b39c02c939dfffd9efdac1630e358bc006d702
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src index 7b5a5c0..975b3f7 160000 --- a/third_party/devtools-frontend/src +++ b/third_party/devtools-frontend/src
@@ -1 +1 @@ -Subproject commit 7b5a5c02f8c5013a12f57f8142ceeee464084381 +Subproject commit 975b3f788309aad670821b7228a4419c6aa28a4c
diff --git a/third_party/fontconfig/BUILD.gn b/third_party/fontconfig/BUILD.gn index f05dc95..4155dfb1 100644 --- a/third_party/fontconfig/BUILD.gn +++ b/third_party/fontconfig/BUILD.gn
@@ -41,6 +41,7 @@ rust_static_library("fontconfig_fontations_ffi") { allow_unsafe = true # Needed for FFI that underpins the `cxx` crate. + edition = "2021" crate_root = "src/fc-fontations/mod.rs" sources = [ "src/fc-fontations/attributes.rs",
diff --git a/third_party/libipp/libipp b/third_party/libipp/libipp index 2209bb8..4be5f77 160000 --- a/third_party/libipp/libipp +++ b/third_party/libipp/libipp
@@ -1 +1 @@ -Subproject commit 2209bb84a8e122dab7c02fe66cc61a7b42873d7f +Subproject commit 4be5f77f672a3a9f1bbf3c935fb0ea8b3f86ce61
diff --git a/third_party/libwebp/README.chromium b/third_party/libwebp/README.chromium index d24da0b..fcb3d41 100644 --- a/third_party/libwebp/README.chromium +++ b/third_party/libwebp/README.chromium
@@ -2,7 +2,7 @@ Short Name: libwebp URL: https://chromium.googlesource.com/webm/libwebp Version: N/A -Revision: b0e8039062eedbcb20ebb1bad62bfeaee2b94ec6 +Revision: c00d83f6642e7838a12bb03bca94237f03cc2e00 Update Mechanism: Manual CPEPrefix: cpe:/a:webmproject:libwebp:1.6.0 License: BSD-3-Clause, Patent
diff --git a/third_party/libwebp/src b/third_party/libwebp/src index b0e8039..c00d83f 160000 --- a/third_party/libwebp/src +++ b/third_party/libwebp/src
@@ -1 +1 @@ -Subproject commit b0e8039062eedbcb20ebb1bad62bfeaee2b94ec6 +Subproject commit c00d83f6642e7838a12bb03bca94237f03cc2e00
diff --git a/third_party/perfetto b/third_party/perfetto index da58fbd..6a960b04 160000 --- a/third_party/perfetto +++ b/third_party/perfetto
@@ -1 +1 @@ -Subproject commit da58fbdf0f14a29a3ccd4f304ccb83ef4940185d +Subproject commit 6a960b04f6e20ace0f0e084be42a660f7ff6490a
diff --git a/third_party/rust/chromium_crates_io/Cargo.lock b/third_party/rust/chromium_crates_io/Cargo.lock index 98e8392..48b6c8cd 100644 --- a/third_party/rust/chromium_crates_io/Cargo.lock +++ b/third_party/rust/chromium_crates_io/Cargo.lock
@@ -358,7 +358,7 @@ [[package]] name = "flate2" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "crc32fast", @@ -679,7 +679,7 @@ [[package]] name = "libc" -version = "0.2.176" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -969,7 +969,7 @@ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]]
diff --git a/third_party/rust/chromium_crates_io/gnrt_config.toml b/third_party/rust/chromium_crates_io/gnrt_config.toml index b526e749..c5a4440 100644 --- a/third_party/rust/chromium_crates_io/gnrt_config.toml +++ b/third_party/rust/chromium_crates_io/gnrt_config.toml
@@ -233,7 +233,7 @@ # https://crbug.com/396419181. configs_to_remove = ['//build/config/compiler:default_optimization'] configs_to_add = ['//build/config/compiler:optimize'] -allow_unsafe = false +allow_unsafe = true [crate.foldhash.extra_kv] allow_unsafe = true
diff --git a/third_party/rust/chromium_crates_io/supply-chain/config.toml b/third_party/rust/chromium_crates_io/supply-chain/config.toml index d24f614..c646054 100644 --- a/third_party/rust/chromium_crates_io/supply-chain/config.toml +++ b/third_party/rust/chromium_crates_io/supply-chain/config.toml
@@ -158,7 +158,7 @@ [policy."fixed_decimal:0.7.0"] criteria = ["crypto-safe", "safe-to-deploy", "ub-risk-2"] -[policy."flate2:1.1.2"] +[policy."flate2:1.1.4"] criteria = ["crypto-safe", "safe-to-deploy", "ub-risk-2"] [policy."foldhash:0.2.0"] @@ -257,7 +257,7 @@ [policy."lazy_static:1.5.0"] criteria = ["crypto-safe", "safe-to-deploy", "ub-risk-2"] -[policy."libc:0.2.176"] +[policy."libc:0.2.177"] criteria = ["crypto-safe", "safe-to-deploy", "ub-risk-2"] [policy."libm:0.2.15"] @@ -371,7 +371,7 @@ [policy."smallvec:1.15.1"] criteria = ["crypto-safe", "safe-to-deploy", "ub-risk-2"] -[policy."stable_deref_trait:1.2.0"] +[policy."stable_deref_trait:1.2.1"] criteria = ["crypto-safe", "safe-to-deploy", "ub-risk-2"] [policy."static_assertions:1.1.0"]
diff --git a/third_party/rust/chromium_crates_io/vendor/flate2-v1/.cargo_vcs_info.json b/third_party/rust/chromium_crates_io/vendor/flate2-v1/.cargo_vcs_info.json index 408a8a1..4361567 100644 --- a/third_party/rust/chromium_crates_io/vendor/flate2-v1/.cargo_vcs_info.json +++ b/third_party/rust/chromium_crates_io/vendor/flate2-v1/.cargo_vcs_info.json
@@ -1,6 +1,6 @@ { "git": { - "sha1": "ac4d950ffdeab209350423c528d876a7a7811abb" + "sha1": "ac412e96d57f40fa6def29ad4b8bee46d1121f54" }, "path_in_vcs": "" } \ No newline at end of file
diff --git a/third_party/rust/chromium_crates_io/vendor/flate2-v1/Cargo.lock b/third_party/rust/chromium_crates_io/vendor/flate2-v1/Cargo.lock index 21e8a6c..4e779ca 100644 --- a/third_party/rust/chromium_crates_io/vendor/flate2-v1/Cargo.lock +++ b/third_party/rust/chromium_crates_io/vendor/flate2-v1/Cargo.lock
@@ -58,7 +58,7 @@ [[package]] name = "flate2" -version = "1.1.2" +version = "1.1.4" dependencies = [ "cloudflare-zlib-sys", "crc32fast", @@ -138,6 +138,7 @@ checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -221,6 +222,12 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/third_party/rust/chromium_crates_io/vendor/flate2-v1/Cargo.toml b/third_party/rust/chromium_crates_io/vendor/flate2-v1/Cargo.toml index 677fca0..50a1ed81 100644 --- a/third_party/rust/chromium_crates_io/vendor/flate2-v1/Cargo.toml +++ b/third_party/rust/chromium_crates_io/vendor/flate2-v1/Cargo.toml
@@ -13,7 +13,7 @@ edition = "2018" rust-version = "1.67.0" name = "flate2" -version = "1.1.2" +version = "1.1.4" authors = [ "Alex Crichton <alex@alexcrichton.com>", "Josh Triplett <josh@joshtriplett.org>", @@ -201,7 +201,7 @@ path = "tests/zero-write.rs" [dependencies.cloudflare-zlib-sys] -version = "0.3.5" +version = "0.3.6" optional = true [dependencies.crc32fast] @@ -227,7 +227,10 @@ [dependencies.miniz_oxide] version = "0.8.5" -features = ["with-alloc"] +features = [ + "with-alloc", + "simd", +] optional = true default-features = false @@ -240,5 +243,8 @@ [target.'cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))'.dependencies.miniz_oxide] version = "0.8.5" -features = ["with-alloc"] +features = [ + "with-alloc", + "simd", +] default-features = false
diff --git a/third_party/rust/chromium_crates_io/vendor/flate2-v1/Cargo.toml.orig b/third_party/rust/chromium_crates_io/vendor/flate2-v1/Cargo.toml.orig index a716c5e..adc8ee9a 100644 --- a/third_party/rust/chromium_crates_io/vendor/flate2-v1/Cargo.toml.orig +++ b/third_party/rust/chromium_crates_io/vendor/flate2-v1/Cargo.toml.orig
@@ -1,7 +1,7 @@ [package] name = "flate2" authors = ["Alex Crichton <alex@alexcrichton.com>", "Josh Triplett <josh@joshtriplett.org>"] -version = "1.1.2" +version = "1.1.4" edition = "2018" license = "MIT OR Apache-2.0" readme = "README.md" @@ -23,12 +23,12 @@ libz-ng-sys = { version = "1.1.16", optional = true } # this matches the default features, but we don't want to depend on the default features staying the same libz-rs-sys = { version = "0.5.1", optional = true, default-features = false, features = ["std", "rust-allocator"] } -cloudflare-zlib-sys = { version = "0.3.5", optional = true } -miniz_oxide = { version = "0.8.5", optional = true, default-features = false, features = ["with-alloc"] } +cloudflare-zlib-sys = { version = "0.3.6", optional = true } +miniz_oxide = { version = "0.8.5", optional = true, default-features = false, features = ["with-alloc", "simd"] } crc32fast = "1.2.0" [target.'cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))'.dependencies] -miniz_oxide = { version = "0.8.5", default-features = false, features = ["with-alloc"] } +miniz_oxide = { version = "0.8.5", default-features = false, features = ["with-alloc", "simd"] } [dev-dependencies] rand = "0.9"
diff --git a/third_party/rust/chromium_crates_io/vendor/flate2-v1/examples/decompress_file.rs b/third_party/rust/chromium_crates_io/vendor/flate2-v1/examples/decompress_file.rs index 6b2ede9..ddf8a12 100644 --- a/third_party/rust/chromium_crates_io/vendor/flate2-v1/examples/decompress_file.rs +++ b/third_party/rust/chromium_crates_io/vendor/flate2-v1/examples/decompress_file.rs
@@ -17,7 +17,7 @@ let start = Instant::now(); let mut decoder = bufread::GzDecoder::new(input); copy(&mut decoder, &mut output).unwrap(); - println!("Source len: {:?}", source_len); + println!("Source len: {source_len:?}"); println!("Target len: {:?}", output.metadata().unwrap().len()); println!("Elapsed: {:?}", start.elapsed()); }
diff --git a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/deflate/bufread.rs b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/deflate/bufread.rs index d2f2a93e..0476ffd 100644 --- a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/deflate/bufread.rs +++ b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/deflate/bufread.rs
@@ -264,7 +264,7 @@ let compressed = { let mut e = write::DeflateEncoder::new(Vec::new(), Compression::default()); - e.write(expected.as_ref()).unwrap(); + e.write_all(expected.as_ref()).unwrap(); let mut b = e.finish().unwrap(); b.push(b'x'); b
diff --git a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/deflate/write.rs b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/deflate/write.rs index 402899a..b4eb490f 100644 --- a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/deflate/write.rs +++ b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/deflate/write.rs
@@ -342,7 +342,7 @@ fn decode_extra_data() { let compressed = { let mut e = DeflateEncoder::new(Vec::new(), Compression::default()); - e.write(STR.as_ref()).unwrap(); + e.write_all(STR.as_ref()).unwrap(); let mut b = e.finish().unwrap(); b.push(b'x'); b
diff --git a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/ffi/c.rs b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/ffi/c.rs index 8b4cc78..e3fb171e5 100644 --- a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/ffi/c.rs +++ b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/ffi/c.rs
@@ -1,7 +1,7 @@ //! Implementation for C backends. -use std::cmp; use std::fmt; use std::marker; +use std::mem::MaybeUninit; use std::os::raw::{c_int, c_uint}; use std::ptr; @@ -226,6 +226,52 @@ pub inner: Stream<DirDecompress>, } +impl Inflate { + unsafe fn decompress_inner( + &mut self, + input: &[u8], + output_ptr: *mut u8, + output_len: usize, + flush: FlushDecompress, + ) -> Result<Status, DecompressError> { + let raw = self.inner.stream_wrapper.inner; + // SAFETY: The field `inner` must always be accessed as a raw pointer, + // since it points to a cyclic structure. No copies of `inner` can be + // retained for longer than the lifetime of `self`. + unsafe { + (*raw).msg = ptr::null_mut(); + (*raw).next_in = input.as_ptr() as *mut u8; + (*raw).avail_in = input.len().min(c_uint::MAX as usize) as c_uint; + (*raw).next_out = output_ptr; + (*raw).avail_out = output_len.min(c_uint::MAX as usize) as c_uint; + + let rc = mz_inflate(raw, flush as c_int); + + // Unfortunately the total counters provided by zlib might be only + // 32 bits wide and overflow while processing large amounts of data. + self.inner.total_in += ((*raw).next_in as usize - input.as_ptr() as usize) as u64; + self.inner.total_out += ((*raw).next_out as usize - output_ptr as usize) as u64; + + // reset these pointers so we don't accidentally read them later + (*raw).next_in = ptr::null_mut(); + (*raw).avail_in = 0; + (*raw).next_out = ptr::null_mut(); + (*raw).avail_out = 0; + + match rc { + MZ_DATA_ERROR | MZ_STREAM_ERROR | MZ_MEM_ERROR => { + mem::decompress_failed(self.inner.msg()) + } + MZ_OK => Ok(Status::Ok), + MZ_BUF_ERROR => Ok(Status::BufError), + MZ_STREAM_END => Ok(Status::StreamEnd), + #[allow(clippy::unnecessary_cast)] + MZ_NEED_DICT => mem::decompress_need_dict((*raw).adler as u32), + c => panic!("unknown return code: {}", c), + } + } + } +} impl InflateBackend for Inflate { fn make(zlib_header: bool, window_bits: u8) -> Self { unsafe { @@ -256,42 +302,15 @@ output: &mut [u8], flush: FlushDecompress, ) -> Result<Status, DecompressError> { - let raw = self.inner.stream_wrapper.inner; - // SAFETY: The field `inner` must always be accessed as a raw pointer, - // since it points to a cyclic structure. No copies of `inner` can be - // retained for longer than the lifetime of `self`. - unsafe { - (*raw).msg = ptr::null_mut(); - (*raw).next_in = input.as_ptr() as *mut u8; - (*raw).avail_in = cmp::min(input.len(), c_uint::MAX as usize) as c_uint; - (*raw).next_out = output.as_mut_ptr(); - (*raw).avail_out = cmp::min(output.len(), c_uint::MAX as usize) as c_uint; - - let rc = mz_inflate(raw, flush as c_int); - - // Unfortunately the total counters provided by zlib might be only - // 32 bits wide and overflow while processing large amounts of data. - self.inner.total_in += ((*raw).next_in as usize - input.as_ptr() as usize) as u64; - self.inner.total_out += ((*raw).next_out as usize - output.as_ptr() as usize) as u64; - - // reset these pointers so we don't accidentally read them later - (*raw).next_in = ptr::null_mut(); - (*raw).avail_in = 0; - (*raw).next_out = ptr::null_mut(); - (*raw).avail_out = 0; - - match rc { - MZ_DATA_ERROR | MZ_STREAM_ERROR | MZ_MEM_ERROR => { - mem::decompress_failed(self.inner.msg()) - } - MZ_OK => Ok(Status::Ok), - MZ_BUF_ERROR => Ok(Status::BufError), - MZ_STREAM_END => Ok(Status::StreamEnd), - #[allow(clippy::unnecessary_cast)] - MZ_NEED_DICT => mem::decompress_need_dict((*raw).adler as u32), - c => panic!("unknown return code: {}", c), - } - } + unsafe { self.decompress_inner(input, output.as_mut_ptr(), output.len(), flush) } + } + fn decompress_uninit( + &mut self, + input: &[u8], + output: &mut [MaybeUninit<u8>], + flush: FlushDecompress, + ) -> Result<Status, DecompressError> { + unsafe { self.decompress_inner(input, output.as_mut_ptr() as *mut _, output.len(), flush) } } fn reset(&mut self, zlib_header: bool) { @@ -325,6 +344,49 @@ pub inner: Stream<DirCompress>, } +impl Deflate { + unsafe fn compress_inner( + &mut self, + input: &[u8], + output_ptr: *mut u8, + output_len: usize, + flush: FlushCompress, + ) -> Result<Status, CompressError> { + let raw = self.inner.stream_wrapper.inner; + // SAFETY: The field `inner` must always be accessed as a raw pointer, + // since it points to a cyclic structure. No copies of `inner` can be + // retained for longer than the lifetime of `self`. + unsafe { + (*raw).msg = ptr::null_mut(); + (*raw).next_in = input.as_ptr() as *mut _; + (*raw).avail_in = input.len().min(c_uint::MAX as usize) as c_uint; + (*raw).next_out = output_ptr; + (*raw).avail_out = output_len.min(c_uint::MAX as usize) as c_uint; + + let rc = mz_deflate(raw, flush as c_int); + + // Unfortunately the total counters provided by zlib might be only + // 32 bits wide and overflow while processing large amounts of data. + + self.inner.total_in += ((*raw).next_in as usize - input.as_ptr() as usize) as u64; + self.inner.total_out += ((*raw).next_out as usize - output_ptr as usize) as u64; + // reset these pointers so we don't accidentally read them later + (*raw).next_in = ptr::null_mut(); + (*raw).avail_in = 0; + (*raw).next_out = ptr::null_mut(); + (*raw).avail_out = 0; + + match rc { + MZ_OK => Ok(Status::Ok), + MZ_BUF_ERROR => Ok(Status::BufError), + MZ_STREAM_END => Ok(Status::StreamEnd), + MZ_STREAM_ERROR => mem::compress_failed(self.inner.msg()), + c => panic!("unknown return code: {}", c), + } + } + } +} + impl DeflateBackend for Deflate { fn make(level: Compression, zlib_header: bool, window_bits: u8) -> Self { unsafe { @@ -358,40 +420,16 @@ output: &mut [u8], flush: FlushCompress, ) -> Result<Status, CompressError> { - let raw = self.inner.stream_wrapper.inner; - // SAFETY: The field `inner` must always be accessed as a raw pointer, - // since it points to a cyclic structure. No copies of `inner` can be - // retained for longer than the lifetime of `self`. - unsafe { - (*raw).msg = ptr::null_mut(); - (*raw).next_in = input.as_ptr() as *mut _; - (*raw).avail_in = cmp::min(input.len(), c_uint::MAX as usize) as c_uint; - (*raw).next_out = output.as_mut_ptr(); - (*raw).avail_out = cmp::min(output.len(), c_uint::MAX as usize) as c_uint; - - let rc = mz_deflate(raw, flush as c_int); - - // Unfortunately the total counters provided by zlib might be only - // 32 bits wide and overflow while processing large amounts of data. - - self.inner.total_in += ((*raw).next_in as usize - input.as_ptr() as usize) as u64; - self.inner.total_out += ((*raw).next_out as usize - output.as_ptr() as usize) as u64; - // reset these pointers so we don't accidentally read them later - (*raw).next_in = ptr::null_mut(); - (*raw).avail_in = 0; - (*raw).next_out = ptr::null_mut(); - (*raw).avail_out = 0; - - match rc { - MZ_OK => Ok(Status::Ok), - MZ_BUF_ERROR => Ok(Status::BufError), - MZ_STREAM_END => Ok(Status::StreamEnd), - MZ_STREAM_ERROR => mem::compress_failed(self.inner.msg()), - c => panic!("unknown return code: {}", c), - } - } + unsafe { self.compress_inner(input, output.as_mut_ptr(), output.len(), flush) } } - + fn compress_uninit( + &mut self, + input: &[u8], + output: &mut [MaybeUninit<u8>], + flush: FlushCompress, + ) -> Result<Status, CompressError> { + unsafe { self.compress_inner(input, output.as_mut_ptr() as *mut _, output.len(), flush) } + } fn reset(&mut self) { self.inner.total_in = 0; self.inner.total_out = 0; @@ -465,13 +503,6 @@ pub const MZ_DEFAULT_WINDOW_BITS: c_int = 15; - #[cfg(feature = "zlib-ng")] - const ZLIB_VERSION: &str = "2.1.0.devel\0"; - #[cfg(all(not(feature = "zlib-ng"), feature = "zlib-rs"))] - const ZLIB_VERSION: &str = "1.3.0-zlib-rs-0.5.1\0"; - #[cfg(not(any(feature = "zlib-ng", feature = "zlib-rs")))] - const ZLIB_VERSION: &str = "1.2.8\0"; - pub unsafe extern "C" fn mz_deflateInit2( stream: *mut mz_stream, level: c_int, @@ -487,7 +518,7 @@ window_bits, mem_level, strategy, - ZLIB_VERSION.as_ptr() as *const c_char, + zlibVersion(), mem::size_of::<mz_stream>() as c_int, ) } @@ -495,7 +526,7 @@ libz::inflateInit2_( stream, window_bits, - ZLIB_VERSION.as_ptr() as *const c_char, + zlibVersion(), mem::size_of::<mz_stream>() as c_int, ) }
diff --git a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/ffi/mod.rs b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/ffi/mod.rs index 20b3cae..856bc651 100644 --- a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/ffi/mod.rs +++ b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/ffi/mod.rs
@@ -2,6 +2,15 @@ use crate::mem::{CompressError, DecompressError, FlushCompress, FlushDecompress, Status}; use crate::Compression; +use std::mem::MaybeUninit; + +fn initialize_buffer(output: &mut [MaybeUninit<u8>]) -> &mut [u8] { + // SAFETY: Here we zero-initialize the output and cast it to [u8] + unsafe { + output.as_mut_ptr().write_bytes(0, output.len()); + &mut *(output as *mut [MaybeUninit<u8>] as *mut [u8]) + } +} /// Traits specifying the interface of the backends. /// @@ -20,6 +29,14 @@ output: &mut [u8], flush: FlushDecompress, ) -> Result<Status, DecompressError>; + fn decompress_uninit( + &mut self, + input: &[u8], + output: &mut [MaybeUninit<u8>], + flush: FlushDecompress, + ) -> Result<Status, DecompressError> { + self.decompress(input, initialize_buffer(output), flush) + } fn reset(&mut self, zlib_header: bool); } @@ -31,6 +48,14 @@ output: &mut [u8], flush: FlushCompress, ) -> Result<Status, CompressError>; + fn compress_uninit( + &mut self, + input: &[u8], + output: &mut [MaybeUninit<u8>], + flush: FlushCompress, + ) -> Result<Status, CompressError> { + self.compress(input, initialize_buffer(output), flush) + } fn reset(&mut self); }
diff --git a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/ffi/rust.rs b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/ffi/rust.rs index 5b3c193..45b881c 100644 --- a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/ffi/rust.rs +++ b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/ffi/rust.rs
@@ -136,7 +136,8 @@ fn from(value: FlushCompress) -> Self { match value { FlushCompress::None => Self::None, - FlushCompress::Partial | FlushCompress::Sync => Self::Sync, + FlushCompress::Partial => Self::Partial, + FlushCompress::Sync => Self::Sync, FlushCompress::Full => Self::Full, FlushCompress::Finish => Self::Finish, }
diff --git a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/gz/bufread.rs b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/gz/bufread.rs index 17046b03..351d0e7 100644 --- a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/gz/bufread.rs +++ b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/gz/bufread.rs
@@ -449,7 +449,7 @@ let compressed = { let mut e = write::GzEncoder::new(Vec::new(), Compression::default()); - e.write(expected.as_ref()).unwrap(); + e.write_all(expected.as_ref()).unwrap(); let mut b = e.finish().unwrap(); b.push(b'x'); b
diff --git a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/gz/mod.rs b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/gz/mod.rs index 7f0ab6a..955eba5 100644 --- a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/gz/mod.rs +++ b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/gz/mod.rs
@@ -512,7 +512,7 @@ if c & 1 != 0 { c = 0xedb88320 ^ (c >> 1); } else { - c = c >> 1; + c >>= 1; } } crc.crc_table[n] = c; @@ -551,7 +551,7 @@ .into_header(Compression::fast()); // Add a CRC to the header - header[3] = header[3] ^ super::FHCRC; + header[3] ^= super::FHCRC; let rfc1952_crc = Rfc1952Crc::new(); let crc32 = rfc1952_crc.crc(&header); let crc16 = crc32 as u16; @@ -574,7 +574,7 @@ #[test] fn fields() { - let r = vec![0, 2, 4, 6]; + let r = [0, 2, 4, 6]; let e = GzBuilder::new() .filename("foo.rs") .comment("bar")
diff --git a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/gz/read.rs b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/gz/read.rs index 5d6844c0..248f3d7 100644 --- a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/gz/read.rs +++ b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/gz/read.rs
@@ -316,16 +316,16 @@ } pub fn set_position(&mut self, pos: u64) { - return self.cursor.set_position(pos); + self.cursor.set_position(pos) } } impl Write for BlockingCursor { fn write(&mut self, buf: &[u8]) -> Result<usize> { - return self.cursor.write(buf); + self.cursor.write(buf) } fn flush(&mut self) -> Result<()> { - return self.cursor.flush(); + self.cursor.flush() } } @@ -345,7 +345,7 @@ } Ok(_n) => {} } - return r; + r } }
diff --git a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/gz/write.rs b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/gz/write.rs index 737971c..1b7f62d 100644 --- a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/gz/write.rs +++ b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/gz/write.rs
@@ -477,13 +477,13 @@ #[test] fn decode_writer_one_chunk() { let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write(STR.as_ref()).unwrap(); + e.write_all(STR.as_ref()).unwrap(); let bytes = e.finish().unwrap(); let mut writer = Vec::new(); let mut decoder = GzDecoder::new(writer); let n = decoder.write(&bytes[..]).unwrap(); - decoder.write(&bytes[n..]).unwrap(); + decoder.write_all(&bytes[n..]).unwrap(); decoder.try_finish().unwrap(); writer = decoder.finish().unwrap(); let return_string = String::from_utf8(writer).expect("String parsing error"); @@ -493,7 +493,7 @@ #[test] fn decode_writer_partial_header() { let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write(STR.as_ref()).unwrap(); + e.write_all(STR.as_ref()).unwrap(); let bytes = e.finish().unwrap(); let mut writer = Vec::new(); @@ -501,7 +501,7 @@ assert_eq!(decoder.write(&bytes[..5]).unwrap(), 5); let n = decoder.write(&bytes[5..]).unwrap(); if n < bytes.len() - 5 { - decoder.write(&bytes[n + 5..]).unwrap(); + decoder.write_all(&bytes[n + 5..]).unwrap(); } writer = decoder.finish().unwrap(); let return_string = String::from_utf8(writer).expect("String parsing error"); @@ -522,7 +522,7 @@ assert_eq!(decoder.write(&bytes[..12]).unwrap(), 12); let n = decoder.write(&bytes[12..]).unwrap(); if n < bytes.len() - 12 { - decoder.write(&bytes[n + 12..]).unwrap(); + decoder.write_all(&bytes[n + 12..]).unwrap(); } assert_eq!( decoder.header().unwrap().filename().unwrap(), @@ -547,7 +547,7 @@ assert_eq!(decoder.write(&bytes[..12]).unwrap(), 12); let n = decoder.write(&bytes[12..]).unwrap(); if n < bytes.len() - 12 { - decoder.write(&bytes[n + 12..]).unwrap(); + decoder.write_all(&bytes[n + 12..]).unwrap(); } assert_eq!( decoder.header().unwrap().comment().unwrap(), @@ -561,13 +561,13 @@ #[test] fn decode_writer_exact_header() { let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write(STR.as_ref()).unwrap(); + e.write_all(STR.as_ref()).unwrap(); let bytes = e.finish().unwrap(); let mut writer = Vec::new(); let mut decoder = GzDecoder::new(writer); assert_eq!(decoder.write(&bytes[..10]).unwrap(), 10); - decoder.write(&bytes[10..]).unwrap(); + decoder.write_all(&bytes[10..]).unwrap(); writer = decoder.finish().unwrap(); let return_string = String::from_utf8(writer).expect("String parsing error"); assert_eq!(return_string, STR); @@ -576,14 +576,14 @@ #[test] fn decode_writer_partial_crc() { let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write(STR.as_ref()).unwrap(); + e.write_all(STR.as_ref()).unwrap(); let bytes = e.finish().unwrap(); let mut writer = Vec::new(); let mut decoder = GzDecoder::new(writer); let l = bytes.len() - 5; let n = decoder.write(&bytes[..l]).unwrap(); - decoder.write(&bytes[n..]).unwrap(); + decoder.write_all(&bytes[n..]).unwrap(); writer = decoder.finish().unwrap(); let return_string = String::from_utf8(writer).expect("String parsing error"); assert_eq!(return_string, STR); @@ -594,7 +594,7 @@ #[test] fn decode_multi_writer() { let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write(STR.as_ref()).unwrap(); + e.write_all(STR.as_ref()).unwrap(); let bytes = e.finish().unwrap().repeat(2); let mut writer = Vec::new(); @@ -617,7 +617,7 @@ fn decode_extra_data() { let compressed = { let mut e = GzEncoder::new(Vec::new(), Compression::default()); - e.write(STR.as_ref()).unwrap(); + e.write_all(STR.as_ref()).unwrap(); let mut b = e.finish().unwrap(); b.push(b'x'); b
diff --git a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/lib.rs b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/lib.rs index 242f7f04..1d7a64d 100644 --- a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/lib.rs +++ b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/lib.rs
@@ -92,7 +92,7 @@ #![deny(missing_debug_implementations)] #![allow(trivial_numeric_casts)] #![cfg_attr(test, deny(warnings))] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] #[cfg(not(feature = "any_impl",))] compile_error!("You need to choose a zlib backend"); @@ -190,18 +190,6 @@ /// /// The integer here is typically on a scale of 0-9 where 0 means "no /// compression" and 9 means "take as long as you'd like". - /// - /// ### Backend differences - /// - /// The [`miniz_oxide`](https://crates.io/crates/miniz_oxide) backend for flate2 - /// does not support level 0 or `Compression::none()`. Instead it interprets them - /// as the default compression level, which is quite slow. - /// `Compression::fast()` should be used instead. - /// - /// `miniz_oxide` also supports a non-compliant compression level 10. - /// It is even slower and may result in higher compression, but - /// **only miniz_oxide will be able to read the data** compressed with level 10. - /// Do **not** use level 10 if you need other software to be able to read it! pub const fn new(level: u32) -> Compression { Compression(level) }
diff --git a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/mem.rs b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/mem.rs index 5d0f64d3..8121d98 100644 --- a/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/mem.rs +++ b/third_party/rust/chromium_crates_io/vendor/flate2-v1/src/mem.rs
@@ -1,6 +1,7 @@ use std::error::Error; use std::fmt; use std::io; +use std::mem::MaybeUninit; use crate::ffi::{self, Backend, Deflate, DeflateBackend, ErrorMessage, Inflate, InflateBackend}; use crate::Compression; @@ -55,7 +56,7 @@ /// /// All input data so far will be available to the decompressor (as with /// `Flush::Sync`). This completes the current deflate block and follows it - /// with an empty fixed codes block that is 10 bytes long, and it assures + /// with an empty fixed codes block that is 10 bits long, and it assures /// that enough bytes are output in order for the decompressor to finish the /// block before the empty fixed code block. Partial = ffi::MZ_PARTIAL_FLUSH as isize, @@ -337,6 +338,20 @@ self.inner.compress(input, output, flush) } + /// Similar to [`Self::compress`] but accepts uninitialized buffer. + /// + /// If you want to avoid the overhead of zero initializing the + /// buffer and you don't want to use a [`Vec`], then please use + /// this API. + pub fn compress_uninit( + &mut self, + input: &[u8], + output: &mut [MaybeUninit<u8>], + flush: FlushCompress, + ) -> Result<Status, CompressError> { + self.inner.compress_uninit(input, output, flush) + } + /// Compresses the input data into the extra space of the output, consuming /// only as much input as needed and writing as much output as possible. /// @@ -351,12 +366,15 @@ output: &mut Vec<u8>, flush: FlushCompress, ) -> Result<Status, CompressError> { - write_to_spare_capacity_of_vec(output, |out| { - let before = self.total_out(); - let ret = self.compress(input, out, flush); - let bytes_written = self.total_out() - before; - (bytes_written as usize, ret) - }) + // SAFETY: bytes_written is the number of bytes written into `out` + unsafe { + write_to_spare_capacity_of_vec(output, |out| { + let before = self.total_out(); + let ret = self.compress_uninit(input, out, flush); + let bytes_written = self.total_out() - before; + (bytes_written as usize, ret) + }) + } } } @@ -455,6 +473,20 @@ self.inner.decompress(input, output, flush) } + /// Similar to [`Self::decompress`] but accepts uninitialized buffer + /// + /// If you want to avoid the overhead of zero initializing the + /// buffer and you don't want to use a [`Vec`], then please use + /// this API. + pub fn decompress_uninit( + &mut self, + input: &[u8], + output: &mut [MaybeUninit<u8>], + flush: FlushDecompress, + ) -> Result<Status, DecompressError> { + self.inner.decompress_uninit(input, output, flush) + } + /// Decompresses the input data into the extra space in the output vector /// specified by `output`. /// @@ -475,12 +507,15 @@ output: &mut Vec<u8>, flush: FlushDecompress, ) -> Result<Status, DecompressError> { - write_to_spare_capacity_of_vec(output, |out| { - let before = self.total_out(); - let ret = self.decompress(input, out, flush); - let bytes_written = self.total_out() - before; - (bytes_written as usize, ret) - }) + // SAFETY: bytes_written is the number of bytes written into `out` + unsafe { + write_to_spare_capacity_of_vec(output, |out| { + let before = self.total_out(); + let ret = self.decompress_uninit(input, out, flush); + let bytes_written = self.total_out() - before; + (bytes_written as usize, ret) + }) + } } /// Specifies the decompression dictionary to use. @@ -543,7 +578,7 @@ DecompressErrorInner::NeedsDictionary { .. } => Some("requires a dictionary"), }; match msg { - Some(msg) => write!(f, "deflate decompression error: {}", msg), + Some(msg) => write!(f, "deflate decompression error: {msg}"), None => write!(f, "deflate decompression error"), } } @@ -567,7 +602,7 @@ impl fmt::Display for CompressError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.msg.get() { - Some(msg) => write!(f, "deflate compression error: {}", msg), + Some(msg) => write!(f, "deflate compression error: {msg}"), None => write!(f, "deflate compression error"), } } @@ -580,18 +615,20 @@ /// /// `writer` needs to return the number of bytes written (and can also return /// another arbitrary return value). -fn write_to_spare_capacity_of_vec<T>( +/// +/// # Safety: +/// +/// The length returned by the `writer` must be equal to actual number of bytes written +/// to the uninitialized slice passed in and initialized. +unsafe fn write_to_spare_capacity_of_vec<T>( output: &mut Vec<u8>, - writer: impl FnOnce(&mut [u8]) -> (usize, T), + writer: impl FnOnce(&mut [MaybeUninit<u8>]) -> (usize, T), ) -> T { let cap = output.capacity(); let len = output.len(); - output.resize(output.capacity(), 0); - let (bytes_written, ret) = writer(&mut output[len..]); - - let new_len = core::cmp::min(len + bytes_written, cap); // Sanitizes `bytes_written`. - output.resize(new_len, 0 /* unused */); + let (bytes_written, ret) = writer(output.spare_capacity_mut()); + output.set_len(cap.min(len + bytes_written)); // Sanitizes `bytes_written`. ret } @@ -608,7 +645,7 @@ #[test] fn issue51() { - let data = vec - 2025-10-09 + +### Added + +- Apple: Add `TIOCGETA`, `TIOCSETA`, `TIOCSETAW`, `TIOCSETAF` constants ([#4736](https://github.com/rust-lang/libc/pull/4736)) +- Apple: Add `pthread_cond_timedwait_relative_np` ([#4719](https://github.com/rust-lang/libc/pull/4719)) +- BSDs: Add `_CS_PATH` constant ([#4738](https://github.com/rust-lang/libc/pull/4738)) +- Linux-like: Add `SIGEMT` for mips* and sparc* architectures ([#4730](https://github.com/rust-lang/libc/pull/4730)) +- OpenBSD: Add `elf_aux_info` ([#4729](https://github.com/rust-lang/libc/pull/4729)) +- Redox: Add more sysconf constants ([#4728](https://github.com/rust-lang/libc/pull/4728)) +- Windows: Add `wcsnlen` ([#4721](https://github.com/rust-lang/libc/pull/4721)) + +### Changed + +- WASIP2: Invert conditional to include p2 APIs ([#4733](https://github.com/rust-lang/libc/pull/4733)) + ## [0.2.176](https://github.com/rust-lang/libc/compare/0.2.175...0.2.176) - 2025-09-23 ### Support
diff --git a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/Cargo.lock b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/Cargo.lock index 1b4b826..5b7b58c 100644 --- a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/Cargo.lock +++ b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/Cargo.lock
@@ -4,7 +4,7 @@ [[package]] name = "libc" -version = "0.2.176" +version = "0.2.177" dependencies = [ "rustc-std-workspace-core", ]
diff --git a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/Cargo.toml b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/Cargo.toml index 19a75f0..d6c80a4 100644 --- a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/Cargo.toml +++ b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/Cargo.toml
@@ -13,7 +13,7 @@ edition = "2021" rust-version = "1.63" name = "libc" -version = "0.2.176" +version = "0.2.177" authors = ["The Rust Project Developers"] build = "build.rs" exclude = [
diff --git a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/Cargo.toml.orig b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/Cargo.toml.orig index 9787fdee..74241d2 100644 --- a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/Cargo.toml.orig +++ b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/Cargo.toml.orig
@@ -1,6 +1,6 @@ [package] name = "libc" -version = "0.2.176" +version = "0.2.177" keywords = ["libc", "ffi", "bindings", "operating", "system"] categories = ["external-ffi-bindings", "no-std", "os"] exclude = ["/ci/*", "/.github/*", "/.cirrus.yml", "/triagebot.toml"]
diff --git a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/cherry-pick-stable.sh b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/cherry-pick-stable.sh new file mode 100755 index 0000000..c338be4f --- /dev/null +++ b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/cherry-pick-stable.sh
@@ -0,0 +1,150 @@ +#!/bin/bash + +set -e + +# Parse arguments +DRY_RUN=false +while [[ $# -gt 0 ]]; do + case $1 in + --dry-run|-d) + DRY_RUN=true + shift + ;; + --help|-h) + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Cherry-pick commits from PRs labeled 'stable-nominated' to current branch" + echo "" + echo "Options:" + echo " -d, --dry-run Show what would be done without making changes" + echo " -h, --help Show this help message" + exit 0 + ;; + *) + echo "Unknown option: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac +done + +if [ "$DRY_RUN" = true ]; then + echo "[DRY RUN MODE - No changes will be made]" + echo "" +fi + +current_branch=$(git branch --show-current) +echo "Current branch: $current_branch" +echo "Fetching PRs with 'stable-nominated' label..." +echo "" + +# Get PRs with stable-nominated label that are merged +# Sort by merge date (oldest first) to preserve merge order and avoid conflicts +# Format: PR number, title, merge commit SHA +prs=$(gh pr list --state merged --label stable-nominated --json number,title,mergeCommit,mergedAt --jq 'sort_by(.mergedAt) | .[] | "\(.number)|\(.title)|\(.mergeCommit.oid)"') + +if [ -z "$prs" ]; then + echo "No PRs found with 'stable-nominated' label." + exit 0 +fi + +# Arrays to track results +declare -a successful +declare -a failed +declare -a skipped + +echo "Found PRs to cherry-pick:" +echo "" + +# Process each PR +while IFS='|' read -r pr_number title commit_sha; do + echo "----------------------------------------" + echo "PR #${pr_number}: ${title}" + echo "Commit: ${commit_sha}" + + # Check if commit already exists in current branch + if git branch --contains "$commit_sha" 2>/dev/null | grep -q "^\*"; then + echo "⏭ Already cherry-picked, skipping" + skipped+=("PR #${pr_number}: ${title}") + echo "" + continue + fi + + # Cherry-pick with -xe flags as specified + if [ "$DRY_RUN" = true ]; then + echo "Would cherry-pick with: git cherry-pick -xe $commit_sha" + echo "Would add backport note: (backport https://github.com/rust-lang/libc/pull/$pr_number)" + successful+=("PR #${pr_number}: ${title} (${commit_sha:0:8})") + else + if git cherry-pick -xe "$commit_sha" 2>&1; then + # Add backport note before the cherry-pick note as per CONTRIBUTING.md + current_msg=$(git log -1 --format=%B) + backport_line="(backport https://github.com/rust-lang/libc/pull/$pr_number)" + + # Insert backport line before "(cherry picked from commit" line + new_msg=$(echo "$current_msg" | sed "/^(cherry picked from commit/i\\ +$backport_line\\ +") + + # Amend the commit with the new message + git commit --amend -m "$new_msg" + + echo "✓ Successfully cherry-picked with backport note" + successful+=("PR #${pr_number}: ${title} (${commit_sha:0:8})") + else + echo "✗ Failed to cherry-pick" + failed+=("PR #${pr_number}: ${title} (${commit_sha:0:8})") + # Abort the failed cherry-pick + git cherry-pick --abort 2>/dev/null || true + fi + fi + echo "" +done <<< "$prs" + +# Print summary +echo "========================================" +if [ "$DRY_RUN" = true ]; then + echo "SUMMARY (DRY RUN)" +else + echo "SUMMARY" +fi +echo "========================================" +echo "" + +if [ ${#successful[@]} -gt 0 ]; then + if [ "$DRY_RUN" = true ]; then + echo "Would cherry-pick (${#successful[@]}):" + else + echo "Successfully cherry-picked (${#successful[@]}):" + fi + for item in "${successful[@]}"; do + echo " ✓ $item" + done + echo "" +fi + +if [ ${#skipped[@]} -gt 0 ]; then + echo "Skipped (${#skipped[@]}):" + for item in "${skipped[@]}"; do + echo " ⏭ $item" + done + echo "" +fi + +if [ ${#failed[@]} -gt 0 ]; then + echo "Failed (${#failed[@]}):" + for item in "${failed[@]}"; do + echo " ✗ $item" + done + echo "" + if [ "$DRY_RUN" = false ]; then + echo "Please resolve conflicts manually and re-run if needed." + fi + exit 1 +fi + +if [ "$DRY_RUN" = true ]; then + echo "Dry run complete! Run without --dry-run to apply changes." +else + echo "All done!" +fi
diff --git a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/apple/mod.rs b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/apple/mod.rs index 1c6e5a8..857508f 100644 --- a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/apple/mod.rs +++ b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/apple/mod.rs
@@ -3341,6 +3341,10 @@ pub const TIOCPTYGRANT: c_uint = 0x20007454; pub const TIOCPTYGNAME: c_uint = 0x40807453; pub const TIOCPTYUNLK: c_uint = 0x20007452; +pub const TIOCGETA: c_ulong = 0x40487413; +pub const TIOCSETA: c_ulong = 0x80487414; +pub const TIOCSETAW: c_ulong = 0x80487415; +pub const TIOCSETAF: c_ulong = 0x80487416; pub const BIOCGRSIG: c_ulong = 0x40044272; pub const BIOCSRSIG: c_ulong = 0x80044273; @@ -5380,6 +5384,11 @@ pub fn mach_host_self() -> mach_port_t; #[deprecated(since = "0.2.55", note = "Use the `mach2` crate instead")] pub fn mach_thread_self() -> mach_port_t; + pub fn pthread_cond_timedwait_relative_np( + cond: *mut pthread_cond_t, + lock: *mut pthread_mutex_t, + timeout: *const crate::timespec, + ) -> c_int; pub fn pthread_once( once_control: *mut crate::pthread_once_t, init_routine: Option<unsafe extern "C" fn()>,
diff --git a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/freebsdlike/dragonfly/mod.rs b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/freebsdlike/dragonfly/mod.rs index dbf18eb..8720bf7 100644 --- a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/freebsdlike/dragonfly/mod.rs +++ b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/freebsdlike/dragonfly/mod.rs
@@ -1359,6 +1359,8 @@ pub const _PC_2_SYMLINKS: c_int = 22; pub const _PC_TIMESTAMP_RESOLUTION: c_int = 23; +pub const _CS_PATH: c_int = 1; + pub const _SC_V7_ILP32_OFF32: c_int = 122; pub const _SC_V7_ILP32_OFFBIG: c_int = 123; pub const _SC_V7_LP64_OFF64: c_int = 124;
diff --git a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/freebsdlike/mod.rs b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/freebsdlike/mod.rs index 71765b1f..4bf6203 100644 --- a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/freebsdlike/mod.rs +++ b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/freebsdlike/mod.rs
@@ -1197,6 +1197,8 @@ pub const _SC_SYMLOOP_MAX: c_int = 120; pub const _SC_PHYS_PAGES: c_int = 121; +pub const _CS_PATH: c_int = 1; + pub const PTHREAD_MUTEX_INITIALIZER: pthread_mutex_t = ptr::null_mut(); pub const PTHREAD_COND_INITIALIZER: pthread_cond_t = ptr::null_mut(); pub const PTHREAD_RWLOCK_INITIALIZER: pthread_rwlock_t = ptr::null_mut();
diff --git a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/netbsdlike/netbsd/mod.rs b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/netbsdlike/netbsd/mod.rs index fba5391..9f08313 100644 --- a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/netbsdlike/netbsd/mod.rs +++ b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/netbsdlike/netbsd/mod.rs
@@ -1613,6 +1613,8 @@ pub const _PC_ACL_EXTENDED: c_int = 14; pub const _PC_MIN_HOLE_SIZE: c_int = 15; +pub const _CS_PATH: c_int = 1; + pub const _SC_SYNCHRONIZED_IO: c_int = 31; pub const _SC_IOV_MAX: c_int = 32; pub const _SC_MAPPED_FILES: c_int = 33;
diff --git a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/netbsdlike/openbsd/mod.rs b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/netbsdlike/openbsd/mod.rs index d2937bc..b28f455 100644 --- a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/netbsdlike/openbsd/mod.rs +++ b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/bsd/netbsdlike/openbsd/mod.rs
@@ -1041,6 +1041,12 @@ pub const AT_SYMLINK_FOLLOW: c_int = 0x04; pub const AT_REMOVEDIR: c_int = 0x08; +pub const AT_NULL: c_int = 0; +pub const AT_IGNORE: c_int = 1; +pub const AT_PAGESZ: c_int = 6; +pub const AT_HWCAP: c_int = 25; +pub const AT_HWCAP2: c_int = 26; + #[deprecated(since = "0.2.64", note = "Not stable across OS versions")] pub const RLIM_NLIMITS: c_int = 9; @@ -1219,6 +1225,8 @@ pub const _PC_SYNC_IO: c_int = 20; pub const _PC_TIMESTAMP_RESOLUTION: c_int = 21; +pub const _CS_PATH: c_int = 1; + pub const _SC_CLK_TCK: c_int = 3; pub const _SC_SEM_NSEMS_MAX: c_int = 31; pub const _SC_SEM_VALUE_MAX: c_int = 32; @@ -2091,6 +2099,8 @@ pub fn fstatfs(fd: c_int, buf: *mut statfs) -> c_int; pub fn getmntinfo(mntbufp: *mut *mut crate::statfs, flags: c_int) -> c_int; pub fn getfsstat(buf: *mut statfs, bufsize: size_t, flags: c_int) -> c_int; + + pub fn elf_aux_info(aux: c_int, buf: *mut c_void, buflen: c_int) -> c_int; } #[link(name = "execinfo")]
diff --git a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/gnu/b32/mips/mod.rs b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/gnu/b32/mips/mod.rs index db0505a..3d2775c 100644 --- a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/gnu/b32/mips/mod.rs +++ b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/gnu/b32/mips/mod.rs
@@ -768,6 +768,7 @@ pub const SA_SIGINFO: c_int = 0x00000008; pub const SA_NOCLDWAIT: c_int = 0x00010000; +pub const SIGEMT: c_int = 7; pub const SIGCHLD: c_int = 18; pub const SIGBUS: c_int = 10; pub const SIGTTIN: c_int = 26;
diff --git a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/gnu/b32/sparc/mod.rs b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/gnu/b32/sparc/mod.rs index f9d6a95..801f31e 100644 --- a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/gnu/b32/sparc/mod.rs +++ b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/gnu/b32/sparc/mod.rs
@@ -316,6 +316,7 @@ pub const SA_SIGINFO: c_int = 0x200; pub const SA_NOCLDWAIT: c_int = 0x100; +pub const SIGEMT: c_int = 7; pub const SIGTTIN: c_int = 21; pub const SIGTTOU: c_int = 22; pub const SIGXCPU: c_int = 24;
diff --git a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/gnu/b64/mips64/mod.rs b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/gnu/b64/mips64/mod.rs index 56f30cd08..7f66330 100644 --- a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/gnu/b64/mips64/mod.rs +++ b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/gnu/b64/mips64/mod.rs
@@ -757,6 +757,7 @@ pub const SA_SIGINFO: c_int = 0x00000008; pub const SA_NOCLDWAIT: c_int = 0x00010000; +pub const SIGEMT: c_int = 7; pub const SIGCHLD: c_int = 18; pub const SIGBUS: c_int = 10; pub const SIGTTIN: c_int = 26;
diff --git a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/gnu/b64/sparc64/mod.rs b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/gnu/b64/sparc64/mod.rs index c4203dc..f18e53a 100644 --- a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/gnu/b64/sparc64/mod.rs +++ b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/gnu/b64/sparc64/mod.rs
@@ -324,6 +324,7 @@ pub const SA_SIGINFO: c_int = 0x200; pub const SA_NOCLDWAIT: c_int = 0x100; +pub const SIGEMT: c_int = 7; pub const SIGTTIN: c_int = 21; pub const SIGTTOU: c_int = 22; pub const SIGXCPU: c_int = 24;
diff --git a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/musl/b32/mips/mod.rs b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/musl/b32/mips/mod.rs index 4f29b27..a623ff9 100644 --- a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/musl/b32/mips/mod.rs +++ b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/musl/b32/mips/mod.rs
@@ -345,6 +345,7 @@ pub const SA_SIGINFO: c_int = 8; pub const SA_NOCLDWAIT: c_int = 0x10000; +pub const SIGEMT: c_int = 7; pub const SIGCHLD: c_int = 18; pub const SIGBUS: c_int = 10; pub const SIGTTIN: c_int = 26;
diff --git a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/musl/b64/mips64.rs b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/musl/b64/mips64.rs index 57a460b..95dd37c 100644 --- a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/musl/b64/mips64.rs +++ b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/musl/b64/mips64.rs
@@ -591,6 +591,7 @@ pub const SA_SIGINFO: c_int = 0x00000008; pub const SA_NOCLDWAIT: c_int = 0x00010000; +pub const SIGEMT: c_int = 7; pub const SIGCHLD: c_int = 18; pub const SIGBUS: c_int = 10; pub const SIGTTIN: c_int = 26;
diff --git a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/uclibc/mips/mod.rs b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/uclibc/mips/mod.rs index 0ad572a9..8d17aa8e 100644 --- a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/uclibc/mips/mod.rs +++ b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/linux_like/linux/uclibc/mips/mod.rs
@@ -163,6 +163,7 @@ pub const SA_SIGINFO: c_uint = 0x00000008; pub const SA_NOCLDWAIT: c_int = 0x00010000; +pub const SIGEMT: c_int = 7; pub const SIGCHLD: c_int = 18; pub const SIGBUS: c_int = 10; pub const SIGTTIN: c_int = 26;
diff --git a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/redox/mod.rs b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/redox/mod.rs index 4a8b2ae..50bdaf4 100644 --- a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/redox/mod.rs +++ b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/unix/redox/mod.rs
@@ -1002,13 +1002,22 @@ pub const _SC_PAGE_SIZE: c_int = 30; // ... pub const _SC_RE_DUP_MAX: c_int = 44; + +pub const _SC_NPROCESSORS_CONF: c_int = 57; +pub const _SC_NPROCESSORS_ONLN: c_int = 58; + // ... +pub const _SC_GETGR_R_SIZE_MAX: c_int = 69; +pub const _SC_GETPW_R_SIZE_MAX: c_int = 70; pub const _SC_LOGIN_NAME_MAX: c_int = 71; pub const _SC_TTY_NAME_MAX: c_int = 72; // ... pub const _SC_SYMLOOP_MAX: c_int = 173; // ... pub const _SC_HOST_NAME_MAX: c_int = 180; +// ... +pub const _SC_SIGQUEUE_MAX: c_int = 190; +pub const _SC_REALTIME_SIGNALS: c_int = 191; // } POSIX.1 // confstr
diff --git a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/wasi/mod.rs b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/wasi/mod.rs index 823b87a..bb3b729 100644 --- a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/wasi/mod.rs +++ b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/wasi/mod.rs
@@ -846,7 +846,7 @@ } cfg_if! { - if #[cfg(target_env = "p2")] { + if #[cfg(not(target_env = "p1"))] { mod p2; pub use self::p2::*; }
diff --git a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/windows/mod.rs b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/windows/mod.rs index 33ff35ef..2f35af8 100644 --- a/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/windows/mod.rs +++ b/third_party/rust/chromium_crates_io/vendor/libc-v0_2/src/windows/mod.rs
@@ -379,6 +379,7 @@ pub fn strtok(s: *mut c_char, t: *const c_char) -> *mut c_char; pub fn strxfrm(s: *mut c_char, ct: *const c_char, n: size_t) -> size_t; pub fn wcslen(buf: *const wchar_t) -> size_t; + pub fn wcsnlen(str: *const wchar_t, numberOfElements: size_t) -> size_t; pub fn wcstombs(dest: *mut c_char, src: *const wchar_t, n: size_t) -> size_t; pub fn memchr(cx: *const c_void, c: c_int, n: size_t) -> *mut c_void;
diff --git a/third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/.cargo_vcs_info.json b/third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/.cargo_vcs_info.json index 4cf101d..88fb89a 100644 --- a/third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/.cargo_vcs_info.json +++ b/third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/.cargo_vcs_info.json
@@ -1,5 +1,6 @@ { "git": { - "sha1": "66f9d8a15b7209c45f58edee6c1b6bb497b7bd31" - } -} + "sha1": "30002b4228f7cdee309217b6bf6eee099dda0b00" + }, + "path_in_vcs": "" +} \ No newline at end of file
diff --git a/third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/Cargo.lock b/third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/Cargo.lock new file mode 100644 index 0000000..baebbb3 --- /dev/null +++ b/third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/Cargo.lock
@@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "stable_deref_trait" +version = "1.2.1"
diff --git a/third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/Cargo.toml b/third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/Cargo.toml index 6bc26c3..7301f60 100644 --- a/third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/Cargo.toml +++ b/third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/Cargo.toml
@@ -3,25 +3,39 @@ # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies -# to registry (e.g., crates.io) dependencies +# to registry (e.g., crates.io) dependencies. # -# If you believe there's an error in this file please file an -# issue against the rust-lang/cargo repository. If you're -# editing this file be aware that the upstream Cargo.toml -# will likely look very different (and much more reasonable) +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. [package] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" authors = ["Robert Grosse <n210241048576@gmail.com>"] -description = "An unsafe marker trait for types like Box and Rc that dereference to a stable address even when moved, and hence can be used with libraries such as owning_ref and rental.\n" -documentation = "https://docs.rs/stable_deref_trait/1.2.0/stable_deref_trait" +build = false +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = """ +An unsafe marker trait for types like Box and Rc that dereference to a stable address even when moved, and hence can be used with libraries such as owning_ref and rental. +""" +documentation = "https://docs.rs/stable_deref_trait/1.2.1/stable_deref_trait" readme = "README.md" -categories = ["memory-management", "no-std"] -license = "MIT/Apache-2.0" +categories = [ + "memory-management", + "no-std", +] +license = "MIT OR Apache-2.0" repository = "https://github.com/storyyeller/stable_deref_trait" [features] alloc = [] default = ["std"] std = ["alloc"] + +[lib] +name = "stable_deref_trait" +path = "src/lib.rs"
diff --git a/third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/Cargo.toml.orig b/third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/Cargo.toml.orig index 04e30b4..8cb5202 100644 --- a/third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/Cargo.toml.orig +++ b/third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/Cargo.toml.orig
@@ -1,11 +1,11 @@ [package] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" authors = ["Robert Grosse <n210241048576@gmail.com>"] -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" readme = "README.md" repository = "https://github.com/storyyeller/stable_deref_trait" -documentation = "https://docs.rs/stable_deref_trait/1.2.0/stable_deref_trait" +documentation = "https://docs.rs/stable_deref_trait/1.2.1/stable_deref_trait" categories = ["memory-management", "no-std"] description = """ An unsafe marker trait for types like Box and Rc that dereference to a stable address even when moved, and hence can be used with libraries such as owning_ref and rental.
diff --git a/third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/src/lib.rs b/third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/src/lib.rs index 069f6cc..ad95729 100644 --- a/third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/src/lib.rs +++ b/third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/src/lib.rs
@@ -136,17 +136,19 @@ use alloc::boxed::Box; #[cfg(feature = "alloc")] use alloc::rc::Rc; -#[cfg(feature = "alloc")] +#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] use alloc::sync::Arc; #[cfg(feature = "alloc")] use alloc::vec::Vec; #[cfg(feature = "alloc")] use alloc::string::String; +#[cfg(feature = "alloc")] +use alloc::borrow::Cow; #[cfg(feature = "std")] -use std::ffi::{CString, OsString}; +use std::ffi::{CStr, CString, OsStr, OsString}; #[cfg(feature = "std")] -use std::path::PathBuf; +use std::path::{Path, PathBuf}; #[cfg(feature = "std")] use std::sync::{MutexGuard, RwLockReadGuard, RwLockWriteGuard}; @@ -167,12 +169,23 @@ unsafe impl StableDeref for PathBuf {} #[cfg(feature = "alloc")] +unsafe impl<'a> StableDeref for Cow<'a, str> {} +#[cfg(feature = "alloc")] +unsafe impl<'a, T: Clone> StableDeref for Cow<'a, [T]> {} +#[cfg(feature = "std")] +unsafe impl<'a> StableDeref for Cow<'a, Path> {} +#[cfg(feature = "std")] +unsafe impl<'a> StableDeref for Cow<'a, CStr> {} +#[cfg(feature = "std")] +unsafe impl<'a> StableDeref for Cow<'a, OsStr> {} + +#[cfg(feature = "alloc")] unsafe impl<T: ?Sized> StableDeref for Rc<T> {} #[cfg(feature = "alloc")] unsafe impl<T: ?Sized> CloneStableDeref for Rc<T> {} -#[cfg(feature = "alloc")] +#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] unsafe impl<T: ?Sized> StableDeref for Arc<T> {} -#[cfg(feature = "alloc")] +#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] unsafe impl<T: ?Sized> CloneStableDeref for Arc<T> {} unsafe impl<'a, T: ?Sized> StableDeref for Ref<'a, T> {}
diff --git a/third_party/rust/flate2/v1/BUILD.gn b/third_party/rust/flate2/v1/BUILD.gn index 6657c3f..f77aa24 100644 --- a/third_party/rust/flate2/v1/BUILD.gn +++ b/third_party/rust/flate2/v1/BUILD.gn
@@ -44,9 +44,9 @@ cargo_pkg_name = "flate2" cargo_pkg_description = "DEFLATE compression and decompression exposed as Read/BufRead/Write streams. Supports miniz_oxide and multiple zlib implementations. Supports zlib, gzip, and raw deflate streams." cargo_pkg_repository = "https://github.com/rust-lang/flate2-rs" - cargo_pkg_version = "1.1.2" + cargo_pkg_version = "1.1.4" - allow_unsafe = false + allow_unsafe = true deps = [ "//third_party/rust/crc32fast/v1:lib",
diff --git a/third_party/rust/flate2/v1/README.chromium b/third_party/rust/flate2/v1/README.chromium index 029b06e..28faceb 100644 --- a/third_party/rust/flate2/v1/README.chromium +++ b/third_party/rust/flate2/v1/README.chromium
@@ -1,7 +1,7 @@ Name: flate2 URL: https://crates.io/crates/flate2 -Version: 1.1.2 -Revision: ac4d950ffdeab209350423c528d876a7a7811abb +Version: 1.1.4 +Revision: ac412e96d57f40fa6def29ad4b8bee46d1121f54 Update Mechanism: Manual (https://crbug.com/449898466) License: Apache-2.0 License File: //third_party/rust/chromium_crates_io/vendor/flate2-v1/LICENSE-APACHE
diff --git a/third_party/rust/libc/v0_2/BUILD.gn b/third_party/rust/libc/v0_2/BUILD.gn index 898142d8..02345cd 100644 --- a/third_party/rust/libc/v0_2/BUILD.gn +++ b/third_party/rust/libc/v0_2/BUILD.gn
@@ -215,7 +215,7 @@ cargo_pkg_name = "libc" cargo_pkg_description = "Raw FFI bindings to platform libraries like libc." cargo_pkg_repository = "https://github.com/rust-lang/libc" - cargo_pkg_version = "0.2.176" + cargo_pkg_version = "0.2.177" allow_unsafe = true
diff --git a/third_party/rust/libc/v0_2/README.chromium b/third_party/rust/libc/v0_2/README.chromium index 82776ea..3cd9f72 100644 --- a/third_party/rust/libc/v0_2/README.chromium +++ b/third_party/rust/libc/v0_2/README.chromium
@@ -1,7 +1,7 @@ Name: libc URL: https://crates.io/crates/libc -Version: 0.2.176 -Revision: 15e1389ae87935c9c08f4449a73c7b979cded21a +Version: 0.2.177 +Revision: 9f598d245e18ecb243118cfde095f24598ec9d5b Update Mechanism: Manual (https://crbug.com/449898466) License: Apache-2.0 License File: //third_party/rust/chromium_crates_io/vendor/libc-v0_2/LICENSE-APACHE
diff --git a/third_party/rust/stable_deref_trait/v1/BUILD.gn b/third_party/rust/stable_deref_trait/v1/BUILD.gn index 067a582..77cc914 100644 --- a/third_party/rust/stable_deref_trait/v1/BUILD.gn +++ b/third_party/rust/stable_deref_trait/v1/BUILD.gn
@@ -22,7 +22,7 @@ cargo_pkg_name = "stable_deref_trait" cargo_pkg_description = "An unsafe marker trait for types like Box and Rc that dereference to a stable address even when moved, and hence can be used with libraries such as owning_ref and rental." cargo_pkg_repository = "https://github.com/storyyeller/stable_deref_trait" - cargo_pkg_version = "1.2.0" + cargo_pkg_version = "1.2.1" allow_unsafe = true
diff --git a/third_party/rust/stable_deref_trait/v1/README.chromium b/third_party/rust/stable_deref_trait/v1/README.chromium index b8f3f34..f13eddcc 100644 --- a/third_party/rust/stable_deref_trait/v1/README.chromium +++ b/third_party/rust/stable_deref_trait/v1/README.chromium
@@ -1,7 +1,7 @@ Name: stable_deref_trait URL: https://crates.io/crates/stable_deref_trait -Version: 1.2.0 -Revision: 66f9d8a15b7209c45f58edee6c1b6bb497b7bd31 +Version: 1.2.1 +Revision: 30002b4228f7cdee309217b6bf6eee099dda0b00 Update Mechanism: Manual (https://crbug.com/449898466) License: Apache-2.0 License File: //third_party/rust/chromium_crates_io/vendor/stable_deref_trait-v1/LICENSE-APACHE
diff --git a/third_party/skia b/third_party/skia index b2b2869..ac8e8aa 160000 --- a/third_party/skia +++ b/third_party/skia
@@ -1 +1 @@ -Subproject commit b2b28690e53f1ef3800684da325a4e887cd1c2a8 +Subproject commit ac8e8aa24d3f181282154de83003380aa7700f3c
diff --git a/third_party/swiftshader b/third_party/swiftshader index 794b0cf..efd5e79e 160000 --- a/third_party/swiftshader +++ b/third_party/swiftshader
@@ -1 +1 @@ -Subproject commit 794b0cfce1d828d187637e6d932bae484fbe0976 +Subproject commit efd5e79e9ca377c898cc09a3fd5abb32af83bd2f
diff --git a/third_party/tflite/OWNERS b/third_party/tflite/OWNERS index e9b6c4f..55e5bc1 100644 --- a/third_party/tflite/OWNERS +++ b/third_party/tflite/OWNERS
@@ -1,7 +1,6 @@ asully@chromium.org holte@chromium.org mcrouse@chromium.org -rajendrant@chromium.org rmcelrath@chromium.org robertogden@chromium.org sophiechang@chromium.org
diff --git a/third_party/webrtc b/third_party/webrtc index 9559309..e311af0 160000 --- a/third_party/webrtc +++ b/third_party/webrtc
@@ -1 +1 @@ -Subproject commit 95593099246e559f60e889332f18d19541edb012 +Subproject commit e311af088e81d4f545696d1162dbbc02cd146f2e
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index 2f42b21e..b0d4a93 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml
@@ -13741,6 +13741,7 @@ <int value="-172544594" label="LacrosNonSyncingProfiles:enabled"/> <int value="-172480221" label="UseDMSAAForTiles:enabled"/> <int value="-171798137" label="HdrAgtm:enabled"/> + <int value="-171725593" label="DevToolsGreenDevUi:enabled"/> <int value="-171232290" label="ListAllDisplayModes:disabled"/> <int value="-171173736" label="VrBrowsingExperimentalFeatures:disabled"/> <int value="-170986053" label="EnableManualFallbacksFilling:enabled"/> @@ -19786,6 +19787,7 @@ <int value="1955238689" label="OmniboxMaxURLMatches:disabled"/> <int value="1955669770" label="MessagesForAndroidPopupBlocked:enabled"/> <int value="1956055092" label="signature-based-sri:enabled"/> + <int value="1956493171" label="DevToolsGreenDevUi:disabled"/> <int value="1957192097" label="WebViewLogFirstPartyPageTimeSpent:disabled"/> <int value="1957273171" label="PageAlmostIdle:disabled"/> <int value="1957358530" label="ContextualSearchSecondTap:enabled"/>
diff --git a/tools/metrics/histograms/metadata/extensions/enums.xml b/tools/metrics/histograms/metadata/extensions/enums.xml index c36ba9e..455011c 100644 --- a/tools/metrics/histograms/metadata/extensions/enums.xml +++ b/tools/metrics/histograms/metadata/extensions/enums.xml
@@ -2816,7 +2816,7 @@ <int value="1925" label="AUTOFILLPRIVATE_ADDORUPDATEENTITYINSTANCE"/> <int value="1926" label="AUTOFILLPRIVATE_REMOVEENTITYINSTANCE"/> <int value="1927" label="ACCESSIBILITY_PRIVATE_ENABLELIVECAPTION"/> - <int value="1928" label="AUTOFILLPRIVATE_GETALLENTITYTYPES"/> + <int value="1928" label="AUTOFILLPRIVATE_GETWRITABLEENTITYTYPES"/> <int value="1929" label="AUTOFILLPRIVATE_GETALLATTRIBUTETYPESFORENTITYTYPENAME"/> <int value="1930" label="AUTOFILLPRIVATE_GETENTITYINSTANCEBYGUID"/>
diff --git a/tools/metrics/histograms/metadata/ios/enums.xml b/tools/metrics/histograms/metadata/ios/enums.xml index 365e9b8a..b72f877 100644 --- a/tools/metrics/histograms/metadata/ios/enums.xml +++ b/tools/metrics/histograms/metadata/ios/enums.xml
@@ -608,6 +608,15 @@ <int value="0" label="Drive"/> </enum> +<!-- LINT.IfChange(IOSFileUploadPanelContextMenuVariant) --> + +<enum name="IOSFileUploadPanelContextMenuVariant"> + <int value="0" label="PhotoPickerAndCameraAndFilePicker"/> + <int value="1" label="PhotoPickerAndFilePicker"/> +</enum> + +<!-- LINT.ThenChange(//ios/chrome/browser/file_upload_panel/ui/constants.h:FileUploadPanelContextMenuVariant) --> + <!-- LINT.IfChange(IOSGeminiEntryPoint) --> <enum name="IOSGeminiEntryPoint"> @@ -1234,7 +1243,7 @@ <int value="1" label="Obsolete - SignIn Sync"/> <int value="2" label="Default Browser"/> <int value="3" label="Autofill"/> - <int value="4" label="Follow"/> + <int value="4" label="Obsolete - Follow"/> <int value="5" label="All Set"/> <int value="6" label="Notifications"/> <int value="7" label="Obsolete in M138 - Docking"/>
diff --git a/tools/metrics/histograms/metadata/ios/histograms.xml b/tools/metrics/histograms/metadata/ios/histograms.xml index 5f214b2..1425fd3 100644 --- a/tools/metrics/histograms/metadata/ios/histograms.xml +++ b/tools/metrics/histograms/metadata/ios/histograms.xml
@@ -1905,6 +1905,17 @@ </summary> </histogram> +<histogram name="IOS.FileUploadPanel.ContextMenuVariant" + enum="IOSFileUploadPanelContextMenuVariant" expires_after="2026-09-04"> + <owner>qpubert@google.com</owner> + <owner>olivierrobin@chromium.org</owner> + <owner>bling-team@google.com</owner> + <summary> + Report the variant of the context menu that was shown when the file upload + panel is presented. + </summary> +</histogram> + <histogram name="IOS.FingerprintingProtection.RuleList.ApplyTrigger.Incognito" enum="FingerprintingProtectionRuleListApplyTrigger" expires_after="2026-08-12">
diff --git a/tools/metrics/histograms/metadata/password/enums.xml b/tools/metrics/histograms/metadata/password/enums.xml index 630a1ad2..55dab6d6 100644 --- a/tools/metrics/histograms/metadata/password/enums.xml +++ b/tools/metrics/histograms/metadata/password/enums.xml
@@ -1821,12 +1821,6 @@ <int value="1" label="Save prompt shown"/> </enum> -<enum name="SavingOnUsernameFirstFlow"> - <int value="0" label="Saved"/> - <int value="1" label="Saved with edited username"/> - <int value="2" label="Not saved"/> -</enum> - <enum name="SharedPrefCredentialsAccessOutcome"> <int value="0" label="No credentials"/> <int value="1" label="Parse error"/>
diff --git a/tools/metrics/histograms/metadata/password/histograms.xml b/tools/metrics/histograms/metadata/password/histograms.xml index 9bb9b5e9..5056471 100644 --- a/tools/metrics/histograms/metadata/password/histograms.xml +++ b/tools/metrics/histograms/metadata/password/histograms.xml
@@ -3923,17 +3923,6 @@ <token key="UserSyncingType" variants="UserSyncingType"/> </histogram> -<histogram name="PasswordManager.SavingOnUsernameFirstFlow" - enum="SavingOnUsernameFirstFlow" expires_after="2025-11-16"> - <owner>kazinova@google.com</owner> - <owner>vasilii@chromium.org</owner> - <summary> - Records user actions when Chrome offers to save a username on a page which - is considered to be a username first flow. Recorded on a successful - submission. - </summary> -</histogram> - <histogram name="PasswordManager.ServerPredictionsWaitDuration" units="ms" expires_after="2026-03-01"> <owner>kenrb@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/settings/histograms.xml b/tools/metrics/histograms/metadata/settings/histograms.xml index 8fcad58..576f19a 100644 --- a/tools/metrics/histograms/metadata/settings/histograms.xml +++ b/tools/metrics/histograms/metadata/settings/histograms.xml
@@ -816,6 +816,29 @@ </summary> </histogram> +<histogram + name="Settings.SafetyHub.AbusiveNotificationPermissionRevocation.{NotificationRevocationSource}.{RevokedStatus}.PermissionChanged" + enum="ContentSetting" expires_after="2026-02-22"> + <owner>skrakowi@chromium.org</owner> + <owner>chrome-counter-abuse-alerts@google.com</owner> + <summary> + Recorded when the user changes notification permission outside of Safety Hub + for sites previously {RevokedStatus} due to abusive notification with reason + {NotificationRevocationSource}. + + This detects occurrence of false positive abusive auto-revocations. + </summary> + <token key="RevokedStatus"> + <variant name="Ignored"/> + <variant name="Revoked"/> + </token> + <token key="NotificationRevocationSource"> + <variant name="ManualSafeBrowsingRevocation"/> + <variant name="SocialEngineeringBlocklist"/> + <variant name="Unknown"/> + </token> +</histogram> + <histogram name="Settings.SafetyHub.AutorevokeUnusedSitePermissions.Changed" enum="BooleanEnabled" expires_after="2026-02-22"> <owner>zalmashni@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/sync/enums.xml b/tools/metrics/histograms/metadata/sync/enums.xml index 77ad583..3386bf56 100644 --- a/tools/metrics/histograms/metadata/sync/enums.xml +++ b/tools/metrics/histograms/metadata/sync/enums.xml
@@ -814,8 +814,8 @@ <int value="0" label="Changes match"/> <int value="1" label="Use local"/> <int value="2" label="Use remote"/> - <int value="3" label="Ignore local encryption"/> - <int value="4" label="Ignore remote encryption"/> + <int value="3" label="Ignore local no-op change"/> + <int value="4" label="Ignore remote no-op change"/> </enum> <!-- LINT.ThenChange(/components/sync/model/conflict_resolution.h:SyncConflictResolution) -->
diff --git a/tools/metrics/histograms/metadata/v8/histograms.xml b/tools/metrics/histograms/metadata/v8/histograms.xml index 46865dc..622c8f2e 100644 --- a/tools/metrics/histograms/metadata/v8/histograms.xml +++ b/tools/metrics/histograms/metadata/v8/histograms.xml
@@ -470,7 +470,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.CollectionRate.Full{Heap}" units="%" - expires_after="2026-02-22"> + expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -491,7 +491,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.CollectionRate.Young" units="%" - expires_after="M144"> + expires_after="M150"> <owner>nikolaos@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -508,7 +508,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.CollectionWeight.Full{Heap}" - units="ms/s" expires_after="2026-02-22"> + units="ms/s" expires_after="M150"> <owner>etiennep@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -529,7 +529,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.CollectionWeight.MainThread.Full{Heap}" - units="ms/s" expires_after="2026-02-22"> + units="ms/s" expires_after="M150"> <owner>etiennep@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -551,7 +551,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.Consumed.{Scope}.Current2.Full" - units="KB" expires_after="2026-02-22"> + units="KB" expires_after="M150"> <owner>etiennep@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -572,7 +572,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.Consumed.{Scope}.Limit2.Full" units="KB" - expires_after="2026-02-22"> + expires_after="M150"> <owner>etiennep@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -593,7 +593,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.Consumed.{Scope}.Max.Full" units="KB" - expires_after="2026-02-22"> + expires_after="M150"> <owner>etiennep@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -614,7 +614,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.Efficiency.Full{Heap}" units="KB/ms" - expires_after="2026-03-08"> + expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -634,7 +634,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.Efficiency.MainThread.Full{Heap}" - units="KB/ms" expires_after="2026-02-22"> + units="KB/ms" expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -655,7 +655,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.Efficiency.MainThread.Young" - units="KB/ms" expires_after="M144"> + units="KB/ms" expires_after="M150"> <owner>nikolaos@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -673,7 +673,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.Efficiency.Young" units="KB/ms" - expires_after="M144"> + expires_after="M150"> <owner>nikolaos@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -690,7 +690,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.ExternalMemory.Full" units="KB" - expires_after="2026-02-22"> + expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -706,7 +706,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.Full.Compact{Heap}" units="ms" - expires_after="2026-02-22"> + expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -726,7 +726,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.Full.Mark{Heap}" units="ms" - expires_after="2026-03-01"> + expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -746,7 +746,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.Full.Sweep{Heap}" units="ms" - expires_after="M144"> + expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -766,7 +766,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.Full.Weak{Heap}" units="ms" - expires_after="2026-03-01"> + expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -786,7 +786,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.Full{Heap}" units="ms" - expires_after="2026-02-22"> + expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -806,7 +806,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.MainThread.Full.Atomic.Compact{Heap}" - units="ms" expires_after="2026-02-22"> + units="ms" expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -828,7 +828,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.MainThread.Full.Atomic.Mark{Heap}" - units="ms" expires_after="M144"> + units="ms" expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -850,7 +850,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.MainThread.Full.Atomic.Sweep{Heap}" - units="ms" expires_after="M144"> + units="ms" expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -872,7 +872,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.MainThread.Full.Atomic.Weak{Heap}" - units="ms" expires_after="M144"> + units="ms" expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -894,7 +894,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.MainThread.Full.Atomic{Heap}" units="ms" - expires_after="2026-02-22"> + expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -916,7 +916,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.MainThread.Full.Compact{Heap}" - units="ms" expires_after="M144"> + units="ms" expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -938,7 +938,7 @@ <histogram name="V8.GC.Cycle{Priority}.MainThread.Full.Incremental.Mark.StartStop" - units="ms" expires_after="2026-02-22"> + units="ms" expires_after="M150"> <owner>etiennep@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -955,7 +955,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.MainThread.Full.Incremental.Mark{Heap}" - units="ms" expires_after="2026-02-22"> + units="ms" expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -976,7 +976,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.MainThread.Full.Incremental.Sweep{Heap}" - units="ms" expires_after="M144"> + units="ms" expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -997,7 +997,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.MainThread.Full.Mark{Heap}" units="ms" - expires_after="M144"> + expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -1018,7 +1018,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.MainThread.Full.Sweep{Heap}" units="ms" - expires_after="M144"> + expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -1039,7 +1039,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.MainThread.Full.Weak{Heap}" units="ms" - expires_after="M144"> + expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -1060,7 +1060,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.MainThread.Full{Heap}" units="ms" - expires_after="2026-02-22"> + expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -1080,7 +1080,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.MainThread.Young" units="ms" - expires_after="2026-02-22"> + expires_after="M150"> <owner>nikolaos@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -1097,7 +1097,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.Memory.Freed.Full{Heap}" units="KB" - expires_after="2026-03-08"> + expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -1117,7 +1117,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.Objects.After.Full{Heap}" units="KB" - expires_after="2026-02-22"> + expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -1137,7 +1137,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.Objects.Before.Full{Heap}" units="KB" - expires_after="2026-02-22"> + expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -1157,7 +1157,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.Objects.Freed.Full{Heap}" units="KB" - expires_after="2026-02-22"> + expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -1177,7 +1177,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.Reason.Full" - enum="GarbageCollectionReason" expires_after="2026-02-22"> + enum="GarbageCollectionReason" expires_after="M150"> <owner>nikolaos@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -1193,7 +1193,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.Reason.Young" - enum="GarbageCollectionReason" expires_after="2026-02-22"> + enum="GarbageCollectionReason" expires_after="M150"> <owner>nikolaos@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -1209,7 +1209,7 @@ </histogram> <histogram name="V8.GC.Cycle{Priority}.TimeSinceLastCycle.Full" units="ms" - expires_after="2026-02-22"> + expires_after="M150"> <owner>etiennep@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -1224,8 +1224,7 @@ </token> </histogram> -<histogram name="V8.GC.Cycle{Priority}.Young" units="ms" - expires_after="2026-02-22"> +<histogram name="V8.GC.Cycle{Priority}.Young" units="ms" expires_after="M150"> <owner>nikolaos@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -1242,7 +1241,7 @@ <histogram name="V8.GC.Event.MainThread.Full.Finalize.{Incrementality}.{Mode}{Visibility}" - units="ms" expires_after="M144"> + units="ms" expires_after="M150"> <owner>mlippautz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -1267,7 +1266,7 @@ </histogram> <histogram name="V8.GC.Event.MainThread.Full.Incremental.Mark{Heap}" units="ms" - expires_after="M144"> + expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -1281,7 +1280,7 @@ </histogram> <histogram name="V8.GC.Event.MainThread.Full.Incremental.Sweep{Heap}" - units="ms" expires_after="M144"> + units="ms" expires_after="M150"> <owner>omerkatz@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -1295,7 +1294,7 @@ </histogram> <histogram name="V8.GC.TimeToCollectionOnBackground" units="microseconds" - expires_after="2026-02-22"> + expires_after="M150"> <owner>dinfuehr@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -1312,7 +1311,7 @@ </histogram> <histogram name="V8.GC.TimeToGlobalSafepoint" units="microseconds" - expires_after="2026-02-22"> + expires_after="M150"> <owner>dinfuehr@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary> @@ -1328,7 +1327,7 @@ </histogram> <histogram name="V8.GC.TimeToSafepoint" units="microseconds" - expires_after="2026-02-22"> + expires_after="M150"> <owner>dinfuehr@chromium.org</owner> <owner>v8-memory-sheriffs@google.com</owner> <summary>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json index 2c912dac..af6fa25f 100644 --- a/tools/perf/core/perfetto_binary_roller/binary_deps.json +++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -6,7 +6,7 @@ }, "win": { "hash": "c499e55c6d305b3cf70c8cff2c5d9498e43d73ba", - "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/c64475bcd53b3eb4dec7117f97efc1234a891a52/trace_processor_shell.exe" + "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/da58fbdf0f14a29a3ccd4f304ccb83ef4940185d/trace_processor_shell.exe" }, "linux_arm": { "hash": "46d798c1864490cbb2ee053d6eda436184470e69", @@ -22,7 +22,7 @@ }, "linux": { "hash": "69c7180ee53d8235069a4ff5758be3c248c7250a", - "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/c64475bcd53b3eb4dec7117f97efc1234a891a52/trace_processor_shell" + "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/6a960b04f6e20ace0f0e084be42a660f7ff6490a/trace_processor_shell" } }, "power_profile.sql": {
diff --git a/tools/typescript/definitions/autofill_private.d.ts b/tools/typescript/definitions/autofill_private.d.ts index f91bebe..2c4e3c87 100644 --- a/tools/typescript/definitions/autofill_private.d.ts +++ b/tools/typescript/definitions/autofill_private.d.ts
@@ -320,7 +320,7 @@ Promise<EntityInstanceWithLabels[]>; export function getEntityInstanceByGuid(guid: string): Promise<EntityInstance>; - export function getAllEntityTypes(): Promise<EntityType[]>; + export function getWritableEntityTypes(): Promise<EntityType[]>; export function getAllAttributeTypesForEntityTypeName( entityTypeName: number): Promise<AttributeType[]>; export function getAutofillAiOptInStatus(): Promise<boolean>;
diff --git a/ui/base/win/win_cursor_factory.cc b/ui/base/win/win_cursor_factory.cc index dfe7864..66dc015 100644 --- a/ui/base/win/win_cursor_factory.cc +++ b/ui/base/win/win_cursor_factory.cc
@@ -22,7 +22,7 @@ #include "ui/base/cursor/platform_cursor.h" #include "ui/base/resource/resource_bundle_win.h" #include "ui/gfx/geometry/point.h" -#include "ui/gfx/icon_util.h" +#include "ui/gfx/win/icon_util.h" #include "ui/resources/grit/ui_unscaled_resources.h" namespace ui {
diff --git a/ui/gfx/BUILD.gn b/ui/gfx/BUILD.gn index 032128a..7a66e76 100644 --- a/ui/gfx/BUILD.gn +++ b/ui/gfx/BUILD.gn
@@ -99,12 +99,8 @@ "font_util.cc", "font_util.h", "frame_data.h", - "gdi_util.cc", - "gdi_util.h", "gpu_extra_info.cc", "gpu_extra_info.h", - "icon_util.cc", - "icon_util.h", "image/buffer_w_stream.cc", "image/buffer_w_stream.h", "image/image.cc", @@ -227,10 +223,14 @@ "win/d3d_shared_fence.h", "win/direct_write.cc", "win/direct_write.h", + "win/gdi_util.cc", + "win/gdi_util.h", "win/get_elevation_icon.cc", "win/get_elevation_icon.h", "win/hwnd_util.cc", "win/hwnd_util.h", + "win/icon_util.cc", + "win/icon_util.h", "win/msg_util.h", "win/physical_size.cc", "win/physical_size.h", @@ -408,13 +408,6 @@ all_dependent_configs = [ ":gfx_win_linker_flags" ] public_deps += [ "//components/crash/core/common" ] - } else { - sources -= [ - "gdi_util.cc", - "gdi_util.h", - "icon_util.cc", - "icon_util.h", - ] } # Linux. @@ -949,12 +942,12 @@ sources += [ "font_render_params_win_unittest.cc", "font_util_win.cc", - "icon_util_unittest.cc", - "icon_util_unittests.rc", - "icon_util_unittests_resource.h", "path_win_unittest.cc", "win/crash_id_helper_unittest.cc", "win/direct_write_unittest.cc", + "win/icon_util_unittest.cc", + "win/icon_util_unittests.rc", + "win/icon_util_unittests_resource.h", "win/text_analysis_source_unittest.cc", ]
diff --git a/ui/gfx/icon_util_unittests_resource.h b/ui/gfx/icon_util_unittests_resource.h deleted file mode 100644 index 54da83a..0000000 --- a/ui/gfx/icon_util_unittests_resource.h +++ /dev/null
@@ -1,10 +0,0 @@ -// Copyright 2014 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef UI_GFX_ICON_UTIL_UNITTESTS_RESOURCE_H_ -#define UI_GFX_ICON_UTIL_UNITTESTS_RESOURCE_H_ - -#define IDR_MAINFRAME 101 - -#endif // UI_GFX_ICON_UTIL_UNITTESTS_RESOURCE_H_ \ No newline at end of file
diff --git a/ui/gfx/gdi_util.cc b/ui/gfx/win/gdi_util.cc similarity index 98% rename from ui/gfx/gdi_util.cc rename to ui/gfx/win/gdi_util.cc index 2f9f2c3d..9570834 100644 --- a/ui/gfx/gdi_util.cc +++ b/ui/gfx/win/gdi_util.cc
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "ui/gfx/gdi_util.h" +#include "ui/gfx/win/gdi_util.h" #include <stddef.h>
diff --git a/ui/gfx/gdi_util.h b/ui/gfx/win/gdi_util.h similarity index 89% rename from ui/gfx/gdi_util.h rename to ui/gfx/win/gdi_util.h index 56daea2..4acf3fb 100644 --- a/ui/gfx/gdi_util.h +++ b/ui/gfx/win/gdi_util.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef UI_GFX_GDI_UTIL_H_ -#define UI_GFX_GDI_UTIL_H_ +#ifndef UI_GFX_WIN_GDI_UTIL_H_ +#define UI_GFX_WIN_GDI_UTIL_H_ #include <windows.h> @@ -28,4 +28,4 @@ } // namespace gfx -#endif // UI_GFX_GDI_UTIL_H_ +#endif // UI_GFX_WIN_GDI_UTIL_H_
diff --git a/ui/gfx/win/get_elevation_icon.cc b/ui/gfx/win/get_elevation_icon.cc index ade779e..e255574 100644 --- a/ui/gfx/win/get_elevation_icon.cc +++ b/ui/gfx/win/get_elevation_icon.cc
@@ -12,7 +12,7 @@ #include "base/win/scoped_gdi_object.h" #include "base/win/win_util.h" #include "ui/gfx/geometry/size.h" -#include "ui/gfx/icon_util.h" +#include "ui/gfx/win/icon_util.h" namespace gfx::win {
diff --git a/ui/gfx/icon_util.cc b/ui/gfx/win/icon_util.cc similarity index 99% rename from ui/gfx/icon_util.cc rename to ui/gfx/win/icon_util.cc index b072d34..9fa33a9d5 100644 --- a/ui/gfx/icon_util.cc +++ b/ui/gfx/win/icon_util.cc
@@ -7,7 +7,7 @@ #pragma allow_unsafe_buffers #endif -#include "ui/gfx/icon_util.h" +#include "ui/gfx/win/icon_util.h" #include <windows.h>
diff --git a/ui/gfx/icon_util.h b/ui/gfx/win/icon_util.h similarity index 98% rename from ui/gfx/icon_util.h rename to ui/gfx/win/icon_util.h index cf87da6..9b36147e 100644 --- a/ui/gfx/icon_util.h +++ b/ui/gfx/win/icon_util.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef UI_GFX_ICON_UTIL_H_ -#define UI_GFX_ICON_UTIL_H_ +#ifndef UI_GFX_WIN_ICON_UTIL_H_ +#define UI_GFX_WIN_ICON_UTIL_H_ #include <windows.h> @@ -267,4 +267,4 @@ const gfx::Size& s); }; -#endif // UI_GFX_ICON_UTIL_H_ +#endif // UI_GFX_WIN_ICON_UTIL_H_
diff --git a/ui/gfx/icon_util_unittest.cc b/ui/gfx/win/icon_util_unittest.cc similarity index 99% rename from ui/gfx/icon_util_unittest.cc rename to ui/gfx/win/icon_util_unittest.cc index 673239ce..b004ba1c 100644 --- a/ui/gfx/icon_util_unittest.cc +++ b/ui/gfx/win/icon_util_unittest.cc
@@ -7,7 +7,7 @@ #pragma allow_unsafe_buffers #endif -#include "ui/gfx/icon_util.h" +#include "ui/gfx/win/icon_util.h" #include <stddef.h> @@ -22,11 +22,11 @@ #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkCanvas.h" #include "ui/gfx/geometry/size.h" -#include "ui/gfx/icon_util_unittests_resource.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_family.h" #include "ui/gfx/image/image_unittest_util.h" #include "ui/gfx/switches.h" +#include "ui/gfx/win/icon_util_unittests_resource.h" namespace {
diff --git a/ui/gfx/icon_util_unittests.ico b/ui/gfx/win/icon_util_unittests.ico similarity index 100% rename from ui/gfx/icon_util_unittests.ico rename to ui/gfx/win/icon_util_unittests.ico Binary files differ
diff --git a/ui/gfx/icon_util_unittests.rc b/ui/gfx/win/icon_util_unittests.rc similarity index 100% rename from ui/gfx/icon_util_unittests.rc rename to ui/gfx/win/icon_util_unittests.rc
diff --git a/ui/gfx/win/icon_util_unittests_resource.h b/ui/gfx/win/icon_util_unittests_resource.h new file mode 100644 index 0000000..369fdfe --- /dev/null +++ b/ui/gfx/win/icon_util_unittests_resource.h
@@ -0,0 +1,10 @@ +// Copyright 2014 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_WIN_ICON_UTIL_UNITTESTS_RESOURCE_H_ +#define UI_GFX_WIN_ICON_UTIL_UNITTESTS_RESOURCE_H_ + +#define IDR_MAINFRAME 101 + +#endif // UI_GFX_WIN_ICON_UTIL_UNITTESTS_RESOURCE_H_ \ No newline at end of file
diff --git a/ui/gl/test/gl_test_helper.cc b/ui/gl/test/gl_test_helper.cc index 53938339a..ce8ce478 100644 --- a/ui/gl/test/gl_test_helper.cc +++ b/ui/gl/test/gl_test_helper.cc
@@ -21,7 +21,7 @@ #include "base/win/scoped_select_object.h" #include "third_party/skia/include/core/SkColorSpace.h" #include "third_party/skia/include/core/SkImageInfo.h" -#include "ui/gfx/gdi_util.h" +#include "ui/gfx/win/gdi_util.h" #include "ui/gl/direct_composition_support.h" #endif
diff --git a/ui/views/controls/menu/menu_controller.cc b/ui/views/controls/menu/menu_controller.cc index 9bb09e12..cfc9d7b 100644 --- a/ui/views/controls/menu/menu_controller.cc +++ b/ui/views/controls/menu/menu_controller.cc
@@ -1724,6 +1724,12 @@ pending_state_.item = menu_item; pending_state_.submenu_open = (selection_types & SELECTION_OPEN_SUBMENU) != 0; + // Possible fix for https:://crbug.com/443019015, in case `this` is getting + // deleted as a side effect of code above. From the crash dumps, it's pretty + // clear that both `cancel_all_timer_` and `this` have been deleted. + if (!this_ref) { + return; + } // Stop timers. StopCancelAllTimer(); // Resets show timer only when pending menu item is changed.
diff --git a/ui/views/win/hwnd_message_handler.cc b/ui/views/win/hwnd_message_handler.cc index 5246e5b..3d5818d 100644 --- a/ui/views/win/hwnd_message_handler.cc +++ b/ui/views/win/hwnd_message_handler.cc
@@ -70,10 +70,10 @@ #include "ui/gfx/canvas.h" #include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/resize_utils.h" -#include "ui/gfx/icon_util.h" #include "ui/gfx/image/image_skia.h" #include "ui/gfx/path_win.h" #include "ui/gfx/win/hwnd_util.h" +#include "ui/gfx/win/icon_util.h" #include "ui/gfx/win/rendering_window_manager.h" #include "ui/latency/latency_info.h" #include "ui/native_theme/native_theme_win.h"
diff --git a/ui/webui/resources/cr_components/searchbox/searchbox.css b/ui/webui/resources/cr_components/searchbox/searchbox.css index a5442645..1e4137e 100644 --- a/ui/webui/resources/cr_components/searchbox/searchbox.css +++ b/ui/webui/resources/cr_components/searchbox/searchbox.css
@@ -434,11 +434,11 @@ padding-top: var(--cr-searchbox-input-padding-top_); } -:host([realbox-layout-mode^='Tall']:not([dropdown-is-visible])) input { +:host([realbox-layout-mode^='Tall']:not([dropdown-is-visible]):not([input-focused_])) input { padding-inline-start: 24px; } -:host([realbox-layout-mode^='Tall']:not([dropdown-is-visible])) cr-searchbox-icon, +:host([realbox-layout-mode^='Tall']:not([dropdown-is-visible]):not([input-focused_])) cr-searchbox-icon, :host([realbox-layout-mode^='Tall'][dropdown-is-visible]) .searchbox-icon-button-container { display: none; } @@ -454,7 +454,8 @@ display: none; } -:host([realbox-layout-mode^='Tall'][dropdown-is-visible]) cr-searchbox-icon { +:host([realbox-layout-mode^='Tall'][dropdown-is-visible]) cr-searchbox-icon, +:host([realbox-layout-mode^='Tall'][input-focused_]) cr-searchbox-icon { height: 24px; inset-inline-start: 12px; top: var(--cr-searchbox-input-padding-top_);
diff --git a/ui/webui/resources/cr_components/searchbox/searchbox.ts b/ui/webui/resources/cr_components/searchbox/searchbox.ts index 4aeb888..fbe05dca 100644 --- a/ui/webui/resources/cr_components/searchbox/searchbox.ts +++ b/ui/webui/resources/cr_components/searchbox/searchbox.ts
@@ -292,6 +292,11 @@ // Private properties //======================================================================== + inputFocused_: { + type: Boolean, + reflect: true, + }, + isLensSearchbox_: { type: Boolean, reflect: true, @@ -407,6 +412,7 @@ accessor composeButtonEnabled: boolean = false; accessor showThumbnail: boolean = false; protected accessor inputAriaLive_: string = ''; + private accessor inputFocused_: boolean = false; private accessor isLensSearchbox_: boolean = loadTimeData.getBoolean('isLensSearchbox'); protected accessor enableThumbnailSizingTweaks_: boolean = @@ -675,6 +681,7 @@ } protected onInputFocus_() { + this.inputFocused_ = true; this.pageHandler_.onFocusChanged(true); this.placeholderCycler_?.stop(); } @@ -807,6 +814,8 @@ return; } + this.inputFocused_ = false; + if (this.lastQueriedInput_ === '') { // Clear the input as well as the matches if the input was empty when // the matches arrived.
diff --git a/v8 b/v8 index 7f18b53..9c21f21 160000 --- a/v8 +++ b/v8
@@ -1 +1 @@ -Subproject commit 7f18b53ba6dc3b556a997eca385c56490a4d13df +Subproject commit 9c21f2111e58e1c9028fb8a2d1e55b124c52b4c3