diff --git a/DEPS b/DEPS index a2847dcb..8cb043b 100644 --- a/DEPS +++ b/DEPS
@@ -195,7 +195,7 @@ # By default, do not check out versions of toolschains and sdks that are # specifically only needed by Lacros. 'checkout_lacros_sdk': False, - 'lacros_sdk_version': '14335.0.0', + 'lacros_sdk_version': '14385.0.0', # Generate location tag metadata to include in tests result data uploaded # to ResultDB. This isn't needed on some configs and the tool that generates @@ -239,15 +239,15 @@ # 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': '21b8ccb7393c700ee3dcfc86101c46f962eca852', + 'skia_revision': 'b7f52780094451f2d97a00ebaf998f3a2c53a144', # 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': '9ae0b044c5778bb5cc97514abc23ad169b12bce9', + 'v8_revision': '4f8420cf9e3d16e48b0abae6a365466f0fff2915', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling ANGLE # and whatever else without interference from each other. - 'angle_revision': '87a8b79a29f261feefa141c00998a5a473546f53', + 'angle_revision': '0f09d378edb90d1fb3058489be82cdf320863031', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling SwiftShader # and whatever else without interference from each other. @@ -306,7 +306,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling catapult # and whatever else without interference from each other. - 'catapult_revision': '49839733a7f26070e8d666d91fae177711154e1d', + 'catapult_revision': 'fcd455d2e403f6ffd4e893131119af007dccadc7', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libFuzzer # and whatever else without interference from each other. @@ -314,7 +314,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': 'c2756dca203815cd5c2d16270847432c97a670c8', + 'devtools_frontend_revision': 'dc4a43902dde2bfa61f0b00cd830ae3c7d079a78', # 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. @@ -354,7 +354,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': 'ed33e05db17882e00437726d5a96281000d0debf', + 'dawn_revision': '370e6bd7341034c840bbea8e2bd017ceab0257ea', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. @@ -398,7 +398,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. - 'libcxxabi_revision': '89f2e82120461d34098edd216e57aa743f441107', + 'libcxxabi_revision': 'ec2a7436b27e5aaa7f0ec0d3e22a187fb3200007', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. @@ -608,7 +608,7 @@ Var('chromium_git') + '/external/github.com/toji/webvr.info.git' + '@' + 'c58ae99b9ff9e2aa4c524633519570bf33536248', 'src/docs/website': { - 'url': Var('chromium_git') + '/website.git' + '@' + '75c2b6829128199436a51e75595ad18804d9e1c0', + 'url': Var('chromium_git') + '/website.git' + '@' + 'bdf47bc33c3c0b32c9a5b0306f0e0c94baff08a4', }, 'src/ios/third_party/earl_grey2/src': { @@ -713,7 +713,7 @@ 'packages': [ { 'package': 'chromium/rts/model/linux-amd64', - 'version': 'gh5602BrGDE3YbMeWKtJUikywV09s3cK9Rl19IdhxoQC', + 'version': 'bx8h2qMq5ktTv_lH-_WkY7nS8q9go7i7r3Ydl7WXf_cC', }, ], 'dep_type': 'cipd', @@ -724,7 +724,7 @@ 'packages': [ { 'package': 'chromium/rts/model/mac-amd64', - 'version': 'qWoE2IzOCsPorHxgIjurEfjSiiCU4f30MzW0W8qKc_4C', + 'version': 'uq89uFZNnJdY9GI6wpkwAWVEo5gw7Oj2SAsUhjj7FIsC', }, ], 'dep_type': 'cipd', @@ -796,7 +796,7 @@ 'packages': [ { 'package': 'chromium/third_party/androidx', - 'version': 'onI7kTN2StbSpqpAwCLMywNKlnTq9LblaQ1cK5tReloC', + 'version': 'PgnGqqy0bbqQJKzX07UHW_YOERUm1QSGiUErq2_HN84C', }, ], 'condition': 'checkout_android', @@ -1015,7 +1015,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' + '@' + 'e9aa4599d44ccc48b4953fe8c63c150bbf768a8e', + 'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '193f9dff46e20187c7da0be686468407726da3b0', 'condition': 'checkout_chromeos', }, @@ -1035,7 +1035,7 @@ }, 'src/third_party/depot_tools': - Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '0a233e176044b6d9b9ff9fb30b589bfb18f9ca04', + Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '85d7fe7fe82ad5876c2bb95784d9b9f282115f92', 'src/third_party/devtools-frontend/src': Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'), @@ -1600,7 +1600,7 @@ 'src/third_party/usrsctp/usrsctplib': Var('chromium_git') + '/external/github.com/sctplab/usrsctp' + '@' + '62d7d0c928c9a040dce96aa2f16c00e7e67d59cb', - 'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@bc087f672f1622c28037ac5f226ce7fa137d0119', + 'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@befff92adbd2455e6eca3599a9cd089c4b12bdd5', 'src/third_party/vulkan_memory_allocator': Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '5e49f57a6e71a026a54eb42e366de09a4142d24e', @@ -1639,7 +1639,7 @@ Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '4f2c3b6ac243f7e97ae1afb664d2b14b65fcf097', 'src/third_party/webrtc': - Var('webrtc_git') + '/src.git' + '@' + 'adf7dc34f036eba9283bb6ee022333009fda05fb', + Var('webrtc_git') + '/src.git' + '@' + 'e33212ade5b86af1dc6f3d66c2e489e28dc662a3', 'src/third_party/libgifcodec': Var('skia_git') + '/libgifcodec' + '@'+ Var('libgifcodec_revision'), @@ -1697,7 +1697,7 @@ Var('chromium_git') + '/v8/v8.git' + '@' + Var('v8_revision'), 'src-internal': { - 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@6be5655714a7d6665f93d25d222862b2787ea77b', + 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@a080672bfbed90bc1757bdf8f1c569f3c1cc955d', 'condition': 'checkout_src_internal', }, @@ -1716,7 +1716,7 @@ 'packages': [ { 'package': 'chromeos_internal/apps/help_app/app', - 'version': '-wgqXJ74QlsAGsin180hstNWIqU7x0B9yjgYQ5yd-KoC', + 'version': 'Lo_CR-eR-KCX5ITWCK7ATbR8GFZhQNhS47vUES5EOzEC', }, ], 'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd index 7f1876d..322a0e4 100644 --- a/ash/ash_strings.grd +++ b/ash/ash_strings.grd
@@ -3993,6 +3993,9 @@ <message name="IDS_ASH_SCREEN_CAPTURE_SETTINGS_A11Y_TITLE" desc="The label of window hosting the screen capture settings menu that will be read by ChromeVox when prompted for title."> Screen capture settings </message> + <message name="IDS_ASH_SCREEN_CAPTURE_SAVE_TO_GOOGLE_DRIVE" desc="The label of the menu item button for selecting the root of Google Drive to store the captured images and videos."> + Google Drive + </message> <!-- Switch Between TABLET/LAPTOP MODE--> <message name="IDS_ASH_SWITCH_TO_TABLET_MODE" desc="Alert of switching to tablet mode.">
diff --git a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_SAVE_TO_GOOGLE_DRIVE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_SAVE_TO_GOOGLE_DRIVE.png.sha1 new file mode 100644 index 0000000..3b4f792 --- /dev/null +++ b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_SAVE_TO_GOOGLE_DRIVE.png.sha1
@@ -0,0 +1 @@ +419dafa65bdab86781b8dd886e283da698850c97 \ No newline at end of file
diff --git a/ash/capture_mode/capture_mode_advanced_settings_view.cc b/ash/capture_mode/capture_mode_advanced_settings_view.cc index a4cb7022..aeeac6276 100644 --- a/ash/capture_mode/capture_mode_advanced_settings_view.cc +++ b/ash/capture_mode/capture_mode_advanced_settings_view.cc
@@ -5,6 +5,7 @@ #include "ash/capture_mode/capture_mode_advanced_settings_view.h" #include <memory> +#include <string> #include "ash/capture_mode/capture_mode_bar_view.h" #include "ash/capture_mode/capture_mode_constants.h" @@ -129,8 +130,13 @@ return; } - save_to_menu_group_->AddOrUpdateExistingOption( - custom_path.BaseName().AsUTF16Unsafe(), kCustomFolder); + const std::u16string folder_name = + controller->IsRootDriveFsPath(custom_path) + ? l10n_util::GetStringUTF16( + IDS_ASH_SCREEN_CAPTURE_SAVE_TO_GOOGLE_DRIVE) + : custom_path.BaseName().AsUTF16Unsafe(); + save_to_menu_group_->AddOrUpdateExistingOption(folder_name, kCustomFolder); + controller->CheckFolderAvailability( custom_path, base::BindOnce(
diff --git a/ash/capture_mode/capture_mode_controller.cc b/ash/capture_mode/capture_mode_controller.cc index e25fded3..4eaf728c 100644 --- a/ash/capture_mode/capture_mode_controller.cc +++ b/ash/capture_mode/capture_mode_controller.cc
@@ -700,6 +700,16 @@ return delegate_->CreateRecordingOverlayView(); } +bool CaptureModeController::IsRootDriveFsPath( + const base::FilePath& path) const { + base::FilePath mounted_path; + if (delegate_->GetDriveFsMountPointPath(&mounted_path)) { + if (path == mounted_path.Append("root")) + return true; + } + return false; +} + void CaptureModeController::OnRecordingEnded( recording::mojom::RecordingStatus status, const gfx::ImageSkia& thumbnail) { @@ -1595,10 +1605,11 @@ return CaptureModeSaveToLocation::kDefault; base::FilePath mounted_path; if (delegate_->GetDriveFsMountPointPath(&mounted_path)) { - if (dir_path == mounted_path) + const auto drive_root_path = mounted_path.Append("root"); + if (dir_path == drive_root_path) return CaptureModeSaveToLocation::kDrive; - if (mounted_path.IsParent(dir_path)) + if (drive_root_path.IsParent(dir_path)) return CaptureModeSaveToLocation::kDriveFolder; } return CaptureModeSaveToLocation::kCustomizedFolder;
diff --git a/ash/capture_mode/capture_mode_controller.h b/ash/capture_mode/capture_mode_controller.h index 0de16c90..d8de5a3 100644 --- a/ash/capture_mode/capture_mode_controller.h +++ b/ash/capture_mode/capture_mode_controller.h
@@ -194,6 +194,10 @@ // content view of the recording overlay widget. std::unique_ptr<RecordingOverlayView> CreateRecordingOverlayView(); + // Returns true if the given `path` is the root folder of DriveFS, false + // otherwise. + bool IsRootDriveFsPath(const base::FilePath& path) const; + // recording::mojom::RecordingServiceClient: void OnRecordingEnded(recording::mojom::RecordingStatus status, const gfx::ImageSkia& thumbnail) override;
diff --git a/ash/capture_mode/capture_mode_unittests.cc b/ash/capture_mode/capture_mode_unittests.cc index 0bf2483..c03108e 100644 --- a/ash/capture_mode/capture_mode_unittests.cc +++ b/ash/capture_mode/capture_mode_unittests.cc
@@ -599,10 +599,10 @@ base::FilePath CreateFolderOnDriveFS(const std::string& custom_folder_name) { auto* test_delegate = CaptureModeController::Get()->delegate_for_testing(); - base::FilePath root_drive_folder; - EXPECT_TRUE(test_delegate->GetDriveFsMountPointPath(&root_drive_folder)); + base::FilePath mount_point_path; + EXPECT_TRUE(test_delegate->GetDriveFsMountPointPath(&mount_point_path)); base::FilePath folder_on_drive_fs = - root_drive_folder.Append(custom_folder_name); + mount_point_path.Append("root").Append(custom_folder_name); base::ScopedAllowBlockingForTesting allow_blocking; const bool result = base::CreateDirectory(folder_on_drive_fs); EXPECT_TRUE(result); @@ -2480,8 +2480,9 @@ // a specific folder on drive. const auto downloads_folder = test_delegate->GetUserDefaultDownloadsFolder(); const base::FilePath custom_folder = CreateCustomFolder("test"); - base::FilePath root_drive_folder; - test_delegate->GetDriveFsMountPointPath(&root_drive_folder); + base::FilePath mount_point_path; + test_delegate->GetDriveFsMountPointPath(&mount_point_path); + const auto root_drive_folder = mount_point_path.Append("root"); const base::FilePath non_root_drive_folder = CreateFolderOnDriveFS("test"); struct { base::FilePath set_save_file_folder;
diff --git a/ash/components/arc/video_accelerator/BUILD.gn b/ash/components/arc/video_accelerator/BUILD.gn index 5b32235..943dd8b 100644 --- a/ash/components/arc/video_accelerator/BUILD.gn +++ b/ash/components/arc/video_accelerator/BUILD.gn
@@ -30,6 +30,7 @@ "//ash/components/arc/mojom:media", "//gpu/ipc/common:common", "//media", + "//media/gpu/chromeos:common", ] } }
diff --git a/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.cc b/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.cc index 3158416..24edbbf 100644 --- a/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.cc +++ b/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.cc
@@ -21,6 +21,7 @@ #include "media/base/video_frame.h" #include "media/base/video_types.h" #include "media/gpu/buffer_validation.h" +#include "media/gpu/chromeos/platform_video_frame_utils.h" #include "media/gpu/chromeos/vd_video_decode_accelerator.h" #include "media/gpu/chromeos/video_decoder_pipeline.h" #include "media/gpu/gpu_video_decode_accelerator_factory.h" @@ -637,6 +638,17 @@ return; } gmb_handle.native_pixmap_handle = std::move(protected_native_pixmap); + + // Explicitly verify the GPU Memory Buffer Handle here. Note that we do not + // do this for non-protected content because the verification happens on + // creation in that path. + if (!media::VerifyGpuMemoryBufferHandle(pixel_format, coded_size_, + gmb_handle)) { + VLOGF(1) << "Invalid GpuMemoryBufferHandle for protected content"; + client_->NotifyError( + mojom::VideoDecodeAccelerator::Result::INVALID_ARGUMENT); + return; + } } else { std::vector<base::ScopedFD> handle_fds = DuplicateFD(std::move(handle_fd), planes.size()); @@ -647,6 +659,8 @@ return; } + // Verification of the GPU Memory Buffer Handle is handled under the hood in + // this call. auto buffer_handle = CreateGpuMemoryBufferHandle( pixel_format, modifier, coded_size_, std::move(handle_fds), planes); if (!buffer_handle) { @@ -657,6 +671,7 @@ } gmb_handle = std::move(buffer_handle).value(); } + gmb_handle.id = media::GetNextGpuMemoryBufferId(); // This is the first time of ImportBufferForPicture() after // AssignPictureBuffers() is called. Call VDA::AssignPictureBuffers() here.
diff --git a/ash/components/arc/video_accelerator/gpu_arc_video_decoder.cc b/ash/components/arc/video_accelerator/gpu_arc_video_decoder.cc index b91d6bc..bbac67a0 100644 --- a/ash/components/arc/video_accelerator/gpu_arc_video_decoder.cc +++ b/ash/components/arc/video_accelerator/gpu_arc_video_decoder.cc
@@ -283,6 +283,7 @@ } const uint64_t modifier = modifier_ptr ? modifier_ptr->val : gfx::NativePixmapHandle::kNoModifier; + // TODO(b/203240043) Assign an ID to the gmb_handle. gfx::GpuMemoryBufferHandle gmb_handle = CreateGpuMemoryHandle(std::move(fd), planes, pixel_format, modifier); if (gmb_handle.is_null()) {
diff --git a/ash/components/device_activity/BUILD.gn b/ash/components/device_activity/BUILD.gn index c250103..5083708e 100644 --- a/ash/components/device_activity/BUILD.gn +++ b/ash/components/device_activity/BUILD.gn
@@ -56,9 +56,12 @@ "//dbus", "//services/device/public/cpp:test_support", "//services/network:test_support", + "//testing/gmock", "//testing/gtest", "//third_party/private_membership:private_membership", ] + + data = [ "//third_party/private_membership/src/internal/testing/regression_test_data/cros_test_data.binarypb" ] } proto_library("fresnel_service_proto") {
diff --git a/ash/components/device_activity/device_activity_client.cc b/ash/components/device_activity/device_activity_client.cc index f4c7804..55be8c2f 100644 --- a/ash/components/device_activity/device_activity_client.cc +++ b/ash/components/device_activity/device_activity_client.cc
@@ -7,13 +7,15 @@ #include "ash/components/device_activity/fresnel_pref_names.h" #include "ash/components/device_activity/fresnel_service.pb.h" #include "base/i18n/time_formatting.h" +// TODO(https://crbug.com/1269900): Migrate to use SFUL library. +#include "base/metrics/histogram_functions.h" +#include "base/strings/strcat.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" #include "components/prefs/pref_service.h" #include "crypto/hmac.h" -#include "google_apis/google_api_keys.h" #include "services/network/public/cpp/shared_url_loader_factory.h" #include "services/network/public/cpp/simple_url_loader.h" #include "services/network/public/mojom/url_response_head.mojom.h" @@ -39,22 +41,92 @@ constexpr base::TimeDelta kOprfRequestTimeout = base::Seconds(10); constexpr base::TimeDelta kQueryRequestTimeout = base::Seconds(60); -// Default response code to use before retrieving the response code from the -// actual response body. -constexpr int32_t kDefaultResponseCode = -1; - -// Response code representing a successfully completed network request. -constexpr int32_t kSuccessResponseCode = 200; - -// TODO(https://crbug.com/1268095): Refactor to pass base url via. constructor. -const char kFresnelBaseUrl[] = - "https://autopush-crosfresnel-pa.sandbox.googleapis.com"; - +// TODO(https://crbug.com/1272922): Move shared configuration constants to +// separate file. const char kFresnelHealthCheckEndpoint[] = "/v1/fresnel/healthCheck"; const char kFresnelImportRequestEndpoint[] = "/v1/fresnel/psmRlweImport"; const char kFresnelOprfRequestEndpoint[] = "/v1/fresnel/psmRlweOprf"; const char kFresnelQueryRequestEndpoint[] = "/v1/fresnel/psmRlweQuery"; +// UMA histograms defined in: +// //tools/metrics/histograms/metadata/ash/histograms.xml. +// +// Count number of times a state has been entered. +const char kHistogramStateCount[] = "Ash.DeviceActiveClient.StateCount"; + +// Duration histogram uses State variant in order to create +// unique histograms measuring durations by State. +const char kHistogramDurationPrefix[] = "Ash.DeviceActiveClient.Duration"; + +// Response histogram uses State variant in order to create +// unique histograms measuring responses by State. +const char kHistogramResponsePrefix[] = "Ash.DeviceActiveClient.Response"; + +// Count the number of boolean membership request results. +const char kDeviceActiveClientQueryMembershipResult[] = + "Ash.DeviceActiveClient.QueryMembershipResult"; + +// Generates the full histogram name for histogram variants based on state. +std::string HistogramVariantName(const std::string& histogram_prefix, + DeviceActivityClient::State state) { + switch (state) { + case DeviceActivityClient::State::kIdle: + return base::StrCat({histogram_prefix, ".Idle"}); + case DeviceActivityClient::State::kCheckingMembershipOprf: + return base::StrCat({histogram_prefix, ".CheckingMembershipOprf"}); + case DeviceActivityClient::State::kCheckingMembershipQuery: + return base::StrCat({histogram_prefix, ".CheckingMembershipQuery"}); + case DeviceActivityClient::State::kCheckingIn: + return base::StrCat({histogram_prefix, ".CheckingIn"}); + case DeviceActivityClient::State::kHealthCheck: + return base::StrCat({histogram_prefix, ".HealthCheck"}); + default: + NOTREACHED() << "Invalid State."; + return base::StrCat({histogram_prefix, ".Unknown"}); + } +} + +void RecordStateCountMetric(DeviceActivityClient::State state) { + base::UmaHistogramEnumeration(kHistogramStateCount, state); +} + +void RecordQueryMembershipResultBoolean(bool is_member) { + base::UmaHistogramBoolean(kDeviceActiveClientQueryMembershipResult, + is_member); +} + +// Histogram sliced by duration and state. +void RecordDurationStateMetric(DeviceActivityClient::State state, + const base::TimeDelta duration) { + std::string duration_state_histogram_name = + HistogramVariantName(kHistogramDurationPrefix, state); + base::UmaHistogramCustomTimes(duration_state_histogram_name, duration, + base::Milliseconds(1), base::Seconds(100), + 100 /* number of histogram buckets */); +} + +// Histogram slices by PSM response and state. +void RecordResponseStateMetric(DeviceActivityClient::State state, + int net_code) { + // Mapping status code to PsmResponse is used to record UMA histograms + // for responses by state. + DeviceActivityClient::PsmResponse response; + switch (net_code) { + case net::OK: + response = DeviceActivityClient::PsmResponse::kSuccess; + break; + case net::ERR_TIMED_OUT: + response = DeviceActivityClient::PsmResponse::kTimeout; + break; + default: + response = DeviceActivityClient::PsmResponse::kError; + break; + } + + base::UmaHistogramEnumeration( + HistogramVariantName(kHistogramResponsePrefix, state), response); +} + std::unique_ptr<network::ResourceRequest> GenerateResourceRequest( const std::string& request_method, const GURL& url, @@ -150,16 +222,24 @@ NetworkStateHandler* handler, PrefService* local_state, scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, + std::unique_ptr<PsmDelegate> psm_delegate, + std::unique_ptr<base::RepeatingTimer> report_timer, + const std::string& fresnel_base_url, + const std::string& api_key, const std::string& psm_device_active_secret) - : api_key_(google_apis::GetFresnelAPIKey()), - report_timer_(ConstructReportTimer()), - network_state_handler_(handler), + : network_state_handler_(handler), local_state_(local_state), url_loader_factory_(url_loader_factory), + psm_delegate_(std::move(psm_delegate)), + report_timer_(std::move(report_timer)), + fresnel_base_url_(fresnel_base_url), + api_key_(api_key), psm_device_active_secret_(psm_device_active_secret) { DCHECK(network_state_handler_); DCHECK(local_state_); DCHECK(url_loader_factory_); + DCHECK(psm_delegate_); + DCHECK(report_timer_); report_timer_->Start(FROM_HERE, kTimeToRepeat, this, &DeviceActivityClient::TransitionOutOfIdle); @@ -172,11 +252,6 @@ network_state_handler_->RemoveObserver(this, FROM_HERE); } -std::unique_ptr<base::RepeatingTimer> -DeviceActivityClient::ConstructReportTimer() { - return std::make_unique<base::RepeatingTimer>(); -} - base::RepeatingTimer* DeviceActivityClient::GetReportTimer() { return report_timer_.get(); } @@ -202,13 +277,10 @@ } GURL DeviceActivityClient::GetFresnelURL() const { - GURL base_url(kFresnelBaseUrl); + GURL base_url(fresnel_base_url_); GURL::Replacements replacements; switch (state_) { - case State::kHealthCheck: - replacements.SetPathStr(kFresnelHealthCheckEndpoint); - break; case State::kCheckingMembershipOprf: replacements.SetPathStr(kFresnelOprfRequestEndpoint); break; @@ -218,8 +290,13 @@ case State::kCheckingIn: replacements.SetPathStr(kFresnelImportRequestEndpoint); break; - case State::kIdle: + case State::kHealthCheck: + replacements.SetPathStr(kFresnelHealthCheckEndpoint); + break; + case State::kIdle: // Fallthrough to |kUnknown| case. + case State::kUnknown: NOTREACHED(); + break; } return base_url.ReplaceComponents(replacements); @@ -262,7 +339,7 @@ std::vector<psm_rlwe::RlwePlaintextId> psm_rlwe_ids = { current_day_psm_id_.value()}; - auto status_or_client = psm_rlwe::PrivateMembershipRlweClient::Create( + auto status_or_client = psm_delegate_->CreatePsmClient( psm_rlwe::RlweUseCase::CROS_FRESNEL_DAILY, psm_rlwe_ids); if (!status_or_client.ok()) { @@ -271,7 +348,9 @@ } psm_rlwe_client_ = std::move(status_or_client.value()); - TransitionToCheckMembershipOprf(); + // During rollout, we perform CheckIn without CheckMembership for powerwash, + // recovery, or RMA devices. + TransitionToCheckIn(); } } @@ -279,9 +358,14 @@ DCHECK_EQ(state_, State::kIdle); DCHECK(!url_loader_); + state_timer_ = base::ElapsedTimer(); + // |state_| must be set correctly in order to generate correct URL. state_ = State::kHealthCheck; + // Report UMA histogram for transitioning state to |kHealthCheck|. + RecordStateCountMetric(state_); + auto resource_request = GenerateResourceRequest( net::HttpRequestHeaders::kGetMethod, GetFresnelURL(), api_key_); @@ -302,22 +386,16 @@ std::unique_ptr<std::string> response_body) { DCHECK_EQ(state_, State::kHealthCheck); - int32_t response_code = kDefaultResponseCode; - const network::mojom::URLResponseHead* response_info = - url_loader_->ResponseInfo(); - - if (response_info && response_info->headers) { - response_code = response_info->headers->response_code(); - } - - // TODO(https://crbug.com/1262216): Add UMA histogram for response code by - // request type. - VLOG(1) << "Health Check Request returned response code " << response_code; - // Use RAII to reset |url_loader_| after current function scope. // Resetting |url_loader_| also invalidates the |response_info| variable. auto url_loader = std::move(url_loader_); + int net_code = url_loader->NetError(); + RecordResponseStateMetric(state_, net_code); + + // Record duration of |kHealthCheck| state. + RecordDurationStateMetric(state_, state_timer_.Elapsed()); + // Transition back to kIdle state after performing a health check on servers. TransitionToIdle(); } @@ -326,12 +404,18 @@ DCHECK_EQ(state_, State::kIdle); DCHECK(!url_loader_); + state_timer_ = base::ElapsedTimer(); + // |state_| must be set correctly in order to generate correct URL. state_ = State::kCheckingMembershipOprf; + // Report UMA histogram for transitioning state to |kCheckingMembershipOprf|. + RecordStateCountMetric(state_); + // Generate PSM Oprf request body. const auto status_or_oprf_request = psm_rlwe_client_->CreateOprfRequest(); if (!status_or_oprf_request.ok()) { + RecordDurationStateMetric(state_, state_timer_.Elapsed()); TransitionToIdle(); return; } @@ -365,31 +449,26 @@ void DeviceActivityClient::OnCheckMembershipOprfDone( std::unique_ptr<std::string> response_body) { - int32_t response_code = kDefaultResponseCode; - const network::mojom::URLResponseHead* response_info = - url_loader_->ResponseInfo(); - if (response_info && response_info->headers) { - response_code = response_info->headers->response_code(); - } + DCHECK_EQ(state_, State::kCheckingMembershipOprf); // Use RAII to reset |url_loader_| after current function scope. // Resetting |url_loader_| also invalidates the |response_info| variable. auto url_loader = std::move(url_loader_); - // TODO(https://crbug.com/1262216): Report UMA histogram response codes of - // Oprf request. - VLOG(1) << "Oprf Check Membership request returned response code " - << response_code; + int net_code = url_loader->NetError(); + RecordResponseStateMetric(state_, net_code); // Convert serialized response body to oprf response protobuf. FresnelPsmRlweOprfResponse psm_oprf_response; if (!response_body || !psm_oprf_response.ParseFromString(*response_body)) { + RecordDurationStateMetric(state_, state_timer_.Elapsed()); TransitionToIdle(); return; } // Parse |fresnel_oprf_response| for oprf_response. if (!psm_oprf_response.has_rlwe_oprf_response()) { + RecordDurationStateMetric(state_, state_timer_.Elapsed()); TransitionToIdle(); return; } @@ -397,6 +476,7 @@ psm_rlwe::PrivateMembershipRlweOprfResponse oprf_response = psm_oprf_response.rlwe_oprf_response(); + RecordDurationStateMetric(state_, state_timer_.Elapsed()); TransitionToCheckMembershipQuery(oprf_response); } @@ -405,13 +485,19 @@ DCHECK_EQ(state_, State::kCheckingMembershipOprf); DCHECK(!url_loader_); + state_timer_ = base::ElapsedTimer(); + // |state_| must be set correctly in order to generate correct URL. state_ = State::kCheckingMembershipQuery; + // Report UMA histogram for transitioning state to |kCheckingMembershipQuery|. + RecordStateCountMetric(state_); + // Generate PSM Query request body. const auto status_or_query_request = psm_rlwe_client_->CreateQueryRequest(oprf_response); if (!status_or_query_request.ok()) { + RecordDurationStateMetric(state_, state_timer_.Elapsed()); TransitionToIdle(); return; } @@ -445,31 +531,26 @@ void DeviceActivityClient::OnCheckMembershipQueryDone( std::unique_ptr<std::string> response_body) { - int32_t response_code = kDefaultResponseCode; - const network::mojom::URLResponseHead* response_info = - url_loader_->ResponseInfo(); - if (response_info && response_info->headers) { - response_code = response_info->headers->response_code(); - } + DCHECK_EQ(state_, State::kCheckingMembershipQuery); // Use RAII to reset |url_loader_| after current function scope. // Resetting |url_loader_| also invalidates the |response_info| variable. auto url_loader = std::move(url_loader_); - // TODO(https://crbug.com/1262216): Report UMA histogram for response codes of - // Query request. - VLOG(1) << "Query Check Membership request returned response code " - << response_code; + int net_code = url_loader->NetError(); + RecordResponseStateMetric(state_, net_code); // Convert serialized response body to fresnel query response protobuf. FresnelPsmRlweQueryResponse psm_query_response; if (!response_body || !psm_query_response.ParseFromString(*response_body)) { + RecordDurationStateMetric(state_, state_timer_.Elapsed()); TransitionToIdle(); return; } // Parse |fresnel_query_response| for psm query_response. if (!psm_query_response.has_rlwe_query_response()) { + RecordDurationStateMetric(state_, state_timer_.Elapsed()); TransitionToIdle(); return; } @@ -479,6 +560,7 @@ auto status_or_response = psm_rlwe_client_->ProcessResponse(query_response); if (!status_or_response.ok()) { + RecordDurationStateMetric(state_, state_timer_.Elapsed()); TransitionToIdle(); return; } @@ -490,23 +572,32 @@ bool is_psm_id_member = membership_response.is_member(); + // Record the query membership result to UMA histogram. + RecordQueryMembershipResultBoolean(is_psm_id_member); + if (!is_psm_id_member) { + RecordDurationStateMetric(state_, state_timer_.Elapsed()); TransitionToCheckIn(); } else { // Update local state to signal ping has already been sent for current day. local_state_->SetTime(prefs::kDeviceActiveLastKnownDailyPingTimestamp, last_transition_out_of_idle_time_); + RecordDurationStateMetric(state_, state_timer_.Elapsed()); TransitionToIdle(); } } void DeviceActivityClient::TransitionToCheckIn() { - DCHECK_EQ(state_, State::kCheckingMembershipQuery); DCHECK(!url_loader_); + state_timer_ = base::ElapsedTimer(); + // |state_| must be set correctly in order to generate correct URL. state_ = State::kCheckingIn; + // Report UMA histogram for transitioning state to |kCheckingIn|. + RecordStateCountMetric(state_); + std::string current_psm_id_str = current_day_psm_id_.value().sensitive_id(); // Generate Fresnel PSM import request body. @@ -541,28 +632,23 @@ void DeviceActivityClient::OnCheckInDone( std::unique_ptr<std::string> response_body) { - int32_t response_code = kDefaultResponseCode; - const network::mojom::URLResponseHead* response_info = - url_loader_->ResponseInfo(); - if (response_info && response_info->headers) { - response_code = response_info->headers->response_code(); - } - - // TODO(https://crbug.com/1262216): Report UMA histogram for response codes of - // Import request. - VLOG(1) << "Check In request returned response code " << response_code; + DCHECK_EQ(state_, State::kCheckingIn); // Use RAII to reset |url_loader_| after current function scope. // Resetting |url_loader_| also invalidates the |response_info| variable. auto url_loader = std::move(url_loader_); - // Successful import request - device active was imported successfully. - if (response_code == kSuccessResponseCode) { + int net_code = url_loader->NetError(); + RecordResponseStateMetric(state_, net_code); + + // Successful import request - PSM ID was imported successfully. + if (net_code == net::OK) { // Update local state pref to record reporting device active. local_state_->SetTime(prefs::kDeviceActiveLastKnownDailyPingTimestamp, last_transition_out_of_idle_time_); } + RecordDurationStateMetric(state_, state_timer_.Elapsed()); TransitionToIdle(); } @@ -572,6 +658,9 @@ current_day_window_id_ = absl::nullopt; current_day_psm_id_ = absl::nullopt; + + // Report UMA histogram for transitioning state back to |kIdle|. + RecordStateCountMetric(state_); } } // namespace device_activity
diff --git a/ash/components/device_activity/device_activity_client.h b/ash/components/device_activity/device_activity_client.h index 757e8ba7..64e5e5d 100644 --- a/ash/components/device_activity/device_activity_client.h +++ b/ash/components/device_activity/device_activity_client.h
@@ -11,6 +11,7 @@ #include "base/memory/scoped_refptr.h" #include "base/memory/weak_ptr.h" #include "base/time/time.h" +#include "base/timer/elapsed_timer.h" #include "base/timer/timer.h" #include "chromeos/network/network_state.h" #include "chromeos/network/network_state_handler.h" @@ -29,6 +30,18 @@ namespace ash { namespace device_activity { +// Create a delegate which can be used to create fakes in unit tests. +// Fake via. delegate is required for creating deterministic unit tests. +class COMPONENT_EXPORT(ASH_DEVICE_ACTIVITY) PsmDelegate { + public: + virtual ~PsmDelegate() = default; + virtual rlwe::StatusOr< + std::unique_ptr<private_membership::rlwe::PrivateMembershipRlweClient>> + CreatePsmClient(private_membership::rlwe::RlweUseCase use_case, + const std::vector<private_membership::rlwe::RlwePlaintextId>& + plaintext_ids) = 0; +}; + // Observes the network for connected state to determine whether the device // is active in a given window. // State Transition flow: @@ -38,12 +51,29 @@ : public chromeos::NetworkStateHandlerObserver { public: // Tracks the state the client is in, given the use case (i.e DAILY). + // These values are persisted to logs. Entries should not be renumbered and + // numeric values should never be reused. enum class State { - kIdle, // Wait on network connection OR |report_timer_| to trigger. - kCheckingMembershipOprf, // Phase 1 of the |CheckMembership| request. - kCheckingMembershipQuery, // Phase 2 of the |CheckMembership| request. - kCheckingIn, // |CheckIn| PSM device active request. - kHealthCheck, // Query to perform server health check. + kUnknown = 0, // Default value, typically we should never be in this state. + kIdle = 1, // Wait on network connection OR |report_timer_| to trigger. + kCheckingMembershipOprf = 2, // Phase 1 of the |CheckMembership| request. + kCheckingMembershipQuery = 3, // Phase 2 of the |CheckMembership| request. + kCheckingIn = 4, // |CheckIn| PSM device active request. + kHealthCheck = 5, // Query to perform server health check. + kMaxValue = kHealthCheck, + }; + + // Categorize PSM response codes which will be used when bucketing UMA + // histograms. + // + // These values are persisted to logs. Entries should not be renumbered and + // numeric values should never be reused. + enum class PsmResponse { + kUnknown = 0, // Uncategorized response type returned. + kSuccess = 1, // Successfully completed PSM request. + kError = 2, // Error completing PSM request. + kTimeout = 3, // Timed out while completing PSM request. + kMaxValue = kTimeout, }; // Fires device active pings while the device network is connected. @@ -51,14 +81,15 @@ NetworkStateHandler* handler, PrefService* local_state, scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, + std::unique_ptr<PsmDelegate> psm_delegate, + std::unique_ptr<base::RepeatingTimer> report_timer, + const std::string& fresnel_server_url, + const std::string& api_key, const std::string& psm_device_active_secret); DeviceActivityClient(const DeviceActivityClient&) = delete; DeviceActivityClient& operator=(const DeviceActivityClient&) = delete; ~DeviceActivityClient() override; - // Initialize repeating timer to run every |kTimeToRepeat|. - virtual std::unique_ptr<base::RepeatingTimer> ConstructReportTimer(); - // Returns pointer to |report_timer_|. base::RepeatingTimer* GetReportTimer(); @@ -67,34 +98,6 @@ State GetState() const; - protected: - // Send Health Check network request and update |state_|. - // Before calling this method: |state_| is expected to be |kIdle|. - // After calling this method: |state_| set to |kHealthCheck|. - virtual void TransitionToHealthCheck(); - - // Send Oprf network request and update |state_|. - // Before calling this method: |state_| is expected to be |kIdle|. - // After calling this method: |state_| set to |kCheckingMembershipOprf|. - virtual void TransitionToCheckMembershipOprf(); - - // Update |state_| to |kIdle|. - virtual void TransitionToIdle(); - - // Send Query network request and update |state_|. - // Before calling this method: |state_| is expected to be - // |kCheckingMembershipOprf|. After calling this method: |state_| set to - // |kCheckingMembershipQuery|. - virtual void TransitionToCheckMembershipQuery( - const private_membership::rlwe::PrivateMembershipRlweOprfResponse& - oprf_response); - - // Send Import network request and update |state_|. - // Before calling this method: |state_| is expected to be - // |kCheckingMembershipQuery|. After calling this method: |state_| set to - // |kCheckingIn|. - virtual void TransitionToCheckIn(); - private: // Handles device network connecting successfully. void OnNetworkOnline(); @@ -105,29 +108,52 @@ // Called when device network comes online as well as by |report_timer_|. void TransitionOutOfIdle(); + // Send Health Check network request and update |state_|. + // Before calling this method: |state_| is expected to be |kIdle|. + // After calling this method: |state_| set to |kHealthCheck|. + void TransitionToHealthCheck(); + // Callback from asynchronous method |TransitionToHealthCheck|. void OnHealthCheckDone(std::unique_ptr<std::string> response_body); + // Send Oprf network request and update |state_|. + // Before calling this method: |state_| is expected to be |kIdle|. + // After calling this method: |state_| set to |kCheckingMembershipOprf|. + void TransitionToCheckMembershipOprf(); + // Callback from asynchronous method |TransitionToCheckMembershipOprf|. void OnCheckMembershipOprfDone(std::unique_ptr<std::string> response_body); + // Send Query network request and update |state_|. + // Before calling this method: |state_| is expected to be + // |kCheckingMembershipOprf|. + // After calling this method: |state_| set to |kCheckingMembershipQuery|. + void TransitionToCheckMembershipQuery( + const private_membership::rlwe::PrivateMembershipRlweOprfResponse& + oprf_response); + // Callback from asynchronous method |TransitionToCheckMembershipQuery|. // Check in PSM id based on |response_body| from CheckMembershipQuery. void OnCheckMembershipQueryDone(std::unique_ptr<std::string> response_body); + // Send Import network request and update |state_|. + // Before calling this method: |state_| is expected to be either + // |kCheckingMembershipQuery| or |kIdle|. + // After calling this method: |state_| set to |kCheckingIn|. + void TransitionToCheckIn(); + // Callback from asynchronous method |TransitionToCheckIn|. void OnCheckInDone(std::unique_ptr<std::string> response_body); + // Updates |state_| to |kIdle| and resets state based member variables. + void TransitionToIdle(); + // Tracks the current state of the DeviceActivityClient. State state_ = State::kIdle; // Keep track of whether the device is connected to the network. bool network_connected_ = false; - // API key used to authenticate with the Fresnel server. This key is read from - // the chrome-internal repository and is not publicly exposed in Chromium. - const std::string api_key_; - // Generated on demand each time the state machine leaves the idle state. // It is reused by several states. It is reset to nullopt. // This field is used apart of PSM Import request. @@ -141,10 +167,10 @@ // Time the device last transitioned out of idle state. base::Time last_transition_out_of_idle_time_; - // Tries reporting device actives every |kTimeToRepeat| from when this class - // is initialized. Time of class initialization depends on when the device is - // turned on (when |ChromeBrowserMainPartsAsh::PostBrowserStart| is run). - std::unique_ptr<base::RepeatingTimer> report_timer_; + // Generated when entering new |state_| and reset when leaving |state_|. + // This field is only used to determine total state duration, which is + // reported to UMA via. histograms. + base::ElapsedTimer state_timer_; // Generated on demand each time the state machine leaves the idle state. // Client Generates protos used in request body of Oprf and Query requests. @@ -172,6 +198,21 @@ // |url_loader_factory_| outlives |url_loader_|. scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_; + // Abstract class used to generate the |psm_rlwe_client_|. + std::unique_ptr<PsmDelegate> psm_delegate_; + + // Tries reporting device actives every |kTimeToRepeat| from when this class + // is initialized. Time of class initialization depends on when the device is + // turned on (when |ChromeBrowserMainPartsAsh::PostBrowserStart| is run). + std::unique_ptr<base::RepeatingTimer> report_timer_; + + // Base Fresnel server URL is set by |DeviceActivityClient| constructor. + const std::string fresnel_base_url_; + + // API key used to authenticate with the Fresnel server. This key is read from + // the chrome-internal repository and is not publicly exposed in Chromium. + const std::string api_key_; + // The ChromeOS platform code will provide a derived PSM device active secret // via callback. //
diff --git a/ash/components/device_activity/device_activity_client_unittest.cc b/ash/components/device_activity/device_activity_client_unittest.cc index 24a52cd2..ba62950d0 100644 --- a/ash/components/device_activity/device_activity_client_unittest.cc +++ b/ash/components/device_activity/device_activity_client_unittest.cc
@@ -6,72 +6,202 @@ #include "ash/components/device_activity/device_activity_controller.h" #include "ash/components/device_activity/fresnel_pref_names.h" +#include "ash/components/device_activity/fresnel_service.pb.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/no_destructor.h" +#include "base/path_service.h" +#include "base/test/metrics/histogram_tester.h" #include "base/test/task_environment.h" +#include "base/timer/mock_timer.h" #include "chromeos/network/network_state_handler_observer.h" #include "chromeos/network/network_state_test_helper.h" #include "components/prefs/testing_pref_service.h" +#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" #include "services/network/test/test_shared_url_loader_factory.h" +#include "services/network/test/test_url_loader_factory.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/cros_system_api/dbus/shill/dbus-constants.h" +#include "third_party/private_membership/src/internal/testing/regression_test_data/regression_test_data.pb.h" #include "third_party/private_membership/src/private_membership_rlwe_client.h" namespace ash { namespace device_activity { + +namespace psm_rlwe = private_membership::rlwe; + namespace { -constexpr char kWifiServiceGuid[] = "wifi_guid"; +// Holds data used to create deterministic PSM network request/response protos. +struct PsmTestData { + psm_rlwe::PrivateMembershipRlweClientRegressionTestData::TestCase test_case; + std::vector<psm_rlwe::RlwePlaintextId> plaintext_ids; + FresnelPsmRlweOprfResponse fresnel_oprf_response; + FresnelPsmRlweQueryResponse fresnel_query_response; +}; +PsmTestData* GetPsmTestData() { + static base::NoDestructor<PsmTestData> data; + return data.get(); +} + +// TODO(https://crbug.com/1272922): Move shared configuration constants to +// separate file. +// +// URLs for the different network requests being performed. +const char kTestFresnelBaseUrl[] = "https://dummy.googleapis.com"; +const char kPsmImportRequestEndpoint[] = "/v1/fresnel/psmRlweImport"; + +// Create fake secrets used by the |DeviceActivityClient|. constexpr char kFakePsmDeviceActiveSecret[] = "FAKE_PSM_DEVICE_ACTIVE_SECRET"; +constexpr char kFakeFresnelApiKey[] = "FAKE_FRESNEL_API_KEY"; + +// Number of test cases exist in cros_test_data.binarypb file, which is part of +// private_membership third_party library. +const int kNumberOfPsmTestCases = 10; + +// PrivateSetMembership regression tests maximum file size which is 4MB. +const size_t kMaxFileSizeInBytes = 4 * (1 << 20); + +std::string GetFresnelTestEndpoint(const std::string& endpoint) { + return kTestFresnelBaseUrl + endpoint; +} + +bool ParseProtoFromFile(const base::FilePath& file_path, + google::protobuf::MessageLite* out_proto) { + if (!out_proto) + return false; + + std::string file_content; + if (!base::ReadFileToStringWithMaxSize(file_path, &file_content, + kMaxFileSizeInBytes)) { + return false; + } + return out_proto->ParseFromString(file_content); +} + +base::TimeDelta TimeUntilNextUTCMidnight() { + const auto now = base::Time::Now(); + return (now.UTCMidnight() + base::Hours(base::Time::kHoursPerDay) - now); +} } // namespace -class MockDeviceActivityClient : public DeviceActivityClient { +class FakePsmDelegate : public PsmDelegate { public: - MockDeviceActivityClient( - NetworkStateHandler* handler, - PrefService* local_state, - scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) - : DeviceActivityClient(handler, - local_state, - url_loader_factory, - kFakePsmDeviceActiveSecret) {} + FakePsmDelegate(const std::string& ec_cipher_key, + const std::string& seed, + const std::vector<psm_rlwe::RlwePlaintextId>& plaintext_ids) + : ec_cipher_key_(ec_cipher_key), + seed_(seed), + plaintext_ids_(plaintext_ids) {} + FakePsmDelegate(const FakePsmDelegate&) = delete; + FakePsmDelegate& operator=(const FakePsmDelegate&) = delete; + ~FakePsmDelegate() override = default; - MOCK_METHOD(void, TransitionToHealthCheck, (), (override)); - MOCK_METHOD(void, TransitionToCheckMembershipOprf, (), (override)); - MOCK_METHOD( - void, - TransitionToCheckMembershipQuery, - (const private_membership::rlwe::PrivateMembershipRlweOprfResponse& - oprf_response), - (override)); - MOCK_METHOD(void, TransitionToCheckIn, (), (override)); + // PsmDelegate: + rlwe::StatusOr< + std::unique_ptr<private_membership::rlwe::PrivateMembershipRlweClient>> + CreatePsmClient(private_membership::rlwe::RlweUseCase use_case, + const std::vector<private_membership::rlwe::RlwePlaintextId>& + plaintext_ids) override { + return psm_rlwe::PrivateMembershipRlweClient::CreateForTesting( + use_case, plaintext_ids_, ec_cipher_key_, seed_); + } + + private: + // Used by the PSM client to generate deterministic request/response protos. + std::string ec_cipher_key_; + std::string seed_; + std::vector<psm_rlwe::RlwePlaintextId> plaintext_ids_; }; class DeviceActivityClientTest : public testing::Test { public: - DeviceActivityClientTest() = default; + DeviceActivityClientTest() + : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) { + // Start base::Time::Now() at least after epoch time by forwarding 24h. + // DeviceActivityClient assumes epoch as a date in the past. + // Remote env. runs unit tests assuming base::Time::Now() is epoch. + task_environment_.FastForwardBy(base::Hours(base::Time::kHoursPerDay)); + task_environment_.RunUntilIdle(); + } DeviceActivityClientTest(const DeviceActivityClientTest&) = delete; DeviceActivityClientTest& operator=(const DeviceActivityClientTest&) = delete; ~DeviceActivityClientTest() override = default; protected: + static void SetUpTestSuite() { + // Initialize |psm_test_case_| which is used to generate deterministic psm + // protos. + CreatePsmTestCase(); + + PsmTestData* psm_test_data = GetPsmTestData(); + + // Return well formed plaintext ids used in faking PSM network requests. + std::vector<psm_rlwe::RlwePlaintextId> fake_plaintext_ids{ + psm_test_data->test_case.plaintext_id()}; + psm_test_data->plaintext_ids = std::move(fake_plaintext_ids); + + // Initialize well formed Oprf and Query response body used to + // deterministically fake PSM network responses. + *psm_test_data->fresnel_oprf_response.mutable_rlwe_oprf_response() = + psm_test_data->test_case.oprf_response(); + *psm_test_data->fresnel_query_response.mutable_rlwe_query_response() = + psm_test_data->test_case.query_response(); + } + + static void CreatePsmTestCase() { + base::FilePath src_root_dir; + ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &src_root_dir)); + const base::FilePath kPsmTestDataPath = + src_root_dir.AppendASCII("third_party") + .AppendASCII("private_membership") + .AppendASCII("src") + .AppendASCII("internal") + .AppendASCII("testing") + .AppendASCII("regression_test_data") + .AppendASCII("cros_test_data.binarypb"); + ASSERT_TRUE(base::PathExists(kPsmTestDataPath)); + psm_rlwe::PrivateMembershipRlweClientRegressionTestData test_data; + ASSERT_TRUE(ParseProtoFromFile(kPsmTestDataPath, &test_data)); + + // Note that the test cases can change since it's read from the binarypb. + // This can cause unexpected failures for the unit tests below. + // As a safety precaution, check whether the number of tests change. + ASSERT_EQ(test_data.test_cases_size(), kNumberOfPsmTestCases); + + // Sets |psm_test_case_| to have one of the fake PSM request/response + // protos. + GetPsmTestData()->test_case = test_data.test_cases(0); + } + // testing::Test: void SetUp() override { + // Initialize pointer to our fake |PsmTestData| object. + psm_test_data_ = GetPsmTestData(); + network_state_test_helper_ = std::make_unique<NetworkStateTestHelper>( /*use_default_devices_and_services=*/false); CreateWifiNetworkConfig(); - // Initialize local state prefs used by device_activity_client class. + // Initialize |local_state_| prefs used by device_activity_client class. DeviceActivityController::RegisterPrefs(local_state_.registry()); + test_shared_loader_factory_ = + base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( + &test_url_loader_factory_); - shared_url_loader_factory_ = - base::MakeRefCounted<network::TestSharedURLLoaderFactory>(); - - device_activity_client_ = std::make_unique<MockDeviceActivityClient>( + device_activity_client_ = std::make_unique<DeviceActivityClient>( network_state_test_helper_->network_state_handler(), &local_state_, - shared_url_loader_factory_); + test_shared_loader_factory_, + std::make_unique<FakePsmDelegate>( + psm_test_data_->test_case.ec_cipher_key(), + psm_test_data_->test_case.seed(), + std::move(psm_test_data_->plaintext_ids)), + std::make_unique<base::MockRepeatingTimer>(), kTestFresnelBaseUrl, + kFakeFresnelApiKey, kFakePsmDeviceActiveSecret); } void TearDown() override {} @@ -81,7 +211,9 @@ std::stringstream ss; ss << "{" - << " \"GUID\": \"" << kWifiServiceGuid << "\"," + << " \"GUID\": \"" + << "wifi_guid" + << "\"," << " \"Type\": \"" << shill::kTypeWifi << "\"," << " \"State\": \"" << shill::kStateOffline << "\"" << "}"; @@ -95,30 +227,243 @@ network_state_test_helper_->SetServiceProperty(wifi_network_service_path_, shill::kStateProperty, base::Value(network_state)); - base::RunLoop().RunUntilIdle(); + task_environment_.RunUntilIdle(); + } + + // Used in tests, after |device_activity_client_| is generated. + // Triggers the repeating timer in the client code. + void FireTimer() { + base::MockRepeatingTimer* mock_timer = + static_cast<base::MockRepeatingTimer*>( + device_activity_client_->GetReportTimer()); + if (mock_timer->IsRunning()) + mock_timer->Fire(); } base::test::TaskEnvironment task_environment_; + + // The underlying |psm_test_data_| object will outlive this testing class. + PsmTestData* psm_test_data_ = nullptr; + std::unique_ptr<NetworkStateTestHelper> network_state_test_helper_; TestingPrefServiceSimple local_state_; - scoped_refptr<network::TestSharedURLLoaderFactory> shared_url_loader_factory_; + scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_; + network::TestURLLoaderFactory test_url_loader_factory_; std::unique_ptr<DeviceActivityClient> device_activity_client_; std::string wifi_network_service_path_; + base::HistogramTester histogram_tester_; }; -TEST_F(DeviceActivityClientTest, DefaultStateIsIdle) { +TEST_F(DeviceActivityClientTest, DefaultStatesAreInitializedProperly) { + EXPECT_EQ(device_activity_client_->GetState(), + DeviceActivityClient::State::kIdle); + EXPECT_EQ( + local_state_.GetTime(prefs::kDeviceActiveLastKnownDailyPingTimestamp), + base::Time::UnixEpoch()); + EXPECT_TRUE(device_activity_client_->GetReportTimer()->IsRunning()); +} + +TEST_F(DeviceActivityClientTest, NetworkRequestsUseFakeApiKey) { + // When network comes online, the device performs an Import network request. + SetWifiNetworkState(shill::kStateOnline); + + network::TestURLLoaderFactory::PendingRequest* request = + test_url_loader_factory_.GetPendingRequest(0); + task_environment_.RunUntilIdle(); + + std::string api_key_header_value; + request->request.headers.GetHeader("X-Goog-Api-Key", &api_key_header_value); + + EXPECT_EQ(api_key_header_value, kFakeFresnelApiKey); +} + +// Fire timer to run |TransitionOutOfIdle|. Network is currently disconnected +// so the client is expected to go back to |kIdle| state. +TEST_F(DeviceActivityClientTest, + FireTimerWithoutNetworkKeepsClientinIdleState) { + SetWifiNetworkState(shill::kStateOffline); + FireTimer(); + EXPECT_EQ(device_activity_client_->GetState(), DeviceActivityClient::State::kIdle); } -TEST_F(DeviceActivityClientTest, DefaultTimeUnixEpoch) { - EXPECT_EQ( - local_state_.GetTime(prefs::kDeviceActiveLastKnownDailyPingTimestamp), - base::Time::UnixEpoch()); +TEST_F(DeviceActivityClientTest, PerformSuccessfulCheckIn) { + // Device active reporting starts checking in on network connect. + SetWifiNetworkState(shill::kStateOnline); + + EXPECT_EQ(device_activity_client_->GetState(), + DeviceActivityClient::State::kCheckingIn); + + base::Time prev_time = + local_state_.GetTime(prefs::kDeviceActiveLastKnownDailyPingTimestamp); + + test_url_loader_factory_.SimulateResponseForPendingRequest( + GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(), + net::HTTP_OK); + task_environment_.RunUntilIdle(); + + base::Time new_time = + local_state_.GetTime(prefs::kDeviceActiveLastKnownDailyPingTimestamp); + + // After a PSM identifier is checked in, the |local_state_| + // |kDeviceActiveLastKnownDailyPingTimestamp| should be updated. + EXPECT_LT(prev_time, new_time); + + EXPECT_EQ(device_activity_client_->GetState(), + DeviceActivityClient::State::kIdle); } -TEST_F(DeviceActivityClientTest, DefaultTimerIsRunning) { - EXPECT_TRUE(device_activity_client_->GetReportTimer()->IsRunning()); +TEST_F(DeviceActivityClientTest, NetworkReconnectsAfterSuccessfulCheckIn) { + // Device active reporting starts checking in on network connect. + SetWifiNetworkState(shill::kStateOnline); + + // Return well formed Import response body. + test_url_loader_factory_.SimulateResponseForPendingRequest( + GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(), + net::HTTP_OK); + task_environment_.RunUntilIdle(); + + // Reconnecting network connection triggers |TransitionOutOfIdle|. + SetWifiNetworkState(shill::kStateOffline); + SetWifiNetworkState(shill::kStateOnline); + + // Check that no additional network requests are pending since the PSM id + // has already been imported. + EXPECT_EQ(test_url_loader_factory_.NumPending(), 0); +} + +TEST_F(DeviceActivityClientTest, CheckInAfterNextUtcMidnight) { + // Device active reporting starts membership check on network connect. + SetWifiNetworkState(shill::kStateOnline); + + // Return well formed Import response body. + test_url_loader_factory_.SimulateResponseForPendingRequest( + GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(), + net::HTTP_OK); + task_environment_.RunUntilIdle(); + + // Return back to |kIdle| state after a successful check-in. + EXPECT_EQ(device_activity_client_->GetState(), + DeviceActivityClient::State::kIdle); + + task_environment_.FastForwardBy(TimeUntilNextUTCMidnight()); + task_environment_.RunUntilIdle(); + + FireTimer(); + + // Check that additional network requests are pending since the PSM id + // has NOT been imported for the new UTC day. + EXPECT_GT(test_url_loader_factory_.NumPending(), 0); + + // Verify state goes directly to |kCheckingIn| since local state is updated + // with the last check in timestamp. + EXPECT_EQ(device_activity_client_->GetState(), + DeviceActivityClient::State::kCheckingIn); + + // Mock Successful |kCheckingIn|. + test_url_loader_factory_.SimulateResponseForPendingRequest( + GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(), + net::HTTP_OK); + task_environment_.RunUntilIdle(); + + // Return back to |kIdle| state after second successful check-in. + EXPECT_EQ(device_activity_client_->GetState(), + DeviceActivityClient::State::kIdle); +} + +TEST_F(DeviceActivityClientTest, DoNotCheckInTwiceBeforeNextUtcDay) { + // Device active reporting starts checking in on network connect. + SetWifiNetworkState(shill::kStateOnline); + + // Return well formed Import response body. + test_url_loader_factory_.SimulateResponseForPendingRequest( + GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(), + net::HTTP_OK); + task_environment_.RunUntilIdle(); + + // Return back to |kIdle| state after the first successful check-in. + EXPECT_EQ(device_activity_client_->GetState(), + DeviceActivityClient::State::kIdle); + + base::TimeDelta before_utc_meridian = + TimeUntilNextUTCMidnight() - base::Minutes(1); + task_environment_.FastForwardBy(before_utc_meridian); + task_environment_.RunUntilIdle(); + + // Trigger attempt to report device active. + FireTimer(); + + // Client should not send any network requests since device is still in same + // UTC day. + EXPECT_EQ(test_url_loader_factory_.NumPending(), 0); + + // Remains in |kIdle| state since the device is still in same UTC day. + EXPECT_EQ(device_activity_client_->GetState(), + DeviceActivityClient::State::kIdle); +} + +// Powerwashing a device resets the |local_state_|. This will result in the +// client re-importing a PSM ID, on the same day. +TEST_F(DeviceActivityClientTest, CheckInAgainOnLocalStateReset) { + // Device active reporting starts membership check on network connect. + SetWifiNetworkState(shill::kStateOnline); + + base::Time prev_time = + local_state_.GetTime(prefs::kDeviceActiveLastKnownDailyPingTimestamp); + + // Mock Successful |kCheckingIn|. + test_url_loader_factory_.SimulateResponseForPendingRequest( + GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(), + net::HTTP_OK); + task_environment_.RunUntilIdle(); + + base::Time new_time = + local_state_.GetTime(prefs::kDeviceActiveLastKnownDailyPingTimestamp); + + // After a PSM identifier is checked in, the |local_state_| + // |kDeviceActiveLastKnownDailyPingTimestamp| should be updated. + EXPECT_LT(prev_time, new_time); + + // Simulate powerwashing device by resetting the |local_state_|. + local_state_.RemoveUserPref(prefs::kDeviceActiveLastKnownDailyPingTimestamp); + + // Retrigger |TransitionOutOfIdle| codepath by either firing timer or + // reconnecting network. + FireTimer(); + + // Verify that the |kCheckingIn| state is reached. + // Indicator is used to verify that we are checking the PSM ID again after + // powerwash/recovery scenario. + EXPECT_EQ(device_activity_client_->GetState(), + DeviceActivityClient::State::kCheckingIn); +} + +TEST_F(DeviceActivityClientTest, InitialUmaHistogramStateCount) { + histogram_tester_.ExpectBucketCount( + "Ash.DeviceActiveClient.StateCount", + DeviceActivityClient::State::kCheckingMembershipOprf, 0); + histogram_tester_.ExpectBucketCount( + "Ash.DeviceActiveClient.StateCount", + DeviceActivityClient::State::kCheckingMembershipQuery, 0); + histogram_tester_.ExpectBucketCount("Ash.DeviceActiveClient.StateCount", + DeviceActivityClient::State::kCheckingIn, + 0); +} + +TEST_F(DeviceActivityClientTest, UmaHistogramStateCountAfterFirstCheckIn) { + // Device active reporting starts membership check on network connect. + SetWifiNetworkState(shill::kStateOnline); + + // Mock successful |kCheckingIn| requests. + test_url_loader_factory_.SimulateResponseForPendingRequest( + GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(), + net::HTTP_OK); + task_environment_.RunUntilIdle(); + + histogram_tester_.ExpectBucketCount("Ash.DeviceActiveClient.StateCount", + DeviceActivityClient::State::kCheckingIn, + 1); } } // namespace device_activity
diff --git a/ash/components/device_activity/device_activity_controller.cc b/ash/components/device_activity/device_activity_controller.cc index b9904e132..4785d69 100644 --- a/ash/components/device_activity/device_activity_controller.cc +++ b/ash/components/device_activity/device_activity_controller.cc
@@ -8,16 +8,44 @@ #include "ash/components/device_activity/fresnel_pref_names.h" #include "base/check_op.h" #include "base/time/time.h" +#include "base/timer/timer.h" #include "chromeos/dbus/session_manager/session_manager_client.h" #include "chromeos/network/network_state.h" #include "chromeos/network/network_state_handler.h" #include "components/prefs/pref_registry_simple.h" +#include "google_apis/google_api_keys.h" +#include "third_party/private_membership/src/private_membership_rlwe_client.h" + +namespace psm_rlwe = private_membership::rlwe; namespace ash { namespace device_activity { namespace { DeviceActivityController* g_ash_device_activity_controller = nullptr; + +// Production edge server for reporting device actives. +// TODO(https://crbug.com/1267432): Enable passing base url as a runtime flag. +const char kFresnelBaseUrl[] = + "https://autopush-crosfresnel-pa.sandbox.googleapis.com"; + +class PsmDelegateImpl : public PsmDelegate { + public: + PsmDelegateImpl() = default; + PsmDelegateImpl(const PsmDelegateImpl&) = delete; + PsmDelegateImpl& operator=(const PsmDelegateImpl&) = delete; + ~PsmDelegateImpl() override = default; + + // PsmDelegate: + rlwe::StatusOr<std::unique_ptr<psm_rlwe::PrivateMembershipRlweClient>> + CreatePsmClient( + psm_rlwe::RlweUseCase use_case, + const std::vector<psm_rlwe::RlwePlaintextId>& plaintext_ids) override { + return psm_rlwe::PrivateMembershipRlweClient::Create(use_case, + plaintext_ids); + } +}; + } // namespace DeviceActivityController* DeviceActivityController::Get() { @@ -67,7 +95,9 @@ if (trigger == Trigger::kNetwork) { da_client_network_ = std::make_unique<DeviceActivityClient>( chromeos::NetworkHandler::Get()->network_state_handler(), local_state, - url_loader_factory, psm_device_active_secret); + url_loader_factory, std::make_unique<PsmDelegateImpl>(), + std::make_unique<base::RepeatingTimer>(), kFresnelBaseUrl, + google_apis::GetFresnelAPIKey(), psm_device_active_secret); } }
diff --git a/ash/frame/header_view.cc b/ash/frame/header_view.cc index e1a68fd..8eaf0a99 100644 --- a/ash/frame/header_view.cc +++ b/ash/frame/header_view.cc
@@ -71,16 +71,18 @@ target_widget_)); caption_button_container_->UpdateCaptionButtonState(false /*=animate*/); - aura::Window* window = target_widget->GetNativeWindow(); frame_header_ = std::make_unique<DefaultFrameHeader>( target_widget, (frame_view ? static_cast<views::View*>(frame_view) : this), caption_button_container_); +} +void HeaderView::Init() { UpdateBackButton(); UpdateCenterButton(); - frame_header_->UpdateFrameColors(); + + aura::Window* window = target_widget_->GetNativeWindow(); window_observation_.Observe(window); Shell::Get()->tablet_mode_controller()->AddObserver(this); }
diff --git a/ash/frame/header_view.h b/ash/frame/header_view.h index 7f10ee91..96ac1e4 100644 --- a/ash/frame/header_view.h +++ b/ash/frame/header_view.h
@@ -64,6 +64,9 @@ METADATA_HEADER(HeaderView); + // Initialize the parts with side effects. + void Init(); + void set_immersive_mode_changed_callback(base::RepeatingClosure callback) { immersive_mode_changed_callback_ = std::move(callback); }
diff --git a/ash/frame/non_client_frame_view_ash.cc b/ash/frame/non_client_frame_view_ash.cc index 1075edc..f51e844 100644 --- a/ash/frame/non_client_frame_view_ash.cc +++ b/ash/frame/non_client_frame_view_ash.cc
@@ -222,6 +222,7 @@ std::make_unique<FrameContextMenuController>(frame, this)) { DCHECK(frame_); + header_view_->Init(); header_view_->set_immersive_mode_changed_callback(base::BindRepeating( &NonClientFrameViewAsh::InvalidateLayout, weak_factory_.GetWeakPtr()));
diff --git a/ash/frame/non_client_frame_view_ash_unittest.cc b/ash/frame/non_client_frame_view_ash_unittest.cc index 7c3bedf6..92a288f 100644 --- a/ash/frame/non_client_frame_view_ash_unittest.cc +++ b/ash/frame/non_client_frame_view_ash_unittest.cc
@@ -12,6 +12,7 @@ #include "ash/resources/vector_icons/vector_icons.h" #include "ash/shell.h" #include "ash/test/ash_test_base.h" +#include "ash/test/test_widget_builder.h" #include "ash/wm/desks/desks_util.h" #include "ash/wm/overview/overview_controller.h" #include "ash/wm/splitview/split_view_controller.h" @@ -865,6 +866,36 @@ delegate->non_client_frame_view()->GetInactiveFrameColorForTest()); } +// Verify that NonClientFrameViewAsh updates the active and inactive colors at +// construction. +TEST_P(NonClientFrameViewAshFrameColorTest, KFrameColorCtor) { + TestWidgetDelegate* delegate = new TestWidgetDelegate(GetParam()); + // Build the window, this implicit constructs the NonClientFrameView. + constexpr SkColor non_default_color = SK_ColorWHITE; + std::unique_ptr<views::Widget> widget = + TestWidgetBuilder() + .SetDelegate(delegate) + .SetBounds(gfx::Rect()) + .SetParent(Shell::GetPrimaryRootWindow()->GetChildById( + desks_util::GetActiveDeskContainerId())) + .SetShow(true) + .SetWindowProperty(kFrameActiveColorKey, non_default_color) + .SetWindowProperty(kFrameInactiveColorKey, non_default_color) + .BuildOwnsNativeWidget(); + + // Check that the default color is different from the one used in the test. + SkColor inactive_color = + widget->GetNativeWindow()->GetProperty(kFrameInactiveColorKey); + SkColor active_color = + widget->GetNativeWindow()->GetProperty(kFrameActiveColorKey); + EXPECT_EQ(active_color, non_default_color); + EXPECT_EQ(inactive_color, non_default_color); + EXPECT_EQ(delegate->non_client_frame_view()->GetInactiveFrameColorForTest(), + non_default_color); + EXPECT_EQ(delegate->non_client_frame_view()->GetActiveFrameColorForTest(), + non_default_color); +} + // Verify that NonClientFrameViewAsh updates the active color based on the // kFrameActiveColorKey window property. TEST_P(NonClientFrameViewAshFrameColorTest, WideFrameInitialColor) {
diff --git a/ash/frame/wide_frame_view.cc b/ash/frame/wide_frame_view.cc index 7c1a474..6fdb624b 100644 --- a/ash/frame/wide_frame_view.cc +++ b/ash/frame/wide_frame_view.cc
@@ -3,6 +3,7 @@ // found in the LICENSE file. #include "ash/frame/wide_frame_view.h" +#include <memory> #include "ash/frame/header_view.h" #include "ash/frame/non_client_frame_view_ash.h" @@ -100,8 +101,9 @@ target_window->AddObserver(this); // Use the HeaderView itself as a frame view because WideFrameView is // is the frame only. - header_view_ = new HeaderView(target, /*frame view=*/nullptr); - AddChildView(header_view_); + header_view_ = AddChildView( + std::make_unique<HeaderView>(target, /*frame view=*/nullptr)); + header_view_->Init(); GetTargetHeaderView()->SetShouldPaintHeader(false); header_view_->set_context_menu_controller( frame_context_menu_controller_.get());
diff --git a/ash/session/fullscreen_controller.cc b/ash/session/fullscreen_controller.cc index 50ff6a4..b8a461a7 100644 --- a/ash/session/fullscreen_controller.cc +++ b/ash/session/fullscreen_controller.cc
@@ -160,4 +160,8 @@ MaybeShowNotification(); } +void FullscreenController::OnLoginScreenUiWindowClosed() { + MaybeShowNotification(); +} + } // namespace ash
diff --git a/ash/session/fullscreen_controller.h b/ash/session/fullscreen_controller.h index 190e0fa..f517d48 100644 --- a/ash/session/fullscreen_controller.h +++ b/ash/session/fullscreen_controller.h
@@ -7,6 +7,7 @@ #include <memory> +#include "ash/ash_export.h" #include "base/time/time.h" #include "chromeos/dbus/power/power_manager_client.h" @@ -17,7 +18,8 @@ class SessionControllerImpl; class FullscreenNotificationBubble; -class FullscreenController : public chromeos::PowerManagerClient::Observer { +class ASH_EXPORT FullscreenController + : public chromeos::PowerManagerClient::Observer { public: explicit FullscreenController(SessionControllerImpl* session_controller); FullscreenController(const FullscreenController&) = delete; @@ -31,6 +33,8 @@ void OnLockStateChanged(bool locked); + void OnLoginScreenUiWindowClosed(); + // Returns the bubble for testing purposes. FullscreenNotificationBubble* bubble_for_test() { return bubble_.get(); }
diff --git a/ash/session/fullscreen_controller_unittest.cc b/ash/session/fullscreen_controller_unittest.cc index 691de07..bd194bb6 100644 --- a/ash/session/fullscreen_controller_unittest.cc +++ b/ash/session/fullscreen_controller_unittest.cc
@@ -49,7 +49,7 @@ CreateFullscreenWindow(); fullscreen_controller_ = - Shell::Get()->session_controller()->fullscreen_controller_for_test(); + Shell::Get()->session_controller()->fullscreen_controller(); GetSessionControllerClient()->LockScreen(); }
diff --git a/ash/session/session_controller_impl.h b/ash/session/session_controller_impl.h index c4823ab2..bcd3fbf 100644 --- a/ash/session/session_controller_impl.h +++ b/ash/session/session_controller_impl.h
@@ -49,6 +49,9 @@ bool session_state_change_in_progress() const { return session_state_change_in_progress_; } + FullscreenController* fullscreen_controller() { + return fullscreen_controller_.get(); + } // Returns the number of signed in users. If 0 is returned, there is either // no session in progress or no active user. @@ -218,10 +221,6 @@ // Test helpers. void ClearUserSessionsForTest(); - FullscreenController* fullscreen_controller_for_test() { - return fullscreen_controller_.get(); - } - private: friend class TestSessionControllerClient;
diff --git a/ash/session/session_controller_impl_unittest.cc b/ash/session/session_controller_impl_unittest.cc index fd74374..41cc4b3 100644 --- a/ash/session/session_controller_impl_unittest.cc +++ b/ash/session/session_controller_impl_unittest.cc
@@ -168,7 +168,7 @@ AshTestBase::SetUp(); controller()->AddObserver(&observer_); - fullscreen_controller_ = controller()->fullscreen_controller_for_test(); + fullscreen_controller_ = controller()->fullscreen_controller(); } void TearDown() override {
diff --git a/ash/strings/ash_strings_lo.xtb b/ash/strings/ash_strings_lo.xtb index ab91f2fa..84574d7 100644 --- a/ash/strings/ash_strings_lo.xtb +++ b/ash/strings/ash_strings_lo.xtb
@@ -338,6 +338,7 @@ <translation id="3307642347673023554">ປ່ຽນເປັນໂໝດແລັບທັອບແລ້ວ</translation> <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> ສາມາດເຂົ້າສູ່ລະບົບໃນພາຍຫຼັງໄດ້ຄືເກົ່າ.</translation> <translation id="3321628682574733415">ລະຫັດພໍ່ແມ່ບໍ່ຖືກຕ້ອງ</translation> +<translation id="3340475855009870209">ກຳລັງສະແກນການດາວໂຫຼດ <ph name="FILENAME" /></translation> <translation id="3341303451326249809">ຖ່າຍຮູບໜ້າຈໍແລ້ວ</translation> <translation id="334252345105450327">ຖ່າຍຮູບໜ້າຈໍ</translation> <translation id="3351879221545518001">ໃນປັດຈຸບັນທ່ານກຳລັງສົ່ງສັນຍານໜ້າຈໍຢູ່.</translation>
diff --git a/ash/strings/ash_strings_th.xtb b/ash/strings/ash_strings_th.xtb index 1ba902d..b2d03afc 100644 --- a/ash/strings/ash_strings_th.xtb +++ b/ash/strings/ash_strings_th.xtb
@@ -338,6 +338,7 @@ <translation id="3307642347673023554">เปลี่ยนเป็นโหมดแล็ปท็อปแล้ว</translation> <translation id="3308453408813785101"><ph name="USER_EMAIL_ADDRESS" /> ยังมีสิทธิ์ลงชื่อเข้าใช้ภายหลังได้</translation> <translation id="3321628682574733415">รหัสผู้ปกครองไม่ถูกต้อง</translation> +<translation id="3340475855009870209">กำลังสแกนไฟล์ที่ดาวน์โหลด <ph name="FILENAME" /></translation> <translation id="3341303451326249809">จับภาพหน้าจอแล้ว</translation> <translation id="334252345105450327">จับภาพหน้าจอ</translation> <translation id="3351879221545518001">คุณกำลังแคสต์หน้าจออยู่</translation>
diff --git a/ash/system/bluetooth/bluetooth_detailed_view_controller.cc b/ash/system/bluetooth/bluetooth_detailed_view_controller.cc index 276af46..8939c1c4 100644 --- a/ash/system/bluetooth/bluetooth_detailed_view_controller.cc +++ b/ash/system/bluetooth/bluetooth_detailed_view_controller.cc
@@ -10,6 +10,7 @@ #include "ash/shell.h" #include "ash/strings/grit/ash_strings.h" #include "ash/system/model/system_tray_model.h" +#include "ash/system/unified/unified_system_tray_controller.h" #include "base/check.h" #include "chromeos/services/bluetooth_config/public/cpp/cros_bluetooth_config_util.h" #include "mojo/public/cpp/bindings/clone_traits.h" @@ -26,7 +27,8 @@ BluetoothDetailedViewController::BluetoothDetailedViewController( UnifiedSystemTrayController* tray_controller) : detailed_view_delegate_( - std::make_unique<DetailedViewDelegate>(tray_controller)) { + std::make_unique<DetailedViewDelegate>(tray_controller)), + tray_controller_(tray_controller) { DCHECK(ash::features::IsBluetoothRevampEnabled()); GetBluetoothConfigService( remote_cros_bluetooth_config_.BindNewPipeAndPassReceiver()); @@ -94,6 +96,7 @@ } void BluetoothDetailedViewController::OnPairNewDeviceRequested() { + tray_controller_->CloseBubble(); // Deletes |this|. Shell::Get()->system_tray_model()->client()->ShowBluetoothPairingDialog( /*device_address=*/absl::nullopt); } @@ -101,8 +104,11 @@ void BluetoothDetailedViewController::OnDeviceListItemSelected( const chromeos::bluetooth_config::mojom::PairedBluetoothDevicePropertiesPtr& device) { - Shell::Get()->system_tray_model()->client()->ShowBluetoothSettings( - device->device_properties->id); + // When CloseBubble() is called |device| will be deleted so we need to make a + // copy of the device ID that was selected. + const std::string device_id = device->device_properties->id; + tray_controller_->CloseBubble(); // Deletes |this|. + Shell::Get()->system_tray_model()->client()->ShowBluetoothSettings(device_id); } void BluetoothDetailedViewController::BluetoothEnabledStateChanged() {
diff --git a/ash/system/bluetooth/bluetooth_detailed_view_controller.h b/ash/system/bluetooth/bluetooth_detailed_view_controller.h index b4ee723f..d19b4b2 100644 --- a/ash/system/bluetooth/bluetooth_detailed_view_controller.h +++ b/ash/system/bluetooth/bluetooth_detailed_view_controller.h
@@ -80,6 +80,7 @@ std::unique_ptr<BluetoothDeviceListController> device_list_controller_; PairedBluetoothDevicePropertiesPtrs connected_devices_; PairedBluetoothDevicePropertiesPtrs previously_connected_devices_; + UnifiedSystemTrayController* tray_controller_; }; } // namespace ash
diff --git a/ash/system/bluetooth/bluetooth_detailed_view_controller_unittest.cc b/ash/system/bluetooth/bluetooth_detailed_view_controller_unittest.cc index 7891cbe..5b0b06a 100644 --- a/ash/system/bluetooth/bluetooth_detailed_view_controller_unittest.cc +++ b/ash/system/bluetooth/bluetooth_detailed_view_controller_unittest.cc
@@ -13,6 +13,9 @@ #include "ash/system/bluetooth/bluetooth_device_list_controller.h" #include "ash/system/bluetooth/fake_bluetooth_detailed_view.h" #include "ash/system/bluetooth/fake_bluetooth_device_list_controller.h" +#include "ash/system/unified/unified_system_tray.h" +#include "ash/system/unified/unified_system_tray_bubble.h" +#include "ash/system/unified/unified_system_tray_controller.h" #include "ash/test/ash_test_base.h" #include "base/check.h" #include "base/run_loop.h" @@ -101,9 +104,11 @@ feature_list_.InitAndEnableFeature(features::kBluetoothRevamp); + GetPrimaryUnifiedSystemTray()->ShowBubble(); + bluetooth_detailed_view_controller_ = std::make_unique<BluetoothDetailedViewController>( - /*tray_controller=*/nullptr); + GetPrimaryUnifiedSystemTray()->bubble()->controller_for_test()); BluetoothDetailedView::Factory::SetFactoryForTesting( &bluetooth_detailed_view_factory_);
diff --git a/ash/webui/shimless_rma/resources/shimless_rma.html b/ash/webui/shimless_rma/resources/shimless_rma.html index 854b6e4..40aa210 100644 --- a/ash/webui/shimless_rma/resources/shimless_rma.html +++ b/ash/webui/shimless_rma/resources/shimless_rma.html
@@ -4,8 +4,8 @@ } .shimless-footer { - text-align: right; margin-top: 10px; + text-align: end; } cr-button { @@ -14,16 +14,11 @@ #back { border: 0; - margin-left: -15px; - } - - #back::before { - content: "<"; - padding-right: 5px; + margin-inline-start: -15px; } #cancel { - margin-right: 5px; + margin-inline-end: 5px; } .busy-icon { @@ -31,11 +26,17 @@ width: 20px; } + #cancelButtonSpinner, #nextButtonCaret, #nextButtonSpinner { margin-inline-start: 5px; } + #backButtonCaret, + #backButtonSpinner { + margin-inline-end: 5px; + } + #busyStateOverlay { background-color: white; display: none; @@ -58,7 +59,15 @@ id="back" on-click="onBackButtonClicked_" disabled="[[isButtonDisabled_(currentPage_.buttonBack, allButtonsDisabled_)]]" hidden$="[[isButtonHidden_(currentPage_.buttonBack)]]"> - [[i18n('backButtonLabel')]] + <paper-spinner-lite id="backButtonSpinner" class="busy-icon" + hidden$="[[!backButtonClicked_]]" active> + </paper-spinner-lite> + <span id="backButtonCaret" hidden$="[[backButtonClicked_]]"> + < + </span> + <span id="backButtonLabel"> + [[i18n('backButtonLabel')]] + </span> </cr-button> </div> <div id="contentContainer"></div> @@ -67,7 +76,12 @@ id="cancel" on-click="onCancelButtonClicked_" disabled="[[isButtonDisabled_(currentPage_.buttonCancel, allButtonsDisabled_)]]" hidden$="[[isButtonHidden_(currentPage_.buttonCancel)]]"> - [[i18n('cancelButtonLabel')]] + <span id="cancelButtonLabel"> + [[i18n('cancelButtonLabel')]] + </span> + <paper-spinner-lite id="cancelButtonSpinner" class="busy-icon" + hidden$="[[!cancelButtonClicked_]]" active> + </paper-spinner-lite> </cr-button> <cr-button id="next" on-click="onNextButtonClicked_"
diff --git a/ash/webui/shimless_rma/resources/shimless_rma.js b/ash/webui/shimless_rma/resources/shimless_rma.js index c75d374ce..f2b156a6 100644 --- a/ash/webui/shimless_rma/resources/shimless_rma.js +++ b/ash/webui/shimless_rma/resources/shimless_rma.js
@@ -278,6 +278,26 @@ type: Boolean, value: false, }, + + /** + * After the back button is clicked, true until the next state is + * processed. + * @protected + */ + backButtonClicked_: { + type: Boolean, + value: false, + }, + + /** + * After the cancel button is clicked, true until the next state is + * processed. + * @protected + */ + cancelButtonClicked_: { + type: Boolean, + value: false, + }, }; } @@ -412,6 +432,8 @@ const pageInfo = StateComponentMapping[state]; assert(pageInfo); this.nextButtonClicked_ = false; + this.backButtonClicked_ = false; + this.cancelButtonClicked_ = false; this.allButtonsDisabled_ = false; pageInfo.buttonCancel = canCancel ? ButtonState.VISIBLE : ButtonState.HIDDEN; @@ -493,6 +515,7 @@ /** @protected */ onBackButtonClicked_() { + this.backButtonClicked_ = true; this.allButtonsDisabled_ = true; this.shimlessRmaService_.transitionPreviousState().then( (stateResult) => this.processStateResult_(stateResult)); @@ -524,9 +547,12 @@ /** @protected */ onCancelButtonClicked_() { + this.cancelButtonClicked_ = true; this.allButtonsDisabled_ = true; - this.shimlessRmaService_.abortRma().then( - (result) => this.handleStandardAndCriticalError_(result.error)); + this.shimlessRmaService_.abortRma().then((result) => { + this.cancelButtonClicked_ = false; + this.handleStandardAndCriticalError_(result.error); + }); } /**
diff --git a/ash/wm/desks/desks_bar_view.cc b/ash/wm/desks/desks_bar_view.cc index 0b435d7..750eb184 100644 --- a/ash/wm/desks/desks_bar_view.cc +++ b/ash/wm/desks/desks_bar_view.cc
@@ -953,7 +953,7 @@ FindMiniViewForDesk(Shell::Get()->desks_controller()->active_desk()) ->UpdateBorderColor(); expanded_state_desks_templates_button_->set_active( - !!overview_grid_->desks_templates_grid_widget()); + overview_grid_->IsShowingDesksTemplatesGrid()); expanded_state_desks_templates_button_->UpdateBorderColor(); }
diff --git a/ash/wm/desks/expanded_desks_bar_button.cc b/ash/wm/desks/expanded_desks_bar_button.cc index 20b3b9d1..4986db4a 100644 --- a/ash/wm/desks/expanded_desks_bar_button.cc +++ b/ash/wm/desks/expanded_desks_bar_button.cc
@@ -67,8 +67,6 @@ SetButtonState(GetEnabled()); } - void OnButtonPressed() override { button_callback_.Run(); } - void SetButtonState(bool enabled) override { outer_button_->UpdateLabelColor(enabled); // Notify the overview highlight if we are about to be disabled. @@ -90,6 +88,10 @@ SchedulePaint(); } + void OnButtonPressed() override { button_callback_.Run(); } + + void UpdateBorderState() override { outer_button_->UpdateBorderColor(); } + private: ExpandedDesksBarButton* outer_button_; // Defines the button behavior and is called in OnButtonPressed. @@ -146,8 +148,9 @@ void ExpandedDesksBarButton::UpdateBorderColor() const { DCHECK(inner_button_); const bool focused = - bar_view_->dragged_item_over_bar() && - IsPointOnButton(bar_view_->last_dragged_item_screen_location()); + inner_button_->IsViewHighlighted() || + (bar_view_->dragged_item_over_bar() && + IsPointOnButton(bar_view_->last_dragged_item_screen_location())); bool should_paint = inner_button_->border_ptr()->SetFocused(focused); // Focus takes priority. if (!focused) {
diff --git a/ash/wm/desks/templates/desks_templates_unittest.cc b/ash/wm/desks/templates/desks_templates_unittest.cc index 3b078cb..d88eb63 100644 --- a/ash/wm/desks/templates/desks_templates_unittest.cc +++ b/ash/wm/desks/templates/desks_templates_unittest.cc
@@ -11,6 +11,7 @@ #include "ash/public/cpp/rounded_image_view.h" #include "ash/session/session_controller_impl.h" #include "ash/shell.h" +#include "ash/style/ash_color_provider.h" #include "ash/style/close_button.h" #include "ash/style/pill_button.h" #include "ash/wm/desks/desk_mini_view.h" @@ -1405,4 +1406,51 @@ static_cast<DesksTemplatesItemView*>(grid_views[2])->GetAccessibleName()); } +// Tests that the color of the desks templates button border is as expected. +// Regression test for https://crbug.com/1265003. +TEST_F(DesksTemplatesTest, DesksTemplatesButtonBorderColor) { + DesksController::Get()->NewDesk(DesksCreationRemovalSource::kKeyboard); + AddEntry(base::GUID::GenerateRandomV4(), "name", base::Time::Now()); + + auto* color_provider = AshColorProvider::Get(); + const SkColor active_color = color_provider->GetContentLayerColor( + AshColorProvider::ContentLayerType::kCurrentDeskColor); + const SkColor focused_color = color_provider->GetControlsLayerColor( + AshColorProvider::ControlsLayerType::kFocusRingColor); + + ToggleOverview(); + WaitForDesksTemplatesUI(); + + views::View* button = GetDesksTemplatesButtonForRoot( + Shell::GetPrimaryRootWindow(), /*zero_state=*/false); + ASSERT_TRUE(button); + + // Helper to get the color of the border of the desks templates button. + auto get_border_color = [button]() { + // The inner button is the one where the border is applied to. + DeskButtonBase* inner_button = + static_cast<ExpandedDesksBarButton*>(button)->inner_button(); + views::Border* border = inner_button->GetBorder(); + DCHECK(border); + return border->color(); + }; + + // The templates button starts of neither focused nor active. + EXPECT_EQ(SK_ColorTRANSPARENT, get_border_color()); + + // Tests that when we are viewing the templates grid, the button border is + // active. + ClickOnView(button); + EXPECT_EQ(active_color, get_border_color()); + + // Tests that when focused, the templates button border has a focused color. + SendKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN); + EXPECT_EQ(focused_color, get_border_color()); + + // Shift focus away from the templates button. The button border should be + // active. + SendKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN); + EXPECT_EQ(active_color, get_border_color()); +} + } // namespace ash
diff --git a/ash/wm/desks/zero_state_button.cc b/ash/wm/desks/zero_state_button.cc index 62b6170c..37bd606 100644 --- a/ash/wm/desks/zero_state_button.cc +++ b/ash/wm/desks/zero_state_button.cc
@@ -121,11 +121,6 @@ UpdateBorderState(); } -void DeskButtonBase::UpdateBorderState() { - border_ptr_->SetFocused(IsViewHighlighted() && GetEnabled()); - SchedulePaint(); -} - void DeskButtonBase::SetShouldPaintBackground(bool should_paint_background) { if (should_paint_background_ == should_paint_background) return; @@ -134,6 +129,11 @@ SchedulePaint(); } +void DeskButtonBase::UpdateBorderState() { + border_ptr_->SetFocused(IsViewHighlighted() && GetEnabled()); + SchedulePaint(); +} + BEGIN_METADATA(DeskButtonBase, views::LabelButton) END_METADATA
diff --git a/ash/wm/desks/zero_state_button.h b/ash/wm/desks/zero_state_button.h index 821d759..e8da446 100644 --- a/ash/wm/desks/zero_state_button.h +++ b/ash/wm/desks/zero_state_button.h
@@ -59,6 +59,8 @@ protected: virtual void OnButtonPressed() = 0; + virtual void UpdateBorderState(); + SkColor background_color_; // If true, paints a background of the button with `background_color_`. The @@ -75,8 +77,6 @@ private: friend class DesksTestApi; - void UpdateBorderState(); - // Owned by this View via `View::border_`. This is just a convenient pointer // to it. WmHighlightItemBorder* border_ptr_;
diff --git a/ash/wm/splitview/OWNERS b/ash/wm/splitview/OWNERS index e615980..d6c3f98 100644 --- a/ash/wm/splitview/OWNERS +++ b/ash/wm/splitview/OWNERS
@@ -1,2 +1,2 @@ -amusbach@chromium.org +cattalyya@chromium.org xdai@chromium.org
diff --git a/base/BUILD.gn b/base/BUILD.gn index 73c4b20..b534e4f3 100644 --- a/base/BUILD.gn +++ b/base/BUILD.gn
@@ -680,6 +680,8 @@ "task/sequence_manager/associated_thread_id.h", "task/sequence_manager/atomic_flag_set.cc", "task/sequence_manager/atomic_flag_set.h", + "task/sequence_manager/delayed_task_handle_delegate.cc", + "task/sequence_manager/delayed_task_handle_delegate.h", "task/sequence_manager/enqueue_order.h", "task/sequence_manager/enqueue_order_generator.cc", "task/sequence_manager/enqueue_order_generator.h",
diff --git a/base/allocator/allocator_shim.h b/base/allocator/allocator_shim.h index 187ce8c..21e57601 100644 --- a/base/allocator/allocator_shim.h +++ b/base/allocator/allocator_shim.h
@@ -175,17 +175,19 @@ using EnableBrp = base::StrongAlias<class EnableBrpTag, bool>; using ThreadCacheOnNonQuarantinablePartition = base::StrongAlias<class ThreadCacheOnNonQuarantinablePartitionTag, bool>; -using ForceSplitPartitions = - base::StrongAlias<class ForceSplitPartitionsTag, bool>; +using SplitMainPartition = base::StrongAlias<class SplitMainPartitionTag, bool>; +using UseDedicatedAlignedPartition = + base::StrongAlias<class UseDedicatedAlignedPartitionTag, bool>; // If |thread_cache_on_non_quarantinable_partition| is specified, the // thread-cache will be enabled on the non-quarantinable partition. The // thread-cache on the main (malloc) partition will be disabled. BASE_EXPORT void ConfigurePartitions( EnableBrp enable_brp, + SplitMainPartition split_main_partition, + UseDedicatedAlignedPartition use_dedicated_aligned_partition, ThreadCacheOnNonQuarantinablePartition - thread_cache_on_non_quarantinable_partition, - ForceSplitPartitions force_split_partitions); + thread_cache_on_non_quarantinable_partition); #if defined(PA_ALLOW_PCSCAN) BASE_EXPORT void EnablePCScan(base::internal::PCScan::InitConfig);
diff --git a/base/allocator/allocator_shim_default_dispatch_to_partition_alloc.cc b/base/allocator/allocator_shim_default_dispatch_to_partition_alloc.cc index 924990e..4197b6b2 100644 --- a/base/allocator/allocator_shim_default_dispatch_to_partition_alloc.cc +++ b/base/allocator/allocator_shim_default_dispatch_to_partition_alloc.cc
@@ -499,12 +499,23 @@ g_allocator_buffer_for_aligned_alloc_partition[sizeof( base::ThreadSafePartitionRoot)]; -void ConfigurePartitions(EnableBrp enable_brp, - ThreadCacheOnNonQuarantinablePartition - thread_cache_on_non_quarantinable_partition, - ForceSplitPartitions force_split_partitions) { +void ConfigurePartitions( + EnableBrp enable_brp, + SplitMainPartition split_main_partition, + UseDedicatedAlignedPartition use_dedicated_aligned_partition, + ThreadCacheOnNonQuarantinablePartition + thread_cache_on_non_quarantinable_partition) { // |thread_cache_on_non_quarantinable_partition| can't be enabled with BRP. PA_CHECK(!enable_brp || !thread_cache_on_non_quarantinable_partition); + // BRP cannot be enabled without splitting the main partition. Furthermore, in + // the "before allocation" mode, it can't be enabled without further splitting + // out the aligned partition. + PA_CHECK(!enable_brp || split_main_partition); +#if !BUILDFLAG(PUT_REF_COUNT_IN_PREVIOUS_SLOT) + PA_CHECK(!enable_brp || use_dedicated_aligned_partition); +#endif + // Can't split out the aligned partition, without splitting the main one. + PA_CHECK(!use_dedicated_aligned_partition || split_main_partition); static bool configured = false; PA_CHECK(!configured); @@ -518,7 +529,9 @@ // When there is no need to split partition, simply enable thread cache in the // existing root. - if (!enable_brp && !force_split_partitions) { + if (!split_main_partition) { + PA_DCHECK(!enable_brp); + PA_DCHECK(!use_dedicated_aligned_partition); PA_DCHECK(!current_root->with_thread_cache); auto* root_with_thread_cache = thread_cache_on_non_quarantinable_partition @@ -531,28 +544,9 @@ current_root->PurgeMemory(PartitionPurgeDecommitEmptySlotSpans | PartitionPurgeDiscardUnusedSystemPages); - // AlignedAlloc relies on natural alignment offered by the allocator (see the - // comment inside PartitionRoot::AlignedAllocFlags). Any extras in front of - // the allocation will mess up that alignment. Such extras are used when - // BackupRefPtr is on, in which case, we need a separate partition, dedicated - // to handle only aligned allocations, where those extras are disabled. - // However, if the "previous slot" variant is used, no dedicated partition is - // needed, as the extras won't interfere with the alignment requirements. - // - // Regardless of everything said above, force_split_partitions==true will - // force creating a dedicated partition for aligned allocations. - const bool use_dedicated_partition_for_aligned_alloc = -#if BUILDFLAG(PUT_REF_COUNT_IN_PREVIOUS_SLOT) - force_split_partitions; -#else - true; -#endif - const bool allow_aligned_alloc_in_main_root = - !use_dedicated_partition_for_aligned_alloc; - auto* new_root = new (g_allocator_buffer_for_new_main_partition) base::ThreadSafePartitionRoot({ - allow_aligned_alloc_in_main_root + !use_dedicated_aligned_partition ? base::PartitionOptions::AlignedAlloc::kAllowed : base::PartitionOptions::AlignedAlloc::kDisallowed, thread_cache_on_non_quarantinable_partition @@ -574,7 +568,7 @@ g_original_root = current_root; base::ThreadSafePartitionRoot* new_aligned_root; - if (use_dedicated_partition_for_aligned_alloc) { + if (use_dedicated_aligned_partition) { // TODO(bartekn): Use the original root instead of creating a new one. It'd // result in one less partition, but come at a cost of commingling types. new_aligned_root = new (g_allocator_buffer_for_aligned_alloc_partition)
diff --git a/base/allocator/partition_alloc_features.cc b/base/allocator/partition_alloc_features.cc index 3d14131..3545a1bf 100644 --- a/base/allocator/partition_alloc_features.cc +++ b/base/allocator/partition_alloc_features.cc
@@ -50,14 +50,17 @@ BackupRefPtrEnabledProcesses::kBrowserOnly, &kBackupRefPtrEnabledProcessesOptions}; -const Feature kPartitionAllocSimulateBRPPartitionSplit{ - "PartitionAllocSimulateBRPPartitionSplit", FEATURE_DISABLED_BY_DEFAULT}; +constexpr FeatureParam<BackupRefPtrMode>::Option kBackupRefPtrModeOptions[] = { + {BackupRefPtrMode::kEnabled, "enabled"}, + {BackupRefPtrMode::kDisabledButSplitPartitions2Way, + "disabled-but-2-way-split"}, + {BackupRefPtrMode::kDisabledButSplitPartitions3Way, + "disabled-but-3-way-split"}, +}; -const base::FeatureParam<BackupRefPtrEnabledProcesses> - kSimulateBRPPartitionSplitProcessesParam{ - &kPartitionAllocSimulateBRPPartitionSplit, "enabled-processes", - BackupRefPtrEnabledProcesses::kAllProcesses, - &kBackupRefPtrEnabledProcessesOptions}; +const base::FeatureParam<BackupRefPtrMode> kBackupRefPtrModeParam{ + &kPartitionAllocBackupRefPtr, "brp-mode", BackupRefPtrMode::kEnabled, + &kBackupRefPtrModeOptions}; #endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
diff --git a/base/allocator/partition_alloc_features.h b/base/allocator/partition_alloc_features.h index 0aed1c7..96405ee 100644 --- a/base/allocator/partition_alloc_features.h +++ b/base/allocator/partition_alloc_features.h
@@ -35,15 +35,26 @@ kAllProcesses, }; +enum class BackupRefPtrMode { + // BRP is enabled in the main partition, as well as certain Renderer-only + // partitions (if enabled in Renderer at all). + // This entails splitting the main partition. + kEnabled, + + // BRP is disabled, but the main partition is split out, as if BRP was enabled + // in the "previous slot" mode. + kDisabledButSplitPartitions2Way, + + // BRP is disabled, but the main partition *and* aligned partition are split + // out, as if BRP was enabled in the "before allocation" mode. + kDisabledButSplitPartitions3Way, +}; + extern const BASE_EXPORT Feature kPartitionAllocBackupRefPtr; extern const BASE_EXPORT base::FeatureParam<BackupRefPtrEnabledProcesses> kBackupRefPtrEnabledProcessesParam; - -extern const BASE_EXPORT Feature kPartitionAllocSimulateBRPPartitionSplit; -// Piggy-back the params on the other feature for simplicity, even though not -// exactly related. -extern const BASE_EXPORT base::FeatureParam<BackupRefPtrEnabledProcesses> - kSimulateBRPPartitionSplitProcessesParam; +extern const BASE_EXPORT base::FeatureParam<BackupRefPtrMode> + kBackupRefPtrModeParam; #endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) extern const BASE_EXPORT Feature kPartitionAllocPCScanMUAwareScheduler;
diff --git a/base/task/sequence_manager/delayed_task_handle_delegate.cc b/base/task/sequence_manager/delayed_task_handle_delegate.cc new file mode 100644 index 0000000..41bfdd15 --- /dev/null +++ b/base/task/sequence_manager/delayed_task_handle_delegate.cc
@@ -0,0 +1,69 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/task/sequence_manager/delayed_task_handle_delegate.h" + +#include "base/task/sequence_manager/task_queue_impl.h" + +namespace base { +namespace sequence_manager { +namespace internal { + +DelayedTaskHandleDelegate::DelayedTaskHandleDelegate(TaskQueueImpl* outer) + : outer_(outer) {} + +DelayedTaskHandleDelegate::~DelayedTaskHandleDelegate() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(!IsValid()); +} + +WeakPtr<DelayedTaskHandleDelegate> DelayedTaskHandleDelegate::AsWeakPtr() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return weak_ptr_factory_.GetWeakPtr(); +} + +bool DelayedTaskHandleDelegate::IsValid() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return weak_ptr_factory_.HasWeakPtrs(); +} + +void DelayedTaskHandleDelegate::CancelTask() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (!IsValid()) + return; + + weak_ptr_factory_.InvalidateWeakPtrs(); + + // If the task is still inside the heap, then it can be removed directly. + if (heap_handle_.IsValid()) + outer_->RemoveCancelableTask(heap_handle_); +} + +void DelayedTaskHandleDelegate::SetHeapHandle(HeapHandle heap_handle) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(heap_handle.IsValid()); + heap_handle_ = heap_handle; +} + +void DelayedTaskHandleDelegate::ClearHeapHandle() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + heap_handle_ = HeapHandle(); +} + +HeapHandle DelayedTaskHandleDelegate::GetHeapHandle() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return heap_handle_; +} + +void DelayedTaskHandleDelegate::WillRunTask() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(IsValid()); + // The task must be removed from the heap before running it. + DCHECK(heap_handle_.IsValid()); + weak_ptr_factory_.InvalidateWeakPtrs(); +} + +} // namespace internal +} // namespace sequence_manager +} // namespace base
diff --git a/base/task/sequence_manager/delayed_task_handle_delegate.h b/base/task/sequence_manager/delayed_task_handle_delegate.h new file mode 100644 index 0000000..108f356e --- /dev/null +++ b/base/task/sequence_manager/delayed_task_handle_delegate.h
@@ -0,0 +1,62 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TASK_SEQUENCE_MANAGER_DELAYED_TASK_HANDLE_DELEGATE_H_ +#define BASE_TASK_SEQUENCE_MANAGER_DELAYED_TASK_HANDLE_DELEGATE_H_ + +#include "base/containers/intrusive_heap.h" +#include "base/memory/weak_ptr.h" +#include "base/sequence_checker.h" +#include "base/task/delayed_task_handle.h" + +namespace base { +namespace sequence_manager { +namespace internal { + +class TaskQueueImpl; + +class DelayedTaskHandleDelegate : public DelayedTaskHandle::Delegate { + public: + explicit DelayedTaskHandleDelegate(TaskQueueImpl* outer); + + DelayedTaskHandleDelegate(const DelayedTaskHandleDelegate&) = delete; + DelayedTaskHandleDelegate& operator=(const DelayedTaskHandleDelegate&) = + delete; + + ~DelayedTaskHandleDelegate() override; + + WeakPtr<DelayedTaskHandleDelegate> AsWeakPtr(); + + // DelayedTaskHandle::Delegate: + bool IsValid() const override; + void CancelTask() override; + + void SetHeapHandle(HeapHandle heap_handle); + void ClearHeapHandle(); + HeapHandle GetHeapHandle(); + + // Indicates that this task will be executed. This will invalidate the handle. + void WillRunTask(); + + private: + // The TaskQueueImpl where the task was posted. + TaskQueueImpl* const outer_ GUARDED_BY_CONTEXT(sequence_checker_); + + // The HeapHandle to the task, if the task is in the DelayedIncomingQueue, + // invalid otherwise. + HeapHandle heap_handle_ GUARDED_BY_CONTEXT(sequence_checker_); + + SEQUENCE_CHECKER(sequence_checker_); + + // Allows TaskQueueImpl to retain a weak reference to |this|. An outstanding + // weak pointer indicates that the task is valid. + WeakPtrFactory<DelayedTaskHandleDelegate> weak_ptr_factory_ + GUARDED_BY_CONTEXT(sequence_checker_){this}; +}; + +} // namespace internal +} // namespace sequence_manager +} // namespace base + +#endif // BASE_TASK_SEQUENCE_MANAGER_DELAYED_TASK_HANDLE_DELEGATE_H_
diff --git a/base/task/sequence_manager/sequence_manager_impl.cc b/base/task/sequence_manager/sequence_manager_impl.cc index 79137482..1264aa9 100644 --- a/base/task/sequence_manager/sequence_manager_impl.cc +++ b/base/task/sequence_manager/sequence_manager_impl.cc
@@ -843,6 +843,10 @@ if (recording_policy == TimeRecordingPolicy::DoRecord) executing_task->task_timing.RecordTaskStart(time_before_task); + // Maybe invalidate the delayed task handle. |pending_task| is guaranteed to + // be valid here (not canceled). + executing_task->pending_task.WillRunTask(); + if (!executing_task->task_queue->GetShouldNotifyObservers()) return;
diff --git a/base/task/sequence_manager/task_queue_impl.cc b/base/task/sequence_manager/task_queue_impl.cc index eec18f6..aa9f704fa 100644 --- a/base/task/sequence_manager/task_queue_impl.cc +++ b/base/task/sequence_manager/task_queue_impl.cc
@@ -11,15 +11,18 @@ #include "base/check.h" #include "base/containers/stack_container.h" +#include "base/feature_list.h" #include "base/logging.h" #include "base/ranges/algorithm.h" #include "base/strings/stringprintf.h" #include "base/task/common/scoped_defer_task_posting.h" +#include "base/task/sequence_manager/delayed_task_handle_delegate.h" #include "base/task/sequence_manager/fence.h" #include "base/task/sequence_manager/sequence_manager_impl.h" #include "base/task/sequence_manager/task_order.h" #include "base/task/sequence_manager/wake_up_queue.h" #include "base/task/sequence_manager/work_queue.h" +#include "base/task/task_features.h" #include "base/task/task_observer.h" #include "base/threading/thread_restrictions.h" #include "base/time/time.h" @@ -73,6 +76,25 @@ return true; } +DelayedTaskHandle TaskQueueImpl::GuardedTaskPoster::PostCancelableTask( + PostedTask task) { + // Do not process new PostTasks while we are handling a PostTask (tracing + // has to do this) as it can lead to a deadlock and defer it instead. + ScopedDeferTaskPosting disallow_task_posting; + + auto token = operations_controller_.TryBeginOperation(); + if (!token) + return DelayedTaskHandle(); + + auto delayed_task_handle_delegate = + std::make_unique<DelayedTaskHandleDelegate>(outer_); + task.delayed_task_handle_delegate = delayed_task_handle_delegate->AsWeakPtr(); + + outer_->PostTask(std::move(task)); + DCHECK(delayed_task_handle_delegate->IsValid()); + return DelayedTaskHandle(std::move(delayed_task_handle_delegate)); +} + TaskQueueImpl::TaskRunner::TaskRunner( scoped_refptr<GuardedTaskPoster> task_poster, scoped_refptr<AssociatedThreadId> associated_thread, @@ -91,6 +113,20 @@ task_type_)); } +DelayedTaskHandle TaskQueueImpl::TaskRunner::PostCancelableDelayedTask( + const Location& location, + OnceClosure callback, + TimeDelta delay) { + if (!FeatureList::IsEnabled(kRemoveCanceledTasksInTaskQueue)) { + return SequencedTaskRunner::PostCancelableDelayedTask( + location, std::move(callback), delay); + } + + return task_poster_->PostCancelableTask( + PostedTask(this, std::move(callback), location, delay, + Nestable::kNestable, task_type_)); +} + bool TaskQueueImpl::TaskRunner::PostNonNestableDelayedTask( const Location& location, OnceClosure callback, @@ -236,6 +272,20 @@ } } +void TaskQueueImpl::RemoveCancelableTask(HeapHandle heap_handle) { + // Can only cancel from the current thread. + DCHECK(associated_thread_->IsBoundToCurrentThread()); + DCHECK(heap_handle.IsValid()); + + main_thread_only().delayed_incoming_queue.remove(heap_handle); + + // Only update the delayed wake up if the top task is removed. + if (heap_handle.index() == 0u) { + LazyNow lazy_now(sequence_manager_->main_thread_clock()); + UpdateWakeUp(&lazy_now); + } +} + void TaskQueueImpl::MaybeLogPostTask(const PostedTask& task) { #if DCHECK_IS_ON() if (!sequence_manager_->settings().log_post_task) @@ -1372,6 +1422,16 @@ queue_.insert(std::move(task)); } +void TaskQueueImpl::DelayedIncomingQueue::remove(HeapHandle heap_handle) { + DCHECK(!empty()); + DCHECK_LT(heap_handle.index(), queue_.size()); + Task task = queue_.take(heap_handle); + if (task.is_high_res) { + pending_high_res_tasks_--; + DCHECK_GE(pending_high_res_tasks_, 0); + } +} + Task TaskQueueImpl::DelayedIncomingQueue::take_top() { DCHECK(!empty()); if (top().is_high_res) {
diff --git a/base/task/sequence_manager/task_queue_impl.h b/base/task/sequence_manager/task_queue_impl.h index 5187a16..550e7f4 100644 --- a/base/task/sequence_manager/task_queue_impl.h +++ b/base/task/sequence_manager/task_queue_impl.h
@@ -277,6 +277,7 @@ private: friend class WorkQueue; friend class WorkQueueTest; + friend class DelayedTaskHandleDelegate; // A TaskQueueImpl instance can be destroyed or unregistered before all its // associated TaskRunner instances are (they are refcounted). Thus we need a @@ -292,6 +293,7 @@ explicit GuardedTaskPoster(TaskQueueImpl* outer); bool PostTask(PostedTask task); + DelayedTaskHandle PostCancelableTask(PostedTask task); void StartAcceptingOperations() { operations_controller_.StartAcceptingOperations(); @@ -320,6 +322,9 @@ bool PostDelayedTask(const Location& location, OnceClosure callback, TimeDelta delay) final; + DelayedTaskHandle PostCancelableDelayedTask(const Location& location, + OnceClosure callback, + TimeDelta delay) final; bool PostNonNestableDelayedTask(const Location& location, OnceClosure callback, TimeDelta delay) final; @@ -342,6 +347,7 @@ ~DelayedIncomingQueue(); void push(Task task); + void remove(HeapHandle heap_handle); Task take_top(); bool empty() const { return queue_.empty(); } size_t size() const { return queue_.size(); } @@ -358,20 +364,7 @@ Value AsValue(TimeTicks now) const; private: - // An implementation of HeapHandleAccessor that doesn't keep the heap - // handles up-to-date. Useful if elements are never accessed using their - // handles. - // TODO(pmonette): Use a full implementation of HeapHandleAccessor once - // TaskQueueImpl supports removing tasks from the - // DelayedIncomingQueue using heap handles. - struct NullHeapHandleAccessor { - void SetHeapHandle(Task* element, HeapHandle handle) const {} - void ClearHeapHandle(Task* element) const {} - HeapHandle GetHeapHandle(const Task* element) const { - return HeapHandle::Invalid(); - } - }; - IntrusiveHeap<Task, std::greater<>, NullHeapHandleAccessor> queue_; + IntrusiveHeap<Task, std::greater<>> queue_; // Number of pending tasks in the queue that need high resolution timing. int pending_high_res_tasks_ = 0; @@ -436,6 +429,7 @@ }; void PostTask(PostedTask task); + void RemoveCancelableTask(HeapHandle heap_handle); void PostImmediateTaskImpl(PostedTask task, CurrentThread current_thread); void PostDelayedTaskImpl(PostedTask task, CurrentThread current_thread);
diff --git a/base/task/sequence_manager/task_queue_unittest.cc b/base/task/sequence_manager/task_queue_unittest.cc index df2123a..e16d2ca 100644 --- a/base/task/sequence_manager/task_queue_unittest.cc +++ b/base/task/sequence_manager/task_queue_unittest.cc
@@ -8,6 +8,9 @@ #include "base/message_loop/message_pump_type.h" #include "base/task/sequence_manager/sequence_manager.h" #include "base/task/sequence_manager/test/sequence_manager_for_test.h" +#include "base/task/task_features.h" +#include "base/test/bind.h" +#include "base/test/scoped_feature_list.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { @@ -96,6 +99,36 @@ voter.reset(); } +TEST(TaskQueueTest, CanceledTaskRemovedIfFeatureEnabled) { + for (bool feature_enabled : {false, true}) { + test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitWithFeatureState(kRemoveCanceledTasksInTaskQueue, + feature_enabled); + + auto sequence_manager = CreateSequenceManagerOnCurrentThreadWithPump( + MessagePump::Create(MessagePumpType::DEFAULT)); + auto queue = sequence_manager->CreateTaskQueue(TaskQueue::Spec("test")); + + // Get the default task runner. + auto task_runner = queue->task_runner(); + EXPECT_EQ(queue->GetNumberOfPendingTasks(), 0u); + + bool task_ran = false; + DelayedTaskHandle delayed_task_handle = + task_runner->PostCancelableDelayedTask( + FROM_HERE, BindLambdaForTesting([&task_ran]() { task_ran = true; }), + Seconds(20)); + EXPECT_EQ(queue->GetNumberOfPendingTasks(), 1u); + + // The task is only removed from the queue if the feature is enabled. + delayed_task_handle.CancelTask(); + EXPECT_EQ(queue->GetNumberOfPendingTasks(), feature_enabled ? 0u : 1u); + + // In any case, the task never actually ran. + EXPECT_FALSE(task_ran); + } +} + } // namespace } // namespace task_queue_unittest } // namespace internal
diff --git a/base/task/sequence_manager/tasks.cc b/base/task/sequence_manager/tasks.cc index ed5c7dc..25a3152 100644 --- a/base/task/sequence_manager/tasks.cc +++ b/base/task/sequence_manager/tasks.cc
@@ -25,7 +25,9 @@ nestable(posted_task.nestable), task_type(posted_task.task_type), task_runner(std::move(posted_task.task_runner)), - enqueue_order_(enqueue_order) { + enqueue_order_(enqueue_order), + delayed_task_handle_delegate_( + std::move(posted_task.delayed_task_handle_delegate)) { DCHECK(!absl::holds_alternative<base::TimeDelta>( posted_task.delay_or_delayed_run_time) || absl::get<base::TimeDelta>(posted_task.delay_or_delayed_run_time) @@ -49,33 +51,78 @@ return TaskOrder(enqueue_order(), delayed_run_time, sequence_num); } -namespace internal { +void Task::SetHeapHandle(HeapHandle heap_handle) { + if (!delayed_task_handle_delegate_) + return; -PostedTask::PostedTask(scoped_refptr<SequencedTaskRunner> task_runner, - OnceClosure callback, - Location location, - TimeDelta delay, - Nestable nestable, - TaskType task_type) + delayed_task_handle_delegate_->SetHeapHandle(heap_handle); +} + +void Task::ClearHeapHandle() { + if (!delayed_task_handle_delegate_) + return; + delayed_task_handle_delegate_->ClearHeapHandle(); +} + +HeapHandle Task::GetHeapHandle() const { + if (!delayed_task_handle_delegate_) + return HeapHandle::Invalid(); + return delayed_task_handle_delegate_->GetHeapHandle(); +} + +bool Task::IsCanceled() const { + CHECK(task); + if (task.IsCancelled()) { + DCHECK(!delayed_task_handle_delegate_); + return true; + } + + if (delayed_task_handle_delegate_) { + return true; + } + + return false; +} + +void Task::WillRunTask() { + if (!delayed_task_handle_delegate_) + return; + + delayed_task_handle_delegate_->WillRunTask(); +} + +namespace internal { +PostedTask::PostedTask( + scoped_refptr<SequencedTaskRunner> task_runner, + OnceClosure callback, + Location location, + TimeDelta delay, + Nestable nestable, + TaskType task_type, + WeakPtr<DelayedTaskHandleDelegate> delayed_task_handle_delegate) : callback(std::move(callback)), location(location), nestable(nestable), task_type(task_type), delay_or_delayed_run_time(delay), - task_runner(std::move(task_runner)) {} + task_runner(std::move(task_runner)), + delayed_task_handle_delegate(std::move(delayed_task_handle_delegate)) {} -PostedTask::PostedTask(scoped_refptr<SequencedTaskRunner> task_runner, - OnceClosure callback, - Location location, - TimeTicks delayed_run_time, - Nestable nestable, - TaskType task_type) +PostedTask::PostedTask( + scoped_refptr<SequencedTaskRunner> task_runner, + OnceClosure callback, + Location location, + TimeTicks delayed_run_time, + Nestable nestable, + TaskType task_type, + WeakPtr<DelayedTaskHandleDelegate> delayed_task_handle_delegate) : callback(std::move(callback)), location(location), nestable(nestable), task_type(task_type), delay_or_delayed_run_time(delayed_run_time), - task_runner(std::move(task_runner)) {} + task_runner(std::move(task_runner)), + delayed_task_handle_delegate(std::move(delayed_task_handle_delegate)) {} PostedTask::PostedTask(PostedTask&& move_from) noexcept = default; PostedTask::~PostedTask() = default;
diff --git a/base/task/sequence_manager/tasks.h b/base/task/sequence_manager/tasks.h index 59a4d81..247aa0c 100644 --- a/base/task/sequence_manager/tasks.h +++ b/base/task/sequence_manager/tasks.h
@@ -5,7 +5,9 @@ #ifndef BASE_TASK_SEQUENCE_MANAGER_TASKS_H_ #define BASE_TASK_SEQUENCE_MANAGER_TASKS_H_ +#include "base/containers/intrusive_heap.h" #include "base/pending_task.h" +#include "base/task/sequence_manager/delayed_task_handle_delegate.h" #include "base/task/sequence_manager/enqueue_order.h" #include "base/task/sequenced_task_runner.h" #include "third_party/abseil-cpp/absl/types/variant.h" @@ -28,13 +30,17 @@ Location location, TimeDelta delay = base::TimeDelta(), Nestable nestable = Nestable::kNestable, - TaskType task_type = kTaskTypeNone); + TaskType task_type = kTaskTypeNone, + WeakPtr<DelayedTaskHandleDelegate> + delayed_task_handle_delegate = nullptr); explicit PostedTask(scoped_refptr<SequencedTaskRunner> task_runner, OnceClosure callback, Location location, TimeTicks delayed_run_time, Nestable nestable = Nestable::kNestable, - TaskType task_type = kTaskTypeNone); + TaskType task_type = kTaskTypeNone, + WeakPtr<DelayedTaskHandleDelegate> + delayed_task_handle_delegate = nullptr); PostedTask(PostedTask&& move_from) noexcept; PostedTask(const PostedTask&) = delete; PostedTask& operator=(const PostedTask&) = delete; @@ -53,6 +59,9 @@ // The task runner this task is running on. Can be used by task runners that // support posting back to the "current sequence". scoped_refptr<SequencedTaskRunner> task_runner; + // The delegate for the DelayedTaskHandle, if this task was posted through + // PostCancelableDelayedTask(), nullptr otherwise. + WeakPtr<DelayedTaskHandleDelegate> delayed_task_handle_delegate; }; } // namespace internal @@ -119,6 +128,19 @@ bool cross_thread_; #endif + // Implement the intrusive heap contract. + void SetHeapHandle(HeapHandle heap_handle); + void ClearHeapHandle(); + HeapHandle GetHeapHandle() const; + + // Returns true if this task was canceled, either through weak pointer + // invalidation or through |delayed_task_handle_delegate_|. + bool IsCanceled() const; + + // Indicates that this task will be executed. Used to invalidate + // |delayed_task_handle_delegate_|, if any, just before task execution. + void WillRunTask(); + private: // `enqueue_order_` is the primary component used to order tasks (see // `TaskOrder`). For immediate tasks, `enqueue_order` is set when posted, but @@ -126,6 +148,10 @@ // otherwise delayed tasks could run before an immediate task posted after the // delayed task. EnqueueOrder enqueue_order_; + + // The delegate for the DelayedTaskHandle, if this task was posted through + // PostCancelableDelayedTask(), nullptr otherwise. + WeakPtr<internal::DelayedTaskHandleDelegate> delayed_task_handle_delegate_; }; } // namespace sequence_manager
diff --git a/base/task/sequence_manager/work_queue.cc b/base/task/sequence_manager/work_queue.cc index 4e99451b..55c77c6 100644 --- a/base/task/sequence_manager/work_queue.cc +++ b/base/task/sequence_manager/work_queue.cc
@@ -230,7 +230,7 @@ while (!tasks_.empty()) { const auto& pending_task = tasks_.front(); - if (pending_task.task && !pending_task.task.IsCancelled()) + if (pending_task.task && !pending_task.IsCanceled()) break; tasks_to_delete->push_back(std::move(tasks_.front())); tasks_.pop_front();
diff --git a/base/task/task_features.cc b/base/task/task_features.cc index 3bccd84..eff2d24c 100644 --- a/base/task/task_features.cc +++ b/base/task/task_features.cc
@@ -46,4 +46,7 @@ const Feature kUseFiveMinutesThreadReclaimTime = { "UseFiveMinutesThreadReclaimTime", base::FEATURE_DISABLED_BY_DEFAULT}; +const BASE_EXPORT Feature kRemoveCanceledTasksInTaskQueue = { + "RemoveCanceledTasksInTaskQueue", base::FEATURE_DISABLED_BY_DEFAULT}; + } // namespace base
diff --git a/base/task/task_features.h b/base/task/task_features.h index 6d039db..48df3de0 100644 --- a/base/task/task_features.h +++ b/base/task/task_features.h
@@ -68,6 +68,9 @@ // minutes, instead of 30 seconds. extern const BASE_EXPORT Feature kUseFiveMinutesThreadReclaimTime; +// Controls whether or not canceled delayed tasks are removed from task queues. +extern const BASE_EXPORT base::Feature kRemoveCanceledTasksInTaskQueue; + } // namespace base #endif // BASE_TASK_TASK_FEATURES_H_
diff --git a/base/task/thread_pool/worker_thread_unittest.cc b/base/task/thread_pool/worker_thread_unittest.cc index 9c668d95..28202d9 100644 --- a/base/task/thread_pool/worker_thread_unittest.cc +++ b/base/task/thread_pool/worker_thread_unittest.cc
@@ -881,9 +881,9 @@ TEST(ThreadPoolWorkerThreadCachePurgeTest, Purge) { // Make sure the thread cache is enabled in the main partition. allocator::ConfigurePartitions( - allocator::EnableBrp(false), - allocator::ThreadCacheOnNonQuarantinablePartition(false), - allocator::ForceSplitPartitions(false)); + allocator::EnableBrp(false), allocator::SplitMainPartition(false), + allocator::UseDedicatedAlignedPartition(false), + allocator::ThreadCacheOnNonQuarantinablePartition(false)); TaskTracker task_tracker; auto delegate = std::make_unique<WorkerThreadThreadCacheDelegate>();
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1 index 92f805a8..919a528c 100644 --- a/build/fuchsia/linux.sdk.sha1 +++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@ -7.20211208.0.1 +7.20211208.1.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1 index 92f805a8..919a528c 100644 --- a/build/fuchsia/mac.sdk.sha1 +++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@ -7.20211208.0.1 +7.20211208.1.1
diff --git a/chrome/VERSION b/chrome/VERSION index ac4c3b3..3ae6dfc2 100644 --- a/chrome/VERSION +++ b/chrome/VERSION
@@ -1,4 +1,4 @@ MAJOR=98 MINOR=0 -BUILD=4755 +BUILD=4756 PATCH=0
diff --git a/chrome/android/features/vr/java/strings/translations/android_chrome_vr_strings_hu.xtb b/chrome/android/features/vr/java/strings/translations/android_chrome_vr_strings_hu.xtb index 95a06a2..d433173 100644 --- a/chrome/android/features/vr/java/strings/translations/android_chrome_vr_strings_hu.xtb +++ b/chrome/android/features/vr/java/strings/translations/android_chrome_vr_strings_hu.xtb
@@ -4,5 +4,8 @@ <translation id="1938981467853765413">Visszajelzés küldése</translation> <translation id="360207483134687714">Segítsen továbbfejleszteni a virtuális valósággal kapcsolatos élményt Chrome-ban</translation> <translation id="3789841737615482174">Telepítés</translation> +<translation id="4088809042407767679">Frissíti a Google VR-szolgáltatásokat?</translation> +<translation id="4648883053543509795">Telepíti a Google VR-szolgáltatásokat?</translation> <translation id="473775607612524610">Frissítés</translation> +<translation id="5010116926836661047">Virtuálisvalóság-tartalom megtekintése</translation> </translationbundle> \ No newline at end of file
diff --git a/chrome/android/features/vr/java/strings/translations/android_chrome_vr_strings_lt.xtb b/chrome/android/features/vr/java/strings/translations/android_chrome_vr_strings_lt.xtb index fefe19d..76ea715 100644 --- a/chrome/android/features/vr/java/strings/translations/android_chrome_vr_strings_lt.xtb +++ b/chrome/android/features/vr/java/strings/translations/android_chrome_vr_strings_lt.xtb
@@ -4,5 +4,8 @@ <translation id="1938981467853765413">Pateikti atsiliepimą</translation> <translation id="360207483134687714">Padėkite pagerinti VR patirtį „Chrome“</translation> <translation id="3789841737615482174">Įdiegti</translation> +<translation id="4088809042407767679">Atnaujinti „Google“ VR paslaugas?</translation> +<translation id="4648883053543509795">Įdiegti „Google“ VR paslaugas?</translation> <translation id="473775607612524610">Atnaujinti</translation> +<translation id="5010116926836661047">Žr. virtualiosios realybės turinį</translation> </translationbundle> \ No newline at end of file
diff --git a/chrome/android/features/vr/java/strings/translations/android_chrome_vr_strings_ms.xtb b/chrome/android/features/vr/java/strings/translations/android_chrome_vr_strings_ms.xtb index 990ba0f..d686ac8 100644 --- a/chrome/android/features/vr/java/strings/translations/android_chrome_vr_strings_ms.xtb +++ b/chrome/android/features/vr/java/strings/translations/android_chrome_vr_strings_ms.xtb
@@ -4,5 +4,8 @@ <translation id="1938981467853765413">Berikan maklum balas</translation> <translation id="360207483134687714">Bantu memperbaik pengalaman VR dalam Chrome</translation> <translation id="3789841737615482174">Pasang</translation> +<translation id="4088809042407767679">Kemas Kini Perkhidmatan VR Google?</translation> +<translation id="4648883053543509795">Pasang Perkhidmatan VR Google?</translation> <translation id="473775607612524610">Kemas kini</translation> +<translation id="5010116926836661047">Lihat kandungan realiti maya</translation> </translationbundle> \ No newline at end of file
diff --git a/chrome/android/java/res/values/styles.xml b/chrome/android/java/res/values/styles.xml index b705018..de69550e 100644 --- a/chrome/android/java/res/values/styles.xml +++ b/chrome/android/java/res/values/styles.xml
@@ -127,7 +127,7 @@ <item name="colorAccent">@macro/default_control_color_active</item> <item name="colorControlHighlight">@color/control_highlight_color</item> <item name="spinnerStyle">@style/SpinnerStyle</item> - <item name="textInputStyle">@style/TextInputStyle</item> + <item name="textInputStyle">@style/Widget.BrowserUI.TextInputLayout</item> <!-- Remove ActionBar --> <item name="windowNoTitle">true</item>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeStrictMode.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeStrictMode.java index 7ea6269..034622c 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeStrictMode.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeStrictMode.java
@@ -211,7 +211,7 @@ ThreadStrictModeInterceptor.Builder threadInterceptor) { // Ignore strict mode violations due to SharedPreferences. threadInterceptor.ignoreExternalClass( - Violation.DETECT_DISK_IO, "android.content.SharedPreferences"); + Violation.DETECT_DISK_IO, "android.app.SharedPreferencesImpl"); threadInterceptor.ignoreExternalMethod( Violation.DETECT_DISK_IO, "android.app.ContextImpl#getSharedPreferences");
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileRenderer.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileRenderer.java index a0b303f..44fbfef 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileRenderer.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileRenderer.java
@@ -18,6 +18,7 @@ import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; import org.chromium.base.ApiCompatibilityUtils; +import org.chromium.base.TraceEvent; import org.chromium.base.library_loader.LibraryLoader; import org.chromium.chrome.R; import org.chromium.chrome.browser.explore_sites.ExploreSitesBridge; @@ -97,27 +98,29 @@ */ public void renderTileSection( List<Tile> sectionTiles, ViewGroup parent, TileGroup.TileSetupDelegate setupDelegate) { - // Map the old tile views by url so they can be reused later. - Map<SiteSuggestion, SuggestionsTileView> oldTileViews = new HashMap<>(); - int childCount = parent.getChildCount(); - for (int i = 0; i < childCount; i++) { - SuggestionsTileView tileView = (SuggestionsTileView) parent.getChildAt(i); - oldTileViews.put(tileView.getData(), tileView); - } - - // Remove all views from the layout because even if they are reused later they'll have to be - // added back in the correct order. - parent.removeAllViews(); - - for (Tile tile : sectionTiles) { - SuggestionsTileView tileView = oldTileViews.get(tile.getData()); - if (tileView == null || tileView.getIconView() == null - || tileView.getIconView().getDrawable() == null - || tile.getSource() == TileSource.EXPLORE) { - tileView = buildTileView(tile, parent, setupDelegate); + try (TraceEvent e = TraceEvent.scoped("TileRenderer.renderTileSection")) { + // Map the old tile views by url so they can be reused later. + Map<SiteSuggestion, SuggestionsTileView> oldTileViews = new HashMap<>(); + int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + SuggestionsTileView tileView = (SuggestionsTileView) parent.getChildAt(i); + oldTileViews.put(tileView.getData(), tileView); } - parent.addView(tileView); + // Remove all views from the layout because even if they are reused later they'll have + // to be added back in the correct order. + parent.removeAllViews(); + + for (Tile tile : sectionTiles) { + SuggestionsTileView tileView = oldTileViews.get(tile.getData()); + if (tileView == null || tileView.getIconView() == null + || tileView.getIconView().getDrawable() == null + || tile.getSource() == TileSource.EXPLORE) { + tileView = buildTileView(tile, parent, setupDelegate); + } + + parent.addView(tileView); + } } }
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp index 69fe8410..a6d084d3 100644 --- a/chrome/app/chromeos_strings.grdp +++ b/chrome/app/chromeos_strings.grdp
@@ -851,6 +851,39 @@ <message name="IDS_BLUETOOTH_PAIRING_ENTER_KEYS" desc="Bluetooth pairing dialog: Message displayed informing the user to enter the keys below on the device being paired."> Enter these keys on <ph name="DEVICE_NAME">$1<ex>Nexus S</ex></ph> </message> + <message name="IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_UNKNOWN" desc="Bluetooth pairing UI: Accessibility label used for a device of unknown type in a list of devices which can be paired."> + Device <ph name="DEVICE_INDEX">$1<ex>1</ex></ph> of <ph name="DEVICE_COUNT">$2<ex>15</ex></ph>, Unknown device named <ph name="DEVICE_NAME">$3<ex>Beats</ex></ph> + </message> + <message name="IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_COMPUTER" desc="Bluetooth pairing UI: Accessibility label used for a computer device in a list of devices which can be paired."> + Device <ph name="DEVICE_INDEX">$1<ex>1</ex></ph> of <ph name="DEVICE_COUNT">$2<ex>15</ex></ph>, Computer named <ph name="DEVICE_NAME">$3<ex>Beats</ex></ph> + </message> + <message name="IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_PHONE" desc="Bluetooth pairing UI: Accessibility label used for a phone device in a list of devices which can be paired."> + Device <ph name="DEVICE_INDEX">$1<ex>1</ex></ph> of <ph name="DEVICE_COUNT">$2<ex>15</ex></ph>, Phone named <ph name="DEVICE_NAME">$3<ex>Beats</ex></ph> + </message> + <message name="IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_HEADSET" desc="Bluetooth pairing UI: Accessibility label used for an audio device in a list of devices which can be paired."> + Device <ph name="DEVICE_INDEX">$1<ex>1</ex></ph> of <ph name="DEVICE_COUNT">$2<ex>15</ex></ph>, Audio device named <ph name="DEVICE_NAME">$3<ex>Beats</ex></ph> + </message> + <message name="IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_VIDEO_CAMERA" desc="Bluetooth pairing UI: Accessibility label used for a video camera device in a list of devices which can be paired."> + Device <ph name="DEVICE_INDEX">$1<ex>1</ex></ph> of <ph name="DEVICE_COUNT">$2<ex>15</ex></ph>, Video camera named <ph name="DEVICE_NAME">$3<ex>Beats</ex></ph> + </message> + <message name="IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_GAME_CONTROLLER" desc="Bluetooth pairing UI: Accessibility label used for a game controller device in a list of devices which can be paired."> + Device <ph name="DEVICE_INDEX">$1<ex>1</ex></ph> of <ph name="DEVICE_COUNT">$2<ex>15</ex></ph>, Game controller named <ph name="DEVICE_NAME">$3<ex>Beats</ex></ph> + </message> + <message name="IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_KEYBOARD" desc="Bluetooth pairing UI: Accessibility label used for a keyboard device in a list of devices which can be paired."> + Device <ph name="DEVICE_INDEX">$1<ex>1</ex></ph> of <ph name="DEVICE_COUNT">$2<ex>15</ex></ph>, Keyboard named <ph name="DEVICE_NAME">$3<ex>Beats</ex></ph> + </message> + <message name="IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_MOUSE" desc="Bluetooth pairing UI: Accessibility label used for a mouse device in a list of devices which can be paired."> + Device <ph name="DEVICE_INDEX">$1<ex>1</ex></ph> of <ph name="DEVICE_COUNT">$2<ex>15</ex></ph>, Mouse named <ph name="DEVICE_NAME">$3<ex>Beats</ex></ph> + </message> + <message name="IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_TABLET" desc="Bluetooth pairing UI: Accessibility label used for a tablet device in a list of devices which can be paired."> + Device <ph name="DEVICE_INDEX">$1<ex>1</ex></ph> of <ph name="DEVICE_COUNT">$2<ex>15</ex></ph>, Tablet named <ph name="DEVICE_NAME">$3<ex>Beats</ex></ph> + </message> + <message name="IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_SECONDARY_ERROR_A11Y_LABEL" desc="Bluetooth pairing dialog: Accessibility label for unpaired Bluetooth device list item, when pairing to a device fails."> + Couldn't pair to device <ph name="DEVICE_NAME">$1<ex>Beats</ex></ph>; select device to try again + </message> + <message name="IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_SECONDARY_PAIRING_A11Y_LABEL" desc="Bluetooth pairing dialog: Accessibility label for unpaired Bluetooth device list item, when pairing to the a device is in progress."> + Pairing to <ph name="DEVICE_NAME">$1<ex>Beats</ex></ph> + </message> <!-- Strings for the OOBE packaged license screen --> <message name="IDS_OOBE_PACKAGED_LICENSE_TITLE" desc="Title of the screen which advertises use of packaged license.">
diff --git a/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_COMPUTER.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_COMPUTER.png.sha1 new file mode 100644 index 0000000..697c12cd --- /dev/null +++ b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_COMPUTER.png.sha1
@@ -0,0 +1 @@ +384318d0fe4e3716ffb32ae3c5ad279fc246acbb \ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_GAME_CONTROLLER.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_GAME_CONTROLLER.png.sha1 new file mode 100644 index 0000000..697c12cd --- /dev/null +++ b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_GAME_CONTROLLER.png.sha1
@@ -0,0 +1 @@ +384318d0fe4e3716ffb32ae3c5ad279fc246acbb \ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_HEADSET.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_HEADSET.png.sha1 new file mode 100644 index 0000000..697c12cd --- /dev/null +++ b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_HEADSET.png.sha1
@@ -0,0 +1 @@ +384318d0fe4e3716ffb32ae3c5ad279fc246acbb \ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_KEYBOARD.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_KEYBOARD.png.sha1 new file mode 100644 index 0000000..697c12cd --- /dev/null +++ b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_KEYBOARD.png.sha1
@@ -0,0 +1 @@ +384318d0fe4e3716ffb32ae3c5ad279fc246acbb \ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_MOUSE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_MOUSE.png.sha1 new file mode 100644 index 0000000..697c12cd --- /dev/null +++ b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_MOUSE.png.sha1
@@ -0,0 +1 @@ +384318d0fe4e3716ffb32ae3c5ad279fc246acbb \ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_PHONE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_PHONE.png.sha1 new file mode 100644 index 0000000..697c12cd --- /dev/null +++ b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_PHONE.png.sha1
@@ -0,0 +1 @@ +384318d0fe4e3716ffb32ae3c5ad279fc246acbb \ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_TABLET.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_TABLET.png.sha1 new file mode 100644 index 0000000..697c12cd --- /dev/null +++ b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_TABLET.png.sha1
@@ -0,0 +1 @@ +384318d0fe4e3716ffb32ae3c5ad279fc246acbb \ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_UNKNOWN.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_UNKNOWN.png.sha1 new file mode 100644 index 0000000..697c12cd --- /dev/null +++ b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_UNKNOWN.png.sha1
@@ -0,0 +1 @@ +384318d0fe4e3716ffb32ae3c5ad279fc246acbb \ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_VIDEO_CAMERA.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_VIDEO_CAMERA.png.sha1 new file mode 100644 index 0000000..697c12cd --- /dev/null +++ b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_VIDEO_CAMERA.png.sha1
@@ -0,0 +1 @@ +384318d0fe4e3716ffb32ae3c5ad279fc246acbb \ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_SECONDARY_ERROR_A11Y_LABEL.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_SECONDARY_ERROR_A11Y_LABEL.png.sha1 new file mode 100644 index 0000000..d135cad --- /dev/null +++ b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_SECONDARY_ERROR_A11Y_LABEL.png.sha1
@@ -0,0 +1 @@ +abd68cb9e244d8a82451b5f4567a847b2d4e3def \ No newline at end of file
diff --git a/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_SECONDARY_PAIRING_A11Y_LABEL.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_SECONDARY_PAIRING_A11Y_LABEL.png.sha1 new file mode 100644 index 0000000..ea08739 --- /dev/null +++ b/chrome/app/chromeos_strings_grdp/IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_SECONDARY_PAIRING_A11Y_LABEL.png.sha1
@@ -0,0 +1 @@ +18fe36a18e4e679249ef2ae6ea6490cb8bacf28d \ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 49e54d32..ca406bc 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd
@@ -7658,33 +7658,18 @@ <message name="IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT" desc="label used in screen capture notification UI to show the screen sharing status when the stream is consumed by the same app initiating the window picker"> <ph name="APP_NAME">$1<ex>html5rocks.com</ex></ph> is sharing your screen. </message> - <message name="IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT_DELEGATED" desc="label used in screen capture notification UI to show the screen sharing status when the stream is consumed by a tab instead of the app initiating the window picker"> - <ph name="APP_NAME">$1<ex>html5rocks.com</ex></ph> is sharing your screen with <ph name="TAB_NAME">$2<ex>https://google.com</ex></ph>. - </message> <message name="IDS_MEDIA_SCREEN_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT" desc="label used in screen capture notification UI to show the screen sharing status when the stream is consumed by the same app initiating the window picker"> <ph name="APP_NAME">$1<ex>html5rocks.com</ex></ph> is sharing your screen and audio. </message> - <message name="IDS_MEDIA_SCREEN_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT_DELEGATED" desc="label used in screen capture notification UI to show the screen sharing status when the stream is consumed by a tab instead of the app initiating the window picker"> - <ph name="APP_NAME">$1<ex>html5rocks.com</ex></ph> is sharing your screen and audio with <ph name="TAB_NAME">$2<ex>https://google.com</ex></ph>. - </message> <message name="IDS_MEDIA_WINDOW_CAPTURE_NOTIFICATION_TEXT" desc="label used in screen capture notification UI to show the screen sharing status when the stream is consumed by the same app initiating the window picker"> <ph name="APP_NAME">$1<ex>html5rocks.com</ex></ph> is sharing a window. </message> - <message name="IDS_MEDIA_WINDOW_CAPTURE_NOTIFICATION_TEXT_DELEGATED" desc="label used in screen capture notification UI to show the screen sharing status when the stream is consumed by a tab instead of the app initiating the window picker"> - <ph name="APP_NAME">$1<ex>html5rocks.com</ex></ph> is sharing a window with <ph name="TAB_NAME">$2<ex>https://google.com</ex></ph>. - </message> <message name="IDS_MEDIA_TAB_CAPTURE_NOTIFICATION_TEXT" desc="label used in screen capture notification UI to show the screen sharing status when the stream is consumed by the same app initiating the window picker"> <ph name="APP_NAME">$1<ex>html5rocks.com</ex></ph> is sharing a Chrome tab. </message> - <message name="IDS_MEDIA_TAB_CAPTURE_NOTIFICATION_TEXT_DELEGATED" desc="label used in screen capture notification UI to show the screen sharing status when the stream is consumed by a tab instead of the app initiating the window picker"> - <ph name="APP_NAME">$1<ex>html5rocks.com</ex></ph> is sharing a Chrome tab with <ph name="TAB_NAME">$2<ex>https://google.com</ex></ph>. - </message> <message name="IDS_MEDIA_TAB_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT" desc="label used in screen capture notification UI to show the screen sharing status when the stream is consumed by the same app initiating the window picker"> <ph name="APP_NAME">$1<ex>html5rocks.com</ex></ph> is sharing a Chrome tab and audio. </message> - <message name="IDS_MEDIA_TAB_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT_DELEGATED" desc="label used in screen capture notification UI to show the screen sharing status when the stream is consumed by a tab instead of the app initiating the window picker"> - <ph name="APP_NAME">$1<ex>html5rocks.com</ex></ph> is sharing a Chrome tab and audio with <ph name="TAB_NAME">$2<ex>https://google.com</ex></ph>. - </message> <message name="IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_HIDE" desc="Text shown on a link that hides the notification that the screen is being captured."> Hide </message>
diff --git a/chrome/app/resources/generated_resources_as.xtb b/chrome/app/resources/generated_resources_as.xtb index 37a4210d..98560a8f 100644 --- a/chrome/app/resources/generated_resources_as.xtb +++ b/chrome/app/resources/generated_resources_as.xtb
@@ -164,6 +164,7 @@ <translation id="1153636665119721804">Googleৰ সুৰক্ষা সম্পৰ্কীয় উন্নত সুবিধাৰ প্ৰ’গ্ৰাম</translation> <translation id="1155545602507378023">নহয়, কেৱল এই ডিভাইচটো</translation> <translation id="1155816283571436363">আপোনাৰ ফ’নটোৰ সৈতে সংযোগ কৰি থকা হৈছে</translation> +<translation id="1158080958325422608">বৰফলা কৰক</translation> <translation id="1158238185437008462">স্মৃতিসমূহ চাওক</translation> <translation id="1161575384898972166">ক্লায়েণ্টৰ প্ৰমাণপত্ৰখন ৰপ্তানি কৰিবলৈ অনুগ্ৰহ কৰি <ph name="TOKEN_NAME" />ত ছাইন ইন কৰক।</translation> <translation id="116173250649946226">আপোনাৰ প্ৰশাসকে এটা ডিফ’ল্ট থীম ছেট কৰিছে যিটো সলনি কৰিব নোৱাৰি।</translation> @@ -695,6 +696,7 @@ <translation id="1700079447639026019">কেতিয়াও কুকিসমূহ ব্যৱহাৰ কৰিব নোৱৰা ছাইটসমূহ</translation> <translation id="1703331064825191675">আপোনাৰ পাছৱৰ্ডসমূহক লৈ কেতিয়াও চিন্তিত নহ’ব</translation> <translation id="1703666494654169921">ছাইটক ভাৰ্ছুৱেল ৰিয়েলিটি ডিভাইচ অথবা ডেটা ব্যৱহাৰ কৰাৰ অনুমতি নিদিব</translation> +<translation id="1704097193565924901">বৰফলাৰ কৰক</translation> <translation id="1704230497453185209">ছাইটক ধ্বনি প্লে’ অনুমতি নিদিব</translation> <translation id="1704970325597567340"><ph name="DATE" />ত সুৰক্ষা পৰীক্ষা চলোৱা হৈছে</translation> <translation id="1706586824377653884">আপোনাৰ প্ৰশাসকৰ দ্বাৰা যোগ কৰা হৈছে</translation> @@ -3585,6 +3587,7 @@ <translation id="4681453295291708042">Nearby Share সুবিধাটো অক্ষম কৰক</translation> <translation id="4681930562518940301">মূল &প্ৰতিচ্ছবি নতুন টেবত খোলক</translation> <translation id="4682551433947286597">ছাইন ইন কৰা স্ক্ৰীণত প্ৰদর্শন হোৱা ৱালপেপাৰ।</translation> +<translation id="4683629100208651599">সৰুফলা কৰক</translation> <translation id="4683947955326903992"><ph name="PERCENTAGE" />% (ডিফ’ল্ট)</translation> <translation id="4684427112815847243">সকলো ছিংক কৰক</translation> <translation id="4684471265911890182"><ph name="APP_NAME" />এ কেমেৰা এক্সেছ কৰিবলৈ চেষ্টা কৰি আছে। এক্সেছৰ অনুমতি দিবলৈ কেমেৰাৰ গোপনীয়তাৰ ছুইচটো অফ কৰক।</translation> @@ -5982,6 +5985,7 @@ <translation id="7257173066616499747">ৱাই-ফাই নেটৱৰ্কসমূহ</translation> <translation id="725758059478686223">প্ৰিণ্টিং সেৱা</translation> <translation id="7257666756905341374">আপুনি প্ৰতিলিপি আৰু পে’ষ্ট কৰা ডেটা পঢ়ক</translation> +<translation id="7258192266780953209">ৰূপান্তৰণ</translation> <translation id="7258225044283673131">এপ্লিকেশ্বনটোৱে সঁহাৰি দিয়া নাই। এপ্টো বন্ধ কৰিবলৈ "জোৰকৈ বন্ধ কৰক" সুবিধাটো বাছনি কৰক।</translation> <translation id="7262004276116528033">এই ছাইন ইন কৰা সেৱাটো <ph name="SAML_DOMAIN" />এ হ’ষ্ট কৰিছে</translation> <translation id="7264564921322372728"><ph name="BEGIN_PARAGRAPH1" />সমস্যা সমাধানৰ এই পদক্ষেপকেইটা লৈ চাওক:
diff --git a/chrome/app/resources/generated_resources_et.xtb b/chrome/app/resources/generated_resources_et.xtb index 9db3cc9d..50ccc70 100644 --- a/chrome/app/resources/generated_resources_et.xtb +++ b/chrome/app/resources/generated_resources_et.xtb
@@ -163,6 +163,7 @@ <translation id="1153356358378277386">Seotud seadmed</translation> <translation id="1153636665119721804">Google'i täiustatud kaitse programm</translation> <translation id="1155816283571436363">Telefoniga ühenduse loomine</translation> +<translation id="1158080958325422608">Muuda suurtäheliseks</translation> <translation id="1158238185437008462">Vaadake mälestusi</translation> <translation id="1161575384898972166">Logige sisse rakendusse <ph name="TOKEN_NAME" /> kliendi sertifikaadi eksportimiseks.</translation> <translation id="116173250649946226">Administraator on seadistanud vaiketeema, mida ei saa muuta.</translation> @@ -695,6 +696,7 @@ <translation id="1700079447639026019">Saidid, mis ei tohi kunagi küpsisefaile kasutada</translation> <translation id="1703331064825191675">Te ei pea kunagi oma paroolide pärast muretsema</translation> <translation id="1703666494654169921">Ära luba saitidel kasutada VR-seadmeid ega -andmeid</translation> +<translation id="1704097193565924901">Suurtäht alguses</translation> <translation id="1704230497453185209">Ära luba saitidel esitada heli</translation> <translation id="1704970325597567340">Ohutuskontroll tehti <ph name="DATE" /></translation> <translation id="1706586824377653884">Lisas teie administraator</translation> @@ -3568,6 +3570,7 @@ <translation id="4681453295291708042">Läheduses jagamise keelamine</translation> <translation id="4681930562518940301">Ava &algkujutis uuel vahelehel</translation> <translation id="4682551433947286597">Taustapildid kuvatakse sisselogimisekraanil.</translation> +<translation id="4683629100208651599">Muuda väiketäheliseks</translation> <translation id="4683947955326903992"><ph name="PERCENTAGE" />% (vaikeseade)</translation> <translation id="4684427112815847243">Sünkrooni kõik</translation> <translation id="4684471265911890182"><ph name="APP_NAME" /> üritab kaamerale juurde pääseda. Juurdepääsu lubamiseks lülitage kaamera privaatsuse lüliti välja.</translation> @@ -5960,6 +5963,7 @@ <translation id="7257173066616499747">WiFi-võrgud</translation> <translation id="725758059478686223">Printimisteenus</translation> <translation id="7257666756905341374">Kopeeritud ja kleebitud andmete lugemine</translation> +<translation id="7258192266780953209">Teisendamised</translation> <translation id="7258225044283673131">Rakendus ei reageeri. Rakenduse sulgemiseks valige käsk „Sundpeata”.</translation> <translation id="7262004276116528033">Sisselogimisteenust hostib <ph name="SAML_DOMAIN" /></translation> <translation id="7264564921322372728"><ph name="BEGIN_PARAGRAPH1" />Proovige neid veaotsingu toiminguid.
diff --git a/chrome/app/resources/generated_resources_fa.xtb b/chrome/app/resources/generated_resources_fa.xtb index ed37a99b..0ae1cc7 100644 --- a/chrome/app/resources/generated_resources_fa.xtb +++ b/chrome/app/resources/generated_resources_fa.xtb
@@ -163,6 +163,7 @@ <translation id="1153356358378277386">دستگاههای مرتبطشده</translation> <translation id="1153636665119721804">برنامه محافظت پیشرفته Google</translation> <translation id="1155816283571436363">درحال اتصال به تلفن</translation> +<translation id="1158080958325422608">تبدیل به حروف بزرگ</translation> <translation id="1158238185437008462">دیدن خاطرات</translation> <translation id="1161575384898972166">لطفاً برای صدور مجوز سرویس گیرنده، به <ph name="TOKEN_NAME" /> وارد شوید.</translation> <translation id="116173250649946226">سرپرستتان طرح زمینهای پیشفرض تنظیم کرده است که نمیتوان تغییر داد.</translation> @@ -693,6 +694,7 @@ <translation id="1700079447639026019">سایتهایی که هرگز نمیتوانند از کوکیها استفاده کنند</translation> <translation id="1703331064825191675">هرگز نگران گذرواژههایتان نباشید</translation> <translation id="1703666494654169921">به سایتها اجازه داده نشود از دادهها یا دستگاههای واقعیت مجازی استفاده کنند</translation> +<translation id="1704097193565924901">بزرگنویسی</translation> <translation id="1704230497453185209">به سایتها اجازه داده نشود صدا پخش کنند</translation> <translation id="1704970325597567340">«بررسی ایمنی» <ph name="DATE" /> اجرا شد</translation> <translation id="1706586824377653884">افزودهشده توسط سرپرست سیستم</translation> @@ -3575,6 +3577,7 @@ <translation id="4681453295291708042">غیرفعال کردن «همرسانی با اطراف»</translation> <translation id="4681930562518940301">باز کردن &تصویر اصلی در برگه جدید</translation> <translation id="4682551433947286597">تصاویر پسزمینه در صفحه ورود به سیستم نمایان میشوند.</translation> +<translation id="4683629100208651599">تبدیل به حروف کوچک</translation> <translation id="4683947955326903992"><ph name="PERCENTAGE" />% (پیشفرض)</translation> <translation id="4684427112815847243">همگامسازی همه</translation> <translation id="4684471265911890182"><ph name="APP_NAME" /> میخواهد به دوربین دسترسی داشته باشد. برای مجاز کردن دسترسی، کلید «حریمخصوصی دوربین» را خاموش کنید.</translation> @@ -5967,6 +5970,7 @@ <translation id="7257173066616499747">شبکههای Wi-Fi</translation> <translation id="725758059478686223">سرویس چاپ</translation> <translation id="7257666756905341374">خواندن دادههایی که کپی و جایگذاری میکنید</translation> +<translation id="7258192266780953209">تبدیلها</translation> <translation id="7258225044283673131">برنامه پاسخ نمیدهد. برای بستن برنامه، «بستن اجباری» را انتخاب کنید.</translation> <translation id="7262004276116528033">این خدمات ورود به سیستم توسط <ph name="SAML_DOMAIN" /> میزبانی شده است</translation> <translation id="7264564921322372728"><ph name="BEGIN_PARAGRAPH1" />این مراحل عیبیابی را دنبال کنید:
diff --git a/chrome/app/resources/generated_resources_fil.xtb b/chrome/app/resources/generated_resources_fil.xtb index 3919a53..9208170 100644 --- a/chrome/app/resources/generated_resources_fil.xtb +++ b/chrome/app/resources/generated_resources_fil.xtb
@@ -164,6 +164,7 @@ <translation id="1153636665119721804">Programa ng Advanced na Proteksyon ng Google</translation> <translation id="1155545602507378023">Hindi, sa device lang na ito</translation> <translation id="1155816283571436363">Pagkonekta sa iyong telepono</translation> +<translation id="1158080958325422608">Gawing Malalaking Titik</translation> <translation id="1158238185437008462">Tingnan ang mga alaala</translation> <translation id="1161575384898972166">Mangyaring mag-sign in sa <ph name="TOKEN_NAME" /> upang i-export ang certificate ng client.</translation> <translation id="116173250649946226">Nagtakda ang iyong administrator ng default na tema na hindi puwedeng baguhin.</translation> @@ -698,6 +699,7 @@ <translation id="1700079447639026019">Mga site na hindi kailanman puwedeng gumamit ng cookies</translation> <translation id="1703331064825191675">Huwag nang mag-alala sa iyong mga password</translation> <translation id="1703666494654169921">Huwag payagan ang mga site na gumamit ng mga virtual reality device o data</translation> +<translation id="1704097193565924901">I-capitalize</translation> <translation id="1704230497453185209">Huwag payagan ang mga site na mag-play ng tunog</translation> <translation id="1704970325597567340">Nagpatakbo ng pag-check sa kaligtasan noong <ph name="DATE" /></translation> <translation id="1706586824377653884">Idinagdag ng iyong administrator</translation> @@ -3590,6 +3592,7 @@ <translation id="4681453295291708042">I-disable ang Nearby Share</translation> <translation id="4681930562518940301">Buksan ang or&ihinal na larawan sa bagong tab</translation> <translation id="4682551433947286597">Lumalabas ang mga wallpaper sa Screen sa Pag-sign in.</translation> +<translation id="4683629100208651599">Gawing Maliliit na Titik</translation> <translation id="4683947955326903992"><ph name="PERCENTAGE" />% (default)</translation> <translation id="4684427112815847243">I-sync lahat</translation> <translation id="4684471265911890182">Sinusubukan ng <ph name="APP_NAME" /> na i-access ang camera. I-off ang switch ng privacy ng camera para payagan ang access.</translation> @@ -5987,6 +5990,7 @@ <translation id="7257173066616499747">Mga Wi-Fi network</translation> <translation id="725758059478686223">Serbisyo sa Pag-print</translation> <translation id="7257666756905341374">Basahin ang data na iyong kinokopya at pine-paste</translation> +<translation id="7258192266780953209">Mga Pagbabago</translation> <translation id="7258225044283673131">Hindi tumutugon ang application. Piliin ang "Puwersahang isara" para isara ang app.</translation> <translation id="7262004276116528033">Hino-host ng <ph name="SAML_DOMAIN" /> ang serbisyo sa pag-sign in</translation> <translation id="7264564921322372728"><ph name="BEGIN_PARAGRAPH1" />Subukan ang mga hakbang na ito sa pag-troubleshoot:
diff --git a/chrome/app/resources/generated_resources_fr.xtb b/chrome/app/resources/generated_resources_fr.xtb index e2c6775..da564830 100644 --- a/chrome/app/resources/generated_resources_fr.xtb +++ b/chrome/app/resources/generated_resources_fr.xtb
@@ -161,6 +161,7 @@ <translation id="1153356358378277386">Appareils associés</translation> <translation id="1153636665119721804">Programme Protection Avancée de Google</translation> <translation id="1155816283571436363">Connexion à votre téléphone</translation> +<translation id="1158080958325422608">Mettre en majuscules</translation> <translation id="1158238185437008462">Voir vos souvenirs</translation> <translation id="1161575384898972166">Connectez-vous à <ph name="TOKEN_NAME" /> pour exporter le certificat client.</translation> <translation id="116173250649946226">Votre administrateur a défini un thème par défaut qui n'est pas modifiable.</translation> @@ -693,6 +694,7 @@ <translation id="1700079447639026019">Sites ne pouvant pas utiliser de cookies</translation> <translation id="1703331064825191675">Ne vous souciez plus jamais de vos mots de passe</translation> <translation id="1703666494654169921">Ne pas autoriser les sites à utiliser des données ni des appareils de réalité virtuelle</translation> +<translation id="1704097193565924901">Mettre une majuscule</translation> <translation id="1704230497453185209">Ne pas autoriser les sites à lire des sons</translation> <translation id="1704970325597567340">Contrôle de sécurité effectué le <ph name="DATE" /></translation> <translation id="1706586824377653884">Ajouté par votre administrateur</translation> @@ -3563,6 +3565,7 @@ <translation id="4681453295291708042">Désactiver le Partage à proximité</translation> <translation id="4681930562518940301">Ouvrir l'image originale dans un nouvel onglet</translation> <translation id="4682551433947286597">Les fonds d'écran s'affichent sur l'écran de connexion.</translation> +<translation id="4683629100208651599">Mettre en minuscules</translation> <translation id="4683947955326903992"><ph name="PERCENTAGE" /> % (par défaut)</translation> <translation id="4684427112815847243">Tout synchroniser</translation> <translation id="4684471265911890182"><ph name="APP_NAME" /> tente d'accéder à la caméra. Pour lui accorder l'accès, désactivez le paramètre de confidentialité de la caméra.</translation> @@ -5956,6 +5959,7 @@ <translation id="7257173066616499747">Réseaux Wi-Fi</translation> <translation id="725758059478686223">Service d'impression</translation> <translation id="7257666756905341374">Accéder aux données que vous copiez et collez</translation> +<translation id="7258192266780953209">Transformations</translation> <translation id="7258225044283673131">L'application ne répond pas. Sélectionnez "Forcer la fermeture" pour la fermer.</translation> <translation id="7262004276116528033">Ce service de connexion est hébergé par <ph name="SAML_DOMAIN" />.</translation> <translation id="7264564921322372728"><ph name="BEGIN_PARAGRAPH1" />Suivez ces étapes de dépannage :
diff --git a/chrome/app/resources/generated_resources_hu.xtb b/chrome/app/resources/generated_resources_hu.xtb index 191301b..065400e 100644 --- a/chrome/app/resources/generated_resources_hu.xtb +++ b/chrome/app/resources/generated_resources_hu.xtb
@@ -162,6 +162,7 @@ <translation id="1152346050262092795">Adja meg újra a jelszavát a fiók igazolásához.</translation> <translation id="1153356358378277386">Párosított eszközök</translation> <translation id="1153636665119721804">A Google Speciális védelem programja</translation> +<translation id="1155545602507378023">Nem, csak ez az eszköz</translation> <translation id="1155816283571436363">Kapcsolódás a telefonhoz…</translation> <translation id="1158238185437008462">Emlékek megtekintése</translation> <translation id="1161575384898972166">Kérjük, jelentkezzen be a(z) <ph name="TOKEN_NAME" /> szolgáltatásba az ügyféltanúsítvány exportálásához.</translation> @@ -395,6 +396,7 @@ <translation id="1410197035576869800">Alkalmazás ikonja</translation> <translation id="1410616244180625362">A(z) <ph name="HOST" /> továbbra is hozzáférhet az Ön kamerájához</translation> <translation id="1410806973194718079">Nem lehetséges a házirendek ellenőrzése</translation> +<translation id="1412681350727866021">További bővítmények</translation> <translation id="1414315029670184034">A webhelyek nem használhatják a kamerát</translation> <translation id="1414648216875402825">A(z) <ph name="PRODUCT_NAME" /> instabil verziójára frissít, amely fejlesztés alatt álló funkciókat tartalmaz. Előfordulhatnak rendszerösszeomlások és váratlan programhibák. Kérjük, fokozott körültekintéssel lépjen tovább.</translation> <translation id="1415708812149920388">A vágólap olvasásához szükséges hozzáférés megtagadva</translation> @@ -946,6 +948,7 @@ <translation id="1937774647013465102">Nem lehet importálni a tároló <ph name="ARCHITECTURE_CONTAINER" /> architektúratípusát, ugyanis az eszköz architektúrájának típusa <ph name="ARCHITECTURE_DEVICE" />. Megpróbálhatja helyreállítani ezt a tárolót egy másik eszközön, vagy a Fájlok alkalmazás megnyitásával hozzáférhet a tároló lemezképében lévő fájlokhoz.</translation> <translation id="1938351510777341717">Külső parancs</translation> <translation id="1940546824932169984">Társított eszközök</translation> +<translation id="1941410638996203291">Kezdési idő: <ph name="TIME" /></translation> <translation id="1942128823046546853">Az Ön összes adatának olvasása és módosítása az összes webhelyen</translation> <translation id="1942600407708803723">Kikapcsolás a képernyő lecsukásakor</translation> <translation id="1944528062465413897">Bluetooth-párosítókód:</translation> @@ -1324,6 +1327,7 @@ <translation id="2320295602967756579">Világos téma bekapcsolása</translation> <translation id="2322193970951063277">Fejlécek és láblécek</translation> <translation id="2322318151094136999">Kérdezzen rá, ha valamelyik webhely hozzá szeretne férni a soros portokhoz (ajánlott)</translation> +<translation id="2322622365472107569">Befejezési idő: <ph name="TIME" /></translation> <translation id="2323018538045954000">Mentett Wi-Fi-hálózatok</translation> <translation id="2325444234681128157">Jelszó megjegyzése</translation> <translation id="2326188115274135041">A PIN-kód megerősítése az automatikus feloldás bekapcsolásához</translation> @@ -1429,6 +1433,7 @@ <translation id="2435248616906486374">A hálózat leválasztva</translation> <translation id="2435457462613246316">Jelszó megjelenítése</translation> <translation id="2436186046335138073">Engedélyezi az összes <ph name="PROTOCOL" /> link megnyitását a(z) <ph name="HANDLER_HOSTNAME" /> számára?</translation> +<translation id="2439626940657133600">Betöltés: <ph name="WINDOW_TITLE" />…</translation> <translation id="2440604414813129000">F&orrás megtekintése</translation> <translation id="244231003699905658">Érvénytelen cím. Ellenőrizze a címet, majd próbálja újra.</translation> <translation id="2442916515643169563">Szövegárnyék</translation> @@ -1933,6 +1938,7 @@ <translation id="2935654492420446828">Iskolai fiók későbbi hozzáadása</translation> <translation id="2936851848721175671">Biztonsági mentés és visszaállítás</translation> <translation id="2938225289965773019"><ph name="PROTOCOL" /> linkek megnyitása</translation> +<translation id="2939908794993783865">További inaktív webhelyek</translation> <translation id="2939938020978911855">Rendelkezésre álló Bluetooth-eszközök megjelenítése</translation> <translation id="2941112035454246133">Alacsony</translation> <translation id="2942279350258725020">Android Messages</translation> @@ -2078,6 +2084,7 @@ <translation id="3090589793601454425">Ne helyezze át</translation> <translation id="3090819949319990166">A külső crx-fájl nem másolható a következő helyre: <ph name="TEMP_CRX_FILE" />.</translation> <translation id="3090871774332213558">„<ph name="DEVICE_NAME" />” párosítva</translation> +<translation id="3093362725605442088">A Chrome OS-eszköz és az összetevő sorozatszámának olvasása.</translation> <translation id="3093714882666365141">A webhelyek nem telepíthetnek fizetéskezelőket</translation> <translation id="3094141017404513551">Ezzel elkülöníti böngészési tevékenységeit ettől a felhasználótól: <ph name="EXISTING_USER" /></translation> <translation id="3095871294753148861">A könyvjelzők, jelszavak és más böngészőadatok szinkronizálva vannak az elsődleges fiókkal.</translation> @@ -2265,6 +2272,7 @@ <translation id="3308852433423051161">A Google Segéd betöltése…</translation> <translation id="3309330461362844500">Tanúsítványprofil azonosítója</translation> <translation id="3311445899360743395">Az alkalmazáshoz kapcsolódó adatok törlődhetnek erről az eszközről.</translation> +<translation id="3312883087018430408">Ha egy adott webhelyen vagy a Chrome egy részében szeretne keresni, gépelje be a kapcsolódó gyorsparancsot a címsávba, majd nyomja le az Ön által preferált billentyűparancsot. Ha például csak a könyvjelzők között szeretne keresni, írja be a „@bookmarks“ kifejezést, majd nyomja meg a Tab vagy a szóköz billentyűt.</translation> <translation id="3313622045786997898">Tanúsítvány aláírási értéke</translation> <translation id="3313950410573257029">Kapcsolat ellenőrzése</translation> <translation id="3315158641124845231"><ph name="PRODUCT_NAME" /> elrejtése</translation> @@ -2684,6 +2692,7 @@ <translation id="3747077776423672805">Az alkalmazásokat a Beállítások > Google Play Áruház > Android-beállítások kezelése > Alkalmazások vagy Alkalmazáskezelő menüben távolíthatja el. Koppintson a törölni kívánt alkalmazásra (lehet, hogy jobbra vagy balra kell csúsztatnia az alkalmazásokat, hogy megtalálja), majd az Eltávolítás vagy Letiltás lehetőségre.</translation> <translation id="3747220812138541072">Gépelés közben megjelenő szövegközi javaslatok mutatása</translation> <translation id="3748706263662799310">Programhiba bejelentése</translation> +<translation id="3750562496035670393">A Chrome erre az eszközre mentette a jelszavát, de ha szeretné, mentheti inkább a Google-fiókjába. Így a Google-fiókjában található összes jelszavához hozzáférhet, amikor be van jelentkezve.</translation> <translation id="3752253558646317685">Emelgesse a gyermeke az ujját az ujjlenyomat mentéséhez.</translation> <translation id="3752582316358263300">OK...</translation> <translation id="3753033997400164841">Elég egyszer menteni, utána bárhol használhatja</translation> @@ -3124,6 +3133,7 @@ <translation id="4194570336751258953">A kattintás érintéssel engedélyezése</translation> <translation id="4195643157523330669">Megnyitás új lapon</translation> <translation id="4195814663415092787">Folytatás ott, ahol abbahagyta</translation> +<translation id="4198268995694216131">További webhelyek</translation> <translation id="4200689466366162458">Egyéni szavak</translation> <translation id="4200983522494130825">Új &lap</translation> <translation id="4201546031411513170">A beállítások között bármikor módosíthatja a szinkronizálni kívánt elemeket.</translation> @@ -3245,6 +3255,7 @@ <translation id="4341577178275615435">A billentyűzettel való böngészés be- vagy kikapcsolásához használja az F7 gombot</translation> <translation id="4341905082470253054">TPM-állapot ellenőrzése…</translation> <translation id="434198521554309404">Gyors. Biztonságos. Egyszerű.</translation> +<translation id="4343250402091037179">Ha egy adott webhelyen vagy a Chrome egy részében szeretne keresni, gépelje be a kapcsolódó gyorsparancsot a címsávba, majd nyomja le az Ön által preferált billentyűparancsot.</translation> <translation id="434404122609091467">Az aktuális szolgáltatóval</translation> <translation id="4345587454538109430">Konfigurálás...</translation> <translation id="4345732373643853732">A szerver nem ismeri a felhasználónevet</translation> @@ -3996,6 +4007,7 @@ <translation id="5153234146675181447">Telefon elfelejtése</translation> <translation id="5154108062446123722">A(z) <ph name="PRINTING_DESTINATION" /> speciális beállításai</translation> <translation id="5154702632169343078">Tárgy</translation> +<translation id="5155327081870541046">Adja meg a címsávban annak a webhelynek a gyorsparancsát, amelyen keresni szeretne (például „@bookmarks“). Ezt követően nyomja le a preferált billentyűparancsot, és írja be a keresési kifejezést.</translation> <translation id="5157635116769074044">Az oldal rögzítése a kezdőképernyőn...</translation> <translation id="5159094275429367735">A Crostini beállítása</translation> <translation id="5159419673777902220">Szülőd letiltotta a bővítményhez tartozó engedélyeket</translation> @@ -4616,6 +4628,7 @@ <translation id="5816434091619127343">A kívánt nyomtatómódosítások használhatatlanná tehetik a nyomtatót.</translation> <translation id="5817069030404929329">Biztosan áthelyezi a jelszavakat erről az eszközről a Google-fiókjába?</translation> <translation id="5817918615728894473">Párosítás</translation> +<translation id="581840385858998009">Háttérkép, avatar, képernyőkímélő és egyebek személyre szabása.</translation> <translation id="5821565227679781414">Parancsikon létrehozása</translation> <translation id="5822095611691580107">Bal fülhallgató akkumulátorszintje: <ph name="BATTERY_PERCENTAGE" />%.</translation> <translation id="5825412242012995131">Be (javasolt)</translation> @@ -4905,6 +4918,7 @@ <translation id="6116921718742659598">Nyelvi és beviteli beállítások módosítása</translation> <translation id="6119927814891883061">Eszköz elnevezése erre: <ph name="DEVICE_NAME" /></translation> <translation id="6120205520491252677">Az oldal rögzítése a kezdőképernyőn...</translation> +<translation id="6121773125605585883">A következő felhasználónévhez és webhelyhez tartozó jelszó megtekintése: <ph name="USERNAME" />, <ph name="WEBSITE" /></translation> <translation id="6122081475643980456">Internetkapcsolata felett más vette át az irányítást</translation> <translation id="6122093587541546701">E-mail cím (nem kötelező):</translation> <translation id="6122095009389448667">A webhely vágólap megtekintésére vonatkozó tiltásának fenntartása</translation> @@ -4981,6 +4995,7 @@ <translation id="6208725777148613371">Nem sikerült menteni ide: <ph name="WEB_DRIVE" /> – <ph name="INTERRUPT_REASON" /></translation> <translation id="6209838773933913227">Az összetevő frissítése folyamatban van</translation> <translation id="6209908325007204267">Eszközéhez Chrome Enterprise Upgrade licenc tartozik, de a felhasználóneve nincs vállalati fiókhoz társítva. Hozzon létre vállalati fiókot másodlagos eszközén a g.co/ChromeEnterpriseAccount címen.</translation> +<translation id="6210282067670792090">Használja a címsávban ezt a billentyűparancsot a keresőmotorokhoz és webhelykereséshez tartozó gyorsparancsokkal.</translation> <translation id="621172521139737651">{COUNT,plural, =0{Összes megnyitása &új lapcsoportban}=1{Megnyitás &új lapcsoportban}other{Összes ({COUNT}) megnyitása &új lapcsoportban}}</translation> <translation id="6212039847102026977">Speciális hálózati tulajdonságok megjelenítése</translation> <translation id="6212168817037875041">A kijelző kikapcsol</translation> @@ -5464,6 +5479,7 @@ <translation id="6709357832553498500">Csatlakozás a következővel: <ph name="EXTENSIONNAME" /></translation> <translation id="6710213216561001401">Előző</translation> <translation id="6711146141291425900">A fiók (<ph name="WEB_DRIVE" />) összekapcsolása letöltésekhez</translation> +<translation id="6712943853047024245">Már mentett jelszót ezzel a felhasználónévvel a következőhöz: <ph name="WEBSITE" /></translation> <translation id="6713233729292711163">Munkaprofil hozzáadása</translation> <translation id="6715803357256707211">Hiba történt a Linux-alkalmazás telepítése során. Részletekért kattintson az értesítésre.</translation> <translation id="671619610707606484">Ezzel törli a webhelyek által tárolt összesen <ph name="TOTAL_USAGE" />-nyi adatot</translation> @@ -6088,6 +6104,7 @@ <translation id="7385854874724088939">Valami nem sikerült a nyomtatás során. Ellenőrizze a nyomtatót, és próbálkozzon újra.</translation> <translation id="7385896526023870365">A bővítmény nem rendelkezik további webhelyhozzáféréssel.</translation> <translation id="7387273928653486359">Elfogadható</translation> +<translation id="7387951778417998929">Ha az alapértelmezettől eltérő keresőmotort szeretne használni, gépelje be a hozzá tartozó gyorsparancsot a címsávba, majd nyomja le a preferált billentyűparancsot. Itt módosíthatja az alapértelmezett keresőmotort is.</translation> <translation id="7388209873137778229">Csak a támogatott eszközök jelennek meg.</translation> <translation id="7392118418926456391">A víruskeresés nem sikerült</translation> <translation id="7392915005464253525">Bezárt ablak újram&egnyitása</translation> @@ -6265,6 +6282,7 @@ <translation id="7559719679815339381">Kérjük, várjon… A kioszkalkalmazás frissítése folyamatban van. Ne távolítsa el az USB-meghajtót.</translation> <translation id="7560756177962144929">A következő eszköz szinkronizálása: <ph name="DEVICE_TYPE" /></translation> <translation id="7561196759112975576">Mindig</translation> +<translation id="7562099761826673163">Az eszköz személyre szabása</translation> <translation id="756445078718366910">Böngészőablak megnyitása</translation> <translation id="7564847347806291057">Folyamat leállítása</translation> <translation id="756503097602602175">A bejelentkezett Google-fiókokat a <ph name="LINK_BEGIN" />Beállításokban<ph name="LINK_END" /> kezelheti. A webhelyek és alkalmazások számára megadott engedélyek az összes fiókra érvényesek lehetnek. Ha nem szeretné, hogy a webhelyek és alkalmazások hozzáférjenek a fiókadataihoz, vendégként jelentkezzen be a(z) <ph name="DEVICE_TYPE" /> eszközön, vagy <ph name="LINK_2_BEGIN" />inkognitó ablakban<ph name="LINK_2_END" /> böngésszen.</translation> @@ -6582,6 +6600,7 @@ <translation id="78526636422538552">A további Google-fiókok hozzáadása ki van kapcsolva</translation> <translation id="7853747251428735">További esz&közök</translation> <translation id="7855678561139483478">Lap áthelyezése új ablakba</translation> +<translation id="7856654138655787862">A Chrome OS diagnosztikai tesztjeinek futtatása.</translation> <translation id="7857093393627376423">Szöveges javaslatok</translation> <translation id="7857949311770343000">Erre az új lap oldalra számított?</translation> <translation id="7858328180167661092"><ph name="APP_NAME" /> (Windows)</translation> @@ -7230,6 +7249,7 @@ <translation id="8551588720239073785">Dátum- és időbeállítások</translation> <translation id="8553342806078037065">Más személyek kezelése</translation> <translation id="8554899698005018844">Nincs nyelv</translation> +<translation id="855604308879080518">Az Android-alkalmazások USB-eszközökhöz való hozzáférésének engedélyezése ezen a Chromebookon. A rendszer minden alkalommal engedélyt kér, amikor USB-eszközt csatlakozhat. Az egyes Android-alkalmazások további engedélyeket kérnek.</translation> <translation id="8557022314818157177">Érintse meg többször a biztonsági kulcsot, amíg meg nem történik az ujjlenyomat rögzítése.</translation> <translation id="8557180006508471423">Kapcsolja be a „Google Chrome” böngészőt Mac típusú számítógépének Helyszolgáltatások menüjében.</translation> <translation id="8560327176991673955">{COUNT,plural, =0{Összes megnyitása &új ablakban}=1{Megnyitás &új ablakban}other{Összes ({COUNT}) megnyitása &új ablakban}}</translation>
diff --git a/chrome/app/resources/generated_resources_ja.xtb b/chrome/app/resources/generated_resources_ja.xtb index b7749361..cef84d99 100644 --- a/chrome/app/resources/generated_resources_ja.xtb +++ b/chrome/app/resources/generated_resources_ja.xtb
@@ -161,6 +161,7 @@ <translation id="1153356358378277386">ペア設定されたデバイス</translation> <translation id="1153636665119721804">Google の高度な保護機能プログラム</translation> <translation id="1155816283571436363">スマートフォンに接続しています</translation> +<translation id="1158080958325422608">大文字にする</translation> <translation id="1158238185437008462">思い出の写真を表示</translation> <translation id="1161575384898972166">クライアント証明書をエクスポートするには <ph name="TOKEN_NAME" /> にログインしてください。</translation> <translation id="116173250649946226">デフォルトのテーマは管理者が設定しており、変更できません。</translation> @@ -691,6 +692,7 @@ <translation id="1700079447639026019">Cookie を使用できないサイト</translation> <translation id="1703331064825191675">パスワードを気にする必要はありません</translation> <translation id="1703666494654169921">サイトにバーチャル リアリティ デバイスとデータの使用を許可しない</translation> +<translation id="1704097193565924901">大文字にする</translation> <translation id="1704230497453185209">サイトに音声の再生を許可しない</translation> <translation id="1704970325597567340">安全確認を <ph name="DATE" /> に実行しました</translation> <translation id="1706586824377653884">管理者により追加</translation> @@ -3554,6 +3556,7 @@ <translation id="4681453295291708042">ニアバイシェアを無効にする</translation> <translation id="4681930562518940301">元の画像を新しいタブで開く(&I)</translation> <translation id="4682551433947286597">壁紙はログイン画面に表示されます。</translation> +<translation id="4683629100208651599">小文字にする</translation> <translation id="4683947955326903992"><ph name="PERCENTAGE" />%(デフォルト)</translation> <translation id="4684427112815847243">すべてを同期する</translation> <translation id="4684471265911890182"><ph name="APP_NAME" /> がカメラにアクセスしようとしています。アクセスを許可するには、カメラのプライバシー スイッチをオフにしてください。</translation> @@ -5934,6 +5937,7 @@ <translation id="7257173066616499747">Wi-Fi ネットワーク</translation> <translation id="725758059478686223">印刷サービス</translation> <translation id="7257666756905341374">コピー&ペーストするデータの読み取り</translation> +<translation id="7258192266780953209">変換</translation> <translation id="7258225044283673131">アプリケーションから応答がありません。終了するには [強制終了] を選択してください。</translation> <translation id="7262004276116528033">このログイン サービスは <ph name="SAML_DOMAIN" /> でホストされています</translation> <translation id="7264564921322372728"><ph name="BEGIN_PARAGRAPH1" />次の解決方法をお試しください。
diff --git a/chrome/app/resources/generated_resources_lt.xtb b/chrome/app/resources/generated_resources_lt.xtb index e31341b..3364c18 100644 --- a/chrome/app/resources/generated_resources_lt.xtb +++ b/chrome/app/resources/generated_resources_lt.xtb
@@ -162,6 +162,7 @@ <translation id="1152346050262092795">Dar kartą įveskite slaptažodį, kad patvirtintumėte paskyrą.</translation> <translation id="1153356358378277386">Susieti įrenginiai</translation> <translation id="1153636665119721804">„Google“ papildomos apsaugos programa</translation> +<translation id="1155545602507378023">Ne, tik šis įrenginys</translation> <translation id="1155816283571436363">Prisijungiama prie telefono</translation> <translation id="1158238185437008462">Peržiūrėti prisiminimus</translation> <translation id="1161575384898972166">Jei norite eksportuoti programos sertifikatą, prisijunkite prie „<ph name="TOKEN_NAME" />“.</translation> @@ -398,6 +399,7 @@ <translation id="1410197035576869800">Programos piktograma</translation> <translation id="1410616244180625362">Toliau leisti <ph name="HOST" /> pasiekti fotoaparatą</translation> <translation id="1410806973194718079">Nepavyko patikrinti politikos</translation> +<translation id="1412681350727866021">Papildomi plėtiniai</translation> <translation id="1414315029670184034">Neleisti svetainėms naudoti fotoaparato</translation> <translation id="1414648216875402825">Atnaujinate į nestabilią „<ph name="PRODUCT_NAME" />“ versiją, kuri apima vis dar kuriamas funkcijas. Bus strigčių ir netikėtų trikčių. Būkite atsargūs.</translation> <translation id="1415708812149920388">Iškarpinės skaitymo prieiga atmesta</translation> @@ -950,6 +952,7 @@ <translation id="1937774647013465102">Nepavyko importuoti sudėtinio rodinio architektūros tipo „<ph name="ARCHITECTURE_CONTAINER" />“ naudojant šį įrenginį, kuris yra „<ph name="ARCHITECTURE_DEVICE" />“. Galite pabandyti atkurti šį sudėtinį rodinį naudodami kitą įrenginį arba galite pasiekti failus šiame sudėtinio rodinio vaizde atidarę Failų programą.</translation> <translation id="1938351510777341717">Išorinė komanda</translation> <translation id="1940546824932169984">Prijungti įrenginiai</translation> +<translation id="1941410638996203291">Pradžios laikas <ph name="TIME" /></translation> <translation id="1942128823046546853">Skaityti ir keisti visus jūsų duomenis visose svetainėse</translation> <translation id="1942600407708803723">Išjungti, kai dangtelis uždarytas</translation> <translation id="1944528062465413897">„Bluetooth“ susiejimo kodas:</translation> @@ -1328,6 +1331,7 @@ <translation id="2320295602967756579">Įgalinti šviesiąją temą</translation> <translation id="2322193970951063277">Antraštės ir poraštės</translation> <translation id="2322318151094136999">Paklausti, kai svetainė nori pasiekti nuosekliuosius prievadus (rekomenduojama)</translation> +<translation id="2322622365472107569">Pabaigos laikas <ph name="TIME" /></translation> <translation id="2323018538045954000">Išsaugoti „Wi-Fi“ tinklai</translation> <translation id="2325444234681128157">Atsiminti slaptažodį</translation> <translation id="2326188115274135041">Patvirtinkite PIN kodą, kad įjungtumėte automatinio atrakinimo funkciją</translation> @@ -1433,6 +1437,7 @@ <translation id="2435248616906486374">Tinklas atjungtas</translation> <translation id="2435457462613246316">Rodyti slaptažodį</translation> <translation id="2436186046335138073">Leisti „<ph name="HANDLER_HOSTNAME" />“ atidaryti visas <ph name="PROTOCOL" /> nuorodas?</translation> +<translation id="2439626940657133600">Įkeliama „<ph name="WINDOW_TITLE" />“</translation> <translation id="2440604414813129000">Rodyti š&altinį</translation> <translation id="244231003699905658">Netinkamas adresas. Patikrinkite adresą ir bandykite dar kartą.</translation> <translation id="2442916515643169563">Teksto šešėlis</translation> @@ -1937,6 +1942,7 @@ <translation id="2935654492420446828">Mokyklos paskyrą pridėkite vėliau</translation> <translation id="2936851848721175671">Sukurti atsarginę kopiją ir atkurti</translation> <translation id="2938225289965773019">Atidaryti „<ph name="PROTOCOL" />“ nuorodas</translation> +<translation id="2939908794993783865">Papildomos neaktyvios svetainės</translation> <translation id="2939938020978911855">Rodyti pasiekiamus „Bluetooth“ įrenginius</translation> <translation id="2941112035454246133">Žema</translation> <translation id="2942279350258725020">Android Messages</translation> @@ -2082,6 +2088,7 @@ <translation id="3090589793601454425">Neperkelti</translation> <translation id="3090819949319990166">Nepavyko nukopijuoti išorinio CRX failo į „<ph name="TEMP_CRX_FILE" />“.</translation> <translation id="3090871774332213558">Įrenginys „<ph name="DEVICE_NAME" />“ susietas</translation> +<translation id="3093362725605442088">Skaityti „Chrome“ OS įrenginio ir komponentų serijos numerius.</translation> <translation id="3093714882666365141">Neleisti svetainėms įdiegti mokėjimo doroklių</translation> <translation id="3094141017404513551">Jūsų naršymo veikla bus atskirta nuo <ph name="EXISTING_USER" /></translation> <translation id="3095871294753148861">Žymės, slaptažodžiai ir kiti naršyklės duomenys sinchronizuojami su pagrindine paskyra.</translation> @@ -2269,6 +2276,7 @@ <translation id="3308852433423051161">Įkeliamas „Google“ padėjėjas...</translation> <translation id="3309330461362844500">Sertifikuojamo profilio ID</translation> <translation id="3311445899360743395">Su šia programa susieti duomenys gali būti pašalinti iš šio įrenginio.</translation> +<translation id="3312883087018430408">Jei norite ieškoti konkrečioje svetainėje ar „Chrome“ dalyje, įveskite šaukinį adreso juostoje ir paspauskite norimą spartųjį klavišą. Pvz., jei norite ieškoti tik Žymėse, įveskite „@bookmarks“, tada paspauskite tabuliavimo arba tarpo klavišą.</translation> <translation id="3313622045786997898">Sertifikato parašo vertė</translation> <translation id="3313950410573257029">Patikrinti ryšį</translation> <translation id="3315158641124845231">Slėpti „<ph name="PRODUCT_NAME" />“</translation> @@ -2687,6 +2695,7 @@ <translation id="3747077776423672805">Jei norite pašalinti programas, eikite į skiltį „Nustatymai“ > „Google Play“ parduotuvė“ > „Tvarkyti „Android“ nuostatas“ > „Programos“ arba „Programų tvarkytuvė“. Tada palieskite programą, kurią norite pašalinti (galbūt reikės perbraukti į dešinę arba į kairę, kad rastumėte programą). Tada palieskite „Pašalinti“ arba „Išjungti“.</translation> <translation id="3747220812138541072">Rodyti eilutinius rašymo pasiūlymus, kurie pateikiami vedant tekstą</translation> <translation id="3748706263662799310">Pateikti ataskaitą apie triktį</translation> +<translation id="3750562496035670393">„Chrome“ išsaugojo jūsų slaptažodį šiame įrenginyje, bet galite jį išsaugoti savo „Google“ paskyroje. Tada visi slaptažodžiai „Google“ paskyroje taip pat bus pasiekiami, kol būsite prisijungę.</translation> <translation id="3752253558646317685">Paprašykite vaiko kelis kartus atitraukti ir vėl pridėti pirštą, kad būtų išsaugotas kontrolinis kodas</translation> <translation id="3752582316358263300">Gerai...</translation> <translation id="3753033997400164841">Išsaugokite vieną kartą. Naudokite visur</translation> @@ -3128,6 +3137,7 @@ <translation id="4194570336751258953">Įgalinti paspaudimą palietus</translation> <translation id="4195643157523330669">Atidaryti naujame skirtuke</translation> <translation id="4195814663415092787">Tęskite ten, kur baigėte</translation> +<translation id="4198268995694216131">Papildomos svetainės</translation> <translation id="4200689466366162458">Tinkinti žodžiai</translation> <translation id="4200983522494130825">Naujas &skirtukas</translation> <translation id="4201546031411513170">Bet kada galite pasirinkti, ką norite sinchronizuoti, skiltyje „Nustatymai“.</translation> @@ -3249,6 +3259,7 @@ <translation id="4341577178275615435">Jei norite įjungti arba išjungti „Caret Browsing“, naudokite spartųjį klavišą F7</translation> <translation id="4341905082470253054">Tikrinama TPM būsena...</translation> <translation id="434198521554309404">Spartu. Saugu. Paprasta.</translation> +<translation id="4343250402091037179">Jei norite ieškoti konkrečioje svetainėje ar „Chrome“ dalyje, įveskite šaukinį adreso juostoje ir paspauskite norimą spartųjį klavišą.</translation> <translation id="434404122609091467">Su dabartiniu paslaugos teikėju</translation> <translation id="4345587454538109430">Konfigūruoti...</translation> <translation id="4345732373643853732">Naudotojo vardas serveriui nežinomas</translation> @@ -4000,6 +4011,7 @@ <translation id="5153234146675181447">Pamiršti telefoną</translation> <translation id="5154108062446123722">Išplėstiniai <ph name="PRINTING_DESTINATION" /> nustatymai</translation> <translation id="5154702632169343078">Tema</translation> +<translation id="5155327081870541046">Adreso juostoje įveskite svetainės, kurios norite ieškoti, šaukinį, pvz., „@bookmarks“. Tada paspauskite norimą spartųjį klavišą ir įveskite paieškos terminą.</translation> <translation id="5157635116769074044">Prisegti šį puslapį pradžios ekrane...</translation> <translation id="5159094275429367735">Nustatyti „Crostini“</translation> <translation id="5159419673777902220">Vienas iš tėvų išjungė plėtinio leidimus</translation> @@ -4619,6 +4631,7 @@ <translation id="5816434091619127343">Atlikus prašomus spausdintuvo pakeitimus, spausdintuvo nebebus galima naudoti.</translation> <translation id="5817069030404929329">Slaptažodžius iš šio įrenginio perkelti į jūsų „Google“ paskyrą?</translation> <translation id="5817918615728894473">Susieti</translation> +<translation id="581840385858998009">Tinkinkite ekrano foną, pseudoportretą, ekrano užsklandą ir kt.</translation> <translation id="5821565227679781414">Sukurti šaukinį</translation> <translation id="5822095611691580107">Kairiosios ausinės akumuliatoriaus energijos lygis: <ph name="BATTERY_PERCENTAGE" /> %.</translation> <translation id="5825412242012995131">Įjungta (rekomenduojama)</translation> @@ -4908,6 +4921,7 @@ <translation id="6116921718742659598">Keisti kalbos ir įvesties nustatymus</translation> <translation id="6119927814891883061">Įrenginį pavadinti „<ph name="DEVICE_NAME" />“</translation> <translation id="6120205520491252677">Prisegti šį puslapį prie pradžios ekrano...</translation> +<translation id="6121773125605585883">Peržiūrėkite <ph name="WEBSITE" /> slaptažodį su naudotojo vardu <ph name="USERNAME" /></translation> <translation id="6122081475643980456">Jūsų interneto ryšys yra kontroliuojamas</translation> <translation id="6122093587541546701">El. paštas (pasirenkama):</translation> <translation id="6122095009389448667">Toliau neleisti šiai svetainei peržiūrėti iškarpinės</translation> @@ -4984,6 +4998,7 @@ <translation id="6208725777148613371">Nepavyko išsaugoti sistemoje „<ph name="WEB_DRIVE" />“ – <ph name="INTERRUPT_REASON" /></translation> <translation id="6209838773933913227">Komponento atnaujinimas</translation> <translation id="6209908325007204267">Prie jūsų įrenginio yra pridėtas „Chrome“ įmonei skirtos versijos licencijos naujovinimas, bet jūsų naudotojo vardas nesusietas su įmonės paskyra. Sukurkite įmonės paskyrą antriniame įrenginyje apsilankę adresu g.co/ChromeEnterpriseAccount.</translation> +<translation id="6210282067670792090">Adreso juostoje naudokite šį spartųjį klavišą su paieškos variklių ir svetainių paieškos šaukiniais</translation> <translation id="621172521139737651">{COUNT,plural, =0{Atidaryti viską &naujoje skirtukų grupėje}=1{Atidaryti &naujoje skirtukų grupėje}one{Atidaryti viską ({COUNT}) &naujoje skirtukų grupėje}few{Atidaryti viską ({COUNT}) &naujoje skirtukų grupėje}many{Atidaryti viską ({COUNT}) &naujoje skirtukų grupėje}other{Atidaryti viską ({COUNT}) &naujoje skirtukų grupėje}}</translation> <translation id="6212039847102026977">Rodyti išplėstines tinklo nuosavybes</translation> <translation id="6212168817037875041">Išjungti ekraną</translation> @@ -5466,6 +5481,7 @@ <translation id="6709357832553498500">Prisijungti naudojant „<ph name="EXTENSIONNAME" />“</translation> <translation id="6710213216561001401">Ankstesnė</translation> <translation id="6711146141291425900">Atsisiuntimams skirtos „<ph name="WEB_DRIVE" />“ paskyros susiejimas</translation> +<translation id="6712943853047024245">Jau išsaugojote <ph name="WEBSITE" /> slaptažodį su šiuo naudotojo vardu</translation> <translation id="6713233729292711163">Pridėti darbo profilį</translation> <translation id="6715803357256707211">Įdiegiant „Linux“ programą įvyko klaida. Spustelėję pranešimą rasite išsamią informaciją.</translation> <translation id="671619610707606484">Bus išvalyta <ph name="TOTAL_USAGE" /> svetainių saugomų duomenų</translation> @@ -6089,6 +6105,7 @@ <translation id="7385854874724088939">Bandant spausdinti atsirado triktis. Patikrinkite spausdintuvą ir bandykite dar kartą.</translation> <translation id="7385896526023870365">Šiam plėtiniui nesuteikta papildoma svetainės prieiga.</translation> <translation id="7387273928653486359">Priimtina</translation> +<translation id="7387951778417998929">Jei norite naudoti kitą nei numatytąjį paieškos variklį, įveskite jo šaukinį adreso juostoje ir paspauskite norimą spartųjį klavišą. Taip pat čia galite pakeisti numatytąjį paieškos variklį.</translation> <translation id="7388209873137778229">Rodomi tik palaikomi įrenginiai.</translation> <translation id="7392118418926456391">Nepavyko žvalgyti, ar yra virusų</translation> <translation id="7392915005464253525">I&š naujo atidaryti uždarytą langą</translation> @@ -6266,6 +6283,7 @@ <translation id="7559719679815339381">Palaukite... Viešojo terminalo programa atnaujinama. Neatjunkite USB atmintuko.</translation> <translation id="7560756177962144929">„<ph name="DEVICE_TYPE" />“ sinchronizavimas</translation> <translation id="7561196759112975576">Visada</translation> +<translation id="7562099761826673163">Įrenginio suasmeninimas</translation> <translation id="756445078718366910">Atidaryti naršyklės langą</translation> <translation id="7564847347806291057">Baigti procesą</translation> <translation id="756503097602602175">„Google“ paskyras, prie kurių prisijungiate, galite tvarkyti skiltyje <ph name="LINK_BEGIN" />„Nustatymai“<ph name="LINK_END" />. Leidimai, kuriuos suteikėte svetainėms ir programoms, gali būti taikomi visoms paskyroms. Jei nenorite, kad svetainės ar programos pasiektų jūsų paskyros informaciją, galite prisijungti prie „<ph name="DEVICE_TYPE" />“ kaip svečias arba naršyti žiniatinklį <ph name="LINK_2_BEGIN" />inkognito lange<ph name="LINK_2_END" />.</translation> @@ -6583,6 +6601,7 @@ <translation id="78526636422538552">Galimybė pridėti daugiau „Google“ paskyrų išjungta</translation> <translation id="7853747251428735">Daugiau įrank&ių</translation> <translation id="7855678561139483478">Skirtuko perkėlimas į naują langą</translation> +<translation id="7856654138655787862">Vykdyti „Chrome“ OS diagnostikos bandymus.</translation> <translation id="7857093393627376423">Teksto pasiūlymai</translation> <translation id="7857949311770343000">Ar tai naujo skirtuko puslapis, kurį tikėjotės išvysti?</translation> <translation id="7858328180167661092">„<ph name="APP_NAME" />“ („Windows“)</translation> @@ -7231,6 +7250,7 @@ <translation id="8551588720239073785">Datos ir laiko nustatymai</translation> <translation id="8553342806078037065">Tvarkyti kitus žmones</translation> <translation id="8554899698005018844">Kalbos nėra</translation> +<translation id="855604308879080518">Leiskite „Android“ programoms pasiekti USB įrenginius šiame „Chromebook“. Leidimo užklausa bus pateikta kaskart prijungus USB įrenginį. Atskiros „Android“ programos prašys papildomų leidimų.</translation> <translation id="8557022314818157177">Lieskite saugos raktą, kol bus užfiksuotas kontrolinis kodas</translation> <translation id="8557180006508471423">Įjunkite „Google Chrome“, nuėję į „Location Services“ „Mac“ kompiuteryje</translation> <translation id="8560327176991673955">{COUNT,plural, =0{Atidaryti viską &naujame lange}=1{Atidaryti &naujame lange}one{Atidaryti viską ({COUNT}) &naujame lange}few{Atidaryti viską ({COUNT}) &naujame lange}many{Atidaryti viską ({COUNT}) &naujame lange}other{Atidaryti viską ({COUNT}) &naujame lange}}</translation>
diff --git a/chrome/app/resources/generated_resources_ms.xtb b/chrome/app/resources/generated_resources_ms.xtb index 28f00d9..5e7ad619 100644 --- a/chrome/app/resources/generated_resources_ms.xtb +++ b/chrome/app/resources/generated_resources_ms.xtb
@@ -162,6 +162,7 @@ <translation id="1152346050262092795">Masukkan kata laluan sekali lagi untuk mengesahkan akaun anda.</translation> <translation id="1153356358378277386">Peranti digandingkan</translation> <translation id="1153636665119721804">Program Perlindungan Lanjutan Google</translation> +<translation id="1155545602507378023">Tidak, peranti ini sahaja</translation> <translation id="1155816283571436363">Menyambung kepada telefon anda</translation> <translation id="1158080958325422608">Jadikan Huruf Besar</translation> <translation id="1158238185437008462">Lihat kenangan</translation> @@ -396,6 +397,7 @@ <translation id="1410197035576869800">Ikon Apl</translation> <translation id="1410616244180625362">Terus benarkan <ph name="HOST" /> mengakses kamera anda</translation> <translation id="1410806973194718079">Tidak dapat menyemak dasar</translation> +<translation id="1412681350727866021">Sambungan tambahan</translation> <translation id="1414315029670184034">Jangan benarkan laman menggunakan kamera anda</translation> <translation id="1414648216875402825">Anda sedang mengemas kini kepada versi <ph name="PRODUCT_NAME" /> tidak stabil yang mengandungi ciri yang masih diproses. Keranapan dan pepijat yang tidak dijangka akan berlaku. Sila teruskan dengan berhati-hati.</translation> <translation id="1415708812149920388">Akses baca papan keratan ditolak</translation> @@ -949,6 +951,7 @@ <translation id="1937774647013465102">Tidak dapat mengimport seni bina bekas jenis <ph name="ARCHITECTURE_CONTAINER" /> dengan peranti ini, iaitu peranti <ph name="ARCHITECTURE_DEVICE" />. Anda boleh cuba memulihkan bekas ini dalam peranti lain atau anda boleh cuba mengakses fail dalam imej bekas ini dengan membuka fail dalam apl Files.</translation> <translation id="1938351510777341717">Perintah Luaran</translation> <translation id="1940546824932169984">Peranti yang disambungkan</translation> +<translation id="1941410638996203291">Masa mula <ph name="TIME" /></translation> <translation id="1942128823046546853">Baca dan ubah semua data anda pada semua laman web</translation> <translation id="1942600407708803723">Matikan apabila penutup ditutup</translation> <translation id="1944528062465413897">Kod gandingan Bluetooth:</translation> @@ -1327,6 +1330,7 @@ <translation id="2320295602967756579">Dayakan tema cerah</translation> <translation id="2322193970951063277">Pengepala dan pengaki</translation> <translation id="2322318151094136999">Tanya apabila tapak mahu mengakses port siri (disyorkan)</translation> +<translation id="2322622365472107569">Masa tamat <ph name="TIME" /></translation> <translation id="2323018538045954000">Rangkaian Wi-Fi yang disimpan</translation> <translation id="2325444234681128157">Ingat kata laluan</translation> <translation id="2326188115274135041">Sahkan PIN untuk menghidupkan buka kunci automatik</translation> @@ -1432,6 +1436,7 @@ <translation id="2435248616906486374">Rangkaian diputuskan</translation> <translation id="2435457462613246316">Paparkan kata laluan</translation> <translation id="2436186046335138073">Benarkan <ph name="HANDLER_HOSTNAME" /> untuk membuka semua <ph name="PROTOCOL" /> pautan?</translation> +<translation id="2439626940657133600">Memuatkan <ph name="WINDOW_TITLE" /></translation> <translation id="2440604414813129000">Lihat s&umber</translation> <translation id="244231003699905658">Alamat tidak sah. Sila semak alamat dan cuba lagi.</translation> <translation id="2442916515643169563">Bayangan teks</translation> @@ -1937,6 +1942,7 @@ <translation id="2935654492420446828">Tambah akaun institusi pengajian kemudian</translation> <translation id="2936851848721175671">Sandarkan & pulihkan</translation> <translation id="2938225289965773019">Buka pautan <ph name="PROTOCOL" /></translation> +<translation id="2939908794993783865">Laman tidak aktif tambahan</translation> <translation id="2939938020978911855">Tunjukkan peranti Bluetooth yang tersedia</translation> <translation id="2941112035454246133">Rendah</translation> <translation id="2942279350258725020">Android Messages</translation> @@ -2082,6 +2088,7 @@ <translation id="3090589793601454425">Jangan alihkan</translation> <translation id="3090819949319990166">Tidak dapat menyalin fail crx luar ke <ph name="TEMP_CRX_FILE" />.</translation> <translation id="3090871774332213558">"<ph name="DEVICE_NAME" />" digandingkan</translation> +<translation id="3093362725605442088">Baca nombor siri peranti dan komponen Chrome OS.</translation> <translation id="3093714882666365141">Jangan benarkan laman memasang pengendali pembayaran</translation> <translation id="3094141017404513551">Tindakan ini akan memisahkan penyemakan imbas anda daripada <ph name="EXISTING_USER" /></translation> <translation id="3095871294753148861">Penanda halaman, kata laluan dan data semakan imbas lain disegerakkan dengan akaun utama.</translation> @@ -2269,6 +2276,7 @@ <translation id="3308852433423051161">Memuatkan Google Assistant...</translation> <translation id="3309330461362844500">ID Profil Sijil</translation> <translation id="3311445899360743395">Data yang berkaitan dengan apl ini mungkin dialih keluar daripada peranti ini.</translation> +<translation id="3312883087018430408">Untuk mencari laman atau bahagian tertentu Chrome, taip pintasan carian dalam bar alamat, diikuti dengan pintasan papan kekunci pilihan anda. Sebagai contoh, untuk mencari dalam Bookmarks sahaja, taip "@bookmarks", kemudian tekan kekunci Tab atau Ruang.</translation> <translation id="3313622045786997898">Nilai Tandatangan Sijil</translation> <translation id="3313950410573257029">Semak sambungan</translation> <translation id="3315158641124845231">Sembunyikan <ph name="PRODUCT_NAME" /></translation> @@ -2688,6 +2696,7 @@ <translation id="3747077776423672805">Untuk mengalih keluar apl, pergi ke Tetapan > Gedung Google Play > Urus pilihan Android > Apl atau Pengurus aplikasi. Kemudian, ketik apl yang ingin dinyahpasang (anda mungkin perlu meleret ke kanan atau ke kiri untuk mencari apl). Kemudian, ketik Nyahpasang atau Lumpuhkan.</translation> <translation id="3747220812138541072">Tunjukkan cadangan penulisan sebaris yang dipaparkan semasa anda menaip</translation> <translation id="3748706263662799310">Laporkan pepijat</translation> +<translation id="3750562496035670393">Chrome menyimpan kata laluan anda pada peranti ini, tetapi anda juga boleh menyimpan kata laluan pada Google Account anda. Kemudian, semua kata laluan dalam Google Account anda juga akan tersedia semasa anda log masuk.</translation> <translation id="3752253558646317685">Minta anak anda mengangkat jarinya beberapa kali untuk menyimpan cap jari</translation> <translation id="3752582316358263300">OK...</translation> <translation id="3753033997400164841">Simpan sekali. Gunakan di mana-mana</translation> @@ -3129,6 +3138,7 @@ <translation id="4194570336751258953">Dayakan ketik untuk klik</translation> <translation id="4195643157523330669">Buka dalam tab baharu</translation> <translation id="4195814663415092787">Sambung semula dari tempat anda berhenti</translation> +<translation id="4198268995694216131">Laman tambahan</translation> <translation id="4200689466366162458">Perkataan tersuai</translation> <translation id="4200983522494130825">Tab &baharu</translation> <translation id="4201546031411513170">Anda boleh memilih item yang hendak disegerakkan dalam tetapan pada bila-bila masa.</translation> @@ -3250,6 +3260,7 @@ <translation id="4341577178275615435">Untuk menghidupkan atau mematikan semak imbas karet, gunakan pintasan F7</translation> <translation id="4341905082470253054">Menyemak status TPM...</translation> <translation id="434198521554309404">Cepat. Selamat. Mudah.</translation> +<translation id="4343250402091037179">Untuk mencari laman atau bahagian tertentu Chrome, taip pintasan carian dalam bar alamat, diikuti dengan pintasan papan kekunci pilihan anda.</translation> <translation id="434404122609091467">Dengan penyedia perkhidmatan semasa anda</translation> <translation id="4345587454538109430">Konfigurasi...</translation> <translation id="4345732373643853732">Nama pengguna tidak diketahui oleh pelayan</translation> @@ -4002,6 +4013,7 @@ <translation id="5153234146675181447">Lupakan telefon</translation> <translation id="5154108062446123722">Tetapan lanjutan untuk <ph name="PRINTING_DESTINATION" /></translation> <translation id="5154702632169343078">Subjek</translation> +<translation id="5155327081870541046">Dalam bar alamat, masukkan pintasan bagi laman tempat anda ingin melakukan carian, seperti "@bookmarks". Kemudian, tekan pintasan papan kekunci pilihan anda dan masukkan istilah carian anda.</translation> <translation id="5157635116769074044">Pin halaman ini pada Skrin Mula...</translation> <translation id="5159094275429367735">Sediakan Crostini</translation> <translation id="5159419673777902220">Ibu bapa anda telah melumpuhkan kebenaran sambungan</translation> @@ -4620,6 +4632,7 @@ <translation id="5816434091619127343">Perubahan pencetak yang diminta akan menjadikan pencetak tidak boleh digunakan.</translation> <translation id="5817069030404929329">Alihkan kata laluan daripada peranti ini kepada Akaun Google anda?</translation> <translation id="5817918615728894473">Gandingkan</translation> +<translation id="581840385858998009">Sesuaikan kertas dinding, avatar, penyelamat skrin dan banyak lagi</translation> <translation id="5821565227679781414">Cipta Pintasan</translation> <translation id="5822095611691580107">Paras bateri fon telinga kiri <ph name="BATTERY_PERCENTAGE" />%.</translation> <translation id="5825412242012995131">Hidup (Disyorkan)</translation> @@ -4909,6 +4922,7 @@ <translation id="6116921718742659598">Tukar tetapan bahasa dan input</translation> <translation id="6119927814891883061">Namakan peranti kepada <ph name="DEVICE_NAME" /></translation> <translation id="6120205520491252677">Pin halaman ini pada skrin Mula...</translation> +<translation id="6121773125605585883">Lihat kata laluan dengan nama pengguna <ph name="USERNAME" /> untuk <ph name="WEBSITE" /></translation> <translation id="6122081475643980456">Sambungan Internet anda sedang dikawal</translation> <translation id="6122093587541546701">E-mel (pilihan):</translation> <translation id="6122095009389448667">Teruskan menyekat tapak ini daripada melihat papan keratan</translation> @@ -4985,6 +4999,7 @@ <translation id="6208725777148613371">Gagal menyimpan pada <ph name="WEB_DRIVE" /> - <ph name="INTERRUPT_REASON" /></translation> <translation id="6209838773933913227">Sedang mengemas kini komponen</translation> <translation id="6209908325007204267">Peranti anda termasuk Peningkatan Chrome Enterprise tetapi nama pengguna anda tidak dikaitkan dengan akaun perusahaan. Sila buat akaun perusahaan dengan melawati g.co/ChromeEnterpriseAccount pada peranti kedua.</translation> +<translation id="6210282067670792090">Dalam bar alamat, gunakan pintasan papan kekunci ini dengan pintasan untuk enjin carian dan carian laman</translation> <translation id="621172521139737651">{COUNT,plural, =0{Buka Semua dalam Kumpulan Tab &Baharu}=1{Buka dalam Kumpulan Tab &Baharu}other{Buka Semua ({COUNT}) dalam Kumpulan Tab &Baharu}}</translation> <translation id="6212039847102026977">Tunjukkan sifat rangkaian terperinci</translation> <translation id="6212168817037875041">Matikan paparan</translation> @@ -5468,6 +5483,7 @@ <translation id="6709357832553498500">Sambung menggunakan <ph name="EXTENSIONNAME" /></translation> <translation id="6710213216561001401">Sebelumnya</translation> <translation id="6711146141291425900">Pautkan akaun <ph name="WEB_DRIVE" /> untuk Muat Turun</translation> +<translation id="6712943853047024245">Anda telah menyimpan kata laluan dengan nama pengguna ini untuk <ph name="WEBSITE" /></translation> <translation id="6713233729292711163">Tambah Profil Kerja</translation> <translation id="6715803357256707211">Ralat telah berlaku semasa pemasangan aplikasi Linux anda. Klik pemberitahuan untuk mendapatkan butiran.</translation> <translation id="671619610707606484">Tindakan ini akan mengosongkan <ph name="TOTAL_USAGE" /> daripada data yang disimpan oleh tapak</translation> @@ -6093,6 +6109,7 @@ <translation id="7385854874724088939">Sesuatu tidak kena apabila mencuba untuk mencetak. Sila periksa pencetak anda dan cuba semula.</translation> <translation id="7385896526023870365">Sambungan ini tiada akses tapak tambahan.</translation> <translation id="7387273928653486359">Sederhana</translation> +<translation id="7387951778417998929">Untuk menggunakan enjin carian lain selain yang lalai, taip pintasan enjin carian tersebut dalam bar alamat diikuti dengan pintasan papan kekunci pilihan anda. Anda juga boleh menukar enjin carian lalai anda di sini.</translation> <translation id="7388209873137778229">Hanya peranti yang disokong ditunjukkan.</translation> <translation id="7392118418926456391">Imbasan virus gagal</translation> <translation id="7392915005464253525">B&uka semula tetingkap ditutup</translation> @@ -6270,6 +6287,7 @@ <translation id="7559719679815339381">Sila tunggu....apl Kiosk sedang dalam proses untuk dikemas kini. Jangan alih keluar batang USB.</translation> <translation id="7560756177962144929">Segerakkan <ph name="DEVICE_TYPE" /> anda</translation> <translation id="7561196759112975576">Sentiasa</translation> +<translation id="7562099761826673163">Peribadikan peranti anda</translation> <translation id="756445078718366910">Buka Tetingkap Penyemak Imbas</translation> <translation id="7564847347806291057">Tamatkan proses</translation> <translation id="756503097602602175">Anda boleh mengurus Akaun Google yang dilog masuk daripada <ph name="LINK_BEGIN" />Tetapan<ph name="LINK_END" />. Kebenaran yang telah anda berikan kepada laman web dan apl boleh digunakan pada semua akaun. Jika anda tidak mahu maklumat akaun anda diakses oleh laman atau apl, anda boleh log masuk ke <ph name="DEVICE_TYPE" /> anda sebagai tetamu atau menyemak imbas web dalam <ph name="LINK_2_BEGIN" />tetingkap Inkognito<ph name="LINK_2_END" />.</translation> @@ -6587,6 +6605,7 @@ <translation id="78526636422538552">Penambahan lebih banyak Akaun Google dilumpuhkan</translation> <translation id="7853747251428735">Lagi Al&atan</translation> <translation id="7855678561139483478">Alihkan tab ke tetingkap baharu</translation> +<translation id="7856654138655787862">Jalankan ujian diagnostik Chrome OS.</translation> <translation id="7857093393627376423">Cadangan teks</translation> <translation id="7857949311770343000">Adakah ini halaman tab baharu yang anda jangkakan?</translation> <translation id="7858328180167661092"><ph name="APP_NAME" /> (Windows)</translation> @@ -7237,6 +7256,7 @@ <translation id="8551588720239073785">Tetapan tarikh dan masa</translation> <translation id="8553342806078037065">Urus orang lain</translation> <translation id="8554899698005018844">Tiada bahasa</translation> +<translation id="855604308879080518">Benarkan Apl Android mengakses peranti USB pada Chromebook ini. Kebenaran akan diminta setiap kali anda memalamkan peranti USB. Setiap Apl Android akan meminta kebenaran tambahan.</translation> <translation id="8557022314818157177">Teruskan menyentuh kunci keselamatan anda sehingga cap jari anda disimpan</translation> <translation id="8557180006508471423">Hidupkan "Google Chrome" dalam Perkhidmatan Lokasi pada Mac anda</translation> <translation id="8560327176991673955">{COUNT,plural, =0{Buka Semua dalam Tetingkap &Baharu}=1{Buka dalam &Tetingkap Baharu}other{Buka Semua ({COUNT}) dalam Tetingkap &Baharu}}</translation>
diff --git a/chrome/app/resources/generated_resources_nl.xtb b/chrome/app/resources/generated_resources_nl.xtb index a863140a..d69ad15e 100644 --- a/chrome/app/resources/generated_resources_nl.xtb +++ b/chrome/app/resources/generated_resources_nl.xtb
@@ -161,6 +161,7 @@ <translation id="1153356358378277386">Gekoppelde apparaten</translation> <translation id="1153636665119721804">Het Google-programma 'Geavanceerde beveiliging'</translation> <translation id="1155816283571436363">Verbinding maken met je telefoon</translation> +<translation id="1158080958325422608">Hoofdletters van maken</translation> <translation id="1158238185437008462">Herinneringen bekijken</translation> <translation id="1161575384898972166">Log in bij <ph name="TOKEN_NAME" /> om het klantcertificaat te exporteren.</translation> <translation id="116173250649946226">Je beheerder heeft een standaardthema ingesteld dat niet kan worden gewijzigd.</translation> @@ -692,6 +693,7 @@ <translation id="1700079447639026019">Sites die nooit cookies mogen gebruiken</translation> <translation id="1703331064825191675">Maak je nooit meer zorgen over je wachtwoorden</translation> <translation id="1703666494654169921">Niet toestaan dat sites virtualreality-apparaten of -gegevens gebruiken</translation> +<translation id="1704097193565924901">Beginhoofdletters van maken</translation> <translation id="1704230497453185209">Niet toestaan dat sites geluid afspelen</translation> <translation id="1704970325597567340">Veiligheidscheck is uitgevoerd op <ph name="DATE" /></translation> <translation id="1706586824377653884">Toegevoegd door je beheerder</translation> @@ -3561,6 +3563,7 @@ <translation id="4681453295291708042">Dichtbij delen uitzetten</translation> <translation id="4681930562518940301">Oorspronkelijke &afbeelding openen op nieuw tabblad</translation> <translation id="4682551433947286597">Achtergronden zijn op het inlogscherm zichtbaar.</translation> +<translation id="4683629100208651599">Kleine letters van maken</translation> <translation id="4683947955326903992"><ph name="PERCENTAGE" />% (standaard)</translation> <translation id="4684427112815847243">Alles synchroniseren</translation> <translation id="4684471265911890182"><ph name="APP_NAME" /> vraagt toegang tot de camera. Zet de privacyschakelaar voor de camera uit om de toegang toe te staan.</translation> @@ -5951,6 +5954,7 @@ <translation id="7257173066616499747">Wifi-netwerken</translation> <translation id="725758059478686223">Afdrukservice</translation> <translation id="7257666756905341374">Gegevens lezen die je kopieert en plakt</translation> +<translation id="7258192266780953209">Transformaties</translation> <translation id="7258225044283673131">De app reageert niet. Selecteer 'Nu sluiten' om de app te sluiten.</translation> <translation id="7262004276116528033">Deze inlogservice wordt gehost door <ph name="SAML_DOMAIN" />.</translation> <translation id="7264564921322372728"><ph name="BEGIN_PARAGRAPH1" />Probeer de volgende stappen om het probleem op te lossen:
diff --git a/chrome/app/resources/generated_resources_vi.xtb b/chrome/app/resources/generated_resources_vi.xtb index a613f87..043f858 100644 --- a/chrome/app/resources/generated_resources_vi.xtb +++ b/chrome/app/resources/generated_resources_vi.xtb
@@ -163,6 +163,7 @@ <translation id="1153356358378277386">Thiết bị đã ghép nối</translation> <translation id="1153636665119721804">Chương trình Bảo vệ nâng cao của Google</translation> <translation id="1155816283571436363">Đang kết nối với điện thoại của bạn</translation> +<translation id="1158080958325422608">Viết hoa toàn bộ</translation> <translation id="1158238185437008462">Xem kỷ niệm</translation> <translation id="1161575384898972166">Vui lòng đăng nhập vào <ph name="TOKEN_NAME" /> để xuất chứng chỉ ứng dụng khách.</translation> <translation id="116173250649946226">Bạn không thể thay đổi giao diện vì đây là giao diện mặc định do quản trị viên đặt.</translation> @@ -688,12 +689,14 @@ <translation id="1692115862433274081">Sử dụng một tài khoản khác</translation> <translation id="1692118695553449118">Tính năng đồng bộ hóa đang bật</translation> <translation id="1692210323591458290">Tím đậm</translation> +<translation id="1695487653372841667">Bạn có thể kiểm soát những dữ liệu nào được chia sẻ với Google. Bạn có thể thay đổi lựa chọn này bất cứ lúc nào trong phần Cài đặt.</translation> <translation id="169675691788639886">Thiết bị đã định cấu hình máy chủ SSH. Không đăng nhập bằng các tài khoản nhạy cảm.</translation> <translation id="1697150536837697295">Nghệ thuật</translation> <translation id="1697686431566694143">Chỉnh sửa tệp</translation> <translation id="1700079447639026019">Các trang web không bao giờ được dùng cookie</translation> <translation id="1703331064825191675">Không bao giờ phải lo lắng về mật khẩu của bạn</translation> <translation id="1703666494654169921">Không cho phép trang web sử dụng dữ liệu hoặc thiết bị thực tế ảo của bạn</translation> +<translation id="1704097193565924901">Viết hoa chữ cái đầu tiên</translation> <translation id="1704230497453185209">Không cho phép trang web phát âm thanh</translation> <translation id="1704970325597567340">Tính năng kiểm tra an toàn đã chạy vào <ph name="DATE" /></translation> <translation id="1706586824377653884">Do quản trị viên thêm</translation> @@ -3577,6 +3580,7 @@ <translation id="4681453295291708042">Tắt tính năng Chia sẻ lân cận</translation> <translation id="4681930562518940301">Mở &hình ảnh gốc trong thẻ mới</translation> <translation id="4682551433947286597">Hình nền xuất hiện trên màn hình đăng nhập.</translation> +<translation id="4683629100208651599">Viết thường</translation> <translation id="4683947955326903992"><ph name="PERCENTAGE" />% (mặc định)</translation> <translation id="4684427112815847243">Đồng bộ hóa mọi thứ</translation> <translation id="4684471265911890182"><ph name="APP_NAME" /> đang cố truy cập vào máy ảnh này. Hãy tắt nút quyền riêng tư khi dùng máy ảnh để cho phép ứng dụng đó truy cập.</translation> @@ -5969,6 +5973,7 @@ <translation id="7257173066616499747">Mạng Wi-Fi</translation> <translation id="725758059478686223">Dịch vụ in</translation> <translation id="7257666756905341374">Đọc dữ liệu bạn sao chép và dán</translation> +<translation id="7258192266780953209">Chuyển đổi</translation> <translation id="7258225044283673131">Ứng dụng không phản hồi. Hãy chọn "Buộc đóng" để đóng ứng dụng.</translation> <translation id="7262004276116528033">Dịch vụ đăng nhập này do <ph name="SAML_DOMAIN" /> lưu trữ</translation> <translation id="7264564921322372728"><ph name="BEGIN_PARAGRAPH1" />Hãy thử các bước khắc phục sự cố sau:
diff --git a/chrome/app/vector_icons/BUILD.gn b/chrome/app/vector_icons/BUILD.gn index 6bd0cbe8..164747b0 100644 --- a/chrome/app/vector_icons/BUILD.gn +++ b/chrome/app/vector_icons/BUILD.gn
@@ -24,6 +24,7 @@ "autofill/webauthn_dialog_header.icon", "autofill/webauthn_dialog_header_dark.icon", "back_arrow_touch.icon", + "backspace.icon", "blocked_redirect.icon", "bookmarkbar_touch_overflow.icon", "browser_tools.icon",
diff --git a/chrome/app/vector_icons/backspace.icon b/chrome/app/vector_icons/backspace.icon new file mode 100644 index 0000000..a1cba2f --- /dev/null +++ b/chrome/app/vector_icons/backspace.icon
@@ -0,0 +1,30 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +CANVAS_DIMENSIONS, 16, +MOVE_TO, 4.48f, 3, +CUBIC_TO, 4.18f, 3, 3.89f, 3.14f, 3.7f, 3.38f, +LINE_TO, 0, 8, +LINE_TO, 3.7f, 12.62f, +CUBIC_TO, 3.89f, 12.86f, 4.18f, 13, 4.48f, 13, +H_LINE_TO, 12.5f, +CUBIC_TO, 13.33f, 13, 14, 12.33f, 14, 11.5f, +V_LINE_TO, 4.5f, +CUBIC_TO, 14, 3.67f, 13.33f, 3, 12.5f, 3, +H_LINE_TO, 4.48f, +CLOSE, +MOVE_TO, 5, 6, +LINE_TO, 6, 5, +LINE_TO, 8, 7, +LINE_TO, 10, 5, +LINE_TO, 11, 6, +LINE_TO, 9, 8, +LINE_TO, 11, 10, +LINE_TO, 10, 11, +LINE_TO, 8, 9, +LINE_TO, 6, 11, +LINE_TO, 5, 10, +LINE_TO, 7, 8, +LINE_TO, 5, 6, +CLOSE
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index 7298ceb03..bcfa8796 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn
@@ -532,6 +532,8 @@ "file_system_access/file_system_access_tab_helper.h", "file_util_service.cc", "file_util_service.h", + "first_party_sets/first_party_sets_util.cc", + "first_party_sets/first_party_sets_util.h", "flag_descriptions.cc", "flag_descriptions.h", "font_pref_change_notifier.cc", @@ -4017,8 +4019,6 @@ "metrics/desktop_session_duration/touch_mode_stats_tracker.h", "metrics/first_web_contents_profiler.cc", "metrics/first_web_contents_profiler.h", - "metrics/first_web_contents_profiler_base.cc", - "metrics/first_web_contents_profiler_base.h", "metrics/incognito_observer_desktop.cc", "metrics/power/power_metrics_reporter.cc", "metrics/power/power_metrics_reporter.h", @@ -5107,6 +5107,8 @@ "chromeos/policy/dlp/dlp_clipboard_bubble_constants.h", "chromeos/policy/dlp/dlp_clipboard_notifier.cc", "chromeos/policy/dlp/dlp_clipboard_notifier.h", + "chromeos/policy/dlp/dlp_confidential_contents.cc", + "chromeos/policy/dlp/dlp_confidential_contents.h", "chromeos/policy/dlp/dlp_content_manager_lacros.cc", "chromeos/policy/dlp/dlp_content_manager_lacros.h", "chromeos/policy/dlp/dlp_content_observer.cc", @@ -5129,6 +5131,10 @@ "chromeos/policy/dlp/dlp_rules_manager_factory.h", "chromeos/policy/dlp/dlp_rules_manager_impl.cc", "chromeos/policy/dlp/dlp_rules_manager_impl.h", + "chromeos/policy/dlp/dlp_warn_dialog.cc", + "chromeos/policy/dlp/dlp_warn_dialog.h", + "chromeos/policy/dlp/dlp_warn_notifier.cc", + "chromeos/policy/dlp/dlp_warn_notifier.h", "feedback/show_feedback_page_lacros.cc", "first_run/first_run_internal_lacros.cc", "lacros/account_manager/account_cache.cc", @@ -5497,6 +5503,7 @@ "//third_party/wtl", "//ui/aura_extra", "//ui/base:fullscreen_win", + "//ui/color:accent_color_observer", ] allow_circular_includes_from += [ "//chrome/browser/safe_browsing/chrome_cleaner" ]
diff --git a/chrome/browser/app_controller_mac_browsertest.mm b/chrome/browser/app_controller_mac_browsertest.mm index 44410f80..d074d97 100644 --- a/chrome/browser/app_controller_mac_browsertest.mm +++ b/chrome/browser/app_controller_mac_browsertest.mm
@@ -935,8 +935,9 @@ EXPECT_EQ(nullptr, [ac bookmarkMenuBridge]); } +// Disabled because of flakiness. See crbug.com/1278031. IN_PROC_BROWSER_TEST_F(AppControllerMainMenuBrowserTest, - ReloadingDestroyedProfileDoesNotCrash) { + DISABLED_ReloadingDestroyedProfileDoesNotCrash) { ProfileManager* profile_manager = g_browser_process->profile_manager(); AppController* ac = base::mac::ObjCCastStrict<AppController>([NSApp delegate]);
diff --git a/chrome/browser/ash/account_manager/account_apps_availability.cc b/chrome/browser/ash/account_manager/account_apps_availability.cc index c372eda..293cbec 100644 --- a/chrome/browser/ash/account_manager/account_apps_availability.cc +++ b/chrome/browser/ash/account_manager/account_apps_availability.cc
@@ -5,12 +5,184 @@ #include "chrome/browser/ash/account_manager/account_apps_availability.h" #include "ash/constants/ash_features.h" +#include "base/bind.h" #include "base/containers/flat_set.h" #include "base/feature_list.h" +#include "base/logging.h" +#include "components/account_manager_core/account.h" +#include "components/account_manager_core/account_manager_facade.h" +#include "components/account_manager_core/pref_names.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "components/user_manager/user_manager.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +// Structure of `account_manager::prefs::kAccountAppsAvailability`. +// `kAccountAppsAvailability` is a dictionary of dictionaries of the following +// format: +// { +// "gaia_id_1": { "is_available_in_arc": <bool> }, +// "gaia_id_2": { "is_available_in_arc": <bool> }, +// } +// Regular users will always have an entry for the primary account in the +// `kAccountAppsAvailability` pref (so it will never be empty). Active Directory +// users may have no Gaia accounts in-session and therefore may have an empty +// `kAccountAppsAvailability` pref. namespace ash { -AccountAppsAvailability::AccountAppsAvailability() = default; +namespace { + +bool IsPrimaryGaiaAccount(const std::string& gaia_id) { + const user_manager::User* user = + user_manager::UserManager::Get()->GetPrimaryUser(); + DCHECK(user); + return user->GetAccountId().GetAccountType() == AccountType::GOOGLE && + user->GetAccountId().GetGaiaId() == gaia_id; +} + +bool IsActiveDirectoryUser() { + const user_manager::User* user = + user_manager::UserManager::Get()->GetPrimaryUser(); + DCHECK(user); + return user->GetType() == user_manager::USER_TYPE_ACTIVE_DIRECTORY; +} + +bool IsPrefInitialized(PrefService* prefs) { + const base::DictionaryValue* accounts = + prefs->GetDictionary(account_manager::prefs::kAccountAppsAvailability); + return accounts && (accounts->DictSize() > 0 || IsActiveDirectoryUser()); +} + +void CompleteGetAccountsAvailableInArc( + const base::flat_set<std::string>& gaia_ids_in_arc, + base::OnceCallback<void(const base::flat_set<account_manager::Account>&)> + callback, + const std::vector<account_manager::Account>& all_accounts) { + base::flat_set<account_manager::Account> result; + for (const auto& account : all_accounts) { + if (account.key.account_type() != account_manager::AccountType::kGaia) + continue; + + if (gaia_ids_in_arc.contains(account.key.id())) + result.insert(account); + } + + DCHECK_EQ(result.size(), gaia_ids_in_arc.size()); + if (result.size() != gaia_ids_in_arc.size()) { + LOG(ERROR) << "Expected " << gaia_ids_in_arc.size() << " accounts, but " + << result.size() << " accounts were found in Account Manager."; + // TODO(crbug.com/1277453): Repair prefs if this happens. + } + std::move(callback).Run(result); +} + +base::flat_set<std::string> GetGaiaIdsAvailableInArc(PrefService* prefs) { + base::flat_set<std::string> result; + const base::DictionaryValue* accounts = + prefs->GetDictionary(account_manager::prefs::kAccountAppsAvailability); + if (!accounts) { + LOG(ERROR) << "Couldn't find " + << account_manager::prefs::kAccountAppsAvailability + << " dict in prefs"; + return result; + } + // See structure of `accounts` at the top of the file. + for (const auto dict : accounts->DictItems()) { + absl::optional<bool> is_available = + dict.second.FindBoolKey(account_manager::prefs::kIsAvailableInArcKey); + if (!is_available.has_value() || !is_available.value()) + continue; + + result.insert(dict.first); + } + + return result; +} + +// Return `true` if account with `gaia_id` should be available in ARC. +// Return `false` if account with `gaia_id` should not be available in ARC. +// Return `nullopt` if account with `gaia_id` is not in prefs (it can happen if +// `SetIsAccountAvailableInArc` wasn't called for this account yet). +absl::optional<bool> IsAccountAvailableInArc(PrefService* prefs, + const std::string& gaia_id) { + const base::DictionaryValue* accounts = + prefs->GetDictionary(account_manager::prefs::kAccountAppsAvailability); + if (!accounts) { + LOG(ERROR) << "Couldn't find " + << account_manager::prefs::kAccountAppsAvailability + << " dict in prefs"; + return absl::nullopt; + } + // See structure of `accounts` at the top of the file. + const base::Value* account_entry = accounts->FindDictKey(gaia_id); + if (!account_entry) + return absl::nullopt; + + DCHECK(account_entry->is_dict()); + absl::optional<bool> is_available_in_arc = + account_entry->FindBoolKey(account_manager::prefs::kIsAvailableInArcKey); + DCHECK(is_available_in_arc); + // If there is no `is_available_in_arc` key, assume that account is available + // in ARC. + // TODO(crbug.com/1277453): Repair prefs if it happens. + return is_available_in_arc.value_or(true); +} + +void AddAccountToPrefs(PrefService* prefs, + const std::string& gaia_id, + bool is_available_in_arc) { + // Account shouldn't already exist. + DCHECK(!IsAccountAvailableInArc(prefs, gaia_id).has_value()); + + base::Value account_entry(base::Value::Type::DICTIONARY); + account_entry.SetKey(account_manager::prefs::kIsAvailableInArcKey, + base::Value(is_available_in_arc)); + + DictionaryPrefUpdate update(prefs, + account_manager::prefs::kAccountAppsAvailability); + update->SetKey(gaia_id, std::move(account_entry)); +} + +void UpdateAccountInPrefs(PrefService* prefs, + const std::string& gaia_id, + bool is_available_in_arc) { + DictionaryPrefUpdate update(prefs, + account_manager::prefs::kAccountAppsAvailability); + base::Value* account_entry = update->FindDictKey(gaia_id); + DCHECK(account_entry); + + account_entry->SetKey(account_manager::prefs::kIsAvailableInArcKey, + base::Value(is_available_in_arc)); +} + +} // namespace + +AccountAppsAvailability::AccountAppsAvailability( + account_manager::AccountManagerFacade* account_manager_facade, + signin::IdentityManager* identity_manager, + PrefService* prefs) + : account_manager_facade_(account_manager_facade), + identity_manager_(identity_manager), + prefs_(prefs) { + DCHECK(account_manager_facade_); + DCHECK(identity_manager_); + DCHECK(prefs_); + + account_manager_facade_observation_.Observe(account_manager_facade_); + identity_manager_observation_.Observe(identity_manager_); + + if (IsPrefInitialized(prefs_)) { + initialization_state_ = InitializationState::kInitialized; + return; + } + + account_manager_facade_->GetAccounts( + base::BindOnce(&AccountAppsAvailability::InitAccountsAvailableInArcPref, + weak_factory_.GetWeakPtr())); +} + AccountAppsAvailability::~AccountAppsAvailability() = default; // static @@ -20,6 +192,12 @@ base::FeatureList::IsEnabled(chromeos::features::kLacrosSupport); } +// static +void AccountAppsAvailability::RegisterPrefs(PrefRegistrySimple* registry) { + registry->RegisterDictionaryPref( + account_manager::prefs::kAccountAppsAvailability); +} + void AccountAppsAvailability::AddObserver(Observer* observer) { observer_list_.AddObserver(observer); } @@ -31,22 +209,152 @@ void AccountAppsAvailability::SetIsAccountAvailableInArc( const account_manager::Account& account, bool is_available) { - NOTIMPLEMENTED(); + DCHECK_EQ(account.key.account_type(), account_manager::AccountType::kGaia); + + if (!IsInitialized()) { + // Using base::Unretained(this) is fine because `initialization_callbacks_` + // is owned by this. + initialization_callbacks_.push_back( + base::BindOnce(&AccountAppsAvailability::SetIsAccountAvailableInArc, + base::Unretained(this), account, is_available)); + return; + } + + absl::optional<bool> current_status = + IsAccountAvailableInArc(prefs_, account.key.id()); + if (!current_status.has_value()) { + AddAccountToPrefs(prefs_, account.key.id(), is_available); + NotifyObservers(account, is_available); + return; + } + + if (current_status.value() == is_available) + return; + + UpdateAccountInPrefs(prefs_, account.key.id(), is_available); + NotifyObservers(account, is_available); } void AccountAppsAvailability::GetAccountsAvailableInArc( base::OnceCallback<void(const base::flat_set<account_manager::Account>&)> callback) { - NOTIMPLEMENTED(); + if (!IsInitialized()) { + // Using base::Unretained(this) is fine because `initialization_callbacks_` + // is owned by this. + initialization_callbacks_.push_back( + base::BindOnce(&AccountAppsAvailability::GetAccountsAvailableInArc, + base::Unretained(this), std::move(callback))); + return; + } + + account_manager_facade_->GetAccounts( + base::BindOnce(&CompleteGetAccountsAvailableInArc, + GetGaiaIdsAvailableInArc(prefs_), std::move(callback))); } void AccountAppsAvailability::OnRefreshTokenUpdatedForAccount( - const CoreAccountInfo& account_info) {} + const CoreAccountInfo& account_info) { + if (!IsInitialized()) { + // Using base::Unretained(this) is fine because `initialization_callbacks_` + // is owned by this. + initialization_callbacks_.push_back(base::BindOnce( + &AccountAppsAvailability::OnRefreshTokenUpdatedForAccount, + base::Unretained(this), account_info)); + return; + } + + NOTIMPLEMENTED(); +} void AccountAppsAvailability::OnAccountUpserted( - const account_manager::Account& account) {} + const account_manager::Account& account) { + if (initialization_state_ == InitializationState::kInitialized || + initialization_state_ == InitializationState::kInProgress) + return; + + // Initialize the prefs list: + account_manager_facade_->GetAccounts( + base::BindOnce(&AccountAppsAvailability::InitAccountsAvailableInArcPref, + weak_factory_.GetWeakPtr())); +} void AccountAppsAvailability::OnAccountRemoved( - const account_manager::Account& account) {} + const account_manager::Account& account) { + if (!IsInitialized()) { + // Using base::Unretained(this) is fine because `initialization_callbacks_` + // is owned by this. + initialization_callbacks_.push_back( + base::BindOnce(&AccountAppsAvailability::OnAccountRemoved, + base::Unretained(this), account)); + return; + } + + NOTIMPLEMENTED(); +} + +bool AccountAppsAvailability::IsInitialized() const { + return initialization_state_ == InitializationState::kInitialized; +} + +void AccountAppsAvailability::InitAccountsAvailableInArcPref( + const std::vector<account_manager::Account>& accounts) { + if (initialization_state_ != InitializationState::kUninitialized) + return; + + // If there are no accounts in Account Manager at the moment, + // `OnAccountUpserted` will be called when the primary account is added. + if (accounts.size() == 0) + return; + + initialization_state_ = InitializationState::kInProgress; + + prefs_->Set(account_manager::prefs::kAccountAppsAvailability, + base::Value(base::Value::Type::DICTIONARY)); + + DictionaryPrefUpdate update(prefs_, + account_manager::prefs::kAccountAppsAvailability); + DCHECK(update->DictEmpty()); + + // See structure of `update` dictionary at the top of the file. + for (const auto& account : accounts) { + if (account.key.account_type() != account_manager::AccountType::kGaia) + continue; + + base::Value account_entry(base::Value::Type::DICTIONARY); + account_entry.SetKey(account_manager::prefs::kIsAvailableInArcKey, + base::Value(true)); + + // Key: `account.key.id()` = Gaia ID + // Value: { "is_available_in_arc": true } + update->SetKey(account.key.id(), std::move(account_entry)); + } + + if (!IsActiveDirectoryUser()) { + // If user type is not active directory, we expect to have at least primary + // account in the list. + DCHECK(!update->DictEmpty()); + } + + initialization_state_ = InitializationState::kInitialized; + + for (auto& callback : initialization_callbacks_) + std::move(callback).Run(); + initialization_callbacks_.clear(); +} + +void AccountAppsAvailability::NotifyObservers( + const account_manager::Account& account, + bool is_available_in_arc) { + if (is_available_in_arc) { + for (auto& observer : observer_list_) { + observer.OnAccountAvailableInArc(account); + } + return; + } + + for (auto& observer : observer_list_) { + observer.OnAccountUnavailableInArc(account); + } +} } // namespace ash
diff --git a/chrome/browser/ash/account_manager/account_apps_availability.h b/chrome/browser/ash/account_manager/account_apps_availability.h index 36546d79..6386e7e 100644 --- a/chrome/browser/ash/account_manager/account_apps_availability.h +++ b/chrome/browser/ash/account_manager/account_apps_availability.h
@@ -7,9 +7,11 @@ #include "base/containers/flat_set.h" #include "base/observer_list.h" +#include "base/scoped_observation.h" #include "components/account_manager_core/account.h" #include "components/account_manager_core/account_manager_facade.h" #include "components/keyed_service/core/keyed_service.h" +#include "components/prefs/pref_registry_simple.h" #include "components/signin/public/identity_manager/identity_manager.h" namespace ash { @@ -48,7 +50,12 @@ const account_manager::Account& account) {} }; - AccountAppsAvailability(); + // The parameters are not owned pointers, and should outlive this class + // instance. + AccountAppsAvailability( + account_manager::AccountManagerFacade* account_manager_facade, + signin::IdentityManager* identity_manager, + PrefService* prefs); ~AccountAppsAvailability() override; AccountAppsAvailability(const AccountAppsAvailability&) = delete; @@ -58,6 +65,8 @@ // enabled. static bool IsArcAccountRestrictionsEnabled(); + static void RegisterPrefs(PrefRegistrySimple* registry); + // Registers an observer. void AddObserver(Observer* observer); // Unregisters an observer that was registered using AddObserver. @@ -70,11 +79,22 @@ // Calls the `callback` with the set of accounts that should be // available in ARC. + // If the class is not initialized yet (`IsInitialized()` is `false`), waits + // for initialization to complete. void GetAccountsAvailableInArc( base::OnceCallback<void(const base::flat_set<account_manager::Account>&)> callback); + // Returns `true` if the class is initialized. + bool IsInitialized() const; + private: + enum InitializationState { + kUninitialized, + kInProgress, + kInitialized, + }; + // `IdentityManager::Observer`: void OnRefreshTokenUpdatedForAccount( const CoreAccountInfo& account_info) override; @@ -83,7 +103,43 @@ void OnAccountUpserted(const account_manager::Account& account) override; void OnAccountRemoved(const account_manager::Account& account) override; + // Initialize the prefs: add all Gaia accounts from Account Manager with + // is_available_in_arc=true. + void InitAccountsAvailableInArcPref( + const std::vector<account_manager::Account>& accounts); + + // Call `OnAccountAvailableInArc` if `is_available_in_arc` is `true`. + // Otherwise call `OnAccountUnavailableInArc`. + void NotifyObservers(const account_manager::Account& account, + bool is_available_in_arc); + + InitializationState initialization_state_ = + InitializationState::kUninitialized; + + // Callbacks waiting on class initialization. + std::vector<base::OnceClosure> initialization_callbacks_; + + // Non-owning pointers: + account_manager::AccountManagerFacade* const account_manager_facade_; + signin::IdentityManager* const identity_manager_; + PrefService* const prefs_; + + // A list of observers registered via `AddObserver`. base::ObserverList<Observer> observer_list_; + + // An observer for `IdentityManager`. Automatically deregisters when + // `this` is destructed. + base::ScopedObservation<signin::IdentityManager, + signin::IdentityManager::Observer> + identity_manager_observation_{this}; + + // An observer for `AccountManagerFacade`. Automatically deregisters when + // `this` is destructed. + base::ScopedObservation<account_manager::AccountManagerFacade, + account_manager::AccountManagerFacade::Observer> + account_manager_facade_observation_{this}; + + base::WeakPtrFactory<AccountAppsAvailability> weak_factory_{this}; }; } // namespace ash
diff --git a/chrome/browser/ash/account_manager/account_apps_availability_factory.cc b/chrome/browser/ash/account_manager/account_apps_availability_factory.cc index 03c90cb..a7618e9 100644 --- a/chrome/browser/ash/account_manager/account_apps_availability_factory.cc +++ b/chrome/browser/ash/account_manager/account_apps_availability_factory.cc
@@ -4,8 +4,12 @@ #include "chrome/browser/ash/account_manager/account_apps_availability_factory.h" +#include "base/files/file_path.h" #include "chrome/browser/ash/account_manager/account_apps_availability.h" +#include "chrome/browser/ash/account_manager/account_manager_util.h" #include "chrome/browser/profiles/profile.h" +#include "chrome/browser/signin/identity_manager_factory.h" +#include "components/account_manager_core/chromeos/account_manager_facade_factory.h" #include "components/keyed_service/content/browser_context_dependency_manager.h" #include "components/keyed_service/content/browser_context_keyed_service_factory.h" #include "components/keyed_service/core/keyed_service.h" @@ -29,13 +33,25 @@ AccountAppsAvailabilityFactory::AccountAppsAvailabilityFactory() : BrowserContextKeyedServiceFactory( "AccountAppsAvailability", - BrowserContextDependencyManager::GetInstance()) {} + BrowserContextDependencyManager::GetInstance()) { + DependsOn(IdentityManagerFactory::GetInstance()); +} AccountAppsAvailabilityFactory::~AccountAppsAvailabilityFactory() = default; KeyedService* AccountAppsAvailabilityFactory::BuildServiceInstanceFor( content::BrowserContext* context) const { - return new AccountAppsAvailability(); + Profile* profile = Profile::FromBrowserContext(context); + DCHECK(profile); + if (!IsAccountManagerAvailable(profile)) + return nullptr; + + if (!AccountAppsAvailability::IsArcAccountRestrictionsEnabled()) + return nullptr; + + return new AccountAppsAvailability( + ::GetAccountManagerFacade(profile->GetPath().value()), + IdentityManagerFactory::GetForProfile(profile), profile->GetPrefs()); } bool AccountAppsAvailabilityFactory::ServiceIsCreatedWithBrowserContext()
diff --git a/chrome/browser/ash/account_manager/account_apps_availability_unittest.cc b/chrome/browser/ash/account_manager/account_apps_availability_unittest.cc new file mode 100644 index 0000000..2de4b2c0 --- /dev/null +++ b/chrome/browser/ash/account_manager/account_apps_availability_unittest.cc
@@ -0,0 +1,283 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ash/account_manager/account_apps_availability.h" + +#include <memory> + +#include "ash/constants/ash_features.h" +#include "base/test/bind.h" +#include "base/test/scoped_feature_list.h" +#include "base/test/task_environment.h" +#include "components/account_manager_core/account.h" +#include "components/account_manager_core/account_manager_facade.h" +#include "components/account_manager_core/mock_account_manager_facade.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/testing_pref_service.h" +#include "components/signin/public/identity_manager/identity_test_environment.h" +#include "components/signin/public/identity_manager/identity_test_utils.h" +#include "components/user_manager/fake_user_manager.h" +#include "components/user_manager/scoped_user_manager.h" +#include "components/user_manager/user_manager_base.h" +#include "google_apis/gaia/oauth2_access_token_fetcher.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::Contains; + +namespace ash { + +namespace { + +constexpr char kPrimaryAccountEmail[] = "primaryaccount@gmail.com"; +constexpr char kSecondaryAccount1Email[] = "secondaryAccount1@gmail.com"; +constexpr char kSecondaryAccount2Email[] = "secondaryAccount2@gmail.com"; + +account_manager::Account CreateAccount(const std::string& email, + const std::string& gaia_id) { + account_manager::AccountKey key(gaia_id, + ::account_manager::AccountType::kGaia); + return {key, email}; +} + +class MockObserver : public AccountAppsAvailability::Observer { + public: + MockObserver() = default; + ~MockObserver() override = default; + + MOCK_METHOD(void, + OnAccountAvailableInArc, + (const account_manager::Account&), + (override)); + MOCK_METHOD(void, + OnAccountUnavailableInArc, + (const account_manager::Account&), + (override)); +}; + +base::flat_set<account_manager::Account> GetAccountsAvailableInArcSync( + AccountAppsAvailability* availability) { + base::flat_set<account_manager::Account> result; + base::RunLoop run_loop; + availability->GetAccountsAvailableInArc(base::BindLambdaForTesting( + [&result, + &run_loop](const base::flat_set<account_manager::Account>& accounts) { + result = accounts; + run_loop.Quit(); + })); + run_loop.Run(); + return result; +} + +MATCHER_P(AccountEqual, other, "") { + return arg.key == other.key && arg.raw_email == other.raw_email; +} + +} // namespace + +class AccountAppsAvailabilityTest : public testing::Test { + protected: + AccountAppsAvailabilityTest() = default; + AccountAppsAvailabilityTest(const AccountAppsAvailabilityTest&) = delete; + AccountAppsAvailabilityTest& operator=(const AccountAppsAvailabilityTest&) = + delete; + ~AccountAppsAvailabilityTest() override = default; + + void SetUp() override { + identity_test_env()->EnableRemovalOfExtendedAccountInfo(); + pref_service_ = std::make_unique<TestingPrefServiceSimple>(); + AccountAppsAvailability::RegisterPrefs(pref_service_->registry()); + + auto fake_user_manager = std::make_unique<user_manager::FakeUserManager>(); + fake_user_manager_ = new user_manager::FakeUserManager(); + scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>( + base::WrapUnique(fake_user_manager_)); + + primary_account_ = identity_test_env()->MakePrimaryAccountAvailable( + kPrimaryAccountEmail, signin::ConsentLevel::kSignin); + LoginUserSession(); + } + + void TearDown() override { pref_service_.reset(); } + + std::unique_ptr<AccountAppsAvailability> CreateAccountAppsAvailability() { + return std::make_unique<AccountAppsAvailability>( + GetAccountManagerFacade(identity_test_env()->identity_manager()), + identity_test_env()->identity_manager(), pref_service_.get()); + } + + signin::IdentityTestEnvironment* identity_test_env() { + return &identity_test_env_; + } + + AccountInfo* primary_account_info() { return &primary_account_; } + + private: + void LoginUserSession() { + auto account_id = AccountId::FromUserEmailGaiaId(primary_account_.email, + primary_account_.gaia); + fake_user_manager_->AddUser(account_id); + fake_user_manager_->UserLoggedIn( + account_id, account_id.GetUserEmail() + "-hash", false, false); + } + + base::test::SingleThreadTaskEnvironment task_environment_; + signin::IdentityTestEnvironment identity_test_env_; + std::unique_ptr<TestingPrefServiceSimple> pref_service_; + AccountInfo primary_account_; + // Owned by `scoped_user_manager_`. + user_manager::FakeUserManager* fake_user_manager_ = nullptr; + std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_; +}; + +TEST_F(AccountAppsAvailabilityTest, InitializationPrefIsPersistedOnDisk) { + auto account_apps_availability = CreateAccountAppsAvailability(); + EXPECT_FALSE(account_apps_availability->IsInitialized()); + // Wait for `GetAccounts` call to finish. + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(account_apps_availability->IsInitialized()); + account_apps_availability.reset(); + + account_apps_availability = CreateAccountAppsAvailability(); + EXPECT_TRUE(account_apps_availability->IsInitialized()); +} + +TEST_F(AccountAppsAvailabilityTest, CallsBeforeInitialization) { + const AccountInfo secondary_account_info = + identity_test_env()->MakeAccountAvailable(kSecondaryAccount1Email); + const account_manager::Account primary_account = + CreateAccount(kPrimaryAccountEmail, primary_account_info()->gaia); + const account_manager::Account secondary_account = + CreateAccount(kSecondaryAccount1Email, secondary_account_info.gaia); + + auto account_apps_availability = CreateAccountAppsAvailability(); + EXPECT_FALSE(account_apps_availability->IsInitialized()); + // Make secondary account unavailable in ARC. + account_apps_availability->SetIsAccountAvailableInArc(secondary_account, + false); + base::flat_set<account_manager::Account> result; + base::RunLoop run_loop; + account_apps_availability->GetAccountsAvailableInArc( + base::BindLambdaForTesting( + [&result, &run_loop]( + const base::flat_set<account_manager::Account>& accounts) { + result = accounts; + run_loop.Quit(); + })); + + // Wait for initialization and for `GetAccountsAvailableInArc` call + // completion. + run_loop.Run(); + EXPECT_TRUE(account_apps_availability->IsInitialized()); + + // Only primary account is available, secondary account was removed. + EXPECT_EQ(result.size(), 1ul); + EXPECT_THAT(result, Contains(AccountEqual(primary_account))); +} + +TEST_F(AccountAppsAvailabilityTest, GetAccountsAvailableInArc) { + const AccountInfo secondary_account_info = + identity_test_env()->MakeAccountAvailable(kSecondaryAccount1Email); + const account_manager::Account primary_account = + CreateAccount(kPrimaryAccountEmail, primary_account_info()->gaia); + const account_manager::Account secondary_account = + CreateAccount(kSecondaryAccount1Email, secondary_account_info.gaia); + + auto account_apps_availability = CreateAccountAppsAvailability(); + // Wait for initialization to finish. + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(account_apps_availability->IsInitialized()); + // All accounts are available after initialization: + auto accounts = + GetAccountsAvailableInArcSync(account_apps_availability.get()); + EXPECT_EQ(accounts.size(), 2ul); + EXPECT_THAT(accounts, Contains(AccountEqual(primary_account))); + EXPECT_THAT(accounts, Contains(AccountEqual(secondary_account))); + + // Remove an account from ARC. + account_apps_availability->SetIsAccountAvailableInArc(secondary_account, + false); + auto accounts_1 = + GetAccountsAvailableInArcSync(account_apps_availability.get()); + EXPECT_EQ(accounts_1.size(), 1ul); + EXPECT_THAT(accounts_1, Contains(AccountEqual(primary_account))); +} + +TEST_F(AccountAppsAvailabilityTest, SetIsAccountAvailableInArc) { + const AccountInfo secondary_account_1_info = + identity_test_env()->MakeAccountAvailable(kSecondaryAccount1Email); + const account_manager::Account primary_account = + CreateAccount(kPrimaryAccountEmail, primary_account_info()->gaia); + const account_manager::Account secondary_account_1 = + CreateAccount(kSecondaryAccount1Email, secondary_account_1_info.gaia); + + auto account_apps_availability = CreateAccountAppsAvailability(); + // Wait for initialization to finish. + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(account_apps_availability->IsInitialized()); + // All accounts are available after initialization: + { + auto accounts = + GetAccountsAvailableInArcSync(account_apps_availability.get()); + EXPECT_EQ(accounts.size(), 2ul); + EXPECT_THAT(accounts, Contains(AccountEqual(primary_account))); + EXPECT_THAT(accounts, Contains(AccountEqual(secondary_account_1))); + } + + // Remove an account from ARC. + account_apps_availability->SetIsAccountAvailableInArc(secondary_account_1, + false); + { + auto accounts = + GetAccountsAvailableInArcSync(account_apps_availability.get()); + EXPECT_EQ(accounts.size(), 1l); + EXPECT_THAT(accounts, Contains(AccountEqual(primary_account))); + } + + const AccountInfo secondary_account_2_info = + identity_test_env()->MakeAccountAvailable(kSecondaryAccount2Email); + const account_manager::Account secondary_account_2 = + CreateAccount(kSecondaryAccount2Email, secondary_account_2_info.gaia); + // Add the account to ARC. + account_apps_availability->SetIsAccountAvailableInArc(secondary_account_2, + true); + { + auto accounts = + GetAccountsAvailableInArcSync(account_apps_availability.get()); + EXPECT_EQ(accounts.size(), 2ul); + EXPECT_THAT(accounts, Contains(AccountEqual(primary_account))); + EXPECT_THAT(accounts, Contains(AccountEqual(secondary_account_2))); + } + + // Remove the first account again. + account_apps_availability->SetIsAccountAvailableInArc(secondary_account_1, + false); + // Add the second account again. + account_apps_availability->SetIsAccountAvailableInArc(secondary_account_2, + true); + { + auto accounts = + GetAccountsAvailableInArcSync(account_apps_availability.get()); + EXPECT_EQ(accounts.size(), 2ul); + EXPECT_THAT(accounts, Contains(AccountEqual(primary_account))); + EXPECT_THAT(accounts, Contains(AccountEqual(secondary_account_2))); + } + + // Add the first account back. + account_apps_availability->SetIsAccountAvailableInArc(secondary_account_1, + true); + // Remove the second account. + account_apps_availability->SetIsAccountAvailableInArc(secondary_account_2, + false); + { + auto accounts = + GetAccountsAvailableInArcSync(account_apps_availability.get()); + EXPECT_EQ(accounts.size(), 2ul); + EXPECT_THAT(accounts, Contains(AccountEqual(primary_account))); + EXPECT_THAT(accounts, Contains(AccountEqual(secondary_account_1))); + } +} + +} // namespace ash
diff --git a/chrome/browser/ash/arc/accessibility/arc_accessibility_helper_bridge.cc b/chrome/browser/ash/arc/accessibility/arc_accessibility_helper_bridge.cc index e18eca52..a955e56 100644 --- a/chrome/browser/ash/arc/accessibility/arc_accessibility_helper_bridge.cc +++ b/chrome/browser/ash/arc/accessibility/arc_accessibility_helper_bridge.cc
@@ -75,12 +75,6 @@ return false; } -float DeviceScaleFactorFromWindow(aura::Window* window) { - if (!window || !window->GetToplevelWindow()) - return 1.0; - return window->GetToplevelWindow()->layer()->device_scale_factor(); -} - void DispatchFocusChange(arc::mojom::AccessibilityNodeInfoData* node_data, Profile* profile) { AccessibilityManager* accessibility_manager = AccessibilityManager::Get();
diff --git a/chrome/browser/ash/arc/process/arc_process.cc b/chrome/browser/ash/arc/process/arc_process.cc index f1d7d4c..664d9fd 100644 --- a/chrome/browser/ash/arc/process/arc_process.cc +++ b/chrome/browser/ash/arc/process/arc_process.cc
@@ -56,23 +56,6 @@ } } -bool IsBackgroundState(ProcessState state) { - switch (state) { - case ProcessState::TRANSIENT_BACKGROUND: - case ProcessState::BACKUP: - case ProcessState::SERVICE: - case ProcessState::RECEIVER: - case ProcessState::TOP_SLEEPING: - case ProcessState::HEAVY_WEIGHT: - case ProcessState::HOME: - case ProcessState::LAST_ACTIVITY: - case ProcessState::CACHED_ACTIVITY: - return true; - default: - return false; - } -} - bool IsCachedState(ProcessState state) { switch (state) { case ProcessState::CACHED_ACTIVITY_CLIENT:
diff --git a/chrome/browser/ash/crosapi/url_handler_ash.cc b/chrome/browser/ash/crosapi/url_handler_ash.cc index 615da82e8..7d48990 100644 --- a/chrome/browser/ash/crosapi/url_handler_ash.cc +++ b/chrome/browser/ash/crosapi/url_handler_ash.cc
@@ -6,6 +6,7 @@ #include "chrome/browser/apps/app_service/launch_utils.h" #include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/settings_window_manager_chromeos.h" #include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h" #include "chrome/browser/ui/webui/chrome_web_ui_controller_factory.h" @@ -34,6 +35,14 @@ return; } + Browser* browser = web_app::FindSystemWebAppBrowser(profile, app_type, + Browser::TYPE_APP, gurl); + if (browser) { + // If there is a matching browser we simply activate it and be done! + browser->window()->Activate(); + return; + } + web_app::SystemAppLaunchParams params; params.url = gurl; int64_t display_id =
diff --git a/chrome/browser/ash/login/lock/screen_locker_browsertest.cc b/chrome/browser/ash/login/lock/screen_locker_browsertest.cc index 99c9dd28..51620bc 100644 --- a/chrome/browser/ash/login/lock/screen_locker_browsertest.cc +++ b/chrome/browser/ash/login/lock/screen_locker_browsertest.cc
@@ -89,7 +89,7 @@ bool IsFullscreenNotificationShowing() { ash::FullscreenController* ash_fullscreen_controller = - Shell::Get()->session_controller()->fullscreen_controller_for_test(); + Shell::Get()->session_controller()->fullscreen_controller(); return ash_fullscreen_controller->bubble_for_test() && ash_fullscreen_controller->bubble_for_test()->widget_for_test() && ash_fullscreen_controller->bubble_for_test()
diff --git a/chrome/browser/ash/policy/dlp/dlp_content_manager.cc b/chrome/browser/ash/policy/dlp/dlp_content_manager.cc index f32f0c3..87453c87 100644 --- a/chrome/browser/ash/policy/dlp/dlp_content_manager.cc +++ b/chrome/browser/ash/policy/dlp/dlp_content_manager.cc
@@ -15,14 +15,14 @@ #include "base/containers/contains.h" #include "base/containers/cxx20_erase.h" #include "base/threading/thread_task_runner_handle.h" -#include "chrome/browser/ash/policy/dlp/dlp_confidential_contents.h" #include "chrome/browser/ash/policy/dlp/dlp_notification_helper.h" -#include "chrome/browser/ash/policy/dlp/dlp_warn_notifier.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.h" #include "chrome/browser/chromeos/policy/dlp/dlp_content_restriction_set.h" #include "chrome/browser/chromeos/policy/dlp/dlp_histogram_helper.h" #include "chrome/browser/chromeos/policy/dlp/dlp_reporting_manager.h" #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h" #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager_factory.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_warn_notifier.h" #include "chrome/browser/ui/ash/capture_mode/chrome_capture_mode_delegate.h" #include "content/public/browser/visibility.h" #include "content/public/browser/web_contents.h"
diff --git a/chrome/browser/ash/policy/dlp/dlp_content_manager.h b/chrome/browser/ash/policy/dlp/dlp_content_manager.h index 3b29cdc..8d13f88 100644 --- a/chrome/browser/ash/policy/dlp/dlp_content_manager.h +++ b/chrome/browser/ash/policy/dlp/dlp_content_manager.h
@@ -14,12 +14,12 @@ #include "base/containers/flat_map.h" #include "base/gtest_prod_util.h" #include "base/time/time.h" -#include "chrome/browser/ash/policy/dlp/dlp_confidential_contents.h" -#include "chrome/browser/ash/policy/dlp/dlp_warn_dialog.h" #include "chrome/browser/ash/policy/dlp/dlp_window_observer.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.h" #include "chrome/browser/chromeos/policy/dlp/dlp_content_observer.h" #include "chrome/browser/chromeos/policy/dlp/dlp_content_restriction_set.h" #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_warn_dialog.h" #include "chrome/browser/ui/ash/screenshot_area.h" #include "content/public/browser/desktop_media_id.h" #include "content/public/browser/media_stream_request.h"
diff --git a/chrome/browser/ash/policy/dlp/dlp_content_manager_test_helper.cc b/chrome/browser/ash/policy/dlp/dlp_content_manager_test_helper.cc index 8dfa4b94..1f0d9de 100644 --- a/chrome/browser/ash/policy/dlp/dlp_content_manager_test_helper.cc +++ b/chrome/browser/ash/policy/dlp/dlp_content_manager_test_helper.cc
@@ -7,8 +7,8 @@ #include <memory> #include "chrome/browser/ash/policy/dlp/dlp_content_manager.h" -#include "chrome/browser/ash/policy/dlp/dlp_warn_notifier.h" #include "chrome/browser/chromeos/policy/dlp/dlp_reporting_manager.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_warn_notifier.h" namespace policy {
diff --git a/chrome/browser/ash/policy/dlp/dlp_content_manager_test_helper.h b/chrome/browser/ash/policy/dlp/dlp_content_manager_test_helper.h index 47ecf77..8ce763ea 100644 --- a/chrome/browser/ash/policy/dlp/dlp_content_manager_test_helper.h +++ b/chrome/browser/ash/policy/dlp/dlp_content_manager_test_helper.h
@@ -7,8 +7,8 @@ #include <memory> #include "base/time/time.h" -#include "chrome/browser/ash/policy/dlp/dlp_confidential_contents.h" #include "chrome/browser/ash/policy/dlp/dlp_content_manager.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.h" #include "chrome/browser/chromeos/policy/dlp/dlp_content_restriction_set.h" #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h"
diff --git a/chrome/browser/ash/policy/dlp/dlp_content_manager_unittest.cc b/chrome/browser/ash/policy/dlp/dlp_content_manager_unittest.cc index c23503e7..e1496fa 100644 --- a/chrome/browser/ash/policy/dlp/dlp_content_manager_unittest.cc +++ b/chrome/browser/ash/policy/dlp/dlp_content_manager_unittest.cc
@@ -15,16 +15,16 @@ #include "base/test/task_environment.h" #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h" #include "chrome/browser/ash/policy/dlp/dlp_content_manager_test_helper.h" -#include "chrome/browser/ash/policy/dlp/dlp_warn_dialog.h" -#include "chrome/browser/ash/policy/dlp/dlp_warn_notifier.h" -#include "chrome/browser/ash/policy/dlp/mock_dlp_warn_notifier.h" #include "chrome/browser/chromeos/policy/dlp/dlp_histogram_helper.h" #include "chrome/browser/chromeos/policy/dlp/dlp_policy_event.pb.h" #include "chrome/browser/chromeos/policy/dlp/dlp_reporting_manager.h" #include "chrome/browser/chromeos/policy/dlp/dlp_reporting_manager_test_helper.h" #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h" #include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager_factory.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_warn_dialog.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_warn_notifier.h" #include "chrome/browser/chromeos/policy/dlp/mock_dlp_rules_manager.h" +#include "chrome/browser/chromeos/policy/dlp/mock_dlp_warn_notifier.h" #include "chrome/browser/notifications/notification_display_service_tester.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/ui/ash/screenshot_area.h"
diff --git a/chrome/browser/autocomplete/keyword_extensions_delegate_impl.cc b/chrome/browser/autocomplete/keyword_extensions_delegate_impl.cc index 1f00acd..fc6995f 100644 --- a/chrome/browser/autocomplete/keyword_extensions_delegate_impl.cc +++ b/chrome/browser/autocomplete/keyword_extensions_delegate_impl.cc
@@ -39,12 +39,11 @@ this, extensions::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED, content::Source<Profile>(profile_->GetOriginalProfile())); - registrar_.Add(this, extensions::NOTIFICATION_EXTENSION_OMNIBOX_INPUT_ENTERED, - content::Source<Profile>(profile_.get())); + + omnibox_observation_.Observe(OmniboxWatcher::GetForBrowserContext(profile_)); } -KeywordExtensionsDelegateImpl::~KeywordExtensionsDelegateImpl() { -} +KeywordExtensionsDelegateImpl::~KeywordExtensionsDelegateImpl() = default; void KeywordExtensionsDelegateImpl::DeleteSuggestion( const TemplateURL* template_url, @@ -128,6 +127,14 @@ } } +// Input has been accepted, so we're done with this input session. Ensure +// we don't send the OnInputCancelled event, or handle any more stray +// suggestions_ready events. +void KeywordExtensionsDelegateImpl::OnOmniboxInputEntered() { + current_keyword_extension_id_.clear(); + IncrementInputId(); +} + void KeywordExtensionsDelegateImpl::Observe( int type, const content::NotificationSource& source, @@ -136,14 +143,6 @@ const AutocompleteInput& input = extension_suggest_last_input_; switch (type) { - case extensions::NOTIFICATION_EXTENSION_OMNIBOX_INPUT_ENTERED: - // Input has been accepted, so we're done with this input session. Ensure - // we don't send the OnInputCancelled event, or handle any more stray - // suggestions_ready events. - current_keyword_extension_id_.clear(); - IncrementInputId(); - return; - case extensions::NOTIFICATION_EXTENSION_OMNIBOX_DEFAULT_SUGGESTION_CHANGED : { DCHECK(model);
diff --git a/chrome/browser/autocomplete/keyword_extensions_delegate_impl.h b/chrome/browser/autocomplete/keyword_extensions_delegate_impl.h index 9d376ee..84c89f8 100644 --- a/chrome/browser/autocomplete/keyword_extensions_delegate_impl.h +++ b/chrome/browser/autocomplete/keyword_extensions_delegate_impl.h
@@ -11,11 +11,13 @@ #include <vector> #include "base/memory/raw_ptr.h" +#include "base/scoped_observation.h" #include "components/omnibox/browser/autocomplete_input.h" #include "components/omnibox/browser/autocomplete_match.h" #include "components/omnibox/browser/autocomplete_provider_listener.h" #include "components/omnibox/browser/keyword_extensions_delegate.h" #include "components/omnibox/browser/keyword_provider.h" +#include "components/omnibox/browser/omnibox_watcher.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" #include "extensions/buildflags/buildflags.h" @@ -27,7 +29,8 @@ class Profile; class KeywordExtensionsDelegateImpl : public KeywordExtensionsDelegate, - public content::NotificationObserver { + public content::NotificationObserver, + public OmniboxWatcher::Observer { public: KeywordExtensionsDelegateImpl(Profile* profile, KeywordProvider* provider); @@ -52,6 +55,9 @@ void EnterExtensionKeywordMode(const std::string& extension_id) override; void MaybeEndExtensionKeywordMode() override; + // OmniboxWatcher::Observer: + void OnOmniboxInputEntered() override; + // content::NotificationObserver: void Observe(int type, const content::NotificationSource& source, @@ -92,6 +98,9 @@ // We need our input IDs to be unique across all profiles, so we keep a global // UID that each provider uses. static int global_input_uid_; + + base::ScopedObservation<OmniboxWatcher, OmniboxWatcher::Observer> + omnibox_observation_{this}; }; #endif // CHROME_BROWSER_AUTOCOMPLETE_KEYWORD_EXTENSIONS_DELEGATE_IMPL_H_
diff --git a/chrome/browser/autofill/form_structure_browsertest.cc b/chrome/browser/autofill/form_structure_browsertest.cc index d30aa7ec..354bfaa 100644 --- a/chrome/browser/autofill/form_structure_browsertest.cc +++ b/chrome/browser/autofill/form_structure_browsertest.cc
@@ -196,13 +196,15 @@ features::kAutofillParsingPatternsLanguageDetection, // TODO(crbug/1165780): Remove once shared labels are launched. features::kAutofillEnableSupportForParsingWithSharedLabels, + // TODO(crbug.com/1277480): Remove once launched. + features::kAutofillEnableNameSurenameParsing, // TODO(crbug/1190334): Remove once launched. features::kAutofillParseMerchantPromoCodeFields}, // Disabled {}); } -FormStructureBrowserTest::~FormStructureBrowserTest() {} +FormStructureBrowserTest::~FormStructureBrowserTest() = default; void FormStructureBrowserTest::SetUpCommandLine( base::CommandLine* command_line) {
diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc index 98556e45..a0e5e4fd 100644 --- a/chrome/browser/chrome_browser_main.cc +++ b/chrome/browser/chrome_browser_main.cc
@@ -62,6 +62,7 @@ #include "chrome/browser/chrome_browser_main_extra_parts.h" #include "chrome/browser/component_updater/registration.h" #include "chrome/browser/defaults.h" +#include "chrome/browser/first_party_sets/first_party_sets_util.h" #include "chrome/browser/first_run/first_run.h" #include "chrome/browser/language/url_language_histogram_factory.h" #include "chrome/browser/lifetime/application_lifetime.h" @@ -158,6 +159,7 @@ #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" +#include "content/public/browser/network_service_instance.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_service.h" @@ -180,6 +182,7 @@ #include "ppapi/buildflags/buildflags.h" #include "printing/buildflags/buildflags.h" #include "rlz/buildflags/buildflags.h" +#include "services/network/public/mojom/network_service.mojom.h" #include "services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/layout.h" @@ -1645,6 +1648,21 @@ speech::SodaInstaller::GetInstance()->Init(profile_->GetPrefs(), browser_process_->local_state()); #endif // !defined(OS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH) + + // Only read and update the persisted sets when First-Party Sets component + // will be installed. + if (base::FeatureList::IsEnabled(net::features::kFirstPartySets)) { + FirstPartySetsUtil::GetInstance()->SendAndUpdatePersistedSets( + user_data_dir_, + /*send_sets=*/ + base::BindOnce( + [](base::OnceCallback<void(const std::string&)> callback, + const std::string& sets) { + content::GetNetworkService() + ->SetPersistedFirstPartySetsAndGetCurrentSets( + sets, std::move(callback)); + })); + } } #if BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn index 560b0bc..bf0c750 100644 --- a/chrome/browser/chromeos/BUILD.gn +++ b/chrome/browser/chromeos/BUILD.gn
@@ -2449,16 +2449,10 @@ "../ash/policy/display/display_rotation_default_handler.h", "../ash/policy/display/display_settings_handler.cc", "../ash/policy/display/display_settings_handler.h", - "../ash/policy/dlp/dlp_confidential_contents.cc", - "../ash/policy/dlp/dlp_confidential_contents.h", "../ash/policy/dlp/dlp_content_manager.cc", "../ash/policy/dlp/dlp_content_manager.h", "../ash/policy/dlp/dlp_notification_helper.cc", "../ash/policy/dlp/dlp_notification_helper.h", - "../ash/policy/dlp/dlp_warn_dialog.cc", - "../ash/policy/dlp/dlp_warn_dialog.h", - "../ash/policy/dlp/dlp_warn_notifier.cc", - "../ash/policy/dlp/dlp_warn_notifier.h", "../ash/policy/dlp/dlp_window_observer.cc", "../ash/policy/dlp/dlp_window_observer.h", "../ash/policy/enrollment/account_status_check_fetcher.cc", @@ -3320,6 +3314,8 @@ "policy/dlp/dlp_clipboard_bubble_constants.h", "policy/dlp/dlp_clipboard_notifier.cc", "policy/dlp/dlp_clipboard_notifier.h", + "policy/dlp/dlp_confidential_contents.cc", + "policy/dlp/dlp_confidential_contents.h", "policy/dlp/dlp_content_observer.cc", "policy/dlp/dlp_content_observer.h", "policy/dlp/dlp_content_restriction_set.cc", @@ -3340,6 +3336,10 @@ "policy/dlp/dlp_rules_manager_factory.h", "policy/dlp/dlp_rules_manager_impl.cc", "policy/dlp/dlp_rules_manager_impl.h", + "policy/dlp/dlp_warn_dialog.cc", + "policy/dlp/dlp_warn_dialog.h", + "policy/dlp/dlp_warn_notifier.cc", + "policy/dlp/dlp_warn_notifier.h", "printing/cups_print_job.cc", "printing/cups_print_job.h", "printing/cups_print_job_manager.cc", @@ -3865,6 +3865,7 @@ sources = [ # TODO(lukasza): Move Drive tests outside of CrOS (crbug.com/498951). + "../ash/account_manager/account_apps_availability_unittest.cc", "../ash/account_manager/account_manager_edu_coexistence_controller_unittest.cc", "../ash/android_sms/android_sms_app_manager_impl_unittest.cc", "../ash/android_sms/android_sms_app_setup_controller_impl_unittest.cc", @@ -4344,12 +4345,9 @@ "../ash/policy/core/user_cloud_policy_manager_ash_unittest.cc", "../ash/policy/core/user_cloud_policy_store_ash_unittest.cc", "../ash/policy/core/user_cloud_policy_token_forwarder_unittest.cc", - "../ash/policy/dlp/dlp_confidential_contents_unittest.cc", "../ash/policy/dlp/dlp_content_manager_unittest.cc", "../ash/policy/dlp/mock_dlp_content_manager.cc", "../ash/policy/dlp/mock_dlp_content_manager.h", - "../ash/policy/dlp/mock_dlp_warn_notifier.cc", - "../ash/policy/dlp/mock_dlp_warn_notifier.h", "../ash/policy/enrollment/account_status_check_fetcher_unittest.cc", "../ash/policy/enrollment/auto_enrollment_client_impl_unittest.cc", "../ash/policy/enrollment/device_cloud_policy_initializer_unittest.cc", @@ -4617,6 +4615,7 @@ "fileapi/test/fake_recent_source.cc", "policy/dlp/data_transfer_dlp_controller_unittest.cc", "policy/dlp/dlp_clipboard_notifier_unittest.cc", + "policy/dlp/dlp_confidential_contents_unittest.cc", "policy/dlp/dlp_content_tab_helper_unittest.cc", "policy/dlp/dlp_data_transfer_notifier_unittest.cc", "policy/dlp/dlp_drag_drop_notifier_unittest.cc", @@ -4630,6 +4629,8 @@ "policy/dlp/mock_dlp_content_observer.h", "policy/dlp/mock_dlp_rules_manager.cc", "policy/dlp/mock_dlp_rules_manager.h", + "policy/dlp/mock_dlp_warn_notifier.cc", + "policy/dlp/mock_dlp_warn_notifier.h", "printing/cups_printer_status_creator_unittest.cc", "printing/cups_printers_manager_unittest.cc", "printing/history/print_job_info_proto_conversions_unittest.cc", @@ -4765,6 +4766,7 @@ "//chromeos/tpm", "//chromeos/tpm:test_support", "//chromeos/ui/frame:test_support", + "//components/account_manager_core:test_support", "//components/arc", "//components/component_updater:test_support", "//components/content_settings/core/browser",
diff --git a/chrome/browser/chromeos/extensions/login_screen/login_screen_ui/ui_handler.cc b/chrome/browser/chromeos/extensions/login_screen/login_screen_ui/ui_handler.cc index 32fd0c1..6ab2814 100644 --- a/chrome/browser/chromeos/extensions/login_screen/login_screen_ui/ui_handler.cc +++ b/chrome/browser/chromeos/extensions/login_screen/login_screen_ui/ui_handler.cc
@@ -10,6 +10,9 @@ #include "ash/public/cpp/login_screen.h" #include "ash/public/cpp/login_screen_model.h" #include "ash/public/cpp/login_types.h" +#include "ash/session/fullscreen_controller.h" +#include "ash/session/session_controller_impl.h" +#include "ash/shell.h" #include "chrome/browser/ash/login/ui/login_screen_extension_ui/create_options.h" #include "chrome/browser/ash/login/ui/login_screen_extension_ui/window.h" #include "chrome/browser/ash/profiles/profile_helper.h" @@ -157,8 +160,21 @@ close_callback_.Reset(); } - if (!HasOpenWindow(extension_id)) + if (!HasOpenWindow(extension_id)) { + // Shell could have no instance in tests. + if (ash::Shell::HasInstance()) { + // Notify the full screen controller that the last login screen UI window + // has been closed so that it could show the full screen notification, if + // required. This is necessary since the active window might still be the + // login screen extension window when the full screen controller is + // notified by the session controller. + ash::Shell::Get() + ->session_controller() + ->fullscreen_controller() + ->OnLoginScreenUiWindowClosed(); + } return; + } ResetWindowAndHide(); }
diff --git a/chrome/browser/ash/policy/dlp/dlp_confidential_contents.cc b/chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.cc similarity index 96% rename from chrome/browser/ash/policy/dlp/dlp_confidential_contents.cc rename to chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.cc index 8ed3f79..d61e6cb 100644 --- a/chrome/browser/ash/policy/dlp/dlp_confidential_contents.cc +++ b/chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.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 "chrome/browser/ash/policy/dlp/dlp_confidential_contents.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.h" #include <memory> #include <vector> @@ -20,7 +20,7 @@ // The maximum number of entries that can be kept in the // DlpConfidentialContentsCache. // TODO(crbug.com/1275926): determine the value to use -static constexpr int kDefaultCacheSizeLimit = 100; +static constexpr size_t kDefaultCacheSizeLimit = 100; // The default timeout after which the entries are evicted from the // DlpConfidentialContentsCache. @@ -162,7 +162,7 @@ }) != entries_.end(); } -int DlpConfidentialContentsCache::GetSizeForTesting() const { +size_t DlpConfidentialContentsCache::GetSizeForTesting() const { return entries_.size(); }
diff --git a/chrome/browser/ash/policy/dlp/dlp_confidential_contents.h b/chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.h similarity index 95% rename from chrome/browser/ash/policy/dlp/dlp_confidential_contents.h rename to chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.h index d6f8f49..0eb8ce9 100644 --- a/chrome/browser/ash/policy/dlp/dlp_confidential_contents.h +++ b/chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.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 CHROME_BROWSER_ASH_POLICY_DLP_DLP_CONFIDENTIAL_CONTENTS_H_ -#define CHROME_BROWSER_ASH_POLICY_DLP_DLP_CONFIDENTIAL_CONTENTS_H_ +#ifndef CHROME_BROWSER_CHROMEOS_POLICY_DLP_DLP_CONFIDENTIAL_CONTENTS_H_ +#define CHROME_BROWSER_CHROMEOS_POLICY_DLP_DLP_CONFIDENTIAL_CONTENTS_H_ #include <list> #include <string> @@ -121,7 +121,7 @@ DlpRulesManager::Restriction restriction) const; // Returns the number of cached entries, useful for testing. - int GetSizeForTesting() const; + size_t GetSizeForTesting() const; // Returns the duration for which the entries are kept in the cache. static base::TimeDelta GetCacheTimeout(); @@ -156,7 +156,7 @@ void OnEvictionTimerUp(const DlpConfidentialContent& content); std::list<std::unique_ptr<Entry>> entries_; - int cache_size_limit_; + size_t cache_size_limit_; // Used to evict cache entries after the timeout. scoped_refptr<base::SingleThreadTaskRunner> task_runner_; @@ -164,4 +164,4 @@ } // namespace policy -#endif // CHROME_BROWSER_ASH_POLICY_DLP_DLP_CONFIDENTIAL_CONTENTS_H_ +#endif // CHROME_BROWSER_CHROMEOS_POLICY_DLP_DLP_CONFIDENTIAL_CONTENTS_H_
diff --git a/chrome/browser/ash/policy/dlp/dlp_confidential_contents_unittest.cc b/chrome/browser/chromeos/policy/dlp/dlp_confidential_contents_unittest.cc similarity index 92% rename from chrome/browser/ash/policy/dlp/dlp_confidential_contents_unittest.cc rename to chrome/browser/chromeos/policy/dlp/dlp_confidential_contents_unittest.cc index 11ed3c8..5e6779e 100644 --- a/chrome/browser/ash/policy/dlp/dlp_confidential_contents_unittest.cc +++ b/chrome/browser/chromeos/policy/dlp/dlp_confidential_contents_unittest.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 "chrome/browser/ash/policy/dlp/dlp_confidential_contents.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.h" #include <string> @@ -84,7 +84,7 @@ auto web_contents = CreateWebContents(title1, url1); contents.Add(web_contents.get()); contents.Add(web_contents.get()); - EXPECT_EQ(contents.GetContents().size(), 1); + EXPECT_EQ(contents.GetContents().size(), 1u); EXPECT_TRUE(Contains(contents, web_contents.get())); } @@ -97,10 +97,10 @@ contents.Add(web_contents1.get()); contents.Add(web_contents2.get()); - EXPECT_EQ(contents.GetContents().size(), 2); + EXPECT_EQ(contents.GetContents().size(), 2u); contents.ClearAndAdd(CreateWebContents(title3, url3).get()); - EXPECT_EQ(contents.GetContents().size(), 1); + EXPECT_EQ(contents.GetContents().size(), 1u); EXPECT_FALSE(Contains(contents, web_contents1.get())); EXPECT_FALSE(Contains(contents, web_contents2.get())); EXPECT_TRUE(Contains(contents, web_contents3.get())); @@ -120,14 +120,14 @@ contents2.Add(web_contents1.get()); contents2.Add(web_contents3.get()); - EXPECT_EQ(contents1.GetContents().size(), 2); - EXPECT_EQ(contents2.GetContents().size(), 2); + EXPECT_EQ(contents1.GetContents().size(), 2u); + EXPECT_EQ(contents2.GetContents().size(), 2u); EXPECT_FALSE(Contains(contents1, web_contents3.get())); contents1.UnionWith(contents2); - EXPECT_EQ(contents1.GetContents().size(), 3); - EXPECT_EQ(contents2.GetContents().size(), 2); + EXPECT_EQ(contents1.GetContents().size(), 3u); + EXPECT_EQ(contents2.GetContents().size(), 2u); EXPECT_TRUE(Contains(contents1, web_contents3.get())); } @@ -166,7 +166,7 @@ cache.Cache(content, kRestriction); cache.Cache(content, kRestriction); - EXPECT_EQ(cache.GetSizeForTesting(), 1); + EXPECT_EQ(cache.GetSizeForTesting(), 1u); } } // namespace policy
diff --git a/chrome/browser/ash/policy/dlp/dlp_warn_dialog.cc b/chrome/browser/chromeos/policy/dlp/dlp_warn_dialog.cc similarity index 89% rename from chrome/browser/ash/policy/dlp/dlp_warn_dialog.cc rename to chrome/browser/chromeos/policy/dlp/dlp_warn_dialog.cc index dfc58f6..d613274c 100644 --- a/chrome/browser/ash/policy/dlp/dlp_warn_dialog.cc +++ b/chrome/browser/chromeos/policy/dlp/dlp_warn_dialog.cc
@@ -2,14 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/ash/policy/dlp/dlp_warn_dialog.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_warn_dialog.h" #include <memory> #include <string> #include <utility> -#include "ash/public/cpp/style/color_provider.h" -#include "chrome/browser/ash/policy/dlp/dlp_confidential_contents.h" +#include "build/chromeos_buildflags.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.h" #include "chrome/browser/ui/views/chrome_layout_provider.h" #include "components/strings/grit/components_strings.h" #include "components/vector_icons/vector_icons.h" @@ -24,6 +24,10 @@ #include "ui/views/layout/box_layout.h" #include "ui/views/widget/widget.h" +#if BUILDFLAG(IS_CHROMEOS_ASH) +#include "ash/public/cpp/style/color_provider.h" +#endif // BUILDFLAG(IS_CHROMEOS_ASH) + namespace policy { namespace { @@ -153,7 +157,10 @@ // icon, dialog title and the informative text. void AddGeneralInformation(views::View* upper_panel, DlpWarnDialog::DlpWarnDialogOptions options) { +// TODO(crbug.com/1261496) Enable dynamic UI color & theme in lacros +#if BUILDFLAG(IS_CHROMEOS_ASH) ash::ColorProvider* color_provider = ash::ColorProvider::Get(); +#endif // BUILDFLAG(IS_CHROMEOS_ASH) views::BoxLayout* layout = upper_panel->SetLayoutManager(std::make_unique<views::BoxLayout>( @@ -164,17 +171,25 @@ views::ImageView* managed_icon = upper_panel->AddChildView(std::make_unique<views::ImageView>()); - managed_icon->SetImage(gfx::CreateVectorIcon( - vector_icons::kBusinessIcon, kManagedIconSize, - color_provider->GetContentLayerColor( - ash::ColorProvider::ContentLayerType::kIconColorPrimary))); +#if BUILDFLAG(IS_CHROMEOS_ASH) + auto color = color_provider->GetContentLayerColor( + ash::ColorProvider::ContentLayerType::kIconColorPrimary); +#elif BUILDFLAG(IS_CHROMEOS_LACROS) + // TODO(crbug.com/1261496) Enable dynamic UI color & theme in lacros + auto color = SK_ColorGRAY; +#endif // BUILDFLAG(IS_CHROMEOS_ASH) + managed_icon->SetImage(gfx::CreateVectorIcon(vector_icons::kBusinessIcon, + kManagedIconSize, color)); views::Label* title_label = upper_panel->AddChildView( std::make_unique<views::Label>(GetTitle(options.restriction))); title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); title_label->SetAllowCharacterBreak(true); +// TODO(crbug.com/1261496) Enable dynamic UI color & theme in lacros +#if BUILDFLAG(IS_CHROMEOS_ASH) title_label->SetEnabledColor(color_provider->GetContentLayerColor( ash::ColorProvider::ContentLayerType::kTextColorPrimary)); +#endif // BUILDFLAG(IS_CHROMEOS_ASH) title_label->SetFontList(gfx::FontList({kFontName}, gfx::Font::NORMAL, kTitleFontSize, gfx::Font::Weight::MEDIUM)); @@ -185,8 +200,11 @@ message->SetMultiLine(true); message->SetHorizontalAlignment(gfx::ALIGN_LEFT); message->SetAllowCharacterBreak(true); +// TODO(crbug.com/1261496) Enable dynamic UI color & theme in lacros +#if BUILDFLAG(IS_CHROMEOS_ASH) message->SetEnabledColor(color_provider->GetContentLayerColor( ash::ColorProvider::ContentLayerType::kTextColorSecondary)); +#endif // BUILDFLAG(IS_CHROMEOS_ASH) message->SetFontList(gfx::FontList({kFontName}, gfx::Font::NORMAL, kBodyFontSize, gfx::Font::Weight::NORMAL)); message->SetLineHeight(kBodyLineHeight); @@ -197,7 +215,10 @@ void AddConfidentialContentRow( views::View* container, const DlpConfidentialContent& confidential_content) { +// TODO(crbug.com/1261496) Enable dynamic UI color & theme in lacros +#if BUILDFLAG(IS_CHROMEOS_ASH) ash::ColorProvider* color_provider = ash::ColorProvider::Get(); +#endif // BUILDFLAG(IS_CHROMEOS_ASH) views::View* row = container->AddChildView(std::make_unique<views::View>()); row->SetLayoutManager(std::make_unique<views::BoxLayout>( @@ -214,8 +235,11 @@ title->SetMultiLine(true); title->SetHorizontalAlignment(gfx::ALIGN_LEFT); title->SetAllowCharacterBreak(true); +// TODO(crbug.com/1261496) Enable dynamic UI color & theme in lacros +#if BUILDFLAG(IS_CHROMEOS_ASH) title->SetEnabledColor(color_provider->GetContentLayerColor( ash::ColorProvider::ContentLayerType::kTextColorSecondary)); +#endif // BUILDFLAG(IS_CHROMEOS_ASH) title->SetFontList(gfx::FontList({kFontName}, gfx::Font::NORMAL, kBodyFontSize, gfx::Font::Weight::NORMAL)); title->SetLineHeight(kConfidentialContentLineHeight);
diff --git a/chrome/browser/ash/policy/dlp/dlp_warn_dialog.h b/chrome/browser/chromeos/policy/dlp/dlp_warn_dialog.h similarity index 91% rename from chrome/browser/ash/policy/dlp/dlp_warn_dialog.h rename to chrome/browser/chromeos/policy/dlp/dlp_warn_dialog.h index 30c5180..0d40e4b 100644 --- a/chrome/browser/ash/policy/dlp/dlp_warn_dialog.h +++ b/chrome/browser/chromeos/policy/dlp/dlp_warn_dialog.h
@@ -2,12 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_ASH_POLICY_DLP_DLP_WARN_DIALOG_H_ -#define CHROME_BROWSER_ASH_POLICY_DLP_DLP_WARN_DIALOG_H_ +#ifndef CHROME_BROWSER_CHROMEOS_POLICY_DLP_DLP_WARN_DIALOG_H_ +#define CHROME_BROWSER_CHROMEOS_POLICY_DLP_DLP_WARN_DIALOG_H_ #include <string> + #include "base/callback_forward.h" -#include "chrome/browser/ash/policy/dlp/dlp_confidential_contents.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "ui/base/metadata/metadata_header_macros.h" #include "ui/views/window/dialog_delegate.h" @@ -78,4 +79,4 @@ } // namespace policy -#endif // CHROME_BROWSER_ASH_POLICY_DLP_DLP_WARN_DIALOG_H_ +#endif // CHROME_BROWSER_CHROMEOS_POLICY_DLP_DLP_WARN_DIALOG_H_
diff --git a/chrome/browser/ash/policy/dlp/dlp_warn_notifier.cc b/chrome/browser/chromeos/policy/dlp/dlp_warn_notifier.cc similarity index 94% rename from chrome/browser/ash/policy/dlp/dlp_warn_notifier.cc rename to chrome/browser/chromeos/policy/dlp/dlp_warn_notifier.cc index 1cfd512d..88e13a4 100644 --- a/chrome/browser/ash/policy/dlp/dlp_warn_notifier.cc +++ b/chrome/browser/chromeos/policy/dlp/dlp_warn_notifier.cc
@@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/ash/policy/dlp/dlp_warn_notifier.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_warn_notifier.h" -#include "chrome/browser/ash/policy/dlp/dlp_warn_dialog.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_warn_dialog.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/window.h" #include "ui/views/widget/widget.h"
diff --git a/chrome/browser/ash/policy/dlp/dlp_warn_notifier.h b/chrome/browser/chromeos/policy/dlp/dlp_warn_notifier.h similarity index 86% rename from chrome/browser/ash/policy/dlp/dlp_warn_notifier.h rename to chrome/browser/chromeos/policy/dlp/dlp_warn_notifier.h index a914a25..0bfd3412 100644 --- a/chrome/browser/ash/policy/dlp/dlp_warn_notifier.h +++ b/chrome/browser/chromeos/policy/dlp/dlp_warn_notifier.h
@@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_ASH_POLICY_DLP_DLP_WARN_NOTIFIER_H_ -#define CHROME_BROWSER_ASH_POLICY_DLP_DLP_WARN_NOTIFIER_H_ +#ifndef CHROME_BROWSER_CHROMEOS_POLICY_DLP_DLP_WARN_NOTIFIER_H_ +#define CHROME_BROWSER_CHROMEOS_POLICY_DLP_DLP_WARN_NOTIFIER_H_ -#include "chrome/browser/ash/policy/dlp/dlp_confidential_contents.h" -#include "chrome/browser/ash/policy/dlp/dlp_warn_dialog.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_warn_dialog.h" namespace policy { @@ -57,4 +57,4 @@ } // namespace policy -#endif // CHROME_BROWSER_ASH_POLICY_DLP_DLP_WARN_NOTIFIER_H_ +#endif // CHROME_BROWSER_CHROMEOS_POLICY_DLP_DLP_WARN_NOTIFIER_H_
diff --git a/chrome/browser/ash/policy/dlp/mock_dlp_warn_notifier.cc b/chrome/browser/chromeos/policy/dlp/mock_dlp_warn_notifier.cc similarity index 78% rename from chrome/browser/ash/policy/dlp/mock_dlp_warn_notifier.cc rename to chrome/browser/chromeos/policy/dlp/mock_dlp_warn_notifier.cc index 708f9eb..f45d37ea 100644 --- a/chrome/browser/ash/policy/dlp/mock_dlp_warn_notifier.cc +++ b/chrome/browser/chromeos/policy/dlp/mock_dlp_warn_notifier.cc
@@ -2,10 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/ash/policy/dlp/mock_dlp_warn_notifier.h" +#include "chrome/browser/chromeos/policy/dlp/mock_dlp_warn_notifier.h" -#include "chrome/browser/ash/policy/dlp/dlp_warn_dialog.h" -#include "chrome/browser/ash/policy/dlp/dlp_warn_notifier.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_warn_dialog.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_warn_notifier.h" #include "testing/gmock/include/gmock/gmock.h" using ::testing::Mock;
diff --git a/chrome/browser/ash/policy/dlp/mock_dlp_warn_notifier.h b/chrome/browser/chromeos/policy/dlp/mock_dlp_warn_notifier.h similarity index 70% rename from chrome/browser/ash/policy/dlp/mock_dlp_warn_notifier.h rename to chrome/browser/chromeos/policy/dlp/mock_dlp_warn_notifier.h index 58469021..4d781ef1 100644 --- a/chrome/browser/ash/policy/dlp/mock_dlp_warn_notifier.h +++ b/chrome/browser/chromeos/policy/dlp/mock_dlp_warn_notifier.h
@@ -2,13 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_ASH_POLICY_DLP_MOCK_DLP_WARN_NOTIFIER_H_ -#define CHROME_BROWSER_ASH_POLICY_DLP_MOCK_DLP_WARN_NOTIFIER_H_ +#ifndef CHROME_BROWSER_CHROMEOS_POLICY_DLP_MOCK_DLP_WARN_NOTIFIER_H_ +#define CHROME_BROWSER_CHROMEOS_POLICY_DLP_MOCK_DLP_WARN_NOTIFIER_H_ #include "base/callback_forward.h" -#include "chrome/browser/ash/policy/dlp/dlp_confidential_contents.h" -#include "chrome/browser/ash/policy/dlp/dlp_warn_dialog.h" -#include "chrome/browser/ash/policy/dlp/dlp_warn_notifier.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_warn_dialog.h" +#include "chrome/browser/chromeos/policy/dlp/dlp_warn_notifier.h" #include "testing/gmock/include/gmock/gmock.h" using ::testing::Mock; @@ -36,4 +36,4 @@ } // namespace policy -#endif // CHROME_BROWSER_ASH_POLICY_DLP_MOCK_DLP_WARN_NOTIFIER_H_ +#endif // CHROME_BROWSER_CHROMEOS_POLICY_DLP_MOCK_DLP_WARN_NOTIFIER_H_
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc index a5c9cb3..de5230cc 100644 --- a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc +++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.cc
@@ -106,7 +106,7 @@ } void StringAnalysisRequest::GetRequestData(DataCallback callback) { - std::move(callback).Run(result_, data_); + std::move(callback).Run(result_, std::move(data_)); } bool ContentAnalysisActionAllowsDataUse( @@ -628,7 +628,7 @@ std::unique_ptr<BinaryUploadService::Request> request, const base::FilePath& path, BinaryUploadService::Result result, - const BinaryUploadService::Request::Data& data) { + BinaryUploadService::Request::Data data) { auto it = std::find(data_.paths.begin(), data_.paths.end(), path); DCHECK(it != data_.paths.end()); size_t index = std::distance(data_.paths.begin(), it);
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h index aaea376..628ddbe8 100644 --- a/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h +++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_delegate.h
@@ -286,7 +286,7 @@ std::unique_ptr<safe_browsing::BinaryUploadService::Request> request, const base::FilePath& path, safe_browsing::BinaryUploadService::Result result, - const safe_browsing::BinaryUploadService::Request::Data& data); + safe_browsing::BinaryUploadService::Request::Data data); // Updates |final_result_| following the precedence established by the // FinalResult enum.
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/win_key_rotation_command.cc b/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/win_key_rotation_command.cc index 88f3b6ee..8ec1c7b9 100644 --- a/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/win_key_rotation_command.cc +++ b/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/win_key_rotation_command.cc
@@ -45,7 +45,8 @@ // this code is duped in two places including this one. HRESULT RunGoogleUpdateElevatedCommand(const wchar_t* command, const std::vector<std::string>& args, - UINT* return_code) { + DWORD* return_code) { + DCHECK(return_code); if (args.size() > kMaxCommandArgs) return E_INVALIDARG; @@ -111,23 +112,26 @@ // If the call requires the return code of the elevated command, poll until // we get it. Waiting for 10 seconds with a polling frenquency of 1 second // are pretty arbitrary choices. - if (return_code) { - base::Time wait_until = base::Time::Now() + base::Seconds(10); - while (base::Time::Now() < wait_until) { - hr = app_command->get_status(return_code); - if (FAILED(hr) || *return_code == COMMAND_STATUS_ERROR || - *return_code == COMMAND_STATUS_COMPLETE) { - break; - } - - base::PlatformThread::Sleep(base::Seconds(1)); + base::Time wait_until = base::Time::Now() + base::Seconds(10); + UINT status = COMMAND_STATUS_INIT; + while (base::Time::Now() < wait_until) { + hr = app_command->get_status(&status); + if (FAILED(hr) || status == COMMAND_STATUS_ERROR || + status == COMMAND_STATUS_COMPLETE) { + break; } + + base::PlatformThread::Sleep(base::Seconds(1)); } - // If the command never completed, tell caller it timed out. - if (*return_code != COMMAND_STATUS_ERROR && - *return_code != COMMAND_STATUS_COMPLETE) { - hr = E_ABORT; + // If the command completed get the final exit code. Otherwise if the + // command did not terminate in error, tell caller it timed out. + if (SUCCEEDED(hr)) { + if (status == COMMAND_STATUS_COMPLETE) { + hr = app_command->get_exitCode(return_code); + } else if (status != COMMAND_STATUS_ERROR) { + hr = E_ABORT; + } } return hr; @@ -165,7 +169,7 @@ std::string token_base64; base::Base64Encode(params.dm_token, &token_base64); - UINT return_code = installer::ROTATE_DTKEY_FAILED; + DWORD return_code = installer::ROTATE_DTKEY_FAILED; // Omaha does not support concurrent elevated commands. If this // fails for that reason, wait a little and try again. Retry count
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/win_key_rotation_command.h b/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/win_key_rotation_command.h index 26ab0b2..dc7967a 100644 --- a/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/win_key_rotation_command.h +++ b/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/win_key_rotation_command.h
@@ -25,7 +25,7 @@ using RunGoogleUpdateElevatedCommandFn = HRESULT (*)(const wchar_t* command, const std::vector<std::string>& args, - UINT* return_code); + DWORD* return_code); // The second constructor is used in tests to override the behaviour of // Google Update.
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/win_key_rotation_command_unittest.cc b/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/win_key_rotation_command_unittest.cc index a26f9ed..02f1bb4 100644 --- a/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/win_key_rotation_command_unittest.cc +++ b/chrome/browser/enterprise/connectors/device_trust/key_management/browser/commands/win_key_rotation_command_unittest.cc
@@ -40,7 +40,7 @@ WinKeyRotationCommand command([](const wchar_t* command, const std::vector<std::string>& args, - UINT* return_code) { + DWORD* return_code) { std::string token_base64; base::Base64Encode(kFakeDMToken, &token_base64); EXPECT_EQ(token_base64, args[0]); @@ -70,7 +70,7 @@ WinKeyRotationCommand command([](const wchar_t* command, const std::vector<std::string>& args, - UINT* return_code) { + DWORD* return_code) { std::string token_base64; base::Base64Encode(kFakeDMToken, &token_base64); EXPECT_EQ(token_base64, args[0]); @@ -100,7 +100,7 @@ WinKeyRotationCommand command([](const wchar_t* command, const std::vector<std::string>& args, - UINT* return_code) { + DWORD* return_code) { std::string token_base64; base::Base64Encode(kFakeDMToken, &token_base64); EXPECT_EQ(token_base64, args[0]); @@ -130,7 +130,7 @@ WinKeyRotationCommand command([](const wchar_t* command, const std::vector<std::string>& args, - UINT* return_code) { + DWORD* return_code) { std::string token_base64; base::Base64Encode(kFakeDMToken, &token_base64); EXPECT_EQ(token_base64, args[0]); @@ -154,4 +154,35 @@ ASSERT_EQ(KeyRotationCommand::Status::FAILED, status); } +TEST_F(WinKeyRotationCommandTest, GeneralFailure) { + KeyRotationCommand::Params params = {kFakeDMToken, kFakeDmServerUrl, kNonce}; + bool was_called = false; + KeyRotationCommand::Status status = KeyRotationCommand::Status::SUCCEEDED; + + WinKeyRotationCommand command([](const wchar_t* command, + const std::vector<std::string>& args, + DWORD* return_code) { + std::string token_base64; + base::Base64Encode(kFakeDMToken, &token_base64); + EXPECT_EQ(token_base64, args[0]); + EXPECT_EQ(kFakeDmServerUrl, args[1]); + EXPECT_EQ(kNonce, args[2]); + // Not setting return_code. + return E_FAIL; + }); + + command.enable_waiting_for_testing(false); + command.Trigger(params, base::BindLambdaForTesting( + [&was_called, + &status](KeyRotationCommand::Status arg_status) { + was_called = true; + status = arg_status; + })); + + RunUntilIdle(); + + ASSERT_TRUE(was_called); + ASSERT_EQ(KeyRotationCommand::Status::FAILED, status); +} + } // namespace enterprise_connectors
diff --git a/chrome/browser/extensions/api/omnibox/omnibox_api.cc b/chrome/browser/extensions/api/omnibox/omnibox_api.cc index b73737e..127ddf7d 100644 --- a/chrome/browser/extensions/api/omnibox/omnibox_api.cc +++ b/chrome/browser/extensions/api/omnibox/omnibox_api.cc
@@ -21,6 +21,7 @@ #include "chrome/browser/search_engines/template_url_service_factory.h" #include "chrome/common/extensions/api/omnibox.h" #include "chrome/common/extensions/api/omnibox/omnibox_handler.h" +#include "components/omnibox/browser/omnibox_watcher.h" #include "components/search_engines/template_url.h" #include "components/search_engines/template_url_service.h" #include "content/public/browser/notification_details.h" @@ -153,10 +154,7 @@ EventRouter::Get(profile) ->DispatchEventToExtension(extension_id, std::move(event)); - content::NotificationService::current()->Notify( - extensions::NOTIFICATION_EXTENSION_OMNIBOX_INPUT_ENTERED, - content::Source<Profile>(profile), - content::NotificationService::NoDetails()); + OmniboxWatcher::GetForBrowserContext(profile)->NotifyInputEntered(); } // static
diff --git a/chrome/browser/feature_guide/notifications/feature_notification_guide_service.h b/chrome/browser/feature_guide/notifications/feature_notification_guide_service.h index 3c9d3f6..904e1b3 100644 --- a/chrome/browser/feature_guide/notifications/feature_notification_guide_service.h +++ b/chrome/browser/feature_guide/notifications/feature_notification_guide_service.h
@@ -5,10 +5,20 @@ #ifndef CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_FEATURE_NOTIFICATION_GUIDE_SERVICE_H_ #define CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_FEATURE_NOTIFICATION_GUIDE_SERVICE_H_ +#include <memory> +#include <set> + +#include "base/callback.h" #include "base/feature_list.h" #include "base/supports_user_data.h" +#include "chrome/browser/feature_guide/notifications/feature_type.h" #include "components/keyed_service/core/keyed_service.h" +namespace notifications { +class NotificationSchedulerClient; +struct NotificationData; +} // namespace notifications + namespace feature_guide { namespace features { @@ -22,6 +32,9 @@ class FeatureNotificationGuideService : public KeyedService, public base::SupportsUserData { public: + using NotificationDataCallback = base::OnceCallback<void( + std::unique_ptr<notifications::NotificationData>)>; + FeatureNotificationGuideService(); ~FeatureNotificationGuideService() override; @@ -29,8 +42,25 @@ delete; FeatureNotificationGuideService& operator=( const FeatureNotificationGuideService&) = delete; + + // Called during initialization to notify about the already scheduled set of + // feature notifications. + virtual void OnSchedulerInitialized(const std::set<std::string>& guids) = 0; + + // Called before the notification is shown. + virtual void BeforeShowNotification( + std::unique_ptr<notifications::NotificationData> notification_data, + NotificationDataCallback callback) = 0; + + // Called when the notification is clicked. + virtual void OnClick(FeatureType feature) = 0; }; +using ServiceGetter = + base::RepeatingCallback<FeatureNotificationGuideService*()>; +std::unique_ptr<notifications::NotificationSchedulerClient> +CreateFeatureNotificationGuideNotificationClient(ServiceGetter service_getter); + } // namespace feature_guide #endif // CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_FEATURE_NOTIFICATION_GUIDE_SERVICE_H_
diff --git a/chrome/browser/feature_guide/notifications/internal/BUILD.gn b/chrome/browser/feature_guide/notifications/internal/BUILD.gn index dd7dd58..ec4cda4 100644 --- a/chrome/browser/feature_guide/notifications/internal/BUILD.gn +++ b/chrome/browser/feature_guide/notifications/internal/BUILD.gn
@@ -9,6 +9,8 @@ source_set("internal") { sources = [ + "feature_notification_guide_notification_client.cc", + "feature_notification_guide_notification_client.h", "feature_notification_guide_service_impl.cc", "feature_notification_guide_service_impl.h", ]
diff --git a/chrome/browser/feature_guide/notifications/internal/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideBridge.java b/chrome/browser/feature_guide/notifications/internal/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideBridge.java index acd4db1..975e41d 100644 --- a/chrome/browser/feature_guide/notifications/internal/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideBridge.java +++ b/chrome/browser/feature_guide/notifications/internal/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideBridge.java
@@ -49,4 +49,4 @@ private void onNotificationClick(int featureType) { mDelegate.onNotificationClick(featureType); } -} \ No newline at end of file +}
diff --git a/chrome/browser/feature_guide/notifications/internal/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideServiceFactory.java b/chrome/browser/feature_guide/notifications/internal/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideServiceFactory.java index 39cddba..d35374f 100644 --- a/chrome/browser/feature_guide/notifications/internal/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideServiceFactory.java +++ b/chrome/browser/feature_guide/notifications/internal/android/java/src/org/chromium/chrome/browser/feature_guide/notifications/FeatureNotificationGuideServiceFactory.java
@@ -27,4 +27,4 @@ interface Natives { FeatureNotificationGuideService getForProfile(Profile profile); } -} \ No newline at end of file +}
diff --git a/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_notification_client.cc b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_notification_client.cc new file mode 100644 index 0000000..70a387e2 --- /dev/null +++ b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_notification_client.cc
@@ -0,0 +1,59 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/feature_guide/notifications/internal/feature_notification_guide_notification_client.h" + +#include "chrome/browser/feature_guide/notifications/feature_notification_guide_service.h" +#include "chrome/browser/notifications/scheduler/public/notification_scheduler_types.h" + +using ThrottleConfigCallback = + notifications::NotificationSchedulerClient::ThrottleConfigCallback; + +namespace feature_guide { + +FeatureNotificationGuideNotificationClient:: + FeatureNotificationGuideNotificationClient(ServiceGetter getter) + : service_getter_(getter) {} + +FeatureNotificationGuideNotificationClient:: + ~FeatureNotificationGuideNotificationClient() = default; + +void FeatureNotificationGuideNotificationClient::BeforeShowNotification( + std::unique_ptr<notifications::NotificationData> notification_data, + NotificationDataCallback callback) { + DCHECK(notification_data.get()); + GetNotificationService()->BeforeShowNotification(std::move(notification_data), + std::move(callback)); +} + +void FeatureNotificationGuideNotificationClient::OnSchedulerInitialized( + bool success, + std::set<std::string> guids) { + if (!success) + return; + + GetNotificationService()->OnSchedulerInitialized(guids); +} + +void FeatureNotificationGuideNotificationClient::OnUserAction( + const notifications::UserActionData& action_data) { + if (action_data.action_type == notifications::UserActionType::kClick) { + FeatureType feature = FeatureType::kInvalid; + // TODO(shaktisahu): Parse feature from action_data. + GetNotificationService()->OnClick(feature); + } +} + +void FeatureNotificationGuideNotificationClient::GetThrottleConfig( + ThrottleConfigCallback callback) { + // No throttle. + std::move(callback).Run(nullptr); +} + +FeatureNotificationGuideService* +FeatureNotificationGuideNotificationClient::GetNotificationService() { + return service_getter_.Run(); +} + +} // namespace feature_guide
diff --git a/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_notification_client.h b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_notification_client.h new file mode 100644 index 0000000..9d34aa9 --- /dev/null +++ b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_notification_client.h
@@ -0,0 +1,52 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_INTERNAL_FEATURE_NOTIFICATION_GUIDE_NOTIFICATION_CLIENT_H_ +#define CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_INTERNAL_FEATURE_NOTIFICATION_GUIDE_NOTIFICATION_CLIENT_H_ + +#include <memory> + +#include "chrome/browser/notifications/scheduler/public/notification_scheduler_client.h" + +namespace notifications { +struct NotificationData; +struct UserActionData; +} // namespace notifications + +namespace feature_guide { +class FeatureNotificationGuideService; + +// The client interface that communicates with notification scheduling system. +class FeatureNotificationGuideNotificationClient + : public notifications::NotificationSchedulerClient { + public: + using ServiceGetter = + base::RepeatingCallback<FeatureNotificationGuideService*()>; + explicit FeatureNotificationGuideNotificationClient(ServiceGetter getter); + ~FeatureNotificationGuideNotificationClient() override; + FeatureNotificationGuideNotificationClient( + const FeatureNotificationGuideNotificationClient&) = delete; + FeatureNotificationGuideNotificationClient operator=( + const FeatureNotificationGuideNotificationClient&) = delete; + + // notifications::NotificationSchedulerClient implementation. + void BeforeShowNotification( + std::unique_ptr<notifications::NotificationData> notification_data, + NotificationDataCallback callback) override; + void OnSchedulerInitialized(bool success, + std::set<std::string> guids) override; + void OnUserAction(const notifications::UserActionData& action_data) override; + void GetThrottleConfig( + notifications::NotificationSchedulerClient::ThrottleConfigCallback + callback) override; + + private: + FeatureNotificationGuideService* GetNotificationService(); + + ServiceGetter service_getter_; +}; + +} // namespace feature_guide + +#endif // CHROME_BROWSER_FEATURE_GUIDE_NOTIFICATIONS_INTERNAL_FEATURE_NOTIFICATION_GUIDE_NOTIFICATION_CLIENT_H_
diff --git a/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.cc b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.cc index f0e77e2..4d8b396 100644 --- a/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.cc +++ b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.cc
@@ -1,15 +1,34 @@ -// Copyright 2020 The Chromium Authors. All rights reserved. +// Copyright 2021 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.h" +#include "chrome/browser/feature_guide/notifications/internal/feature_notification_guide_notification_client.h" +#include "chrome/browser/notifications/scheduler/public/notification_data.h" +#include "chrome/browser/notifications/scheduler/public/notification_params.h" + namespace feature_guide { +std::unique_ptr<notifications::NotificationSchedulerClient> +CreateFeatureNotificationGuideNotificationClient(ServiceGetter service_getter) { + return std::make_unique<FeatureNotificationGuideNotificationClient>( + service_getter); +} + FeatureNotificationGuideServiceImpl::FeatureNotificationGuideServiceImpl() = default; FeatureNotificationGuideServiceImpl::~FeatureNotificationGuideServiceImpl() = default; +void FeatureNotificationGuideServiceImpl::OnSchedulerInitialized( + const std::set<std::string>& guids) {} + +void FeatureNotificationGuideServiceImpl::BeforeShowNotification( + std::unique_ptr<notifications::NotificationData> notification_data, + NotificationDataCallback callback) {} + +void FeatureNotificationGuideServiceImpl::OnClick(FeatureType feature) {} + } // namespace feature_guide
diff --git a/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.h b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.h index abcbea8..5d26e462 100644 --- a/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.h +++ b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl.h
@@ -7,6 +7,16 @@ #include "base/memory/weak_ptr.h" #include "chrome/browser/feature_guide/notifications/feature_notification_guide_service.h" +#include "chrome/browser/feature_guide/notifications/feature_type.h" + +namespace feature_engagement { +class Tracker; +} // namespace feature_engagement + +namespace notifications { +class NotificationScheduleService; +struct NotificationData; +} // namespace notifications namespace feature_guide { @@ -16,6 +26,12 @@ FeatureNotificationGuideServiceImpl(); ~FeatureNotificationGuideServiceImpl() override; + void OnSchedulerInitialized(const std::set<std::string>& guids) override; + void BeforeShowNotification( + std::unique_ptr<notifications::NotificationData> notification_data, + NotificationDataCallback callback) override; + void OnClick(FeatureType feature) override; + private: base::WeakPtrFactory<FeatureNotificationGuideServiceImpl> weak_ptr_factory_{ this};
diff --git a/chrome/browser/first_party_sets/first_party_sets_util.cc b/chrome/browser/first_party_sets/first_party_sets_util.cc new file mode 100644 index 0000000..b6759b7 --- /dev/null +++ b/chrome/browser/first_party_sets/first_party_sets_util.cc
@@ -0,0 +1,86 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/first_party_sets/first_party_sets_util.h" + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/task/post_task.h" +#include "base/task/thread_pool.h" +#include "content/public/browser/browser_thread.h" + +namespace { + +constexpr base::FilePath::CharType kPersistedFirstPartySetsFileName[] = + FILE_PATH_LITERAL("persisted_first_party_sets.json"); + +// Reads the sets as raw JSON from their storage file, returning the raw sets on +// success and empty string on failure. +std::string LoadSetsFromDisk(const base::FilePath& path) { + DCHECK(!path.empty()); + + std::string result; + if (!base::ReadFileToString(path, &result)) { + VLOG(1) << "Failed loading serialized First-Party Sets file from " + << path.MaybeAsASCII(); + return ""; + } + return result; +} + +// Writes the sets as raw JSON to the storage file. +// +// TODO(crbug.com/1219656): To handle the cases of file corrupting due to +// incomplete writes, write to a temp file then rename over the old file. +void MaybeWriteSetsToDisk(const base::FilePath& path, const std::string& sets) { + DCHECK(!path.empty()); + + if (!base::WriteFile(path, sets)) { + VLOG(1) << "Failed writing serialized First-Party Sets to file " + << path.MaybeAsASCII(); + } +} + +void OnGetUpdatedSets(const base::FilePath& path, const std::string& sets) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + base::ThreadPool::PostTask( + FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT}, + base::BindOnce(&MaybeWriteSetsToDisk, path, sets)); +} + +void SendPersistedSets( + base::OnceCallback<void(base::OnceCallback<void(const std::string&)>, + const std::string&)> send_sets, + const base::FilePath& path, + const std::string& sets) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + std::move(send_sets).Run(base::BindOnce(&OnGetUpdatedSets, path), sets); +} + +} // namespace + +// static +FirstPartySetsUtil* FirstPartySetsUtil::GetInstance() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + static base::NoDestructor<FirstPartySetsUtil> instance; + return instance.get(); +} + +void FirstPartySetsUtil::SendAndUpdatePersistedSets( + const base::FilePath& user_data_dir, + base::OnceCallback<void(base::OnceCallback<void(const std::string&)>, + const std::string&)> send_sets) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DCHECK(!user_data_dir.empty()); + const base::FilePath persisted_sets_path = + user_data_dir.Append(kPersistedFirstPartySetsFileName); + + base::ThreadPool::PostTaskAndReplyWithResult( + FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT}, + base::BindOnce(&LoadSetsFromDisk, persisted_sets_path), + base::BindOnce(&SendPersistedSets, std::move(send_sets), + persisted_sets_path)); +} \ No newline at end of file
diff --git a/chrome/browser/first_party_sets/first_party_sets_util.h b/chrome/browser/first_party_sets/first_party_sets_util.h new file mode 100644 index 0000000..30b6d54 --- /dev/null +++ b/chrome/browser/first_party_sets/first_party_sets_util.h
@@ -0,0 +1,39 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_FIRST_PARTY_SETS_FIRST_PARTY_SETS_UTIL_H_ +#define CHROME_BROWSER_FIRST_PARTY_SETS_FIRST_PARTY_SETS_UTIL_H_ + +#include <string> + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/no_destructor.h" + +class FirstPartySetsUtil { + public: + static FirstPartySetsUtil* GetInstance(); + + // Note that FirstPartySetsUtil is a singleton that's never destroyed. + ~FirstPartySetsUtil() = delete; + FirstPartySetsUtil(const FirstPartySetsUtil&) = delete; + FirstPartySetsUtil& operator=(const FirstPartySetsUtil&) = delete; + + // This method reads the persisted First-Party Sets from the file under + // `user_data_dir`, then invokes `send_sets` with the read data (could be + // empty) and with a callback that should eventually be invoked with the + // current First-Party Sets (encoded as a string). The callback writes the + // current First-Party Sets to the file in `user_data_dir`. + void SendAndUpdatePersistedSets( + const base::FilePath& user_data_dir, + base::OnceCallback<void(base::OnceCallback<void(const std::string&)>, + const std::string&)> send_sets); + + private: + friend class base::NoDestructor<FirstPartySetsUtil>; + + FirstPartySetsUtil() = default; +}; + +#endif // CHROME_BROWSER_FIRST_PARTY_SETS_FIRST_PARTY_SETS_UTIL_H_
diff --git a/chrome/browser/first_party_sets/first_party_sets_util_unittest.cc b/chrome/browser/first_party_sets/first_party_sets_util_unittest.cc new file mode 100644 index 0000000..05efadd --- /dev/null +++ b/chrome/browser/first_party_sets/first_party_sets_util_unittest.cc
@@ -0,0 +1,82 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/first_party_sets/first_party_sets_util.h" + +#include <string> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/json/json_reader.h" +#include "base/run_loop.h" +#include "base/sequence_checker.h" +#include "base/test/bind.h" +#include "content/public/test/browser_task_environment.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +class FirstPartySetsUtilTest : public ::testing::Test { + public: + FirstPartySetsUtilTest() { + CHECK(scoped_dir_.CreateUniqueTempDir()); + CHECK(PathExists(scoped_dir_.GetPath())); + + persisted_sets_path_ = scoped_dir_.GetPath().Append( + FILE_PATH_LITERAL("persisted_first_party_sets.json")); + } + + protected: + base::ScopedTempDir scoped_dir_; + base::FilePath persisted_sets_path_; + // Need to satisfy DCHECK_CURRENTLY_ON(BrowserThread::UI). + content::BrowserTaskEnvironment env_; +}; + +TEST_F(FirstPartySetsUtilTest, SendAndUpdatePersistedSets_FileNotExist) { + SEQUENCE_CHECKER(sequence_checker); + const std::string expected_updated_sets = "updated first party sets"; + + FirstPartySetsUtil::GetInstance()->SendAndUpdatePersistedSets( + scoped_dir_.GetPath(), + /*send_sets=*/ + base::BindLambdaForTesting( + [&](base::OnceCallback<void(const std::string&)> callback, + const std::string& got) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker); + EXPECT_EQ(got, ""); + std::move(callback).Run(expected_updated_sets); + })); + + env_.RunUntilIdle(); + + std::string got; + ASSERT_TRUE(base::ReadFileToString(persisted_sets_path_, &got)); + EXPECT_EQ(got, expected_updated_sets); +} + +TEST_F(FirstPartySetsUtilTest, SendAndUpdatePersistedSets) { + SEQUENCE_CHECKER(sequence_checker); + const std::string expected_read_sets = "persisted first party sets"; + const std::string expected_updated_sets = "updated first party sets"; + + ASSERT_TRUE(base::WriteFile(persisted_sets_path_, expected_read_sets)); + + FirstPartySetsUtil::GetInstance()->SendAndUpdatePersistedSets( + scoped_dir_.GetPath(), + /*send_sets=*/ + base::BindLambdaForTesting( + [&](base::OnceCallback<void(const std::string&)> callback, + const std::string& got) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker); + EXPECT_EQ(got, expected_read_sets); + std::move(callback).Run(expected_updated_sets); + })); + + env_.RunUntilIdle(); + + std::string got; + ASSERT_TRUE(base::ReadFileToString(persisted_sets_path_, &got)); + EXPECT_EQ(got, expected_updated_sets); +} \ No newline at end of file
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json index ec74a13..298608a 100644 --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json
@@ -4349,12 +4349,12 @@ { "name": "paint-preview-demo", "owners": [ "ckitagawa", "fredmello", "yashard" ], - "expiry_milestone": 98 + "expiry_milestone": 101 }, { "name": "paint-preview-startup", "owners": [ "ckitagawa", "fredmello", "yashard" ], - "expiry_milestone": 98 + "expiry_milestone": 101 }, { "name": "partitioned-cookies",
diff --git a/chrome/browser/login_detection/password_store_sites.cc b/chrome/browser/login_detection/password_store_sites.cc index 711ff4fe..912c533 100644 --- a/chrome/browser/login_detection/password_store_sites.cc +++ b/chrome/browser/login_detection/password_store_sites.cc
@@ -38,10 +38,15 @@ void PasswordStoreSites::OnLoginsRetained( password_manager::PasswordStoreInterface* /*store*/, - const std::vector<password_manager::PasswordForm>& /*retained_passwords*/) { + const std::vector<password_manager::PasswordForm>& retained_passwords) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - // Fetch the login list again. - password_store_->GetAllLogins(weak_ptr_factory_.GetWeakPtr()); + password_sites_ = std::set<std::string>(); + for (const auto& entry : retained_passwords) { + if (!entry.url.SchemeIsHTTPOrHTTPS()) { + continue; + } + password_sites_->insert(GetSiteNameForURL(entry.url)); + } } void PasswordStoreSites::OnGetPasswordStoreResults(
diff --git a/chrome/browser/media/webrtc/desktop_capture_access_handler.cc b/chrome/browser/media/webrtc/desktop_capture_access_handler.cc index a7f7870..e5f7db6 100644 --- a/chrome/browser/media/webrtc/desktop_capture_access_handler.cc +++ b/chrome/browser/media/webrtc/desktop_capture_access_handler.cc
@@ -69,6 +69,13 @@ namespace { +// Currently, loopback audio capture is only supported on Windows and ChromeOS. +#if defined(USE_CRAS) || defined(OS_WIN) +constexpr bool kIsLoopbackAudioSupported = true; +#else +constexpr bool kIsLoopbackAudioSupported = false; +#endif + // Helper to get title of the calling application shown in the screen capture // notification. std::u16string GetApplicationTitle(content::WebContents* web_contents, @@ -110,12 +117,12 @@ const extensions::AppWindowRegistry::AppWindowList& window_list = extensions::AppWindowRegistry::Get(web_contents->GetBrowserContext()) ->app_windows(); - for (auto iter = window_list.begin(); iter != window_list.end(); ++iter) { - if ((*iter)->web_contents() == web_contents) - return (*iter)->GetNativeWindow(); + for (extensions::AppWindow* app_window : window_list) { + if (app_window->web_contents() == web_contents) + return app_window->GetNativeWindow(); } - return NULL; + return nullptr; } #endif @@ -137,6 +144,30 @@ } } +// Checks whether audio should be captured for the given |media_id| and +// |request|. +bool ShouldCaptureAudio(const content::DesktopMediaID& media_id, + const content::MediaStreamRequest& request) { + // This value is essentially from the checkbox on picker window, so it + // corresponds to user permission. + const bool audio_permitted = media_id.audio_share; + + // This value is essentially from whether getUserMedia requests audio stream. + const bool audio_requested = + request.audio_type == + blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE; + + // This value shows for a given capture type, whether the system or our code + // can support audio sharing. Currently audio is only supported for screen and + // tab/webcontents capture streams. + const bool audio_supported = + (media_id.type == content::DesktopMediaID::TYPE_SCREEN && + kIsLoopbackAudioSupported) || + media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS; + + return audio_permitted && audio_requested && audio_supported; +} + } // namespace // Holds pending request information so that we display one picker UI at a time @@ -176,21 +207,12 @@ const content::MediaStreamRequest& request, content::MediaResponseCallback callback, const extensions::Extension* extension) { - blink::MediaStreamDevices devices; - std::unique_ptr<content::MediaStreamUI> ui; - DCHECK_EQ(request.video_type, blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE); UpdateExtensionTrusted(request, extension); - bool loopback_audio_supported = false; -#if defined(USE_CRAS) || defined(OS_WIN) - // Currently loopback audio capture is supported only on Windows and ChromeOS. - loopback_audio_supported = true; -#endif - - bool screen_capture_enabled = + const bool screen_capture_enabled = base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnableUserMediaScreenCapturing) || IsExtensionAllowedForScreenCapture(extension) || @@ -201,110 +223,70 @@ base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kAllowHttpScreenCapture); - // If basic conditions (screen capturing is enabled and origin is secure) - // aren't fulfilled, we'll use "invalid state" as result. Otherwise, we set - // it after checking permission. - // TODO(grunell): It would be good to change this result for something else, - // probably a new one. - blink::mojom::MediaStreamRequestResult result = - blink::mojom::MediaStreamRequestResult::INVALID_STATE; + if (!screen_capture_enabled || !origin_is_secure) { + std::move(callback).Run( + blink::MediaStreamDevices(), + blink::mojom::MediaStreamRequestResult::INVALID_STATE, nullptr); + return; + } - // Approve request only when the following conditions are met: - // 1. Screen capturing is enabled via command line switch or white-listed for - // the given origin. - // 2. Request comes from a page with a secure origin or from an extension. - if (screen_capture_enabled && origin_is_secure) { - // Get title of the calling application prior to showing the message box. - // chrome::ShowQuestionMessageBox() starts a nested run loop which may - // allow |web_contents| to be destroyed on the UI thread before the messag - // box is closed. See http://crbug.com/326690. - std::u16string application_title = - GetApplicationTitle(web_contents, extension); -#if !defined(OS_ANDROID) - gfx::NativeWindow parent_window = - FindParentWindowForWebContents(web_contents); -#else - gfx::NativeWindow parent_window = NULL; -#endif + if (!IsRequestApproved(web_contents, request, extension)) { + std::move(callback).Run( + blink::MediaStreamDevices(), + blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, nullptr); + return; + } - // Some extensions do not require user approval, because they provide their - // own user approval UI. - bool is_approved = IsDefaultApproved(extension) || - IsDefaultApproved(request.security_origin); - if (!is_approved) { - std::u16string application_name = - base::UTF8ToUTF16(request.security_origin.spec()); - if (extension) - application_name = base::UTF8ToUTF16(extension->name()); - std::u16string confirmation_text = l10n_util::GetStringFUTF16( - request.audio_type == blink::mojom::MediaStreamType::NO_SERVICE - ? IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TEXT - : IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT, - application_name); - chrome::MessageBoxResult mb_result = chrome::ShowQuestionMessageBoxSync( - parent_window, - l10n_util::GetStringFUTF16( - IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE, application_name), - confirmation_text); - is_approved = (mb_result == chrome::MESSAGE_BOX_RESULT_YES); - } + if (!content::WebContents::FromRenderFrameHost( + content::RenderFrameHost::FromID(request.render_process_id, + request.render_frame_id))) { + std::move(callback).Run( + blink::MediaStreamDevices(), + blink::mojom::MediaStreamRequestResult::INVALID_STATE, nullptr); + return; + } - if (is_approved) { - content::DesktopMediaID screen_id; #if BUILDFLAG(IS_CHROMEOS_ASH) - screen_id = content::DesktopMediaID::RegisterNativeWindow( + const content::DesktopMediaID screen_id = + content::DesktopMediaID::RegisterNativeWindow( content::DesktopMediaID::TYPE_SCREEN, primary_root_window_for_testing_ ? primary_root_window_for_testing_ : ash::Shell::Get()->GetPrimaryRootWindow()); - if (policy::DlpContentManager::Get()->IsScreenCaptureRestricted( - screen_id)) { - std::move(callback).Run( - devices, blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, - std::move(ui)); - return; - } + if (policy::DlpContentManager::Get()->IsScreenCaptureRestricted(screen_id)) { + std::move(callback).Run( + blink::MediaStreamDevices(), + blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, nullptr); + return; + } #else // BUILDFLAG(IS_CHROMEOS_ASH) - screen_id = content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, - webrtc::kFullDesktopScreenId); + const content::DesktopMediaID screen_id = content::DesktopMediaID( + content::DesktopMediaID::TYPE_SCREEN, webrtc::kFullDesktopScreenId); #endif // !BUILDFLAG(IS_CHROMEOS_ASH) - bool capture_audio = - (request.audio_type == - blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE && - loopback_audio_supported); + const bool capture_audio = + (request.audio_type == + blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE && + kIsLoopbackAudioSupported); - // Determine if the extension is required to display a notification. - const bool display_notification = - display_notification_ && ShouldDisplayNotification(extension) && - !HasNotificationExemption(request.security_origin); + // Determine if the extension is required to display a notification. + const bool display_notification = + display_notification_ && ShouldDisplayNotification(extension) && + !HasNotificationExemption(request.security_origin); - if (!content::WebContents::FromRenderFrameHost( - content::RenderFrameHost::FromID(request.render_process_id, - request.render_frame_id))) { - std::move(callback).Run( - devices, blink::mojom::MediaStreamRequestResult::INVALID_STATE, - std::move(ui)); - return; - } - ui = GetDevicesForDesktopCapture( - web_contents, url::Origin::Create(request.security_origin), &devices, - screen_id, blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE, - blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE, - capture_audio, request.disable_local_echo, - request.region_capture_capable, display_notification, - application_title, application_title); - DCHECK(!devices.empty()); - } + const std::u16string application_title = + GetApplicationTitle(web_contents, extension); - // The only case when devices can be empty is if the user has denied - // permission. - result = devices.empty() - ? blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED - : blink::mojom::MediaStreamRequestResult::OK; - } + blink::MediaStreamDevices devices; + std::unique_ptr<content::MediaStreamUI> ui; + ui = GetDevicesForDesktopCapture(request, web_contents, screen_id, + capture_audio, request.disable_local_echo, + display_notification, application_title, + &devices); + DCHECK(!devices.empty()); - std::move(callback).Run(devices, result, std::move(ui)); + std::move(callback).Run(devices, blink::mojom::MediaStreamRequestResult::OK, + std::move(ui)); } bool DesktopCaptureAccessHandler::IsDefaultApproved( @@ -322,6 +304,36 @@ return url.spec() == chrome::kChromeUIFeedbackURL; } +bool DesktopCaptureAccessHandler::IsRequestApproved( + content::WebContents* web_contents, + const content::MediaStreamRequest& request, + const extensions::Extension* extension) { +#if !defined(OS_ANDROID) + gfx::NativeWindow parent_window = + FindParentWindowForWebContents(web_contents); +#else + gfx::NativeWindow parent_window = nullptr; +#endif + + if (IsDefaultApproved(extension) || + IsDefaultApproved(request.security_origin)) + return true; + + const std::u16string application_name = base::UTF8ToUTF16( + extension ? extension->name() : request.security_origin.spec()); + const std::u16string confirmation_text = l10n_util::GetStringFUTF16( + request.audio_type == blink::mojom::MediaStreamType::NO_SERVICE + ? IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TEXT + : IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT, + application_name); + const chrome::MessageBoxResult mb_result = chrome::ShowQuestionMessageBoxSync( + parent_window, + l10n_util::GetStringFUTF16(IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE, + application_name), + confirmation_text); + return mb_result == chrome::MESSAGE_BOX_RESULT_YES; +} + bool DesktopCaptureAccessHandler::SupportsStreamType( content::WebContents* web_contents, const blink::mojom::MediaStreamType type, @@ -344,14 +356,12 @@ content::MediaResponseCallback callback, const extensions::Extension* extension) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - blink::MediaStreamDevices devices; - std::unique_ptr<content::MediaStreamUI> ui; if (request.video_type != blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE) { std::move(callback).Run( - devices, blink::mojom::MediaStreamRequestResult::INVALID_STATE, - std::move(ui)); + blink::MediaStreamDevices(), + blink::mojom::MediaStreamRequestResult::INVALID_STATE, nullptr); return; } @@ -361,8 +371,8 @@ if (allowed_capture_level == AllowedScreenCaptureLevel::kDisallowed) { std::move(callback).Run( - devices, blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, - std::move(ui)); + blink::MediaStreamDevices(), + blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, nullptr); return; } @@ -377,8 +387,8 @@ if (request.requested_video_device_id.empty()) { if (allowed_capture_level < AllowedScreenCaptureLevel::kDesktop) { std::move(callback).Run( - devices, blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, - std::move(ui)); + blink::MediaStreamDevices(), + blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, nullptr); return; } #if defined(OS_MAC) @@ -406,7 +416,8 @@ content::RenderFrameHost::FromID(request.render_process_id, request.render_frame_id)); content::RenderFrameHost* const main_frame = - web_contents_for_stream ? web_contents_for_stream->GetMainFrame() : NULL; + web_contents_for_stream ? web_contents_for_stream->GetMainFrame() + : nullptr; if (main_frame) { media_id = content::DesktopStreamsRegistry::GetInstance()->RequestMediaForStreamId( @@ -419,23 +430,23 @@ // Received invalid device id. if (media_id.type == content::DesktopMediaID::TYPE_NONE) { std::move(callback).Run( - devices, blink::mojom::MediaStreamRequestResult::INVALID_STATE, - std::move(ui)); + blink::MediaStreamDevices(), + blink::mojom::MediaStreamRequestResult::INVALID_STATE, nullptr); return; } if (!IsMediaTypeAllowed(allowed_capture_level, media_id.type)) { std::move(callback).Run( - devices, blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, - std::move(ui)); + blink::MediaStreamDevices(), + blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, nullptr); return; } #if BUILDFLAG(IS_CHROMEOS_ASH) { if (policy::DlpContentManager::Get()->IsScreenCaptureRestricted(media_id)) { std::move(callback).Run( - devices, blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, - std::move(ui)); + blink::MediaStreamDevices(), + blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, nullptr); return; } } @@ -458,48 +469,18 @@ media_id.web_contents_id.render_process_id, media_id.web_contents_id.main_render_frame_id))) { std::move(callback).Run( - devices, blink::mojom::MediaStreamRequestResult::TAB_CAPTURE_FAILURE, - std::move(ui)); + blink::MediaStreamDevices(), + blink::mojom::MediaStreamRequestResult::TAB_CAPTURE_FAILURE, nullptr); return; } - bool loopback_audio_supported = false; -#if defined(USE_CRAS) || defined(OS_WIN) - // Currently loopback audio capture is supported only on Windows and ChromeOS. - loopback_audio_supported = true; -#endif - - // This value essentially from the checkbox on picker window, so it - // corresponds to user permission. - const bool audio_permitted = media_id.audio_share; - - // This value essentially from whether getUserMedia requests audio stream. - const bool audio_requested = - request.audio_type == - blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE; - - // This value shows for a given capture type, whether the system or our code - // can support audio sharing. Currently audio is only supported for screen and - // tab/webcontents capture streams. - const bool audio_supported = - (media_id.type == content::DesktopMediaID::TYPE_SCREEN && - loopback_audio_supported) || - media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS; - - const bool capture_audio = - audio_permitted && audio_requested && audio_supported; - - // Determine if the extension is required to display a notification. - const bool display_notification = - display_notification_ && ShouldDisplayNotification(extension); - + blink::MediaStreamDevices devices; + std::unique_ptr<content::MediaStreamUI> ui; ui = GetDevicesForDesktopCapture( - web_contents, url::Origin::Create(request.security_origin), &devices, - media_id, blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE, - blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE, capture_audio, - request.disable_local_echo, request.region_capture_capable, - display_notification, GetApplicationTitle(web_contents, extension), - GetApplicationTitle(web_contents, extension)); + request, web_contents, media_id, ShouldCaptureAudio(media_id, request), + request.disable_local_echo, + (display_notification_ && ShouldDisplayNotification(extension)), + GetApplicationTitle(web_contents, extension), &devices); UpdateExtensionTrusted(request, extension); std::move(callback).Run(devices, blink::mojom::MediaStreamRequestResult::OK, std::move(ui)); @@ -511,6 +492,8 @@ content::MediaResponseCallback callback, const extensions::Extension* extension) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DCHECK_EQ(request.video_type, + blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE); std::unique_ptr<DesktopMediaPicker> picker; @@ -605,10 +588,8 @@ picker_params.app_name = GetApplicationTitle(web_contents, pending_request.extension); picker_params.target_name = picker_params.app_name; - picker_params.request_audio = (pending_request.request.audio_type == - blink::mojom::MediaStreamType::NO_SERVICE) - ? false - : true; + picker_params.request_audio = pending_request.request.audio_type != + blink::mojom::MediaStreamType::NO_SERVICE; picker_params.restricted_by_policy = (capture_level != AllowedScreenCaptureLevel::kUnrestricted); pending_request.picker->Show(picker_params, std::move(source_lists), @@ -636,33 +617,44 @@ } PendingAccessRequest& pending_request = *queue.front(); - blink::MediaStreamDevices devices; - blink::mojom::MediaStreamRequestResult request_result = - blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED; - const extensions::Extension* extension = pending_request.extension; - std::unique_ptr<content::MediaStreamUI> ui; + if (media_id.is_null()) { - request_result = blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED; - } else { - request_result = blink::mojom::MediaStreamRequestResult::OK; - // Determine if the extension is required to display a notification. - const bool display_notification = - display_notification_ && ShouldDisplayNotification(extension); - ui = GetDevicesForDesktopCapture( - web_contents, - url::Origin::Create(pending_request.request.security_origin), &devices, - media_id, pending_request.request.video_type, - pending_request.request.audio_type, media_id.audio_share, - pending_request.request.disable_local_echo, - pending_request.request.region_capture_capable, display_notification, - GetApplicationTitle(web_contents, extension), - GetApplicationTitle(web_contents, extension)); + std::move(pending_request.callback) + .Run(blink::MediaStreamDevices(), + blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, + nullptr); + + queue.pop_front(); + if (!queue.empty()) + ProcessQueuedAccessRequest(queue, web_contents); + return; } - std::move(pending_request.callback) - .Run(devices, request_result, std::move(ui)); - queue.pop_front(); +#if BUILDFLAG(IS_CHROMEOS_ASH) + if (policy::DlpContentManager::Get()->IsScreenCaptureRestricted(media_id)) { + std::move(pending_request.callback) + .Run(blink::MediaStreamDevices(), + blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, + nullptr); + queue.pop_front(); + if (!queue.empty()) + ProcessQueuedAccessRequest(queue, web_contents); + return; + } +#endif // BUILDFLAG(IS_CHROMEOS_ASH) + + const extensions::Extension* extension = pending_request.extension; + blink::MediaStreamDevices devices; + std::unique_ptr<content::MediaStreamUI> ui = GetDevicesForDesktopCapture( + pending_request.request, web_contents, media_id, media_id.audio_share, + pending_request.request.disable_local_echo, + display_notification_ && ShouldDisplayNotification(extension), + GetApplicationTitle(web_contents, extension), &devices); + std::move(pending_request.callback) + .Run(devices, blink::mojom::MediaStreamRequestResult::OK, std::move(ui)); + + queue.pop_front(); if (!queue.empty()) ProcessQueuedAccessRequest(queue, web_contents); }
diff --git a/chrome/browser/media/webrtc/desktop_capture_access_handler.h b/chrome/browser/media/webrtc/desktop_capture_access_handler.h index a21e0b2..a72f25f9 100644 --- a/chrome/browser/media/webrtc/desktop_capture_access_handler.h +++ b/chrome/browser/media/webrtc/desktop_capture_access_handler.h
@@ -88,6 +88,13 @@ // Currently chrome://feedback/ is default approved. static bool IsDefaultApproved(const GURL& url); + // Returns whether the request is approved or not. Some extensions do not + // require user approval, because they provide their own user approval UI. For + // others, shows a message box and asks for user approval. + static bool IsRequestApproved(content::WebContents* web_contents, + const content::MediaStreamRequest& request, + const extensions::Extension* extension); + // WebContentsCollection::Observer: void WebContentsDestroyed(content::WebContents* web_contents) override;
diff --git a/chrome/browser/media/webrtc/desktop_capture_access_handler_unittest.cc b/chrome/browser/media/webrtc/desktop_capture_access_handler_unittest.cc index fe705a9..15c040f 100644 --- a/chrome/browser/media/webrtc/desktop_capture_access_handler_unittest.cc +++ b/chrome/browser/media/webrtc/desktop_capture_access_handler_unittest.cc
@@ -41,10 +41,13 @@ #include "chrome/common/chrome_features.h" #endif +constexpr char kOrigin[] = "https://origin/"; +constexpr char kComponentExtension[] = "Component Extension"; + class DesktopCaptureAccessHandlerTest : public ChromeRenderViewHostTestHarness { public: - DesktopCaptureAccessHandlerTest() {} - ~DesktopCaptureAccessHandlerTest() override {} + DesktopCaptureAccessHandlerTest() = default; + ~DesktopCaptureAccessHandlerTest() override = default; void SetUp() override { ChromeRenderViewHostTestHarness::SetUp(); @@ -108,7 +111,7 @@ request_audio ? blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE : blink::mojom::MediaStreamType::NO_SERVICE; content::MediaStreamRequest request( - 0, 0, 0, GURL("http://origin/"), false, request_type, std::string(), + 0, 0, 0, GURL(kOrigin), false, request_type, std::string(), std::string(), audio_type, blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE, /*disable_local_echo=*/false, @@ -210,9 +213,9 @@ true /* cancelled */}}; picker_factory_->SetTestFlags(test_flags, base::size(test_flags)); content::MediaStreamRequest request( - render_process_id, render_frame_id, page_request_id, - GURL("http://origin/"), false, blink::MEDIA_DEVICE_UPDATE, std::string(), - std::string(), blink::mojom::MediaStreamType::NO_SERVICE, stream_type, + render_process_id, render_frame_id, page_request_id, GURL(kOrigin), false, + blink::MEDIA_DEVICE_UPDATE, std::string(), std::string(), + blink::mojom::MediaStreamType::NO_SERVICE, stream_type, /*disable_local_echo=*/false, /*request_pan_tilt_zoom_permission=*/false); content::MediaResponseCallback callback; access_handler_->HandleRequest(web_contents(), request, std::move(callback), @@ -242,8 +245,8 @@ true /* cancelled */}}; picker_factory_->SetTestFlags(test_flags, base::size(test_flags)); content::MediaStreamRequest request( - 0, 0, 0, GURL("http://origin/"), false, blink::MEDIA_DEVICE_UPDATE, - std::string(), std::string(), blink::mojom::MediaStreamType::NO_SERVICE, + 0, 0, 0, GURL(kOrigin), false, blink::MEDIA_DEVICE_UPDATE, std::string(), + std::string(), blink::mojom::MediaStreamType::NO_SERVICE, blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE, /*disable_local_echo=*/false, /*request_pan_tilt_zoom_permission=*/false); content::MediaResponseCallback callback; @@ -280,9 +283,9 @@ blink::mojom::MediaStreamRequestResult result; blink::MediaStreamDevices devices; base::RunLoop wait_loop[kTestFlagCount]; - for (size_t i = 0; i < kTestFlagCount; ++i) { + for (base::RunLoop& loop : wait_loop) { content::MediaStreamRequest request( - 0, 0, 0, GURL("http://origin/"), false, blink::MEDIA_DEVICE_UPDATE, + 0, 0, 0, GURL(kOrigin), false, blink::MEDIA_DEVICE_UPDATE, std::string(), std::string(), blink::mojom::MediaStreamType::NO_SERVICE, blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE, /*disable_local_echo=*/false, @@ -298,7 +301,7 @@ *devices_result = devices; wait_loop->Quit(); }, - &wait_loop[i], &result, &devices); + &loop, &result, &devices); access_handler_->HandleRequest(web_contents(), request, std::move(callback), nullptr /* extension */); } @@ -327,7 +330,7 @@ TEST_F(DesktopCaptureAccessHandlerTest, GenerateStreamSuccess) { blink::mojom::MediaStreamRequestResult result; blink::MediaStreamDevices devices; - const GURL origin("http://origin/"); + const GURL origin(kOrigin); const std::string id = content::DesktopStreamsRegistry::GetInstance()->RegisterStream( web_contents()->GetMainFrame()->GetProcess()->GetID(), @@ -351,7 +354,7 @@ base::CommandLine::ForCurrentProcess()->AppendSwitch( switches::kAllowHttpScreenCapture); - extensions::ExtensionBuilder extensionBuilder("Component Extension"); + extensions::ExtensionBuilder extensionBuilder(kComponentExtension); extensionBuilder.SetLocation(extensions::mojom::ManifestLocation::kComponent); auto extension = extensionBuilder.Build(); @@ -364,10 +367,10 @@ blink::mojom::MediaStreamRequestResult result; blink::MediaStreamDevices devices; - const GURL origin("http://origin/"); ProcessGenerateStreamRequest(/*requested_video_device_id=*/std::string(), - origin, extension.get(), &result, &devices); + GURL(kOrigin), extension.get(), &result, + &devices); EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result); EXPECT_EQ(1u, devices.size()); @@ -377,7 +380,7 @@ TEST_F(DesktopCaptureAccessHandlerTest, ScreenCaptureAccessDlpRestricted) { // Setup Data Leak Prevention restriction. policy::MockDlpContentManager mock_dlp_content_manager; - policy::ScopedDlpContentManagerForTesting scoped_dlp_content_manager_( + policy::ScopedDlpContentManagerForTesting scoped_dlp_content_manager( &mock_dlp_content_manager); EXPECT_CALL(mock_dlp_content_manager, IsScreenCaptureRestricted(testing::_)) .Times(1) @@ -385,10 +388,8 @@ base::CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableUserMediaScreenCapturing); - base::CommandLine::ForCurrentProcess()->AppendSwitch( - switches::kAllowHttpScreenCapture); - extensions::ExtensionBuilder extensionBuilder("Component Extension"); + extensions::ExtensionBuilder extensionBuilder(kComponentExtension); extensionBuilder.SetLocation(extensions::mojom::ManifestLocation::kComponent); auto extension = extensionBuilder.Build(); @@ -399,10 +400,10 @@ blink::mojom::MediaStreamRequestResult result; blink::MediaStreamDevices devices; - const GURL origin("http://origin/"); ProcessGenerateStreamRequest(/*requested_video_device_id=*/std::string(), - origin, extension.get(), &result, &devices); + GURL(kOrigin), extension.get(), &result, + &devices); EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, result); EXPECT_EQ(0u, devices.size()); @@ -411,29 +412,49 @@ TEST_F(DesktopCaptureAccessHandlerTest, GenerateStreamDlpRestricted) { // Setup Data Leak Prevention restriction. policy::MockDlpContentManager mock_dlp_content_manager; - policy::ScopedDlpContentManagerForTesting scoped_dlp_content_manager_( + policy::ScopedDlpContentManagerForTesting scoped_dlp_content_manager( &mock_dlp_content_manager); EXPECT_CALL(mock_dlp_content_manager, IsScreenCaptureRestricted(testing::_)) .Times(1) .WillOnce(testing::Return(true)); - blink::mojom::MediaStreamRequestResult result; - blink::MediaStreamDevices devices; - const GURL origin("http://origin/"); const std::string id = content::DesktopStreamsRegistry::GetInstance()->RegisterStream( web_contents()->GetMainFrame()->GetProcess()->GetID(), web_contents()->GetMainFrame()->GetRoutingID(), - url::Origin::Create(origin), + url::Origin::Create(GURL(kOrigin)), content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, content::DesktopMediaID::kFakeId), /*extension_name=*/"", content::DesktopStreamRegistryType::kRegistryStreamTypeDesktop); + blink::mojom::MediaStreamRequestResult result = + blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED; + blink::MediaStreamDevices devices; - ProcessGenerateStreamRequest(id, origin, /*extension=*/nullptr, &result, - &devices); + ProcessGenerateStreamRequest(id, GURL(kOrigin), /*extension=*/nullptr, + &result, &devices); EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, result); EXPECT_EQ(0u, devices.size()); } + +TEST_F(DesktopCaptureAccessHandlerTest, ChangeSourceDlpRestricted) { + // Setup Data Leak Prevention restriction. + policy::MockDlpContentManager mock_dlp_content_manager; + policy::ScopedDlpContentManagerForTesting scoped_dlp_content_manager( + &mock_dlp_content_manager); + EXPECT_CALL(mock_dlp_content_manager, IsScreenCaptureRestricted(testing::_)) + .Times(1) + .WillOnce(testing::Return(true)); + + blink::mojom::MediaStreamRequestResult result = + blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED; + blink::MediaStreamDevices devices; + ProcessDeviceUpdateRequest( + content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, + content::DesktopMediaID::kFakeId), + &result, &devices, blink::MEDIA_DEVICE_UPDATE, /*request audio=*/false); + EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, result); + EXPECT_EQ(0u, devices.size()); +} #endif
diff --git a/chrome/browser/media/webrtc/desktop_capture_devices_util.cc b/chrome/browser/media/webrtc/desktop_capture_devices_util.cc index 61406e4a..c90d2ba 100644 --- a/chrome/browser/media/webrtc/desktop_capture_devices_util.cc +++ b/chrome/browser/media/webrtc/desktop_capture_devices_util.cc
@@ -7,6 +7,7 @@ #include <string> #include <utility> +#include "base/check_op.h" #include "base/strings/strcat.h" #include "base/strings/string_util.h" #include "base/unguessable_token.h" @@ -127,73 +128,36 @@ display_surface, logical_surface, cursor, std::move(capture_handle)); } -std::u16string GetStopSharingUIString( - const std::u16string& application_title, - const std::u16string& registered_extension_name, - bool capture_audio, - content::DesktopMediaID::Type capture_type) { - if (!capture_audio) { - if (application_title == registered_extension_name) { - switch (capture_type) { - case content::DesktopMediaID::TYPE_SCREEN: - return l10n_util::GetStringFUTF16( - IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT, application_title); - case content::DesktopMediaID::TYPE_WINDOW: - return l10n_util::GetStringFUTF16( - IDS_MEDIA_WINDOW_CAPTURE_NOTIFICATION_TEXT, application_title); - case content::DesktopMediaID::TYPE_WEB_CONTENTS: - return l10n_util::GetStringFUTF16( - IDS_MEDIA_TAB_CAPTURE_NOTIFICATION_TEXT, application_title); - case content::DesktopMediaID::TYPE_NONE: - NOTREACHED(); - } - } else { - switch (capture_type) { - case content::DesktopMediaID::TYPE_SCREEN: - return l10n_util::GetStringFUTF16( - IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT_DELEGATED, - registered_extension_name, application_title); - case content::DesktopMediaID::TYPE_WINDOW: - return l10n_util::GetStringFUTF16( - IDS_MEDIA_WINDOW_CAPTURE_NOTIFICATION_TEXT_DELEGATED, - registered_extension_name, application_title); - case content::DesktopMediaID::TYPE_WEB_CONTENTS: - return l10n_util::GetStringFUTF16( - IDS_MEDIA_TAB_CAPTURE_NOTIFICATION_TEXT_DELEGATED, - registered_extension_name, application_title); - case content::DesktopMediaID::TYPE_NONE: - NOTREACHED(); - } +std::u16string GetNotificationText(const std::u16string& application_title, + bool capture_audio, + content::DesktopMediaID::Type capture_type) { + if (capture_audio) { + switch (capture_type) { + case content::DesktopMediaID::TYPE_SCREEN: + return l10n_util::GetStringFUTF16( + IDS_MEDIA_SCREEN_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT, + application_title); + case content::DesktopMediaID::TYPE_WEB_CONTENTS: + return l10n_util::GetStringFUTF16( + IDS_MEDIA_TAB_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT, + application_title); + case content::DesktopMediaID::TYPE_NONE: + case content::DesktopMediaID::TYPE_WINDOW: + NOTREACHED(); } - } else { // The case with audio - if (application_title == registered_extension_name) { - switch (capture_type) { - case content::DesktopMediaID::TYPE_SCREEN: - return l10n_util::GetStringFUTF16( - IDS_MEDIA_SCREEN_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT, - application_title); - case content::DesktopMediaID::TYPE_WEB_CONTENTS: - return l10n_util::GetStringFUTF16( - IDS_MEDIA_TAB_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT, - application_title); - case content::DesktopMediaID::TYPE_NONE: - case content::DesktopMediaID::TYPE_WINDOW: - NOTREACHED(); - } - } else { - switch (capture_type) { - case content::DesktopMediaID::TYPE_SCREEN: - return l10n_util::GetStringFUTF16( - IDS_MEDIA_SCREEN_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT_DELEGATED, - registered_extension_name, application_title); - case content::DesktopMediaID::TYPE_WEB_CONTENTS: - return l10n_util::GetStringFUTF16( - IDS_MEDIA_TAB_CAPTURE_WITH_AUDIO_NOTIFICATION_TEXT_DELEGATED, - registered_extension_name, application_title); - case content::DesktopMediaID::TYPE_NONE: - case content::DesktopMediaID::TYPE_WINDOW: - NOTREACHED(); - } + } else { + switch (capture_type) { + case content::DesktopMediaID::TYPE_SCREEN: + return l10n_util::GetStringFUTF16( + IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT, application_title); + case content::DesktopMediaID::TYPE_WINDOW: + return l10n_util::GetStringFUTF16( + IDS_MEDIA_WINDOW_CAPTURE_NOTIFICATION_TEXT, application_title); + case content::DesktopMediaID::TYPE_WEB_CONTENTS: + return l10n_util::GetStringFUTF16( + IDS_MEDIA_TAB_CAPTURE_NOTIFICATION_TEXT, application_title); + case content::DesktopMediaID::TYPE_NONE: + NOTREACHED(); } } return std::u16string(); @@ -241,51 +205,45 @@ } // namespace std::unique_ptr<content::MediaStreamUI> GetDevicesForDesktopCapture( + const content::MediaStreamRequest& request, content::WebContents* web_contents, - const url::Origin& capturer_origin, - blink::MediaStreamDevices* devices, const content::DesktopMediaID& media_id, - blink::mojom::MediaStreamType devices_video_type, - blink::mojom::MediaStreamType devices_audio_type, bool capture_audio, bool disable_local_echo, - bool region_capture_capable, bool display_notification, const std::u16string& application_title, - const std::u16string& registered_extension_name) { + blink::MediaStreamDevices* out_devices) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DVLOG(2) << __func__ << ": media_id " << media_id.ToString() << ", capture_audio " << capture_audio << ", disable_local_echo " << disable_local_echo << ", display_notification " << display_notification << ", application_title " - << application_title << ", extension_name " - << registered_extension_name; + << application_title; // Add selected desktop source to the list. - auto device = blink::MediaStreamDevice( - devices_video_type, media_id.ToString(), - DeviceName(web_contents, devices_video_type, media_id)); + blink::MediaStreamDevice device( + request.video_type, media_id.ToString(), + DeviceName(web_contents, request.video_type, media_id)); device.display_media_info = DesktopMediaIDToDisplayMediaInformation( - web_contents, capturer_origin, media_id); - devices->push_back(device); + web_contents, url::Origin::Create(request.security_origin), media_id); + out_devices->push_back(device); + if (capture_audio) { + DCHECK_NE(request.audio_type, blink::mojom::MediaStreamType::NO_SERVICE); + if (media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS) { content::WebContentsMediaCaptureId web_id = media_id.web_contents_id; web_id.disable_local_echo = disable_local_echo; - devices->push_back(blink::MediaStreamDevice( - devices_audio_type, web_id.ToString(), "Tab audio")); - } else if (disable_local_echo) { - // Use the special loopback device ID for system audio capture. - devices->push_back(blink::MediaStreamDevice( - devices_audio_type, - media::AudioDeviceDescription::kLoopbackWithMuteDeviceId, - "System Audio")); + out_devices->push_back(blink::MediaStreamDevice( + request.audio_type, web_id.ToString(), "Tab audio")); } else { // Use the special loopback device ID for system audio capture. - devices->push_back(blink::MediaStreamDevice( - devices_audio_type, - media::AudioDeviceDescription::kLoopbackInputDeviceId, + out_devices->push_back(blink::MediaStreamDevice( + request.audio_type, + (disable_local_echo + ? media::AudioDeviceDescription::kLoopbackWithMuteDeviceId + : media::AudioDeviceDescription::kLoopbackInputDeviceId), "System Audio")); } } @@ -299,17 +257,17 @@ capturer_id = web_contents->GetMainFrame()->GetGlobalId(); } notification_ui = TabSharingUI::Create( - capturer_id, media_id, application_title, region_capture_capable, + capturer_id, media_id, application_title, + request.region_capture_capable, /*favicons_used_for_switch_to_tab_button=*/false); } else { notification_ui = ScreenCaptureNotificationUI::Create( - GetStopSharingUIString(application_title, registered_extension_name, - capture_audio, media_id.type)); + GetNotificationText(application_title, capture_audio, media_id.type)); } } return MediaCaptureDevicesDispatcher::GetInstance() ->GetMediaStreamCaptureIndicator() - ->RegisterMediaStream(web_contents, *devices, std::move(notification_ui), - application_title); + ->RegisterMediaStream(web_contents, *out_devices, + std::move(notification_ui), application_title); }
diff --git a/chrome/browser/media/webrtc/desktop_capture_devices_util.h b/chrome/browser/media/webrtc/desktop_capture_devices_util.h index e0de62e2..cecba33 100644 --- a/chrome/browser/media/webrtc/desktop_capture_devices_util.h +++ b/chrome/browser/media/webrtc/desktop_capture_devices_util.h
@@ -13,21 +13,18 @@ #include "content/public/browser/web_contents.h" #include "third_party/blink/public/common/mediastream/media_stream_request.h" -// Helper to get list of media stream devices for desktop capture in |devices|. -// Registers to display notification if |display_notification| is true. -// Returns an instance of MediaStreamUI to be passed to content layer. +// Helper to get the list of media stream devices for desktop capture and store +// them in |out_devices|. Registers to display notification if +// |display_notification| is true. Returns an instance of MediaStreamUI to be +// passed to content layer. std::unique_ptr<content::MediaStreamUI> GetDevicesForDesktopCapture( + const content::MediaStreamRequest& request, content::WebContents* web_contents, - const url::Origin& capturer_origin, - blink::MediaStreamDevices* devices, const content::DesktopMediaID& media_id, - blink::mojom::MediaStreamType devices_video_type, - blink::mojom::MediaStreamType devices_audio_type, bool capture_audio, bool disable_local_echo, - bool region_capture_capable, bool display_notification, const std::u16string& application_title, - const std::u16string& registered_extension_name); + blink::MediaStreamDevices* out_devices); #endif // CHROME_BROWSER_MEDIA_WEBRTC_DESKTOP_CAPTURE_DEVICES_UTIL_H_
diff --git a/chrome/browser/media/webrtc/display_media_access_handler.cc b/chrome/browser/media/webrtc/display_media_access_handler.cc index f70a4ce..884b14aa 100644 --- a/chrome/browser/media/webrtc/display_media_access_handler.cc +++ b/chrome/browser/media/webrtc/display_media_access_handler.cc
@@ -403,13 +403,8 @@ (media_id.type == content::DesktopMediaID::TYPE_WEB_CONTENTS) && media_id.web_contents_id.disable_local_echo; ui = GetDevicesForDesktopCapture( - web_contents, - url::Origin::Create(pending_request.request.security_origin), &devices, - media_id, pending_request.request.video_type, - blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE, - media_id.audio_share, disable_local_echo, - pending_request.request.region_capture_capable, display_notification_, - application_title, application_title); + pending_request.request, web_contents, media_id, media_id.audio_share, + disable_local_echo, display_notification_, application_title, &devices); UpdateTarget(pending_request.request, media_id); }
diff --git a/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc b/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc index 8c57028..6e07e8a 100644 --- a/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc +++ b/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc
@@ -36,8 +36,8 @@ class DisplayMediaAccessHandlerTest : public ChromeRenderViewHostTestHarness { public: - DisplayMediaAccessHandlerTest() {} - ~DisplayMediaAccessHandlerTest() override {} + DisplayMediaAccessHandlerTest() = default; + ~DisplayMediaAccessHandlerTest() override = default; void SetUp() override { ChromeRenderViewHostTestHarness::SetUp(); @@ -264,11 +264,11 @@ .WillOnce([](const content::DesktopMediaID& media_id, const std::u16string& application_title, base::OnceCallback<void(bool)> callback) { - // Disallow - std::move(callback).Run(false); + std::move(callback).Run(/*should_proceed=*/false); }); - blink::mojom::MediaStreamRequestResult result; + blink::mojom::MediaStreamRequestResult result = + blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED; blink::MediaStreamDevices devices; ProcessRequest(media_id, &result, &devices, /*request_audio=*/false); @@ -514,8 +514,7 @@ .WillOnce([](const content::DesktopMediaID& media_id, const std::u16string& application_title, base::OnceCallback<void(bool)> callback) { - // Disallow - std::move(callback).Run(false); + std::move(callback).Run(/*should_proceed=*/false); }); ChangeSourceRequestTest(
diff --git a/chrome/browser/media/webrtc/tab_capture_access_handler_unittest.cc b/chrome/browser/media/webrtc/tab_capture_access_handler_unittest.cc index f92ef9c..c185961f 100644 --- a/chrome/browser/media/webrtc/tab_capture_access_handler_unittest.cc +++ b/chrome/browser/media/webrtc/tab_capture_access_handler_unittest.cc
@@ -120,7 +120,8 @@ web_contents(), /*extension_id=*/"", /*is_anonymous=*/false, GURL("http://origin/"), source, /*extension_name=*/"", web_contents()); - blink::mojom::MediaStreamRequestResult result; + blink::mojom::MediaStreamRequestResult result = + blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED; blink::MediaStreamDevices devices; ProcessRequest(source, &result, &devices);
diff --git a/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc b/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc index db350d8..ad7b81d 100644 --- a/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc +++ b/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc
@@ -624,7 +624,10 @@ // only at cases where one isn't affected by the other. bool brp_enabled = #if BUILDFLAG(USE_BACKUP_REF_PTR) - base::FeatureList::IsEnabled(base::features::kPartitionAllocBackupRefPtr); + base::FeatureList::IsEnabled( + base::features::kPartitionAllocBackupRefPtr) && + (base::features::kBackupRefPtrModeParam.Get() == + base::features::BackupRefPtrMode::kEnabled); #else false; #endif
diff --git a/chrome/browser/metrics/first_web_contents_profiler.cc b/chrome/browser/metrics/first_web_contents_profiler.cc index 50bfe5af..e7066db 100644 --- a/chrome/browser/metrics/first_web_contents_profiler.cc +++ b/chrome/browser/metrics/first_web_contents_profiler.cc
@@ -14,40 +14,78 @@ #include "base/memory/memory_pressure_monitor.h" #include "base/metrics/histogram_functions.h" #include "base/time/time.h" -#include "chrome/browser/metrics/first_web_contents_profiler_base.h" +#include "build/build_config.h" +#include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" #include "components/startup_metric_utils/browser/startup_metric_utils.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/navigation_entry.h" +#include "content/public/browser/navigation_handle.h" +#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_observer.h" -namespace metrics { namespace { -void RecordFirstWebContentsFinishReason( - StartupProfilingFinishReason finish_reason) { +// Reasons for which profiling is deemed complete. Logged in UMA (do not re- +// order or re-assign). +enum class FinishReason { + // All metrics were successfully gathered. + kDone = 0, + // Abandon if blocking UI was shown during startup. + kAbandonBlockingUI = 1, + // Abandon if the WebContents is hidden (lowers scheduling priority). + kAbandonContentHidden = 2, + // Abandon if the WebContents is destroyed. + kAbandonContentDestroyed = 3, + // Abandon if the WebContents navigates away from its initial page, as it: + // (1) is no longer a fair timing; and + // (2) can cause http://crbug.com/525209 where the first paint didn't fire + // for the initial content but fires after a lot of idle time when the + // user finally navigates to another page that does trigger it. + kAbandonNewNavigation = 4, + // Abandon if the WebContents fails to load (e.g. network error, etc.). + kAbandonNavigationError = 5, + // Abandon if no WebContents was visible at the beginning of startup + kAbandonNoInitiallyVisibleContent = 6, + kMaxValue = kAbandonNoInitiallyVisibleContent +}; + +void RecordFinishReason(FinishReason finish_reason) { base::UmaHistogramEnumeration("Startup.FirstWebContents.FinishReason", finish_reason); } -class FirstWebContentsProfiler : public FirstWebContentsProfilerBase { +// Note: Instances of this class self destroy when the first non-empty paint +// happens, or when an event prevents it from being recorded. +class FirstWebContentsProfiler : public content::WebContentsObserver { public: explicit FirstWebContentsProfiler(content::WebContents* web_contents); FirstWebContentsProfiler(const FirstWebContentsProfiler&) = delete; FirstWebContentsProfiler& operator=(const FirstWebContentsProfiler&) = delete; - protected: - // FirstWebContentsProfilerBase: - void RecordFinishReason(StartupProfilingFinishReason finish_reason) override; - void RecordNavigationFinished(base::TimeTicks navigation_start) override; - void RecordFirstNonEmptyPaint() override; - bool WasStartupInterrupted() override; - private: ~FirstWebContentsProfiler() override = default; + // content::WebContentsObserver: + void DidStartNavigation( + content::NavigationHandle* navigation_handle) override; + void DidFinishNavigation( + content::NavigationHandle* navigation_handle) override; + void DidFirstVisuallyNonEmptyPaint() override; + void OnVisibilityChanged(content::Visibility visibility) override; + void WebContentsDestroyed() override; + + // Logs |finish_reason| to UMA and deletes this FirstWebContentsProfiler. + void FinishedCollectingMetrics(FinishReason finish_reason); + + // Whether a main frame navigation finished since this was created. + bool did_finish_first_navigation_ = false; + // Memory pressure listener that will be used to check if memory pressure has // an impact on startup. base::MemoryPressureListener memory_pressure_listener_; @@ -55,7 +93,7 @@ FirstWebContentsProfiler::FirstWebContentsProfiler( content::WebContents* web_contents) - : FirstWebContentsProfilerBase(web_contents), + : content::WebContentsObserver(web_contents), memory_pressure_listener_( FROM_HERE, base::BindRepeating(&startup_metric_utils:: @@ -67,44 +105,119 @@ DCHECK(web_contents->GetController().GetPendingEntry()); } -void FirstWebContentsProfiler::RecordFinishReason( - StartupProfilingFinishReason finish_reason) { - RecordFirstWebContentsFinishReason(finish_reason); +void FirstWebContentsProfiler::DidStartNavigation( + content::NavigationHandle* navigation_handle) { + // The profiler is concerned with the primary main frame navigation only. + if (!navigation_handle->IsInPrimaryMainFrame() || + navigation_handle->IsSameDocument()) { + return; + } + + // FirstWebContentsProfiler is created after DidStartNavigation() has been + // dispatched for the first top-level navigation. If another + // DidStartNavigation() is received, it means that a new navigation was + // initiated. + FinishedCollectingMetrics(FinishReason::kAbandonNewNavigation); } -void FirstWebContentsProfiler::RecordNavigationFinished( - base::TimeTicks navigation_start) { +void FirstWebContentsProfiler::DidFinishNavigation( + content::NavigationHandle* navigation_handle) { + if (startup_metric_utils::WasMainWindowStartupInterrupted()) { + FinishedCollectingMetrics(FinishReason::kAbandonBlockingUI); + return; + } + + // Ignore subframe navigations, pre-rendering, and same-document navigations. + if (!navigation_handle->IsInPrimaryMainFrame() || + navigation_handle->IsSameDocument()) { + return; + } + + if (!navigation_handle->HasCommitted() || + navigation_handle->IsErrorPage()) { + FinishedCollectingMetrics(FinishReason::kAbandonNavigationError); + return; + } + + // It is not possible to get a second top-level DidFinishNavigation() without + // first having a DidStartNavigation(), which would have deleted |this|. + DCHECK(!did_finish_first_navigation_); + + did_finish_first_navigation_ = true; + startup_metric_utils::RecordFirstWebContentsMainNavigationStart( - navigation_start); + navigation_handle->NavigationStart()); startup_metric_utils::RecordFirstWebContentsMainNavigationFinished( base::TimeTicks::Now()); } -void FirstWebContentsProfiler::RecordFirstNonEmptyPaint() { +void FirstWebContentsProfiler::DidFirstVisuallyNonEmptyPaint() { + DCHECK(did_finish_first_navigation_); + + if (startup_metric_utils::WasMainWindowStartupInterrupted()) { + FinishedCollectingMetrics(FinishReason::kAbandonBlockingUI); + return; + } + startup_metric_utils::RecordFirstWebContentsNonEmptyPaint( base::TimeTicks::Now(), web_contents()->GetMainFrame()->GetProcess()->GetLastInitTime()); + + FinishedCollectingMetrics(FinishReason::kDone); } -bool FirstWebContentsProfiler::WasStartupInterrupted() { - return startup_metric_utils::WasMainWindowStartupInterrupted(); +void FirstWebContentsProfiler::OnVisibilityChanged( + content::Visibility visibility) { + if (visibility != content::Visibility::VISIBLE) { + // Stop profiling if the content gets hidden as its load may be + // deprioritized and timing it becomes meaningless. + FinishedCollectingMetrics(FinishReason::kAbandonContentHidden); + } +} + +void FirstWebContentsProfiler::WebContentsDestroyed() { + FinishedCollectingMetrics(FinishReason::kAbandonContentDestroyed); +} + +void FirstWebContentsProfiler::FinishedCollectingMetrics( + FinishReason finish_reason) { + RecordFinishReason(finish_reason); + delete this; } } // namespace +namespace metrics { + void BeginFirstWebContentsProfiling() { - content::WebContents* visible_contents = nullptr; const BrowserList* browser_list = BrowserList::GetInstance(); + + content::WebContents* visible_contents = nullptr; for (Browser* browser : *browser_list) { - visible_contents = - FirstWebContentsProfilerBase::GetVisibleContents(browser); - if (visible_contents) - break; + if (!browser->window()->IsVisible()) + continue; + + // The active WebContents may be hidden when the window height is small. + content::WebContents* contents = + browser->tab_strip_model()->GetActiveWebContents(); + +#if defined(OS_MAC) + // TODO(https://crbug.com/1032348): It is incorrect to have a visible + // browser window with no active WebContents, but reports on Mac show that + // it happens. + if (!contents) + continue; +#endif // defined(OS_MAC) + + if (contents->GetVisibility() != content::Visibility::VISIBLE) + continue; + + visible_contents = contents; + break; } if (!visible_contents) { - RecordFirstWebContentsFinishReason( - StartupProfilingFinishReason::kAbandonNoInitiallyVisibleContent); + RecordFinishReason(FinishReason::kAbandonNoInitiallyVisibleContent); return; }
diff --git a/chrome/browser/metrics/first_web_contents_profiler_base.cc b/chrome/browser/metrics/first_web_contents_profiler_base.cc deleted file mode 100644 index 3f2d06b..0000000 --- a/chrome/browser/metrics/first_web_contents_profiler_base.cc +++ /dev/null
@@ -1,123 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/metrics/first_web_contents_profiler_base.h" - -#include "build/build_config.h" -#include "chrome/browser/ui/browser.h" -#include "chrome/browser/ui/browser_window.h" -#include "chrome/browser/ui/tabs/tab_strip_model.h" -#include "content/public/browser/navigation_entry.h" -#include "content/public/browser/navigation_handle.h" -#include "content/public/browser/web_contents_observer.h" - -namespace metrics { - -FirstWebContentsProfilerBase::FirstWebContentsProfilerBase( - content::WebContents* web_contents) - : content::WebContentsObserver(web_contents) {} - -FirstWebContentsProfilerBase::~FirstWebContentsProfilerBase() = default; - -// static -content::WebContents* FirstWebContentsProfilerBase::GetVisibleContents( - Browser* browser) { - if (!browser->window()->IsVisible()) - return nullptr; - - // The active WebContents may be hidden when the window height is small. - content::WebContents* contents = - browser->tab_strip_model()->GetActiveWebContents(); - -#if defined(OS_MAC) - // TODO(https://crbug.com/1032348): It is incorrect to have a visible - // browser window with no active WebContents, but reports on Mac show that - // it happens. - if (!contents) - return nullptr; -#endif // defined(OS_MAC) - - if (contents->GetVisibility() != content::Visibility::VISIBLE) - return nullptr; - - return contents; -} - -void FirstWebContentsProfilerBase::DidStartNavigation( - content::NavigationHandle* navigation_handle) { - // The profiler is concerned with the primary main frame navigation only. - if (!navigation_handle->IsInPrimaryMainFrame() || - navigation_handle->IsSameDocument()) { - return; - } - - // The profiler is created after DidStartNavigation() has been dispatched for - // the first top-level navigation. If another DidStartNavigation() is - // received, it means that a new navigation was initiated. - FinishedCollectingMetrics( - StartupProfilingFinishReason::kAbandonNewNavigation); -} - -void FirstWebContentsProfilerBase::DidFinishNavigation( - content::NavigationHandle* navigation_handle) { - if (WasStartupInterrupted()) { - FinishedCollectingMetrics(StartupProfilingFinishReason::kAbandonBlockingUI); - return; - } - - // Ignore subframe navigations, pre-rendering, and same-document navigations. - if (!navigation_handle->IsInPrimaryMainFrame() || - navigation_handle->IsSameDocument()) { - return; - } - - if (!navigation_handle->HasCommitted() || navigation_handle->IsErrorPage()) { - FinishedCollectingMetrics( - StartupProfilingFinishReason::kAbandonNavigationError); - return; - } - - // It is not possible to get a second top-level DidFinishNavigation() without - // first having a DidStartNavigation(), which would have deleted |this|. - DCHECK(!did_finish_first_navigation_); - - did_finish_first_navigation_ = true; - - RecordNavigationFinished(navigation_handle->NavigationStart()); -} - -void FirstWebContentsProfilerBase::DidFirstVisuallyNonEmptyPaint() { - DCHECK(did_finish_first_navigation_); - - if (WasStartupInterrupted()) { - FinishedCollectingMetrics(StartupProfilingFinishReason::kAbandonBlockingUI); - return; - } - - RecordFirstNonEmptyPaint(); - FinishedCollectingMetrics(StartupProfilingFinishReason::kDone); -} - -void FirstWebContentsProfilerBase::OnVisibilityChanged( - content::Visibility visibility) { - if (visibility != content::Visibility::VISIBLE) { - // Stop profiling if the content gets hidden as its load may be - // deprioritized and timing it becomes meaningless. - FinishedCollectingMetrics( - StartupProfilingFinishReason::kAbandonContentHidden); - } -} - -void FirstWebContentsProfilerBase::WebContentsDestroyed() { - FinishedCollectingMetrics( - StartupProfilingFinishReason::kAbandonContentDestroyed); -} - -void FirstWebContentsProfilerBase::FinishedCollectingMetrics( - StartupProfilingFinishReason finish_reason) { - RecordFinishReason(finish_reason); - delete this; -} - -} // namespace metrics
diff --git a/chrome/browser/metrics/first_web_contents_profiler_base.h b/chrome/browser/metrics/first_web_contents_profiler_base.h deleted file mode 100644 index 92fbd79..0000000 --- a/chrome/browser/metrics/first_web_contents_profiler_base.h +++ /dev/null
@@ -1,94 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CHROME_BROWSER_METRICS_FIRST_WEB_CONTENTS_PROFILER_BASE_H_ -#define CHROME_BROWSER_METRICS_FIRST_WEB_CONTENTS_PROFILER_BASE_H_ - -#include "base/time/time.h" -#include "content/public/browser/web_contents_observer.h" - -namespace content { -class WebContents; -} - -class Browser; - -namespace metrics { - -// Reasons for which profiling is deemed complete. Logged in UMA (do not re- -// order or re-assign). -enum class StartupProfilingFinishReason { - // All metrics were successfully gathered. - kDone = 0, - // Abandon if blocking UI was shown during startup. - kAbandonBlockingUI = 1, - // Abandon if the WebContents is hidden (lowers scheduling priority). - kAbandonContentHidden = 2, - // Abandon if the WebContents is destroyed. - kAbandonContentDestroyed = 3, - // Abandon if the WebContents navigates away from its initial page, as it: - // (1) is no longer a fair timing; and - // (2) can cause http://crbug.com/525209 where the first paint didn't fire - // for the initial content but fires after a lot of idle time when the - // user finally navigates to another page that does trigger it. - kAbandonNewNavigation = 4, - // Abandon if the WebContents fails to load (e.g. network error, etc.). - kAbandonNavigationError = 5, - // Abandon if no WebContents was visible at the beginning of startup - kAbandonNoInitiallyVisibleContent = 6, - // Abandon if the WebContents was already painted. We set up the profiler too - // late and it missed the first non empty paint event. - kAbandonAlreadyPaintedContent = 7, - kMaxValue = kAbandonAlreadyPaintedContent -}; - -// Note: Instances of this class self destroy when the first non-empty paint -// happens, or when an event prevents it from being recorded. -class FirstWebContentsProfilerBase : public content::WebContentsObserver { - public: - FirstWebContentsProfilerBase(const FirstWebContentsProfilerBase&) = delete; - FirstWebContentsProfilerBase& operator=(const FirstWebContentsProfilerBase&) = - delete; - - // Returns a visible webcontents from `browser` that can be observed for - // startup profiling, or `nullptr` if no compatible one was obtained. - static content::WebContents* GetVisibleContents(Browser* browser); - - protected: - explicit FirstWebContentsProfilerBase(content::WebContents* web_contents); - - // Protected destructor as `FirstWebContentsProfilerBase` deletes itself. - ~FirstWebContentsProfilerBase() override; - - // Whether to abort recording metrics if the main window startup was - // interrupted. Recording metrics for startups with interruptions pollutes the - // collected data, however some flows (e.g. startup on ProfilePicker) - // specifically define their metrics to work around the interruptions. - virtual bool WasStartupInterrupted() = 0; - - virtual void RecordFinishReason( - StartupProfilingFinishReason finish_reason) = 0; - virtual void RecordNavigationFinished(base::TimeTicks navigation_start) = 0; - virtual void RecordFirstNonEmptyPaint() = 0; - - private: - // content::WebContentsObserver: - void DidStartNavigation( - content::NavigationHandle* navigation_handle) override; - void DidFinishNavigation( - content::NavigationHandle* navigation_handle) override; - void DidFirstVisuallyNonEmptyPaint() override; - void OnVisibilityChanged(content::Visibility visibility) override; - void WebContentsDestroyed() override; - - // Logs |finish_reason| to UMA and deletes this profiler. - void FinishedCollectingMetrics(StartupProfilingFinishReason finish_reason); - - // Whether a main frame navigation finished since this was created. - bool did_finish_first_navigation_ = false; -}; - -} // namespace metrics - -#endif // CHROME_BROWSER_METRICS_FIRST_WEB_CONTENTS_PROFILER_BASE_H_
diff --git a/chrome/browser/metrics/process_memory_metrics_emitter.cc b/chrome/browser/metrics/process_memory_metrics_emitter.cc index cc14e99e..a73df77 100644 --- a/chrome/browser/metrics/process_memory_metrics_emitter.cc +++ b/chrome/browser/metrics/process_memory_metrics_emitter.cc
@@ -685,6 +685,14 @@ #endif MEMORY_METRICS_HISTOGRAM_MB(GetPrivateFootprintHistogramName(process_type), pmd.os_dump().private_footprint_kb / kKiB); + ProfileManager* profile_manager = g_browser_process->profile_manager(); + if (process_type == HistogramProcessType::kBrowser && profile_manager && + profile_manager->HasZombieProfile()) { + // Measure impact of the DestroyProfileOnBrowserClose experiment. + MEMORY_METRICS_HISTOGRAM_MB( + GetPrivateFootprintHistogramName(process_type) + ".HasZombieProfile", + pmd.os_dump().private_footprint_kb / kKiB); + } MEMORY_METRICS_HISTOGRAM_MB(std::string(kMemoryHistogramPrefix) + process_name + ".SharedMemoryFootprint", pmd.os_dump().shared_footprint_kb / kKiB); @@ -1108,6 +1116,13 @@ #endif UMA_HISTOGRAM_MEMORY_LARGE_MB("Memory.Total.PrivateMemoryFootprint", private_footprint_total_kb / kKiB); + ProfileManager* profile_manager = g_browser_process->profile_manager(); + if (profile_manager && profile_manager->HasZombieProfile()) { + // Measure impact of the DestroyProfileOnBrowserClose experiment. + UMA_HISTOGRAM_MEMORY_LARGE_MB( + "Memory.Total.PrivateMemoryFootprint.HasZombieProfile", + private_footprint_total_kb / kKiB); + } // The pseudo metric of Memory.Total.PrivateMemoryFootprint. Only used to // assess field trial data quality. UMA_HISTOGRAM_MEMORY_LARGE_MB(
diff --git a/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc b/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc index 25c52bf4..4b1be17 100644 --- a/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc +++ b/chrome/browser/metrics/process_memory_metrics_emitter_browsertest.cc
@@ -18,7 +18,13 @@ #include "base/trace_event/memory_dump_manager.h" #include "base/trace_event/trace_config_memory_test_util.h" #include "build/build_config.h" +#include "chrome/browser/browser_features.h" +#include "chrome/browser/browser_process.h" #include "chrome/browser/extensions/extension_browsertest.h" +#include "chrome/browser/profiles/profile_keep_alive_types.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/profiles/profile_observer.h" +#include "chrome/browser/profiles/scoped_profile_keep_alive.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/tracing.h" #include "chrome/test/base/ui_test_utils.h" @@ -296,6 +302,8 @@ count_for_resident_set, ValueRestriction::ABOVE_ZERO); CheckMemoryMetric("Memory.Total.PrivateMemoryFootprint", histogram_tester, count, ValueRestriction::ABOVE_ZERO); + CheckMemoryMetric("Memory.Total.PrivateMemoryFootprint.HasZombieProfile", + histogram_tester, 0, ValueRestriction::NONE); CheckMemoryMetric("Memory.Total.RendererPrivateMemoryFootprint", histogram_tester, count, ValueRestriction::ABOVE_ZERO); CheckMemoryMetric("Memory.Total.RendererMalloc", histogram_tester, count, @@ -317,6 +325,31 @@ number_of_extension_processes); } +class ProfileDestructionWatcher : public ProfileObserver { + public: + explicit ProfileDestructionWatcher(Profile* profile) { + observation_.Observe(profile); + } + + ProfileDestructionWatcher(const ProfileDestructionWatcher&) = delete; + ProfileDestructionWatcher& operator=(const ProfileDestructionWatcher&) = + delete; + + ~ProfileDestructionWatcher() override = default; + + void WaitForDestruction() { run_loop_.Run(); } + + private: + // ProfileObserver: + void OnProfileWillBeDestroyed(Profile* profile) override { + observation_.Reset(); + run_loop_.Quit(); + } + + base::RunLoop run_loop_; + base::ScopedObservation<Profile, ProfileObserver> observation_{this}; +}; + } // namespace class ProcessMemoryMetricsEmitterTest @@ -580,6 +613,56 @@ CheckPageInfoUkmMetrics(url, true); } +#if defined(OS_WIN) || defined(OS_MAC) || defined(OS_LINUX) +// TODO(crbug.com/732501): Re-enable on Win once not flaky. +#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || defined(OS_WIN) +#define MAYBE_HasZombieProfile DISABLED_HasZombieProfile +#else +#define MAYBE_HasZombieProfile HasZombieProfile +#endif +IN_PROC_BROWSER_TEST_F(ProcessMemoryMetricsEmitterTest, + MAYBE_HasZombieProfile) { + ASSERT_TRUE( + base::FeatureList::IsEnabled(features::kDestroyProfileOnBrowserClose)); + + ASSERT_TRUE(embedded_test_server()->Start()); + const GURL url = embedded_test_server()->GetURL("foo.com", "/empty.html"); + ui_test_utils::NavigateToURLWithDisposition( + browser(), url, WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); + + // Create a second Profile. + base::ScopedAllowBlockingForTesting allow_blocking; + ProfileManager* profile_manager = g_browser_process->profile_manager(); + Profile* profile2 = profile_manager->GetProfile( + profile_manager->GenerateNextProfileDirectoryPath()); + + // Now destroy the second Profile, so HasZombieProfile() returns true. + ProfileDestructionWatcher destruction_watcher(profile2); + { + ScopedProfileKeepAlive keep_alive(profile2, + ProfileKeepAliveOrigin::kBrowserWindow); + } + destruction_watcher.WaitForDestruction(); + + base::HistogramTester histogram_tester; + base::RunLoop run_loop; + + // Intentionally let emitter leave scope to check that it correctly keeps + // itself alive. + { + scoped_refptr<ProcessMemoryMetricsEmitterFake> emitter( + new ProcessMemoryMetricsEmitterFake(&run_loop, + test_ukm_recorder_.get())); + emitter->FetchAndEmitProcessMemoryMetrics(); + } + run_loop.Run(); + + CheckMemoryMetric("Memory.Total.PrivateMemoryFootprint.HasZombieProfile", + histogram_tester, 1, ValueRestriction::NONE); +} +#endif + // TODO(https://crbug.com/990148): Re-enable on Win and Linux once not flaky. #if BUILDFLAG(ENABLE_EXTENSIONS) #if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
diff --git a/chrome/browser/metrics/process_memory_metrics_emitter_unittest.cc b/chrome/browser/metrics/process_memory_metrics_emitter_unittest.cc index 0996aea..6bba6b7 100644 --- a/chrome/browser/metrics/process_memory_metrics_emitter_unittest.cc +++ b/chrome/browser/metrics/process_memory_metrics_emitter_unittest.cc
@@ -933,6 +933,8 @@ histograms.ExpectTotalCount("Memory.Renderer.ResidentSet", 0); histograms.ExpectTotalCount("Memory.Total.PrivateMemoryFootprint", 0); + histograms.ExpectTotalCount( + "Memory.Total.PrivateMemoryFootprint.HasZombieProfile", 0); histograms.ExpectTotalCount("Memory.Total.RendererPrivateMemoryFootprint", 0); histograms.ExpectTotalCount("Memory.Total.RendererMalloc", 0); histograms.ExpectTotalCount("Memory.Total.SharedMemoryFootprint", 0); @@ -963,6 +965,8 @@ kTestRendererResidentSet, 2); #endif + histograms.ExpectTotalCount( + "Memory.Total.PrivateMemoryFootprint.HasZombieProfile", 0); histograms.ExpectUniqueSample("Memory.Total.PrivateMemoryFootprint", 2 * kTestRendererPrivateMemoryFootprint, 1); histograms.ExpectUniqueSample("Memory.Total.RendererPrivateMemoryFootprint",
diff --git a/chrome/browser/notifications/notification_ui_manager_unittest.cc b/chrome/browser/notifications/notification_ui_manager_unittest.cc index 174b9625..7c83ac9f 100644 --- a/chrome/browser/notifications/notification_ui_manager_unittest.cc +++ b/chrome/browser/notifications/notification_ui_manager_unittest.cc
@@ -67,33 +67,30 @@ }; TEST_F(NotificationUIManagerTest, SetupNotificationManager) { - TestingProfile profile; - notification_manager()->Add(GetANotification("test"), &profile); + notification_manager()->Add(GetANotification("test"), profile()); } TEST_F(NotificationUIManagerTest, AddNotificationOnShutdown) { - TestingProfile profile; EXPECT_TRUE(message_center()->NotificationCount() == 0); - notification_manager()->Add(GetANotification("test"), &profile); + notification_manager()->Add(GetANotification("test"), profile()); EXPECT_TRUE(message_center()->NotificationCount() == 1); // Verify the number of notifications does not increase when trying to add a // notifcation on shutdown. notification_manager()->StartShutdown(); EXPECT_TRUE(message_center()->NotificationCount() == 0); - notification_manager()->Add(GetANotification("test2"), &profile); + notification_manager()->Add(GetANotification("test2"), profile()); EXPECT_TRUE(message_center()->NotificationCount() == 0); base::RunLoop().RunUntilIdle(); } TEST_F(NotificationUIManagerTest, UpdateNotification) { - TestingProfile profile; EXPECT_TRUE(message_center()->NotificationCount() == 0); - notification_manager()->Add(GetANotification("test"), &profile); + notification_manager()->Add(GetANotification("test"), profile()); EXPECT_TRUE(message_center()->NotificationCount() == 1); ASSERT_TRUE( - notification_manager()->Update(GetANotification("test"), &profile)); + notification_manager()->Update(GetANotification("test"), profile())); EXPECT_TRUE(message_center()->NotificationCount() == 1); base::RunLoop().RunUntilIdle(); @@ -101,11 +98,10 @@ // Regression test for crbug.com/767868 TEST_F(NotificationUIManagerTest, GetAllIdsReturnsOriginalId) { - TestingProfile profile; EXPECT_TRUE(message_center()->NotificationCount() == 0); - notification_manager()->Add(GetANotification("test"), &profile); + notification_manager()->Add(GetANotification("test"), profile()); std::set<std::string> ids = notification_manager()->GetAllIdsByProfile( - ProfileNotification::GetProfileID(&profile)); + ProfileNotification::GetProfileID(profile())); ASSERT_EQ(1u, ids.size()); EXPECT_EQ(*ids.begin(), "test"); }
diff --git a/chrome/browser/notifications/proto/client_state.proto b/chrome/browser/notifications/proto/client_state.proto index 1eee1ca..057b7bb 100644 --- a/chrome/browser/notifications/proto/client_state.proto +++ b/chrome/browser/notifications/proto/client_state.proto
@@ -20,6 +20,7 @@ CHROME_UPDATE = 2; PREFETCH = 3; READING_LIST = 4; + FEATURE_GUIDE = 5; } // Contains details about suppression and recovery after suppression expired.
diff --git a/chrome/browser/notifications/scheduler/internal/impression_history_tracker.cc b/chrome/browser/notifications/scheduler/internal/impression_history_tracker.cc index e09ccc06..baef16dd 100644 --- a/chrome/browser/notifications/scheduler/internal/impression_history_tracker.cc +++ b/chrome/browser/notifications/scheduler/internal/impression_history_tracker.cc
@@ -56,6 +56,8 @@ return "Prefetch"; case SchedulerClientType::kReadingList: return "ReadingList"; + case SchedulerClientType::kFeatureGuide: + return "FeatureGuide"; } }
diff --git a/chrome/browser/notifications/scheduler/internal/proto_conversion.cc b/chrome/browser/notifications/scheduler/internal/proto_conversion.cc index 54b62dc..6bfb2e8b 100644 --- a/chrome/browser/notifications/scheduler/internal/proto_conversion.cc +++ b/chrome/browser/notifications/scheduler/internal/proto_conversion.cc
@@ -59,6 +59,8 @@ return proto::SchedulerClientType::PREFETCH; case SchedulerClientType::kReadingList: return proto::SchedulerClientType::READING_LIST; + case SchedulerClientType::kFeatureGuide: + return proto::SchedulerClientType::FEATURE_GUIDE; } NOTREACHED(); } @@ -83,6 +85,8 @@ return SchedulerClientType::kPrefetch; case proto::SchedulerClientType::READING_LIST: return SchedulerClientType::kReadingList; + case proto::SchedulerClientType::FEATURE_GUIDE: + return SchedulerClientType::kFeatureGuide; } NOTREACHED(); }
diff --git a/chrome/browser/notifications/scheduler/internal/scheduler_config.cc b/chrome/browser/notifications/scheduler/internal/scheduler_config.cc index 1e3a6a0..a51bce61 100644 --- a/chrome/browser/notifications/scheduler/internal/scheduler_config.cc +++ b/chrome/browser/notifications/scheduler/internal/scheduler_config.cc
@@ -37,7 +37,7 @@ constexpr int kDefaultDismissCount = 3; // The notification data is hold for one week. -constexpr base::TimeDelta kDefaultNotificationExpiration = base::Days(7); +constexpr base::TimeDelta kDefaultNotificationExpiration = base::Days(20); // The impression history is hold for 4 weeks. constexpr base::TimeDelta kDefaultImpressionExpiration = base::Days(28);
diff --git a/chrome/browser/notifications/scheduler/internal/stats.cc b/chrome/browser/notifications/scheduler/internal/stats.cc index 898fc5d..c81c9dd0 100644 --- a/chrome/browser/notifications/scheduler/internal/stats.cc +++ b/chrome/browser/notifications/scheduler/internal/stats.cc
@@ -35,6 +35,8 @@ return "Prefetch"; case SchedulerClientType::kReadingList: return "ReadingList"; + case SchedulerClientType::kFeatureGuide: + return "FeatureGuide"; } }
diff --git a/chrome/browser/notifications/scheduler/notification_schedule_service_factory.cc b/chrome/browser/notifications/scheduler/notification_schedule_service_factory.cc index 9c3cce79..4600ecbb 100644 --- a/chrome/browser/notifications/scheduler/notification_schedule_service_factory.cc +++ b/chrome/browser/notifications/scheduler/notification_schedule_service_factory.cc
@@ -9,6 +9,8 @@ #include "base/memory/singleton.h" #include "build/build_config.h" +#include "chrome/browser/feature_guide/notifications/feature_notification_guide_service.h" +#include "chrome/browser/feature_guide/notifications/feature_notification_guide_service_factory.h" #include "chrome/browser/notifications/scheduler/notification_background_task_scheduler_impl.h" #include "chrome/browser/notifications/scheduler/public/display_agent.h" #include "chrome/browser/notifications/scheduler/public/notification_schedule_service.h" @@ -48,6 +50,19 @@ std::move(reading_list_client)); } + if (base::FeatureList::IsEnabled( + feature_guide::features::kFeatureNotificationGuide)) { + Profile* profile = ProfileManager::GetProfileFromProfileKey(key); + auto feature_guide_service_getter = base::BindRepeating( + &feature_guide::FeatureNotificationGuideServiceFactory::GetForProfile, + profile); + + client_registrar->RegisterClient( + notifications::SchedulerClientType::kFeatureGuide, + CreateFeatureNotificationGuideNotificationClient( + feature_guide_service_getter)); + } + #endif // defined(OS_ANDROID) return client_registrar; }
diff --git a/chrome/browser/notifications/scheduler/public/notification_scheduler_types.h b/chrome/browser/notifications/scheduler/public/notification_scheduler_types.h index 9539e19..dc5aaa26 100644 --- a/chrome/browser/notifications/scheduler/public/notification_scheduler_types.h +++ b/chrome/browser/notifications/scheduler/public/notification_scheduler_types.h
@@ -35,7 +35,9 @@ kPrefetch = 3, // Reading list weekly notification. kReadingList = 4, - kMaxValue = kReadingList + // Feature guide specific notifications. + kFeatureGuide = 5, + kMaxValue = kFeatureGuide }; // The type of user feedback from a displayed notification.
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc index 9f548f6d..d94d7bbb 100644 --- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc +++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
@@ -305,25 +305,8 @@ DCHECK(request_context != optimization_guide::proto::RequestContext::CONTEXT_UNSPECIFIED); - // TODO(crbug/1275612): Hook this up into hints manager to actually fetch. - for (const auto& url : urls) { - base::flat_map<optimization_guide::proto::OptimizationType, - optimization_guide::OptimizationGuideDecisionWithMetadata> - decisions; - decisions.reserve(optimization_types.size()); - for (const auto optimization_type : optimization_types) { - optimization_guide::OptimizationMetadata metadata; - optimization_guide::OptimizationTypeDecision optimization_type_decision = - hints_manager_->CanApplyOptimization(url, optimization_type, - &metadata); - optimization_guide::OptimizationGuideDecision decision = - optimization_guide::ChromeHintsManager:: - GetOptimizationGuideDecisionFromOptimizationTypeDecision( - optimization_type_decision); - decisions[optimization_type] = {decision, metadata}; - } - callback.Run(url, decisions); - } + hints_manager_->CanApplyOptimizationOnDemand(urls, optimization_types, + request_context, callback); } void OptimizationGuideKeyedService::AddHintForTesting(
diff --git a/chrome/browser/password_manager/android/BUILD.gn b/chrome/browser/password_manager/android/BUILD.gn index 67c4331..465ff601 100644 --- a/chrome/browser/password_manager/android/BUILD.gn +++ b/chrome/browser/password_manager/android/BUILD.gn
@@ -35,6 +35,7 @@ deps = [ ":jni_headers", + "//components/autofill/core/browser:browser", "//components/password_manager/core/browser:browser", "//components/password_manager/core/browser:password_form", "//components/sync/model:model",
diff --git a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackend.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackend.java index 3ce30b3..d5170c0 100644 --- a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackend.java +++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackend.java
@@ -48,10 +48,9 @@ * @param loginsReply Callback that is called on success with serialized {@link * org.chromium.components.sync.protocol.ListPasswordsResult} data. * @param failureCallback A callback that is called on failure for any reason. May return sync. - * TODO(crbug.com/1229655): Remove default keyword after downstream implementation. */ - default void getLoginsForSignonRealm(String signonRealm, Callback<byte[]> loginsReply, - Callback<Exception> failureCallback){}; + void getLoginsForSignonRealm( + String signonRealm, Callback<byte[]> loginsReply, Callback<Exception> failureCallback); /** * Triggers an async call to add a login to the store.
diff --git a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeImpl.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeImpl.java index 29487ffd..3c4c295 100644 --- a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeImpl.java +++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeImpl.java
@@ -67,6 +67,15 @@ } @CalledByNative + void getLoginsForSignonRealm(@JobId int jobId, String signonRealm) { + mBackend.getLoginsForSignonRealm(signonRealm, passwords -> { + if (mNativeBackendBridge == 0) return; + PasswordStoreAndroidBackendBridgeImplJni.get().onCompleteWithLogins( + mNativeBackendBridge, jobId, passwords); + }, exception -> handleAndroidBackendException(jobId, exception)); + } + + @CalledByNative void addLogin(@JobId int jobId, byte[] pwdWithLocalData) { mBackend.addLogin(pwdWithLocalData, () -> { if (mNativeBackendBridge == 0) return;
diff --git a/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeTest.java b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeTest.java index e1aa6148..2431635 100644 --- a/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeTest.java +++ b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordStoreAndroidBackendBridgeTest.java
@@ -174,6 +174,40 @@ } @Test + public void testGetLoginsForSignonRealmCallsBridgeOnSuccess() { + final int kTestTaskId = 1337; + + // Ensure the backend is called with a valid success callback. + mBackendBridge.getLoginsForSignonRealm(kTestTaskId, "https://test_signon_realm.com"); + ArgumentCaptor<Callback<byte[]>> successCallback = ArgumentCaptor.forClass(Callback.class); + verify(mBackendMock).getLoginsForSignonRealm(any(), successCallback.capture(), any()); + assertNotNull(successCallback.getValue()); + + byte[] kExpectedList = sTestLogins.build().toByteArray(); + successCallback.getValue().onResult(kExpectedList); + verify(mBridgeJniMock) + .onCompleteWithLogins(sDummyNativePointer, kTestTaskId, kExpectedList); + } + + @Test + public void testGetLoginsForSignonRealmCallsBridgeOnFailure() { + final int kTestTaskId = 42069; + + // Ensure the backend is called with a valid failure callback. + mBackendBridge.getLoginsForSignonRealm(kTestTaskId, "https://test_signon_realm.com"); + ArgumentCaptor<Callback<Exception>> failureCallback = + ArgumentCaptor.forClass(Callback.class); + verify(mBackendMock).getLoginsForSignonRealm(any(), any(), failureCallback.capture()); + assertNotNull(failureCallback.getValue()); + + Exception kExpectedException = new Exception("Sample failure"); + failureCallback.getValue().onResult(kExpectedException); + verify(mBridgeJniMock) + .onError( + sDummyNativePointer, kTestTaskId, AndroidBackendErrorType.UNCATEGORIZED, 0); + } + + @Test public void testAddLoginCallsBridgeOnSuccess() { final int kTestTaskId = 1337;
diff --git a/chrome/browser/password_manager/android/password_store_android_backend.cc b/chrome/browser/password_manager/android/password_store_android_backend.cc index b120ce6..903a3c5 100644 --- a/chrome/browser/password_manager/android/password_store_android_backend.cc +++ b/chrome/browser/password_manager/android/password_store_android_backend.cc
@@ -18,6 +18,7 @@ #include "base/strings/strcat.h" #include "base/threading/sequenced_task_runner_handle.h" #include "chrome/browser/password_manager/android/password_store_android_backend_bridge.h" +#include "components/autofill/core/browser/autofill_regexes.h" #include "components/password_manager/core/browser/login_database.h" #include "components/password_manager/core/browser/password_form.h" #include "components/password_manager/core/browser/password_store_backend.h" @@ -32,6 +33,12 @@ namespace { +using autofill::MatchesPattern; +using base::UTF8ToUTF16; +using password_manager::GetExpressionForFederatedMatching; +using password_manager::GetRegexForPSLFederatedMatching; +using password_manager::GetRegexForPSLMatching; + using JobId = PasswordStoreAndroidBackendBridge::JobId; std::vector<std::unique_ptr<PasswordForm>> WrapPasswordsIntoPointers( @@ -45,6 +52,81 @@ return password_ptrs; } +std::string FormToSignonRealmQuery(const PasswordFormDigest& form, + bool include_psl) { + if (include_psl) { + // Check PSL matches and matches for exact signon realm. + return GetRegistryControlledDomain(GURL(form.signon_realm)); + } + if (form.scheme == PasswordForm::Scheme::kHtml) { + // Check federated matches and matches for exact signon realm. + return form.url.host(); + } + // Check matches for exact signon realm. + return form.signon_realm; +} + +bool MatchesIncludedPSLAndFederation(const PasswordForm& retrieved_login, + const PasswordFormDigest& form_to_match, + bool include_psl) { + if (retrieved_login.signon_realm == form_to_match.signon_realm) + return true; + + std::u16string retrieved_login_signon_realm = + UTF8ToUTF16(retrieved_login.signon_realm); + const bool include_federated = + form_to_match.scheme == PasswordForm::Scheme::kHtml; + + if (include_psl) { + const std::u16string psl_regex = + UTF8ToUTF16(GetRegexForPSLMatching(form_to_match.signon_realm)); + if (MatchesPattern(retrieved_login_signon_realm, psl_regex)) + return true; + if (include_federated) { + const std::u16string psl_federated_regex = UTF8ToUTF16( + GetRegexForPSLFederatedMatching(form_to_match.signon_realm)); + if (MatchesPattern(retrieved_login_signon_realm, psl_federated_regex)) + return true; + } + } else if (include_federated) { + const std::u16string federated_regex = + UTF8ToUTF16("^" + GetExpressionForFederatedMatching(form_to_match.url)); + return include_federated && + MatchesPattern(retrieved_login_signon_realm, federated_regex); + } + return false; +} + +void ValidateSignonRealm(const PasswordFormDigest& form_digest_to_match, + bool include_psl, + LoginsReply callback, + LoginsResultOrError logins_or_error) { + if (absl::holds_alternative<PasswordStoreBackendError>(logins_or_error)) + std::move(callback).Run({}); + LoginsResult retrieved_logins = + std::move(absl::get<LoginsResult>(logins_or_error)); + LoginsResult matching_logins; + for (auto it = retrieved_logins.begin(); it != retrieved_logins.end();) { + if (MatchesIncludedPSLAndFederation(*it->get(), form_digest_to_match, + include_psl)) { + matching_logins.push_back(std::move(*it)); + // std::vector::erase returns the iterator for the next element. + it = retrieved_logins.erase(it); + } else { + it++; + } + } + std::move(callback).Run(std::move(matching_logins)); +} + +LoginsResult JoinRetrievedLogins(std::vector<LoginsResult> results) { + LoginsResult joined_logins; + for (auto& logins : results) { + std::move(logins.begin(), logins.end(), std::back_inserter(joined_logins)); + } + return joined_logins; +} + } // namespace PasswordStoreAndroidBackend::JobReturnHandler::JobReturnHandler() = default; @@ -56,6 +138,12 @@ metric_infix_(std::move(metric_infix)) {} PasswordStoreAndroidBackend::JobReturnHandler::JobReturnHandler( + LoginsReply callback, + MetricInfix metric_infix) + : success_callback_(std::move(callback)), + metric_infix_(std::move(metric_infix)) {} + +PasswordStoreAndroidBackend::JobReturnHandler::JobReturnHandler( PasswordStoreChangeListReply callback, MetricInfix metric_infix) : success_callback_(std::move(callback)), @@ -219,14 +307,38 @@ "GetAutofillableLoginsAsync"))); } +// TODO(https://crbug.com/1229655): Replace LoginsReply with LoginsOrErrorReply. void PasswordStoreAndroidBackend::FillMatchingLoginsAsync( LoginsReply callback, bool include_psl, const std::vector<PasswordFormDigest>& forms) { - // TODO(https://crbug.com/1229654): Implement. - // Run callback with an empty forms list to facilitate testing of other - // backend methods while this method is not implemented. - std::move(callback).Run({}); + if (forms.empty()) { + std::move(callback).Run({}); + return; + } + + LoginsReply record_metrics_and_reply = base::BindOnce( + [](JobReturnHandler handler, LoginsResult logins) { + handler.RecordMetrics(/*error=*/absl::nullopt); + std::move(handler).Get<LoginsReply>().Run(std::move(logins)); + }, + JobReturnHandler(std::move(callback), JobReturnHandler::MetricInfix( + "FillMatchingLoginsAsync"))); + + auto barrier_callback = base::BarrierCallback<LoginsResult>( + forms.size(), base::BindOnce(&JoinRetrievedLogins) + .Then(std::move(record_metrics_and_reply))); + + // Create and run a callbacks chain that retrieves the logins. + base::RepeatingClosure callbacks_chain = base::DoNothing(); + + for (const PasswordFormDigest& form : forms) { + callbacks_chain = base::BindRepeating( + &PasswordStoreAndroidBackend::GetLoginsAsync, + weak_ptr_factory_.GetWeakPtr(), std::move(form), include_psl, + barrier_callback.Then(std::move(callbacks_chain))); + } + std::move(callbacks_chain).Run(); } void PasswordStoreAndroidBackend::AddLoginAsync( @@ -414,4 +526,15 @@ return reply; } +void PasswordStoreAndroidBackend::GetLoginsAsync(const PasswordFormDigest& form, + bool include_psl, + LoginsReply callback) { + JobId job_id = bridge_->GetLoginsForSignonRealm( + FormToSignonRealmQuery(form, include_psl)); + QueueNewJob(job_id, JobReturnHandler( + base::BindOnce(&ValidateSignonRealm, std::move(form), + include_psl, std::move(callback)), + JobReturnHandler::MetricInfix("GetLoginsAsync"))); +} + } // namespace password_manager
diff --git a/chrome/browser/password_manager/android/password_store_android_backend.h b/chrome/browser/password_manager/android/password_store_android_backend.h index c2dee33d..e23d32d0 100644 --- a/chrome/browser/password_manager/android/password_store_android_backend.h +++ b/chrome/browser/password_manager/android/password_store_android_backend.h
@@ -79,10 +79,11 @@ this}; }; - // Wraps the handler for an asynchronous job (if successful). Also provides - // means to record metrics about the job (if successful or not). An object of - // this type shall be created and stored in |request_for_job_| once an - // asynchronous begins, and destroyed once the job is finished. + // Wraps the handler for one or multiple asynchronous jobs (if successful). + // Also provides means to record metrics about the jobs (if successful or + // not). An object of this type shall be created and stored in + // |request_for_job_| once an asynchronous begins, and destroyed once jobs are + // finished. class JobReturnHandler { public: using ErrorReply = base::OnceClosure; @@ -90,6 +91,7 @@ JobReturnHandler(); JobReturnHandler(LoginsOrErrorReply callback, MetricInfix metric_name); + JobReturnHandler(LoginsReply callback, MetricInfix metric_name); JobReturnHandler(PasswordStoreChangeListReply callback, MetricInfix metric_infix); JobReturnHandler(JobReturnHandler&&); @@ -117,7 +119,7 @@ void RecordMetrics(absl::optional<AndroidBackendError> error) const; private: - absl::variant<LoginsOrErrorReply, PasswordStoreChangeListReply> + absl::variant<LoginsReply, LoginsOrErrorReply, PasswordStoreChangeListReply> success_callback_; MetricInfix metric_infix_; base::Time start_ = base::Time::Now(); @@ -128,6 +130,8 @@ // like a bulk deletion just as well as the normal, rather small job load. using JobMap = base::small_map< std::unordered_map<JobId, JobReturnHandler, JobId::Hasher>>; + using AccumulatedLoginsReply = + base::OnceCallback<void(std::unique_ptr<LoginsResult>)>; using AccumulatedPasswordStoreChangeListReply = base::OnceCallback<void(std::unique_ptr<PasswordStoreChangeList>)>; @@ -181,6 +185,11 @@ void QueueNewJob(JobId job_id, JobReturnHandler return_handler); JobReturnHandler GetAndEraseJob(JobId job_id); + // Gets logins matching |form|. + void GetLoginsAsync(const PasswordFormDigest& form, + bool include_psl, + LoginsReply callback); + // Filters |logins| created between |delete_begin| and |delete_end| time // that match |url_filer| and asynchronously removes them. void FilterAndRemoveLogins(
diff --git a/chrome/browser/password_manager/android/password_store_android_backend_bridge.h b/chrome/browser/password_manager/android/password_store_android_backend_bridge.h index ffce8e9..a49944d 100644 --- a/chrome/browser/password_manager/android/password_store_android_backend_bridge.h +++ b/chrome/browser/password_manager/android/password_store_android_backend_bridge.h
@@ -67,6 +67,14 @@ // `OnCompleteWithLogins` when the job with the returned JobId succeeds. virtual JobId GetAutofillableLogins() WARN_UNUSED_RESULT = 0; + // Triggers an asynchronous request to retrieve stored passwords with + // matching |signon_realm|. The returned results must be validated (e.g + // matching "sample.com" also returns logins for "not-sample.com"). + // The registered `Consumer` is notified with `OnCompleteWithLogins` when the + // job with the returned JobId succeeds. + virtual JobId GetLoginsForSignonRealm(const std::string& signon_realm) + WARN_UNUSED_RESULT = 0; + // Triggers an asynchronous request to add |form| to store. The // registered `Consumer` is notified with `OnLoginsChanged` when the // job with the returned JobId succeeds.
diff --git a/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.cc b/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.cc index fb39a1a..8790f73 100644 --- a/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.cc +++ b/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.cc
@@ -9,6 +9,7 @@ #include "base/android/jni_android.h" #include "base/android/jni_array.h" +#include "base/android/jni_string.h" #include "chrome/browser/password_manager/android/jni_headers/PasswordStoreAndroidBackendBridgeImpl_jni.h" #include "components/password_manager/core/browser/password_form.h" #include "components/password_manager/core/browser/sync/password_proto_utils.h" @@ -131,6 +132,16 @@ return job_id; } +JobId PasswordStoreAndroidBackendBridgeImpl::GetLoginsForSignonRealm( + const std::string& signon_realm) { + JobId job_id = GetNextJobId(); + Java_PasswordStoreAndroidBackendBridgeImpl_getLoginsForSignonRealm( + base::android::AttachCurrentThread(), java_object_, job_id.value(), + base::android::ConvertUTF8ToJavaString( + base::android::AttachCurrentThread(), signon_realm)); + return job_id; +} + JobId PasswordStoreAndroidBackendBridgeImpl::AddLogin( const password_manager::PasswordForm& form) { JobId job_id = GetNextJobId();
diff --git a/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.h b/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.h index 95dd10f..bb48152 100644 --- a/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.h +++ b/chrome/browser/password_manager/android/password_store_android_backend_bridge_impl.h
@@ -62,6 +62,8 @@ void SetConsumer(base::WeakPtr<Consumer> consumer) override; JobId GetAllLogins() override WARN_UNUSED_RESULT; JobId GetAutofillableLogins() override WARN_UNUSED_RESULT; + JobId GetLoginsForSignonRealm(const std::string& signon_realm) override + WARN_UNUSED_RESULT; JobId AddLogin(const password_manager::PasswordForm& form) override WARN_UNUSED_RESULT; JobId UpdateLogin(const password_manager::PasswordForm& form) override
diff --git a/chrome/browser/password_manager/android/password_store_android_backend_unittest.cc b/chrome/browser/password_manager/android/password_store_android_backend_unittest.cc index 08bd7b5..57a71b27 100644 --- a/chrome/browser/password_manager/android/password_store_android_backend_unittest.cc +++ b/chrome/browser/password_manager/android/password_store_android_backend_unittest.cc
@@ -53,12 +53,13 @@ PasswordForm CreateTestLogin(const std::u16string& username_value, const std::u16string& password_value, - GURL url, + const std::string& url, base::Time date_created) { PasswordForm form; form.username_value = username_value; form.password_value = password_value; - form.url = url; + form.url = GURL(url); + form.signon_realm = url; form.date_created = date_created; return form; } @@ -79,6 +80,7 @@ MOCK_METHOD(void, SetConsumer, (base::WeakPtr<Consumer>), (override)); MOCK_METHOD(JobId, GetAllLogins, (), (override)); MOCK_METHOD(JobId, GetAutofillableLogins, (), (override)); + MOCK_METHOD(JobId, GetLoginsForSignonRealm, (const std::string&), (override)); MOCK_METHOD(JobId, AddLogin, (const PasswordForm&), (override)); MOCK_METHOD(JobId, UpdateLogin, (const PasswordForm&), (override)); MOCK_METHOD(JobId, RemoveLogin, (const PasswordForm&), (override)); @@ -141,6 +143,119 @@ RunUntilIdle(); } +TEST_F(PasswordStoreAndroidBackendTest, FillMatchingLoginsNoPSL) { + base::HistogramTester histogram_tester; + backend().InitBackend(PasswordStoreAndroidBackend::RemoteChangesReceived(), + base::RepeatingClosure(), base::DoNothing()); + base::MockCallback<LoginsReply> mock_reply; + + const JobId kFirstJobId{1337}; + EXPECT_CALL(*bridge(), GetLoginsForSignonRealm).WillOnce(Return(kFirstJobId)); + constexpr auto kLatencyDelta = base::Milliseconds(123u); + + std::string TestURL1("https://firstexample.com"); + std::string TestURL2("https://secondexample.com"); + + std::vector<PasswordFormDigest> forms; + forms.push_back(PasswordFormDigest(PasswordForm::Scheme::kHtml, TestURL1, + GURL(TestURL1))); + forms.push_back(PasswordFormDigest(PasswordForm::Scheme::kHtml, TestURL2, + GURL(TestURL2))); + backend().FillMatchingLoginsAsync(mock_reply.Get(), /*include_psl=*/false, + forms); + + // Imitate login retrieval. + PasswordForm matching_signon_realm = + CreateTestLogin(kTestUsername, kTestPassword, TestURL1, kTestDateCreated); + PasswordForm matching_federated = CreateTestLogin( + kTestUsername, kTestPassword, + "federation://secondexample.com/google.com/", kTestDateCreated); + PasswordForm not_matching = + CreateTestLogin(kTestUsername, kTestPassword, + "https://mobile.secondexample.com/", kTestDateCreated); + + const JobId kSecondJobId{1338}; + EXPECT_CALL(*bridge(), GetLoginsForSignonRealm) + .WillOnce(Return(kSecondJobId)); + // Logins will be retrieved for forms from |forms| in a backwards order. + consumer().OnCompleteWithLogins(kFirstJobId, + {matching_federated, not_matching}); + RunUntilIdle(); + + // Retrieving logins for the last form should trigger the final callback. + LoginsResult expected_logins; + expected_logins.push_back(std::make_unique<PasswordForm>(matching_federated)); + expected_logins.push_back( + std::make_unique<PasswordForm>(matching_signon_realm)); + EXPECT_CALL(mock_reply, + Run(UnorderedPasswordFormElementsAre(&expected_logins))); + + task_environment_.FastForwardBy(kLatencyDelta); + consumer().OnCompleteWithLogins(kSecondJobId, {matching_signon_realm}); + RunUntilIdle(); + + histogram_tester.ExpectTimeBucketCount( + "PasswordManager.PasswordStoreAndroidBackend.FillMatchingLoginsAsync." + "Latency", + kLatencyDelta, 1); +} + +TEST_F(PasswordStoreAndroidBackendTest, FillMatchingLoginsPSL) { + base::HistogramTester histogram_tester; + backend().InitBackend(PasswordStoreAndroidBackend::RemoteChangesReceived(), + base::RepeatingClosure(), base::DoNothing()); + base::MockCallback<LoginsReply> mock_reply; + + const JobId kFirstJobId{1337}; + EXPECT_CALL(*bridge(), GetLoginsForSignonRealm).WillOnce(Return(kFirstJobId)); + constexpr auto kLatencyDelta = base::Milliseconds(123u); + + std::string TestURL1("https://firstexample.com"); + std::string TestURL2("https://secondexample.com"); + + std::vector<PasswordFormDigest> forms; + forms.push_back(PasswordFormDigest(PasswordForm::Scheme::kHtml, TestURL1, + GURL(TestURL1))); + forms.push_back(PasswordFormDigest(PasswordForm::Scheme::kHtml, TestURL2, + GURL(TestURL2))); + backend().FillMatchingLoginsAsync(mock_reply.Get(), /*include_psl=*/true, + forms); + + // Imitate login retrieval. + PasswordForm psl_matching = + CreateTestLogin(kTestUsername, kTestPassword, + "https://mobile.firstexample.com/", kTestDateCreated); + PasswordForm not_matching = + CreateTestLogin(kTestUsername, kTestPassword, + "https://nomatchfirstexample.com/", kTestDateCreated); + PasswordForm psl_matching_federated = CreateTestLogin( + kTestUsername, kTestPassword, + "federation://mobile.secondexample.com/google.com/", kTestDateCreated); + + const JobId kSecondJobId{1338}; + EXPECT_CALL(*bridge(), GetLoginsForSignonRealm) + .WillOnce(Return(kSecondJobId)); + // Logins will be retrieved for forms from |forms| in a backwards order. + consumer().OnCompleteWithLogins(kFirstJobId, {psl_matching_federated}); + RunUntilIdle(); + + // Retrieving logins for the last form should trigger the final callback. + LoginsResult expected_logins; + expected_logins.push_back(std::make_unique<PasswordForm>(psl_matching)); + expected_logins.push_back( + std::make_unique<PasswordForm>(psl_matching_federated)); + EXPECT_CALL(mock_reply, + Run(UnorderedPasswordFormElementsAre(&expected_logins))); + + task_environment_.FastForwardBy(kLatencyDelta); + consumer().OnCompleteWithLogins(kSecondJobId, {psl_matching, not_matching}); + RunUntilIdle(); + histogram_tester.ExpectTimeBucketCount( + "PasswordManager.PasswordStoreAndroidBackend.FillMatchingLoginsAsync." + "Latency", + kLatencyDelta, 1); +} + TEST_F(PasswordStoreAndroidBackendTest, CallsBridgeForAutofillableLogins) { backend().InitBackend(PasswordStoreAndroidBackend::RemoteChangesReceived(), base::RepeatingClosure(), base::DoNothing()); @@ -162,8 +277,8 @@ const JobId kJobId{13388}; base::MockCallback<PasswordStoreChangeListReply> mock_reply; - PasswordForm form = CreateTestLogin(kTestUsername, kTestPassword, - GURL(kTestUrl), kTestDateCreated); + PasswordForm form = + CreateTestLogin(kTestUsername, kTestPassword, kTestUrl, kTestDateCreated); EXPECT_CALL(*bridge(), RemoveLogin(form)).WillOnce(Return(kJobId)); backend().RemoveLoginAsync(form, mock_reply.Get()); @@ -198,12 +313,11 @@ // forms. const JobId kRemoveLoginJobId{13388}; EXPECT_CALL(*bridge(), RemoveLogin).WillOnce(Return(kRemoveLoginJobId)); - PasswordForm form_to_delete = - CreateTestLogin(kTestUsername, kTestPassword, GURL(kTestUrl), + PasswordForm form_to_delete = CreateTestLogin( + kTestUsername, kTestPassword, kTestUrl, base::Time::FromTimeT(1500)); + PasswordForm form_to_keep = + CreateTestLogin(kTestUsername, kTestPassword, "https://differentsite.com", base::Time::FromTimeT(1500)); - PasswordForm form_to_keep = CreateTestLogin(kTestUsername, kTestPassword, - GURL("https://differentsite.com"), - base::Time::FromTimeT(1500)); consumer().OnCompleteWithLogins(kGetLoginsJobId, {form_to_delete, form_to_keep}); RunUntilIdle(); @@ -237,12 +351,10 @@ // forms. const JobId kRemoveLoginJobId{13388}; EXPECT_CALL(*bridge(), RemoveLogin).WillOnce(Return(kRemoveLoginJobId)); - PasswordForm form_to_delete = - CreateTestLogin(kTestUsername, kTestPassword, GURL(kTestUrl), - base::Time::FromTimeT(1500)); - PasswordForm form_to_keep = - CreateTestLogin(kTestUsername, kTestPassword, GURL(kTestUrl), - base::Time::FromTimeT(2500)); + PasswordForm form_to_delete = CreateTestLogin( + kTestUsername, kTestPassword, kTestUrl, base::Time::FromTimeT(1500)); + PasswordForm form_to_keep = CreateTestLogin( + kTestUsername, kTestPassword, kTestUrl, base::Time::FromTimeT(2500)); consumer().OnCompleteWithLogins(kGetLoginsJobId, {form_to_delete, form_to_keep}); RunUntilIdle(); @@ -262,8 +374,8 @@ const JobId kJobId{13388}; base::MockCallback<PasswordStoreChangeListReply> mock_reply; - PasswordForm form = CreateTestLogin(kTestUsername, kTestPassword, - GURL(kTestUrl), kTestDateCreated); + PasswordForm form = + CreateTestLogin(kTestUsername, kTestPassword, kTestUrl, kTestDateCreated); EXPECT_CALL(*bridge(), AddLogin(form)).WillOnce(Return(kJobId)); backend().AddLoginAsync(form, mock_reply.Get()); @@ -281,8 +393,8 @@ const JobId kJobId{13388}; base::MockCallback<PasswordStoreChangeListReply> mock_reply; - PasswordForm form = CreateTestLogin(kTestUsername, kTestPassword, - GURL(kTestUrl), kTestDateCreated); + PasswordForm form = + CreateTestLogin(kTestUsername, kTestPassword, kTestUrl, kTestDateCreated); EXPECT_CALL(*bridge(), UpdateLogin(form)).WillOnce(Return(kJobId)); backend().UpdateLoginAsync(form, mock_reply.Get()); @@ -384,8 +496,8 @@ base::MockCallback<PasswordStoreChangeListReply> mock_reply; EXPECT_CALL(*bridge(), AddLogin).WillOnce(Return(kJobId)); - PasswordForm form = CreateTestLogin(kTestUsername, kTestPassword, - GURL(kTestUrl), kTestDateCreated); + PasswordForm form = + CreateTestLogin(kTestUsername, kTestPassword, kTestUrl, kTestDateCreated); backend().AddLoginAsync(form, mock_reply.Get()); EXPECT_CALL(mock_reply, Run); task_environment_.FastForwardBy(kLatencyDelta); @@ -429,8 +541,8 @@ base::MockCallback<PasswordStoreChangeListReply> mock_reply; EXPECT_CALL(*bridge(), UpdateLogin).WillOnce(Return(kJobId)); - PasswordForm form = CreateTestLogin(kTestUsername, kTestPassword, - GURL(kTestUrl), kTestDateCreated); + PasswordForm form = + CreateTestLogin(kTestUsername, kTestPassword, kTestUrl, kTestDateCreated); backend().UpdateLoginAsync(form, mock_reply.Get()); EXPECT_CALL(mock_reply, Run); task_environment_.FastForwardBy(kLatencyDelta); @@ -474,8 +586,8 @@ base::MockCallback<PasswordStoreChangeListReply> mock_reply; EXPECT_CALL(*bridge(), RemoveLogin).WillOnce(Return(kJobId)); - PasswordForm form = CreateTestLogin(kTestUsername, kTestPassword, - GURL(kTestUrl), kTestDateCreated); + PasswordForm form = + CreateTestLogin(kTestUsername, kTestPassword, kTestUrl, kTestDateCreated); backend().RemoveLoginAsync(form, mock_reply.Get()); EXPECT_CALL(mock_reply, Run); task_environment_.FastForwardBy(kLatencyDelta);
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc index 079d3be96..b134bc61 100644 --- a/chrome/browser/prefs/browser_prefs.cc +++ b/chrome/browser/prefs/browser_prefs.cc
@@ -278,6 +278,7 @@ #include "ash/public/cpp/ash_prefs.h" #include "chrome/browser/apps/app_service/metrics/app_platform_metrics_service.h" #include "chrome/browser/apps/app_service/webapk/webapk_prefs.h" +#include "chrome/browser/ash/account_manager/account_apps_availability.h" #include "chrome/browser/ash/account_manager/account_manager_edu_coexistence_controller.h" #include "chrome/browser/ash/app_mode/arc/arc_kiosk_app_manager.h" #include "chrome/browser/ash/app_mode/kiosk_app_manager.h" @@ -1313,6 +1314,7 @@ apps::webapk_prefs::RegisterProfilePrefs(registry); arc::prefs::RegisterProfilePrefs(registry); ArcAppListPrefs::RegisterProfilePrefs(registry); + ash::AccountAppsAvailability::RegisterPrefs(registry); account_manager::AccountManager::RegisterPrefs(registry); ash::ApkWebAppService::RegisterProfilePrefs(registry); ash::app_time::AppActivityRegistry::RegisterProfilePrefs(registry);
diff --git a/chrome/browser/profiles/profile_manager.cc b/chrome/browser/profiles/profile_manager.cc index 1199176..9888256 100644 --- a/chrome/browser/profiles/profile_manager.cc +++ b/chrome/browser/profiles/profile_manager.cc
@@ -507,6 +507,9 @@ for (auto& observer : observers_) { observer.OnProfileManagerDestroying(); } + + base::UmaHistogramBoolean("Profile.DidDestroyProfileBeforeShutdown", + could_have_destroyed_profile_); if (base::FeatureList::IsEnabled(features::kDestroyProfileOnBrowserClose)) { // Ideally, all the keepalives should've been cleared already. Report // metrics for incorrect usage of ScopedProfileKeepAlive. @@ -1368,11 +1371,18 @@ return info->keep_alives[origin] > 0; } +bool ProfileManager::HasZombieProfile() const { + for (const base::FilePath& dir : ever_loaded_profiles_) { + const ProfileInfo* info = GetProfileInfoByPath(dir); + if (!info || GetTotalRefCount(info->keep_alives) == 0) + return true; + } + return false; +} + void ProfileManager::AddKeepAlive(const Profile* profile, ProfileKeepAliveOrigin origin) { DCHECK_NE(ProfileKeepAliveOrigin::kWaitingForFirstBrowserWindow, origin); - if (!base::FeatureList::IsEnabled(features::kDestroyProfileOnBrowserClose)) - return; DCHECK(profile); DCHECK(!profile->IsOffTheRecord()); @@ -1389,8 +1399,10 @@ return; } - DCHECK_NE(0, GetTotalRefCount(info->keep_alives)) - << "AddKeepAlive() on a soon-to-be-deleted Profile is not allowed"; + if (base::FeatureList::IsEnabled(features::kDestroyProfileOnBrowserClose)) { + DCHECK_NE(0, GetTotalRefCount(info->keep_alives)) + << "AddKeepAlive() on a soon-to-be-deleted Profile is not allowed"; + } info->keep_alives[origin]++; @@ -1404,8 +1416,6 @@ void ProfileManager::RemoveKeepAlive(const Profile* profile, ProfileKeepAliveOrigin origin) { DCHECK_NE(ProfileKeepAliveOrigin::kWaitingForFirstBrowserWindow, origin); - if (!base::FeatureList::IsEnabled(features::kDestroyProfileOnBrowserClose)) - return; DCHECK(profile); DCHECK(!profile->IsOffTheRecord()); @@ -1433,8 +1443,6 @@ } void ProfileManager::ClearFirstBrowserWindowKeepAlive(const Profile* profile) { - DCHECK(base::FeatureList::IsEnabled(features::kDestroyProfileOnBrowserClose)); - DCHECK(profile); DCHECK(!profile->IsOffTheRecord()); @@ -1460,6 +1468,13 @@ if (GetTotalRefCount(info->keep_alives) != 0) return; + could_have_destroyed_profile_ = true; + + // When DestroyProfileOnBrowserClose is disabled: record memory metrics, but + // don't actually delete the Profile. + if (!base::FeatureList::IsEnabled(features::kDestroyProfileOnBrowserClose)) + return; + if (!info->GetCreatedProfile()) { NOTREACHED() << "Attempted to delete profile " << info->GetRawProfile()->GetDebugName() @@ -1629,8 +1644,7 @@ ProfileManager::ProfileInfo::ProfileInfo() { // The profile should have a refcount >=1 until AddKeepAlive() is called. - if (base::FeatureList::IsEnabled(features::kDestroyProfileOnBrowserClose)) - keep_alives[ProfileKeepAliveOrigin::kWaitingForFirstBrowserWindow] = 1; + keep_alives[ProfileKeepAliveOrigin::kWaitingForFirstBrowserWindow] = 1; } ProfileManager::ProfileInfo::~ProfileInfo() { @@ -2069,6 +2083,7 @@ ProfileInfo* info_raw = info.get(); profiles_info_.insert( std::make_pair(profile_ptr->GetPath(), std::move(info))); + ever_loaded_profiles_.insert(profile_ptr->GetPath()); return info_raw; } @@ -2079,6 +2094,7 @@ auto info = ProfileInfo::FromUnownedProfile(profile); ProfileInfo* info_raw = info.get(); profiles_info_.insert(std::make_pair(path, std::move(info))); + ever_loaded_profiles_.insert(path); return info_raw; }
diff --git a/chrome/browser/profiles/profile_manager.h b/chrome/browser/profiles/profile_manager.h index ac83755a..19e84b0a 100644 --- a/chrome/browser/profiles/profile_manager.h +++ b/chrome/browser/profiles/profile_manager.h
@@ -12,6 +12,7 @@ #include <list> #include <map> #include <memory> +#include <set> #include <string> #include <vector> @@ -286,6 +287,20 @@ bool HasKeepAliveForTesting(const Profile* profile, ProfileKeepAliveOrigin origin); + // Returns true if there's at least one profile in a "zombie" state, which + // means either: + // + // - this profile was destroyed from memory, + // - this profile has a refcount of 0, meaning it's safe to destroy. + // + // Looks at the list of profiles that were loaded during this browsing + // session, to determine if they're all still loaded in memory and look at + // their refcount. + // + // This is used for an A/B test, that measures the impact of the + // DestroyProfileOnBrowserClose variation on memory usage. + bool HasZombieProfile() const; + protected: // Creates a new profile by calling into the profile's profile creation // method. Virtual so that unittests can return a TestingProfile instead @@ -561,6 +576,15 @@ std::vector<Profile*> active_profiles_; bool closing_all_browsers_ = false; + // Becomes true once the refcount for any profile hits 0. This is used to + // measure how often DestroyProfileOnBrowserClose logic triggers. + bool could_have_destroyed_profile_ = false; + + // Set of profile dirs that were loaded during this browsing session at some + // point (or are currently loaded). This is used to measure memory savings + // from DestroyProfileOnBrowserClose. + std::set<base::FilePath> ever_loaded_profiles_; + // Controls whether to initialize some services. Only disabled for testing. bool do_final_services_init_ = true;
diff --git a/chrome/browser/profiles/scoped_profile_keep_alive.cc b/chrome/browser/profiles/scoped_profile_keep_alive.cc index b8f6a1f..e5f483b 100644 --- a/chrome/browser/profiles/scoped_profile_keep_alive.cc +++ b/chrome/browser/profiles/scoped_profile_keep_alive.cc
@@ -4,6 +4,8 @@ #include "chrome/browser/profiles/scoped_profile_keep_alive.h" +#include "base/feature_list.h" +#include "chrome/browser/browser_features.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/profiles/profile_keep_alive_types.h" #include "chrome/browser/profiles/profile_manager.h" @@ -23,13 +25,19 @@ ScopedProfileKeepAlive::~ScopedProfileKeepAlive() { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - // The object that owns ScopedProfileKeepAlive might be owned by Profile, - // in which case triggering ~Profile() from here causes UAF bugs. Post - // RemoveKeepAlive() to a task to avoid this. - content::GetUIThreadTaskRunner({})->PostTask( - FROM_HERE, - base::BindOnce(&ScopedProfileKeepAlive::RemoveKeepAliveOnUIThread, - profile_, origin_)); + if (!base::FeatureList::IsEnabled(features::kDestroyProfileOnBrowserClose)) { + // Decrementing the refcount won't cause Profile destruction in this + // configuration, so we don't need to post a task. + RemoveKeepAliveOnUIThread(profile_, origin_); + } else { + // The object that owns ScopedProfileKeepAlive might be owned by Profile, + // in which case triggering ~Profile() from here causes UAF bugs. Post + // RemoveKeepAlive() to a task to avoid this. + content::GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, + base::BindOnce(&ScopedProfileKeepAlive::RemoveKeepAliveOnUIThread, + profile_, origin_)); + } } // static
diff --git a/chrome/browser/resources/chromeos/accessibility/strings/accessibility_strings_hu.xtb b/chrome/browser/resources/chromeos/accessibility/strings/accessibility_strings_hu.xtb index 5c29d43..73d8d90 100644 --- a/chrome/browser/resources/chromeos/accessibility/strings/accessibility_strings_hu.xtb +++ b/chrome/browser/resources/chromeos/accessibility/strings/accessibility_strings_hu.xtb
@@ -30,6 +30,7 @@ <translation id="1189258430971676908">Gyakorlóterület: Legördülő listák</translation> <translation id="1195238899008218998">Utószó</translation> <translation id="1197088940767939838">Narancssárga</translation> +<translation id="1198865190323699001">Érintéses kézmozdulatok</translation> <translation id="1201402288615127009">Tovább</translation> <translation id="1206619573307042055">fényújság</translation> <translation id="1207086294218137981">Nincs következő 4. szintű címsor</translation>
diff --git a/chrome/browser/resources/chromeos/accessibility/strings/accessibility_strings_lt.xtb b/chrome/browser/resources/chromeos/accessibility/strings/accessibility_strings_lt.xtb index ed4d66a0..ce492cd 100644 --- a/chrome/browser/resources/chromeos/accessibility/strings/accessibility_strings_lt.xtb +++ b/chrome/browser/resources/chromeos/accessibility/strings/accessibility_strings_lt.xtb
@@ -30,6 +30,7 @@ <translation id="1189258430971676908">Praktinių užduočių sritis: išskleidžiamieji sąrašai</translation> <translation id="1195238899008218998">Baigiamasis žodis</translation> <translation id="1197088940767939838">Oranžinė</translation> +<translation id="1198865190323699001">Lietimo gestai</translation> <translation id="1201402288615127009">Kitas</translation> <translation id="1206619573307042055">marquee</translation> <translation id="1207086294218137981">Nėra kitos 4 lygio antraštės</translation>
diff --git a/chrome/browser/resources/chromeos/accessibility/strings/accessibility_strings_ms.xtb b/chrome/browser/resources/chromeos/accessibility/strings/accessibility_strings_ms.xtb index af76c29..8da8192 100644 --- a/chrome/browser/resources/chromeos/accessibility/strings/accessibility_strings_ms.xtb +++ b/chrome/browser/resources/chromeos/accessibility/strings/accessibility_strings_ms.xtb
@@ -30,6 +30,7 @@ <translation id="1189258430971676908">Kawasan latihan: Senarai lungsur</translation> <translation id="1195238899008218998">Kata hujungan</translation> <translation id="1197088940767939838">Oren</translation> +<translation id="1198865190323699001">Gerak Isyarat Sentuh</translation> <translation id="1201402288615127009">Seterusnya</translation> <translation id="1206619573307042055">marki</translation> <translation id="1207086294218137981">Tiada tajuk tahap 4 seterusnya</translation>
diff --git a/chrome/browser/resources/nearby_internals/nearby_ui_trigger_browser_proxy.js b/chrome/browser/resources/nearby_internals/nearby_ui_trigger_browser_proxy.js index 11305be1..ad5d20eb 100644 --- a/chrome/browser/resources/nearby_internals/nearby_ui_trigger_browser_proxy.js +++ b/chrome/browser/resources/nearby_internals/nearby_ui_trigger_browser_proxy.js
@@ -139,6 +139,13 @@ notifyFastPairPairing() { chrome.send('notifyFastPairPairing'); } + + /** + * Tells C++ side to trigger a Fast Pair associate account notification. + */ + notifyFastPairAssociateAccount() { + chrome.send('notifyFastPairAssociateAccount'); + } } addSingletonGetter(NearbyUiTriggerBrowserProxy);
diff --git a/chrome/browser/resources/nearby_internals/ui_trigger_tab.html b/chrome/browser/resources/nearby_internals/ui_trigger_tab.html index a1c71475..97c88c8 100644 --- a/chrome/browser/resources/nearby_internals/ui_trigger_tab.html +++ b/chrome/browser/resources/nearby_internals/ui_trigger_tab.html
@@ -55,26 +55,20 @@ <cr-button class="internals-button" on-click="onClearPrefsButtonClicked_"> Reset Nearby </cr-button> - <cr-button class="internals-button" on-click="onFastPairErrorNotificationClicked_"> - Fast Pair Error Notification - </cr-button> - <cr-button class="internals-button" on-click="onFastPairDiscoveryNotificationClicked_"> - Fast Pair Discovery Notification - </cr-button> - <cr-button class="internals-button" on-click="onFastPairPairingNotificationClicked_"> - Fast Pair Pairing Notification - </cr-button> <span id="flex"></span> <cr-button on-click="onAcceptClicked_" class="internals-button" - class="trigger-button" disabled="[[!shareTargetSelectOptionList_.length]]"> + class="trigger-button" + disabled="[[!shareTargetSelectOptionList_.length]]"> Accept </cr-button> <cr-button on-click="onRejectClicked_" class="internals-button" - class="trigger-button" disabled="[[!shareTargetSelectOptionList_.length]]"> + class="trigger-button" + disabled="[[!shareTargetSelectOptionList_.length]]"> Reject </cr-button> <cr-button on-click="onOpenClicked_" class="internals-button" - class="trigger-button" disabled="[[!shareTargetSelectOptionList_.length]]"> + class="trigger-button" + disabled="[[!shareTargetSelectOptionList_.length]]"> Open </cr-button> </div> @@ -103,16 +97,37 @@ </template> </select> <cr-button on-click="onSendTextClicked_" class="internals-button" - class="trigger-button" disabled="[[!shareTargetSelectOptionList_.length]]"> + class="trigger-button" + disabled="[[!shareTargetSelectOptionList_.length]]"> Send Text </cr-button> <cr-button on-click="onCancelClicked_" class="internals-button" - class="trigger-button" disabled="[[!shareTargetSelectOptionList_.length]]"> + class="trigger-button" + disabled="[[!shareTargetSelectOptionList_.length]]"> Cancel </cr-button> </div> + <div class="buttons"> + <cr-button class="internals-button" + on-click="onFastPairErrorNotificationClicked_"> + Fast Pair Error Notification + </cr-button> + <cr-button class="internals-button" + on-click="onFastPairDiscoveryNotificationClicked_"> + Fast Pair Discovery Notification + </cr-button> + <cr-button class="internals-button" + on-click="onFastPairPairingNotificationClicked_"> + Fast Pair Pairing Notification + </cr-button> + <cr-button class="internals-button" + on-click="onFastPairAssociateAccountNotificationClicked_"> + Fast Pair Save Account Notification + </cr-button> + </div> </div> -<iron-list items="[[uiTriggerObjectList_]]" as="generic-object" id="logging-section"> +<iron-list items="[[uiTriggerObjectList_]]" as="generic-object" + id="logging-section"> <template> <ui-trigger-object item="[[generic-object]]"> </ui-trigger-object>
diff --git a/chrome/browser/resources/nearby_internals/ui_trigger_tab.js b/chrome/browser/resources/nearby_internals/ui_trigger_tab.js index aba9071..8091f15f 100644 --- a/chrome/browser/resources/nearby_internals/ui_trigger_tab.js +++ b/chrome/browser/resources/nearby_internals/ui_trigger_tab.js
@@ -129,6 +129,10 @@ this.browserProxy_.notifyFastPairPairing(); }, + onFastPairAssociateAccountNotificationClicked_() { + this.browserProxy_.notifyFastPairAssociateAccount(); + }, + /** * Triggers RegisterReceiveSurface with Foreground as Receive state. * @private
diff --git a/chrome/browser/resources/settings/people_page/import_data_dialog.html b/chrome/browser/resources/settings/people_page/import_data_dialog.html index 0b27460f..fa352e0 100644 --- a/chrome/browser/resources/settings/people_page/import_data_dialog.html +++ b/chrome/browser/resources/settings/people_page/import_data_dialog.html
@@ -51,31 +51,41 @@ </template> </select> <div class="description">$i18n{importDescription}</div> - <settings-checkbox id="importDialogHistory" - hidden="[[!selected_.history]]" - pref="{{prefs.import_dialog_history}}" - label="$i18n{importHistory}" no-set-pref> - </settings-checkbox> - <settings-checkbox id="importDialogBookmarks" - hidden="[[!selected_.favorites]]" - pref="{{prefs.import_dialog_bookmarks}}" - label="$i18n{importFavorites}" no-set-pref> - </settings-checkbox> - <settings-checkbox id="importDialogSavedPasswords" - hidden="[[!selected_.passwords]]" - pref="{{prefs.import_dialog_saved_passwords}}" - label="$i18n{importPasswords}" no-set-pref> - </settings-checkbox> - <settings-checkbox id="importDialogSearchEngine" - hidden="[[!selected_.search]]" - pref="{{prefs.import_dialog_search_engine}}" - label="$i18n{importSearch}" no-set-pref> - </settings-checkbox> - <settings-checkbox id="importDialogAutofillFormData" - hidden="[[!selected_.autofillFormData]]" - pref="{{prefs.import_dialog_autofill_form_data}}" - label="$i18n{importAutofillFormData}" no-set-pref> - </settings-checkbox> + + <!-- + A parent div is needed here to prevent bugs caused by elements + becoming hidden interfering with NVDA reading out changes to the + select menu. The checkboxes just need to have a different parent + than the select menu. + See https://github.com/nvaccess/nvda/issues/13116. + --> + <div> + <settings-checkbox id="importDialogHistory" + hidden="[[!selected_.history]]" + pref="{{prefs.import_dialog_history}}" + label="$i18n{importHistory}" no-set-pref> + </settings-checkbox> + <settings-checkbox id="importDialogBookmarks" + hidden="[[!selected_.favorites]]" + pref="{{prefs.import_dialog_bookmarks}}" + label="$i18n{importFavorites}" no-set-pref> + </settings-checkbox> + <settings-checkbox id="importDialogSavedPasswords" + hidden="[[!selected_.passwords]]" + pref="{{prefs.import_dialog_saved_passwords}}" + label="$i18n{importPasswords}" no-set-pref> + </settings-checkbox> + <settings-checkbox id="importDialogSearchEngine" + hidden="[[!selected_.search]]" + pref="{{prefs.import_dialog_search_engine}}" + label="$i18n{importSearch}" no-set-pref> + </settings-checkbox> + <settings-checkbox id="importDialogAutofillFormData" + hidden="[[!selected_.autofillFormData]]" + pref="{{prefs.import_dialog_autofill_form_data}}" + label="$i18n{importAutofillFormData}" no-set-pref> + </settings-checkbox> + </div> </div> </div> <div slot="button-container">
diff --git a/chrome/browser/resources/settings/people_page/import_data_dialog.ts b/chrome/browser/resources/settings/people_page/import_data_dialog.ts index 4a39694..567bc7f4 100644 --- a/chrome/browser/resources/settings/people_page/import_data_dialog.ts +++ b/chrome/browser/resources/settings/people_page/import_data_dialog.ts
@@ -144,16 +144,6 @@ } private onBrowserProfileSelectionChange_() { - // <if expr="is_win"> - // https://github.com/nvaccess/nvda/issues/13116: Set the selected value in - // a timeout to avoid NVDA bugging out from content changing on the page. - setTimeout(() => { - this.selected_ = - this.browserProfiles_[this.$.browserSelect.selectedIndex]; - }); - return; - // </if> - this.selected_ = this.browserProfiles_[this.$.browserSelect.selectedIndex]; }
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc index 22ebf758..359017a6 100644 --- a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc +++ b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.cc
@@ -360,7 +360,7 @@ void BinaryUploadService::OnGetRequestData(Request* request, Result result, - const Request::Data& data) { + Request::Data data) { if (!IsActive(request)) return; @@ -595,7 +595,10 @@ } BinaryUploadService::Request::Data::Data() = default; -BinaryUploadService::Request::Data::Data(const Data&) = default; +BinaryUploadService::Request::Data::Data(Data&&) = default; +BinaryUploadService::Request::Data& +BinaryUploadService::Request::Data::operator=( + BinaryUploadService::Request::Data&& other) = default; BinaryUploadService::Request::Data::~Data() = default; BinaryUploadService::Request::Request(ContentAnalysisCallback callback,
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h index d611020..72b69c8 100644 --- a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h +++ b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h
@@ -44,7 +44,7 @@ explicit BinaryUploadService(Profile* profile); // This constructor is useful in tests, if you want to keep a reference to the - // service's |binary_fcm_service_|. + // service's `binary_fcm_service_`. BinaryUploadService( scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, Profile* profile, @@ -100,7 +100,7 @@ // string). class Request { public: - // |callback| will run on the UI thread. + // `callback` will run on the UI thread. Request(ContentAnalysisCallback, GURL url); virtual ~Request(); Request(const Request&) = delete; @@ -111,7 +111,10 @@ // Structure of data returned in the callback to GetRequestData(). struct Data { Data(); - Data(const Data&); + Data(Data&&); + Data& operator=(Data&&); + Data(const Data&) = delete; + Data& operator=(const Data&) = delete; ~Data(); // The data content. Only populated for string requests. @@ -123,7 +126,7 @@ // The SHA256 of the data. std::string hash; - // The size of the data. This can differ from |contents.size()| when the + // The size of the data. This can differ from `contents.size()` when the // file is too large for deep scanning. This field will contain the true // size. uint64_t size = 0; @@ -132,15 +135,10 @@ std::string mime_type; }; - // Asynchronously returns the file contents to upload. - // TODO(drubery): This could allocate up to kMaxUploadSizeBytes of memory - // for a large file upload. We should see how often that causes errors, - // and possibly implement some sort of streaming interface so we don't use - // so much memory. - // - // |result| is set to SUCCESS if getting the request data succeeded or + // Aynchronously returns the data required to make a MultipartUploadRequest. + // `result` is set to SUCCESS if getting the request data succeeded or // some value describing the error. - using DataCallback = base::OnceCallback<void(Result, const Data&)>; + using DataCallback = base::OnceCallback<void(Result, Data)>; virtual void GetRequestData(DataCallback callback) = 0; // Returns the URL to send the request to. @@ -183,7 +181,7 @@ const std::string& digest() const; const std::string& content_type() const; - // Finish the request, with the given |result| and |response| from the + // Finish the request, with the given `result` and `response` from the // server. void FinishRequest(Result result, enterprise_connectors::ContentAnalysisResponse response); @@ -221,18 +219,18 @@ const std::string& dm_token, enterprise_connectors::AnalysisConnector connector); - // Run every matching callback in |authorization_callbacks_| and remove them. + // Run every matching callback in `authorization_callbacks_` and remove them. void RunAuthorizationCallbacks( const std::string& dm_token, enterprise_connectors::AnalysisConnector connector); - // Resets |can_upload_data_|. Called every 24 hour by |timer_|. + // Resets `can_upload_data_`. Called every 24 hour by `timer_`. void ResetAuthorizationData(const GURL& url); // Performs cleanup needed at shutdown. void Shutdown() override; - // Sets |can_upload_data_| for tests. + // Sets `can_upload_data_` for tests. void SetAuthForTesting(const std::string& dm_token, bool authorized); // Returns the URL that requests are uploaded to. Scans for enterprise go to a @@ -255,15 +253,13 @@ void QueueForDeepScanning(std::unique_ptr<Request> request); // Upload the given file contents for deep scanning. The results will be - // returned asynchronously by calling |request|'s |callback|. This must be + // returned asynchronously by calling `request`'s `callback`. This must be // called on the UI thread. virtual void UploadForDeepScanning(std::unique_ptr<Request> request); void OnGetInstanceID(Request* request, const std::string& token); - void OnGetRequestData(Request* request, - Result result, - const Request::Data& data); + void OnGetRequestData(Request* request, Result result, Request::Data data); void OnUploadComplete(Request* request, bool success, @@ -304,9 +300,9 @@ // Called at the end of the FinishRequest method. void FinishRequestCleanup(Request* request, const std::string& instance_id); - // Tries to start uploads from |request_queue_| depending on the number of + // Tries to start uploads from `request_queue_` depending on the number of // currently active requests. This should be called whenever - // |active_requests_| shrinks so queued requests are started as soon as + // `active_requests_` shrinks so queued requests are started as soon as // possible. void PopRequestQueue();
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_unittest.cc b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_unittest.cc index 69b11d33..cc72f1c2 100644 --- a/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_unittest.cc +++ b/chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service_unittest.cc
@@ -236,7 +236,7 @@ data.contents = "contents"; data.size = data.contents.size(); std::move(callback).Run(BinaryUploadService::Result::SUCCESS, - data); + std::move(data)); })); return request; } @@ -271,7 +271,7 @@ Invoke([](BinaryUploadService::Request::DataCallback callback) { BinaryUploadService::Request::Data data; std::move(callback).Run(BinaryUploadService::Result::FILE_TOO_LARGE, - data); + std::move(data)); })); UploadForDeepScanning(std::move(request));
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/file_analysis_request.cc b/chrome/browser/safe_browsing/cloud_content_scanning/file_analysis_request.cc index e4da4e2..70e057c 100644 --- a/chrome/browser/safe_browsing/cloud_content_scanning/file_analysis_request.cc +++ b/chrome/browser/safe_browsing/cloud_content_scanning/file_analysis_request.cc
@@ -120,7 +120,7 @@ return {file_size <= BinaryUploadService::kMaxUploadSizeBytes ? BinaryUploadService::Result::SUCCESS : BinaryUploadService::Result::FILE_TOO_LARGE, - file_data}; + std::move(file_data)}; } bool IsZipFile(const base::FilePath::StringType& extension, @@ -298,7 +298,14 @@ void FileAnalysisRequest::RunCallback() { if (!data_callback_.is_null()) { - std::move(data_callback_).Run(cached_result_, cached_data_); + // Manually copy `cached_data_` since it is move-only. + BinaryUploadService::Request::Data data; + data.hash = cached_data_.hash; + data.mime_type = cached_data_.mime_type; + data.path = cached_data_.path; + data.size = cached_data_.size; + + std::move(data_callback_).Run(cached_result_, std::move(data)); } }
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/file_analysis_request_unittest.cc b/chrome/browser/safe_browsing/cloud_content_scanning/file_analysis_request_unittest.cc index 26f74d2..2b0fd494 100644 --- a/chrome/browser/safe_browsing/cloud_content_scanning/file_analysis_request_unittest.cc +++ b/chrome/browser/safe_browsing/cloud_content_scanning/file_analysis_request_unittest.cc
@@ -85,11 +85,11 @@ request->GetRequestData(base::BindLambdaForTesting( [&run_loop, &called, &out_result, &out_data]( BinaryUploadService::Result result, - const BinaryUploadService::Request::Data& data) { + BinaryUploadService::Request::Data data) { called = true; run_loop.Quit(); *out_result = result; - *out_data = data; + *out_data = std::move(data); })); run_loop.Run(); @@ -116,7 +116,7 @@ base::RunLoop run_loop; request->GetRequestData(base::BindLambdaForTesting( [&run_loop, &called](BinaryUploadService::Result result, - const BinaryUploadService::Request::Data& data) { + BinaryUploadService::Request::Data data) { called = true; run_loop.Quit(); @@ -142,7 +142,7 @@ base::RunLoop run_loop; request->GetRequestData(base::BindLambdaForTesting( [&run_loop, &called](BinaryUploadService::Result result, - const BinaryUploadService::Request::Data& data) { + BinaryUploadService::Request::Data data) { called = true; run_loop.Quit(); @@ -168,7 +168,7 @@ base::RunLoop run_loop; request->GetRequestData(base::BindLambdaForTesting( [&run_loop, &called](BinaryUploadService::Result result, - const BinaryUploadService::Request::Data& data) { + BinaryUploadService::Request::Data data) { called = true; run_loop.Quit(); @@ -272,7 +272,7 @@ base::RunLoop run_loop; request->GetRequestData(base::BindLambdaForTesting( [&run_loop](BinaryUploadService::Result result, - const BinaryUploadService::Request::Data& data) { + BinaryUploadService::Request::Data data) { run_loop.Quit(); })); run_loop.Run(); @@ -300,7 +300,7 @@ base::RunLoop run_loop; request->GetRequestData(base::BindLambdaForTesting( [&run_loop](BinaryUploadService::Result result, - const BinaryUploadService::Request::Data& data) { + BinaryUploadService::Request::Data data) { run_loop.Quit(); })); run_loop.Run(); @@ -329,11 +329,11 @@ request->GetRequestData(base::BindLambdaForTesting( [&run_loop, &called, &async_result, &async_data]( BinaryUploadService::Result result, - const BinaryUploadService::Request::Data& data) { + BinaryUploadService::Request::Data data) { called = true; run_loop.Quit(); async_result = result; - async_data = data; + async_data = std::move(data); })); run_loop.Run(); @@ -341,14 +341,14 @@ BinaryUploadService::Result sync_result; BinaryUploadService::Request::Data sync_data; - request->GetRequestData(base::BindLambdaForTesting( - [&run_loop, &called, &sync_result, &sync_data]( - BinaryUploadService::Result result, - const BinaryUploadService::Request::Data& data) { + request->GetRequestData( + base::BindLambdaForTesting([&run_loop, &called, &sync_result, &sync_data]( + BinaryUploadService::Result result, + BinaryUploadService::Request::Data data) { called = true; run_loop.Quit(); sync_result = result; - sync_data = data; + sync_data = std::move(data); })); EXPECT_EQ(sync_result, async_result); @@ -379,11 +379,11 @@ request->GetRequestData(base::BindLambdaForTesting( [&run_loop, &called, &result, &data]( BinaryUploadService::Result tmp_result, - const BinaryUploadService::Request::Data& tmp_data) { + BinaryUploadService::Request::Data tmp_data) { called = true; run_loop.Quit(); result = tmp_result; - data = tmp_data; + data = std::move(tmp_data); })); run_loop.Run(); @@ -417,9 +417,8 @@ base::RunLoop run_loop; request->GetRequestData(base::BindLambdaForTesting( - [&run_loop, &file_contents]( - BinaryUploadService::Result result, - const BinaryUploadService::Request::Data& data) { + [&run_loop, &file_contents](BinaryUploadService::Result result, + BinaryUploadService::Request::Data data) { run_loop.Quit(); EXPECT_EQ(result, BinaryUploadService::Result::SUCCESS); @@ -479,11 +478,11 @@ request->GetRequestData(base::BindLambdaForTesting( [&run_loop, &called, &result, &data]( BinaryUploadService::Result tmp_result, - const BinaryUploadService::Request::Data& tmp_data) { + BinaryUploadService::Request::Data tmp_data) { called = true; run_loop.Quit(); result = tmp_result; - data = tmp_data; + data = std::move(tmp_data); })); run_loop.Run(); @@ -526,11 +525,11 @@ request->GetRequestData(base::BindLambdaForTesting( [&run_loop, &called, &result, &data]( BinaryUploadService::Result tmp_result, - const BinaryUploadService::Request::Data& tmp_data) { + BinaryUploadService::Request::Data tmp_data) { called = true; run_loop.Quit(); result = tmp_result; - data = tmp_data; + data = std::move(tmp_data); })); run_loop.Run(); @@ -571,11 +570,11 @@ request->GetRequestData(base::BindLambdaForTesting( [&run_loop, &called, &result, &data]( BinaryUploadService::Result tmp_result, - const BinaryUploadService::Request::Data& tmp_data) { + BinaryUploadService::Request::Data tmp_data) { called = true; run_loop.Quit(); result = tmp_result; - data = tmp_data; + data = std::move(tmp_data); })); run_loop.Run();
diff --git a/chrome/browser/safe_browsing/cloud_content_scanning/file_opening_job_unittest.cc b/chrome/browser/safe_browsing/cloud_content_scanning/file_opening_job_unittest.cc index 1c34ad45..2015445 100644 --- a/chrome/browser/safe_browsing/cloud_content_scanning/file_opening_job_unittest.cc +++ b/chrome/browser/safe_browsing/cloud_content_scanning/file_opening_job_unittest.cc
@@ -25,7 +25,7 @@ void OnGotFileData(std::unique_ptr<FileAnalysisRequest> request, BinaryUploadService::Result result, - const BinaryUploadService::Request::Data& data) { + BinaryUploadService::Request::Data data) { EXPECT_EQ(BinaryUploadService::Result::SUCCESS, result); EXPECT_TRUE(data.contents.empty()); EXPECT_FALSE(data.mime_type.empty());
diff --git a/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc b/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc index 0260fef7..b1e0700 100644 --- a/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc +++ b/chrome/browser/safe_browsing/download_protection/deep_scanning_request.cc
@@ -488,7 +488,7 @@ const base::FilePath& current_path, std::unique_ptr<FileAnalysisRequest> request, BinaryUploadService::Result result, - const BinaryUploadService::Request::Data& data) { + BinaryUploadService::Request::Data data) { file_metadata_.insert({current_path, enterprise_connectors::FileMetadata( final_path.AsUTF8Unsafe(), data.hash, data.mime_type, data.size)});
diff --git a/chrome/browser/safe_browsing/download_protection/deep_scanning_request.h b/chrome/browser/safe_browsing/download_protection/deep_scanning_request.h index 6747e41..e2be85af 100644 --- a/chrome/browser/safe_browsing/download_protection/deep_scanning_request.h +++ b/chrome/browser/safe_browsing/download_protection/deep_scanning_request.h
@@ -174,7 +174,7 @@ const base::FilePath& current_path, std::unique_ptr<FileAnalysisRequest> request, BinaryUploadService::Result result, - const BinaryUploadService::Request::Data& data); + BinaryUploadService::Request::Data data); // Helper function to simplify checking if the report-only feature is set in // conjunction with the corresponding policy value.
diff --git a/chrome/browser/themes/theme_helper_win.cc b/chrome/browser/themes/theme_helper_win.cc index a8c508d..20caee0b 100644 --- a/chrome/browser/themes/theme_helper_win.cc +++ b/chrome/browser/themes/theme_helper_win.cc
@@ -14,116 +14,14 @@ #include "chrome/grit/theme_resources.h" #include "skia/ext/skia_utils_win.h" #include "ui/base/win/shell.h" +#include "ui/color/win/accent_color_observer.h" #include "ui/gfx/color_utils.h" #include "ui/native_theme/native_theme.h" #include "ui/views/views_features.h" namespace { -SkColor GetDefaultInactiveFrameColor() { - return base::win::GetVersion() < base::win::Version::WIN10 - ? SkColorSetRGB(0xEB, 0xEB, 0xEB) - : SK_ColorWHITE; -} - -} // namespace - -ThemeHelperWin::ThemeHelperWin() { - // This just checks for Windows 8+ instead of calling DwmColorsAllowed() - // because we want to monitor the frame color even when a custom frame is in - // use, so that it will be correct if at any time the user switches to the - // native frame. - if (base::win::GetVersion() >= base::win::Version::WIN8) { - dwm_key_ = std::make_unique<base::win::RegKey>( - HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\DWM", KEY_READ); - if (dwm_key_->Valid()) - OnDwmKeyUpdated(); - else - dwm_key_.reset(); - } -} - -ThemeHelperWin::~ThemeHelperWin() = default; - -bool ThemeHelperWin::ShouldUseNativeFrame( - const CustomThemeSupplier* theme_supplier) const { - const bool use_native_frame_if_enabled = - ShouldCustomDrawSystemTitlebar() || - !HasCustomImage(IDR_THEME_FRAME, theme_supplier); - return use_native_frame_if_enabled && ui::win::IsAeroGlassEnabled(); -} - -bool ThemeHelperWin::ShouldUseIncreasedContrastThemeSupplier( - ui::NativeTheme* native_theme) const { - // On Windows the platform provides the high contrast colors, so don't use the - // IncreasedContrastThemeSupplier. - return false; -} - -SkColor ThemeHelperWin::GetDefaultColor( - int id, - bool incognito, - const CustomThemeSupplier* theme_supplier) const { - // In high contrast mode on Windows the platform provides the color. Try to - // get that color first. - SkColor color; - if (ui::NativeTheme::GetInstanceForNativeUi()->InForcedColorsMode() && - GetPlatformHighContrastColor(id, &color)) { - return color; - } - - if (DwmColorsAllowed(theme_supplier)) { - // In Windows 10, native inactive borders are #555555 with 50% alpha. - // Prior to version 1809, native active borders use the accent color. - // In version 1809 and following, the active border is #262626 with 66% - // alpha unless the accent color is also used for the frame. - if (id == ThemeProperties::COLOR_ACCENT_BORDER_ACTIVE) { - return (base::win::GetVersion() >= base::win::Version::WIN10_RS5 && - !dwm_frame_color_) - ? SkColorSetARGB(0xa8, 0x26, 0x26, 0x26) - : dwm_accent_border_color_; - } - if (id == ThemeProperties::COLOR_ACCENT_BORDER_INACTIVE) - return SkColorSetARGB(0x80, 0x55, 0x55, 0x55); - - // When we're custom-drawing the titlebar we want to use either the colors - // we calculated in OnDwmKeyUpdated() or the default colors. When we're not - // custom-drawing the titlebar we want to match the color Windows actually - // uses because some things (like the incognito icon) use this color to - // decide whether they should draw in light or dark mode. Incognito colors - // should be the same as non-incognito in all cases here. - if (id == ThemeProperties::COLOR_FRAME_ACTIVE) { - if (dwm_frame_color_) - return dwm_frame_color_.value(); - if (!ShouldCustomDrawSystemTitlebar()) - return SK_ColorWHITE; - // Fall through and use default. - } - if (id == ThemeProperties::COLOR_FRAME_INACTIVE) { - if (!ShouldCustomDrawSystemTitlebar()) { - return inactive_frame_color_from_registry_ - ? dwm_inactive_frame_color_.value() - : GetDefaultInactiveFrameColor(); - } - if (dwm_frame_color_ && !inactive_frame_color_from_registry_) { - // Tint to create inactive color. Always use the non-incognito version - // of the tint, since the frame should look the same in both modes. - return color_utils::HSLShift( - dwm_frame_color_.value(), - GetTint(ThemeProperties::TINT_FRAME_INACTIVE, false, - theme_supplier)); - } - if (dwm_inactive_frame_color_) - return dwm_inactive_frame_color_.value(); - // Fall through and use default. - } - } - - return ThemeHelper::GetDefaultColor(id, incognito, theme_supplier); -} - -bool ThemeHelperWin::GetPlatformHighContrastColor(int id, - SkColor* color) const { +bool GetPlatformHighContrastColor(int id, SkColor* color) { ui::NativeTheme::SystemThemeColor system_theme_color = ui::NativeTheme::SystemThemeColor::kNotSupported; @@ -229,71 +127,117 @@ return true; } +SkColor GetDefaultInactiveFrameColor() { + return base::win::GetVersion() < base::win::Version::WIN10 + ? SkColorSetRGB(0xEB, 0xEB, 0xEB) + : SK_ColorWHITE; +} + +} // namespace + +ThemeHelperWin::ThemeHelperWin() { + subscription_ = ui::AccentColorObserver::Get()->Subscribe(base::BindRepeating( + &ThemeHelperWin::OnAccentColorUpdated, base::Unretained(this))); + FetchAccentColors(); +} + +ThemeHelperWin::~ThemeHelperWin() = default; + +bool ThemeHelperWin::ShouldUseNativeFrame( + const CustomThemeSupplier* theme_supplier) const { + const bool use_native_frame_if_enabled = + ShouldCustomDrawSystemTitlebar() || + !HasCustomImage(IDR_THEME_FRAME, theme_supplier); + return use_native_frame_if_enabled && ui::win::IsAeroGlassEnabled(); +} + +bool ThemeHelperWin::ShouldUseIncreasedContrastThemeSupplier( + ui::NativeTheme* native_theme) const { + // On Windows the platform provides the high contrast colors, so don't use the + // IncreasedContrastThemeSupplier. + return false; +} + +SkColor ThemeHelperWin::GetDefaultColor( + int id, + bool incognito, + const CustomThemeSupplier* theme_supplier) const { + // In high contrast mode on Windows the platform provides the color. Try to + // get that color first. + SkColor color; + if (ui::NativeTheme::GetInstanceForNativeUi()->InForcedColorsMode() && + GetPlatformHighContrastColor(id, &color)) { + return color; + } + + if (DwmColorsAllowed(theme_supplier)) { + // In Windows 10, native inactive borders are #555555 with 50% alpha. + // Prior to version 1809, native active borders use the accent color. + // In version 1809 and following, the active border is #262626 with 66% + // alpha unless the accent color is also used for the frame. + if (id == ThemeProperties::COLOR_ACCENT_BORDER_ACTIVE) { + return (base::win::GetVersion() >= base::win::Version::WIN10_RS5 && + !dwm_frame_color_) + ? SkColorSetARGB(0xa8, 0x26, 0x26, 0x26) + : dwm_accent_border_color_; + } + if (id == ThemeProperties::COLOR_ACCENT_BORDER_INACTIVE) + return SkColorSetARGB(0x80, 0x55, 0x55, 0x55); + + // When we're custom-drawing the titlebar we want to use either the colors + // we calculated in OnDwmKeyUpdated() or the default colors. When we're not + // custom-drawing the titlebar we want to match the color Windows actually + // uses because some things (like the incognito icon) use this color to + // decide whether they should draw in light or dark mode. Incognito colors + // should be the same as non-incognito in all cases here. + if (id == ThemeProperties::COLOR_FRAME_ACTIVE) { + if (dwm_frame_color_) + return dwm_frame_color_.value(); + if (!ShouldCustomDrawSystemTitlebar()) + return SK_ColorWHITE; + // Fall through and use default. + } + if (id == ThemeProperties::COLOR_FRAME_INACTIVE) { + if (dwm_inactive_frame_color_) + return dwm_inactive_frame_color_.value(); + if (!ShouldCustomDrawSystemTitlebar()) + return GetDefaultInactiveFrameColor(); + if (dwm_frame_color_) { + // Tint to create inactive color. Always use the non-incognito version + // of the tint, since the frame should look the same in both modes. + return color_utils::HSLShift( + dwm_frame_color_.value(), + GetTint(ThemeProperties::TINT_FRAME_INACTIVE, false, + theme_supplier)); + } + // Fall through and use default. + } + } + + return ThemeHelper::GetDefaultColor(id, incognito, theme_supplier); +} + bool ThemeHelperWin::DwmColorsAllowed( const CustomThemeSupplier* theme_supplier) const { return ShouldUseNativeFrame(theme_supplier) && (base::win::GetVersion() >= base::win::Version::WIN8); } -void ThemeHelperWin::OnDwmKeyUpdated() { - dwm_accent_border_color_ = GetDefaultInactiveFrameColor(); - DWORD colorization_color, colorization_color_balance; - if ((dwm_key_->ReadValueDW(L"ColorizationColor", &colorization_color) == - ERROR_SUCCESS) && - (dwm_key_->ReadValueDW(L"ColorizationColorBalance", - &colorization_color_balance) == ERROR_SUCCESS)) { - // The accent border color is a linear blend between the colorization - // color and the neutral #d9d9d9. colorization_color_balance is the - // percentage of the colorization color in that blend. - // - // On Windows version 1611 colorization_color_balance can be 0xfffffff3 if - // the accent color is taken from the background and either the background - // is a solid color or was just changed to a slideshow. It's unclear what - // that value's supposed to mean, so change it to 80 to match Edge's - // behavior. - if (colorization_color_balance > 100) - colorization_color_balance = 80; +void ThemeHelperWin::OnAccentColorUpdated() { + FetchAccentColors(); + ui::NativeTheme::GetInstanceForNativeUi()->NotifyOnNativeThemeUpdated(); +} - // colorization_color's high byte is not an alpha value, so replace it - // with 0xff to make an opaque ARGB color. - SkColor input_color = SkColorSetA(colorization_color, 0xff); +void ThemeHelperWin::FetchAccentColors() { + const auto* accent_color_observer = ui::AccentColorObserver::Get(); + dwm_accent_border_color_ = + accent_color_observer->accent_border_color().value_or( + GetDefaultInactiveFrameColor()); - dwm_accent_border_color_ = - color_utils::AlphaBlend(input_color, SkColorSetRGB(0xd9, 0xd9, 0xd9), - colorization_color_balance / 100.0f); - } - - inactive_frame_color_from_registry_ = false; if (base::win::GetVersion() < base::win::Version::WIN10) { dwm_frame_color_ = dwm_accent_border_color_; } else { - DWORD accent_color, color_prevalence; - bool use_dwm_frame_color = - dwm_key_->ReadValueDW(L"AccentColor", &accent_color) == ERROR_SUCCESS && - dwm_key_->ReadValueDW(L"ColorPrevalence", &color_prevalence) == - ERROR_SUCCESS && - color_prevalence == 1; - if (use_dwm_frame_color) { - dwm_frame_color_ = skia::COLORREFToSkColor(accent_color); - DWORD accent_color_inactive; - if (dwm_key_->ReadValueDW(L"AccentColorInactive", - &accent_color_inactive) == ERROR_SUCCESS) { - dwm_inactive_frame_color_ = - skia::COLORREFToSkColor(accent_color_inactive); - inactive_frame_color_from_registry_ = true; - } - } else { - dwm_frame_color_.reset(); - dwm_inactive_frame_color_.reset(); - } - } - - // Notify native theme observers that the native theme has changed. - ui::NativeTheme::GetInstanceForNativeUi()->NotifyOnNativeThemeUpdated(); - - // Watch for future changes. - if (!dwm_key_->StartWatching(base::BindOnce(&ThemeHelperWin::OnDwmKeyUpdated, - base::Unretained(this)))) { - dwm_key_.reset(); + dwm_frame_color_ = accent_color_observer->accent_color(); + dwm_inactive_frame_color_ = accent_color_observer->accent_color_inactive(); } }
diff --git a/chrome/browser/themes/theme_helper_win.h b/chrome/browser/themes/theme_helper_win.h index 9ad716d..ef35bd8 100644 --- a/chrome/browser/themes/theme_helper_win.h +++ b/chrome/browser/themes/theme_helper_win.h
@@ -5,7 +5,7 @@ #ifndef CHROME_BROWSER_THEMES_THEME_HELPER_WIN_H_ #define CHROME_BROWSER_THEMES_THEME_HELPER_WIN_H_ -#include "base/win/registry.h" +#include "base/callback_list.h" #include "chrome/browser/themes/theme_helper.h" #include "third_party/abseil-cpp/absl/types/optional.h" @@ -31,27 +31,23 @@ bool incognito, const CustomThemeSupplier* theme_supplier) const override; - bool GetPlatformHighContrastColor(int id, SkColor* color) const; - // Returns true if colors from DWM can be used, i.e. this is a native frame - // on Windows 10. + // on Windows 8+. bool DwmColorsAllowed(const CustomThemeSupplier* theme_supplier) const; - // Callback executed when |dwm_key_| is updated. This re-reads the active - // frame color and updates |use_dwm_frame_color_|, |dwm_frame_color_| and + // Callback executed when the accent color is updated. This re-reads the + // accent color and updates |dwm_frame_color_| and // |dwm_inactive_frame_color_|. - void OnDwmKeyUpdated(); + void OnAccentColorUpdated(); - // Registry key containing the params that determine the DWM frame color. - std::unique_ptr<base::win::RegKey> dwm_key_; + // Re-reads the accent colors and updates member variables. + void FetchAccentColors(); + + base::CallbackListSubscription subscription_; // The frame color when active. If empty the default colors should be used. absl::optional<SkColor> dwm_frame_color_; - // True if we took |dwm_inactive_frame_color_| from the registry (vs - // calculating it ourselves) and thus Windows will use it too. - bool inactive_frame_color_from_registry_ = false; - // The frame color when inactive. If empty the default colors should be used. absl::optional<SkColor> dwm_inactive_frame_color_;
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn index cc4b0021..e56ade2 100644 --- a/chrome/browser/ui/BUILD.gn +++ b/chrome/browser/ui/BUILD.gn
@@ -2902,6 +2902,7 @@ "//ash/public/cpp/resources:ash_public_unscaled_resources", "//ash/quick_pair/common", "//ash/quick_pair/keyed_service", + "//ash/quick_pair/repository", "//ash/quick_pair/ui", "//ash/services/recording/public/mojom", "//ash/shortcut_viewer",
diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuItemViewBinder.java b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuItemViewBinder.java index 02d7d9a9..96ffb62 100644 --- a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuItemViewBinder.java +++ b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuItemViewBinder.java
@@ -128,7 +128,7 @@ checkbox.setChecked(checked); ApiCompatibilityUtils.setImageTintList(checkbox, AppCompatResources.getColorStateList( - checkbox.getContext(), R.color.checkbox_tint)); + checkbox.getContext(), R.color.selection_control_button_tint)); setupMenuButton(checkbox, buttonModel, appMenuClickHandler); } else if (subIcon != null) { // Display an icon alongside the MenuItem.
diff --git a/chrome/browser/ui/android/strings/translations/android_chrome_strings_as.xtb b/chrome/browser/ui/android/strings/translations/android_chrome_strings_as.xtb index bc58ffb..063f2a60 100644 --- a/chrome/browser/ui/android/strings/translations/android_chrome_strings_as.xtb +++ b/chrome/browser/ui/android/strings/translations/android_chrome_strings_as.xtb
@@ -132,6 +132,7 @@ <translation id="1807246157184219062">পাতল</translation> <translation id="1810845389119482123">প্ৰাৰম্ভিক ছিংক ছেট আপ সমাপ্ত হোৱা নাই</translation> <translation id="1829244130665387512">পৃষ্ঠাত বিচাৰক</translation> +<translation id="1832459821645506983">হয়, মই সন্মত</translation> <translation id="1843805151597803366">অধিক ভাল অনুবাদ পাবলৈ, Google Searchক বৰ্তমানৰ পৃষ্ঠাখন ব্যৱহাৰ কৰিবলৈ দিয়ক</translation> <translation id="1856325424225101786">লাইট ম’ড ৰিছেট কৰিবনে?</translation> <translation id="1868024384445905608">Chromeএ এতিয়া ফাইলবোৰ অধিক দ্ৰুতভাৱে ডাউনল’ড কৰিব পাৰে</translation> @@ -289,6 +290,7 @@ <translation id="2718846868787000099">আপুনি অগ্ৰাধিকাৰ দিয়া ভাষাত সমল দেখুৱাবলৈ আপুনি চোৱা ছাইটসমূহে আপোনাৰ অগ্ৰাধিকাৰসমূহ চাব পাৰে</translation> <translation id="2723001399770238859">অডিঅ’</translation> <translation id="2728754400939377704">ছাইট অনুসৰি সজাওক</translation> +<translation id="2732063072010454421">এটা উন্নত কণ্ঠধ্বনিৰ অভিজ্ঞতা লাভ কৰক</translation> <translation id="2739256783402597439">2G</translation> <translation id="2744248271121720757">ক্ষিপ্ৰভাৱে সন্ধান কৰিবলৈ আৰু প্ৰাসংগিক কার্যসমূহ চাবলৈ কোনো এটা শব্দত টিপক</translation> <translation id="2760989362628427051">আপোনাৰ ডিভাইচত গাঢ় থীম অথবা বেটাৰী সঞ্চয়কাৰী অন থাকিলে গাঢ় ৰঙৰ থীম অন কৰক</translation> @@ -835,6 +837,7 @@ <translation id="587735546353481577">এটা ছাইট ফ’ল’ কৰিবলৈ, ছাইটটোলৈ গৈ Chromeৰ মেনুখন খুলি ফ’ল’ কৰকত টিপক।</translation> <translation id="5880748256563468367">ফীডলৈ যাওক</translation> <translation id="5884076754568147479">আপোনাক কামবোৰ সম্পূর্ণ কৰাত সহায় কৰিবলৈ, আপুনি Assistant ব্যৱহাৰ কৰা ছাইটসমূহৰ URL আৰু সমলবোৰ তথা আপুনি Assistantৰ জৰিয়তে দাখিল কৰা তথ্য Googleএ লাভ কৰিব</translation> +<translation id="5906513782029855931">ছাইটৰ URL জনাটোৱে Google Assistantক কার্য সম্পূর্ণ কৰাৰ ক্ষেত্ৰত আপোনাক সহায় কৰিবলৈ দিয়ে। আপুনি Chromeৰ ছেটিঙত Assistant অফ কৰিব পাৰে।</translation> <translation id="5916664084637901428">অন আছে</translation> <translation id="5919204609460789179">ছিংক আৰম্ভ কৰিবলৈ <ph name="PRODUCT_NAME" /> আপডে'ট কৰক</translation> <translation id="5937580074298050696"><ph name="AMOUNT" /> ৰাহি কৰা হৈছে</translation>
diff --git a/chrome/browser/ui/android/strings/translations/android_chrome_strings_et.xtb b/chrome/browser/ui/android/strings/translations/android_chrome_strings_et.xtb index 851bb644..a980bb5 100644 --- a/chrome/browser/ui/android/strings/translations/android_chrome_strings_et.xtb +++ b/chrome/browser/ui/android/strings/translations/android_chrome_strings_et.xtb
@@ -132,6 +132,7 @@ <translation id="1807246157184219062">Hele</translation> <translation id="1810845389119482123">Sünkroonimise algseadistus ei jõudnud lõpule</translation> <translation id="1829244130665387512">Otsi leheküljelt</translation> +<translation id="1832459821645506983">Jah, sobib</translation> <translation id="1843805151597803366">Paremate tõlgete saamiseks lubage Google'i otsingul praegust lehte kasutada</translation> <translation id="1856325424225101786">Kas lähtestada lihtsustatud režiim?</translation> <translation id="1868024384445905608">Chrome laadib nüüd faile kiiremini alla</translation> @@ -289,6 +290,7 @@ <translation id="2718846868787000099">Sisu kuvamiseks teie eelistatud keeltes näevad teie külastatavad saidid teie eelistusi</translation> <translation id="2723001399770238859">heli</translation> <translation id="2728754400939377704">Sordi saidi alusel</translation> +<translation id="2732063072010454421">Hankige parem häälotsingu kasutuskogemus</translation> <translation id="2739256783402597439">2G</translation> <translation id="2744248271121720757">Puudutage sõna, et kohe otsida või seotud toiminguid näha</translation> <translation id="2760989362628427051">Aktiveeri tume teema, kui tume teema või akusäästja on seadmes sisse lülitatud</translation> @@ -835,6 +837,7 @@ <translation id="587735546353481577">Saidi jälgimiseks minge saidile, avage Chrome'i menüü ja puudutage nuppu Jälgi.</translation> <translation id="5880748256563468367">Ava voog</translation> <translation id="5884076754568147479">Selleks et aidata teil toiminguid teha, saadetakse Google'ile nende saitide URL-id ja sisu, millel assistenti kasutate, ning teave, mille assistendi kaudu esitate</translation> +<translation id="5906513782029855931">Kui teate saitide URL-e, saab Google'i assistent aidata teil ülesandeid täita. Assistendi saab Chrome'i seadetes välja lülitada.</translation> <translation id="5916664084637901428">Sees</translation> <translation id="5919204609460789179">Sünkroonimise alustamiseks värskendage teenust <ph name="PRODUCT_NAME" /></translation> <translation id="5937580074298050696"><ph name="AMOUNT" /> on säästetud</translation>
diff --git a/chrome/browser/ui/android/strings/translations/android_chrome_strings_fa.xtb b/chrome/browser/ui/android/strings/translations/android_chrome_strings_fa.xtb index 78bd569d..3cb706c 100644 --- a/chrome/browser/ui/android/strings/translations/android_chrome_strings_fa.xtb +++ b/chrome/browser/ui/android/strings/translations/android_chrome_strings_fa.xtb
@@ -132,6 +132,7 @@ <translation id="1807246157184219062">روشن</translation> <translation id="1810845389119482123">همگامسازی اولیه کامل نشد</translation> <translation id="1829244130665387512">یافتن در صفحه</translation> +<translation id="1832459821645506983">بله، موافقم</translation> <translation id="1843805151597803366">برای دریافت ترجمههای بهتر، به «جستجوی Google» اجازه دهید از صفحه کنونی استفاده کند</translation> <translation id="1856325424225101786">«حالت ساده» بازنشانی شود؟</translation> <translation id="1868024384445905608">اکنون Chrome فایلها را سریعتر بارگیری میکند</translation> @@ -289,6 +290,7 @@ <translation id="2718846868787000099">برای نشان دادن محتوا به زبانهای دلخواهتان، سایتهایی که بازدید میکنید میتوانند اولویتهایتان را ببینند</translation> <translation id="2723001399770238859">صدا</translation> <translation id="2728754400939377704">بهترتیب سایت</translation> +<translation id="2732063072010454421">صدای بهتری را تجربه کنید</translation> <translation id="2739256783402597439">2G</translation> <translation id="2744248271121720757">برای جستجوی فوری، روی کلمهای ضربه بزنید یا اقدامهای مرتبط را ببینید</translation> <translation id="2760989362628427051">روشن شدن طرح زمینه تیره وقتی طرح زمینه تیره دستگاه یا «بهینهسازی باتری» روشن است</translation> @@ -835,6 +837,7 @@ <translation id="587735546353481577">برای دنبال کردن سایت، به سایت موردنظر بروید، منوی Chrome را باز کنید و روی «دنبال کردن» ضربه بزنید.</translation> <translation id="5880748256563468367">رفتن به جارزن</translation> <translation id="5884076754568147479">برای کمک به انجام کارها، Google نشانیهای وب و محتوای سایتهایی را که در آنها از «دستیار» استفاده میکنید، و همچنین اطلاعاتی را که ازطریق «دستیار» ارسال میکنید دریافت خواهد کرد</translation> +<translation id="5906513782029855931">دانستن نشانی وبسایتها به «دستیار Google» امکان میدهد در تکمیل تکالیف به شما کمک کند. میتوانید «دستیار» را در «تنظیمات Chrome» خاموش کنید.</translation> <translation id="5916664084637901428">روشن</translation> <translation id="5919204609460789179">برای شروع همگامسازی، <ph name="PRODUCT_NAME" /> را بهروزرسانی کنید</translation> <translation id="5937580074298050696"><ph name="AMOUNT" /> صرفهجوییشده</translation>
diff --git a/chrome/browser/ui/android/strings/translations/android_chrome_strings_fil.xtb b/chrome/browser/ui/android/strings/translations/android_chrome_strings_fil.xtb index fe85231..659a900d 100644 --- a/chrome/browser/ui/android/strings/translations/android_chrome_strings_fil.xtb +++ b/chrome/browser/ui/android/strings/translations/android_chrome_strings_fil.xtb
@@ -132,6 +132,7 @@ <translation id="1807246157184219062">Maliwanag</translation> <translation id="1810845389119482123">Hindi natapos ang paunang pag-set up ng pag-sync</translation> <translation id="1829244130665387512">Hanapin sa page</translation> +<translation id="1832459821645506983">Oo, payag ako</translation> <translation id="1843805151597803366">Para makakuha ng mas mahuhusay na pagsasalin, payagan ang Google Search na gamitin ang kasalukuyang page</translation> <translation id="1856325424225101786">I-reset ang Lite mode?</translation> <translation id="1868024384445905608">Mas mabilis nang mag-download ng mga file ang Chrome</translation> @@ -289,6 +290,7 @@ <translation id="2718846868787000099">Para ipakita ang content sa iyong mga gustong wika, makikita ng mga site na binibisita mo ang iyong mga kagustuhan</translation> <translation id="2723001399770238859">audio</translation> <translation id="2728754400939377704">Pagbukud-bukurin ayon sa site</translation> +<translation id="2732063072010454421">Mas magandang karanasan sa paghahanap gamit ang boses</translation> <translation id="2739256783402597439">2G</translation> <translation id="2744248271121720757">Mag-tap ng salita upang hanapin agad ito o tingnan ang mga nauugnay na pagkilos</translation> <translation id="2760989362628427051">I-on ang madilim na tema kapag naka-on ang madilim na tema o Pantipid ng Baterya ng iyong device</translation> @@ -835,6 +837,7 @@ <translation id="587735546353481577">Para subaybayan ang isang site, pumunta sa site, buksan ang menu ng Chrome, at i-tap ang Subaybayan.</translation> <translation id="5880748256563468367">Pumunta sa feed</translation> <translation id="5884076754568147479">Para tulungan kang tapusin ang mga gawain, matatanggap ng Google ang mga URL at content ng mga site kung saan ka gumamit ng Assistant, gayundin ang impormasyong isinumite mo sa pamamagitan ng Assistant</translation> +<translation id="5906513782029855931">Kapag alam ang mga URL ng mga site, matutulungan ka ng Google Assistant na tumapos ng mga gawain. Puwede mong i-off ang Assistant sa mga setting ng Chrome.</translation> <translation id="5916664084637901428">Naka-on</translation> <translation id="5919204609460789179">I-update ang <ph name="PRODUCT_NAME" /> upang simulan ang pag-sync</translation> <translation id="5937580074298050696"><ph name="AMOUNT" /> ang natipid</translation>
diff --git a/chrome/browser/ui/android/strings/translations/android_chrome_strings_fr.xtb b/chrome/browser/ui/android/strings/translations/android_chrome_strings_fr.xtb index 4f5301f..8eae1d6 100644 --- a/chrome/browser/ui/android/strings/translations/android_chrome_strings_fr.xtb +++ b/chrome/browser/ui/android/strings/translations/android_chrome_strings_fr.xtb
@@ -132,6 +132,7 @@ <translation id="1807246157184219062">Clair</translation> <translation id="1810845389119482123">Configuration de la synchronisation initiale non terminée</translation> <translation id="1829244130665387512">Rechercher sur la page</translation> +<translation id="1832459821645506983">Oui, j'accepte</translation> <translation id="1843805151597803366">Pour obtenir de meilleures traductions, autorisez la recherche Google à accéder au contenu de la page actuelle</translation> <translation id="1856325424225101786">Réinitialiser le mode simplifié ?</translation> <translation id="1868024384445905608">Chrome permet désormais de télécharger des fichiers plus vite</translation> @@ -289,6 +290,7 @@ <translation id="2718846868787000099">Permet d'afficher le contenu dans vos langues favorites. Les sites que vous visitez peuvent voir vos préférences</translation> <translation id="2723001399770238859">audio</translation> <translation id="2728754400939377704">Trier par site</translation> +<translation id="2732063072010454421">Améliorez l'expérience vocale</translation> <translation id="2739256783402597439">2G</translation> <translation id="2744248271121720757">Appuyez sur un mot pour lancer une recherche instantanée ou afficher les actions associées</translation> <translation id="2760989362628427051">Activer le thème sombre lorsque le thème sombre ou l'économiseur de batterie de votre appareil est activé</translation> @@ -835,6 +837,7 @@ <translation id="587735546353481577">Pour suivre un site, accédez-y, ouvrez le menu Chrome et appuyez sur "Suivre".</translation> <translation id="5880748256563468367">Accéder au flux</translation> <translation id="5884076754568147479">Pour vous aider à effectuer des tâches, Google recevra les URL et le contenu des sites sur lesquels vous utilisez l'Assistant, ainsi que les informations que vous envoyez via celui-ci</translation> +<translation id="5906513782029855931">Si l'Assistant Google connaît les URL des sites, il peut vous aider à effectuer des tâches. Vous pouvez désactiver l'Assistant dans les paramètres de Chrome.</translation> <translation id="5916664084637901428">Activé</translation> <translation id="5919204609460789179">Mettre à jour <ph name="PRODUCT_NAME" /> pour lancer la synchronisation</translation> <translation id="5937580074298050696"><ph name="AMOUNT" /> économisé(s)</translation>
diff --git a/chrome/browser/ui/android/strings/translations/android_chrome_strings_ja.xtb b/chrome/browser/ui/android/strings/translations/android_chrome_strings_ja.xtb index 188e449..cac5a40 100644 --- a/chrome/browser/ui/android/strings/translations/android_chrome_strings_ja.xtb +++ b/chrome/browser/ui/android/strings/translations/android_chrome_strings_ja.xtb
@@ -132,6 +132,7 @@ <translation id="1807246157184219062">明</translation> <translation id="1810845389119482123">最初の同期設定が終了していません</translation> <translation id="1829244130665387512">ページ内検索</translation> +<translation id="1832459821645506983">同意する</translation> <translation id="1843805151597803366">翻訳を改善するには、Google 検索に現在のページへのアクセスを許可してください</translation> <translation id="1856325424225101786">ライトモードをリセットしますか?</translation> <translation id="1868024384445905608">Chrome でのファイルのダウンロードがさらに速くなりました</translation> @@ -289,6 +290,7 @@ <translation id="2718846868787000099">ご希望の言語でコンテンツを表示するため、アクセス先のサイトには設定内容が伝えられます</translation> <translation id="2723001399770238859">音声</translation> <translation id="2728754400939377704">サイトで並べ替え</translation> +<translation id="2732063072010454421">音声操作の利便性を向上</translation> <translation id="2739256783402597439">2G</translation> <translation id="2744248271121720757">単語をタップすると、検索をすばやく実行したり、関連する操作メニューを確認したりできます</translation> <translation id="2760989362628427051">デバイスのダークモードまたはバッテリー セーバーがオンのときにダークモードをオンにする</translation> @@ -835,6 +837,7 @@ <translation id="587735546353481577">サイトをフォローするには、サイトにアクセスして、Chrome メニューを開き、[フォロー] をタップします。</translation> <translation id="5880748256563468367">フィードに移動</translation> <translation id="5884076754568147479">タスクを完了するため、アシスタントを使用したサイトの URL とコンテンツ、およびアシスタントで送信した情報が Google に送られます。</translation> +<translation id="5906513782029855931">サイトの URL を送信して、Google アシスタントでタスクをサポートできるようにします。アシスタントは、Chrome の設定で無効にできます。</translation> <translation id="5916664084637901428">オン</translation> <translation id="5919204609460789179">同期を開始するには <ph name="PRODUCT_NAME" /> を更新してください</translation> <translation id="5937580074298050696"><ph name="AMOUNT" /> 節約</translation>
diff --git a/chrome/browser/ui/android/strings/translations/android_chrome_strings_nl.xtb b/chrome/browser/ui/android/strings/translations/android_chrome_strings_nl.xtb index a81e069a..e5b5bc1 100644 --- a/chrome/browser/ui/android/strings/translations/android_chrome_strings_nl.xtb +++ b/chrome/browser/ui/android/strings/translations/android_chrome_strings_nl.xtb
@@ -132,6 +132,7 @@ <translation id="1807246157184219062">Licht</translation> <translation id="1810845389119482123">Eerste synchronisatieconfiguratie niet voltooid</translation> <translation id="1829244130665387512">Zoeken op pagina</translation> +<translation id="1832459821645506983">Ja, is goed</translation> <translation id="1843805151597803366">Sta Google Zoeken toe de huidige pagina te gebruiken om betere vertalingen te krijgen</translation> <translation id="1856325424225101786">Lite-versie resetten?</translation> <translation id="1868024384445905608">Chrome downloadt bestanden nu sneller</translation> @@ -289,6 +290,7 @@ <translation id="2718846868787000099">Bezochte sites kunnen je voorkeuren bekijken, zodat ze content in je voorkeurstalen kunnen bekijken</translation> <translation id="2723001399770238859">audio</translation> <translation id="2728754400939377704">Sorteren op site</translation> +<translation id="2732063072010454421">Krijg betere spraakfunctionaliteit</translation> <translation id="2739256783402597439">2G</translation> <translation id="2744248271121720757">Tik op een woord om meteen te zoeken of gerelateerde acties te bekijken</translation> <translation id="2760989362628427051">Zet het donkere thema aan als het donkere thema of de batterijbesparing van je apparaat aanstaat</translation> @@ -835,6 +837,7 @@ <translation id="587735546353481577">Als je een site wilt volgen, ga je naar de site, open je het Chrome-menu en tik je op Volgen.</translation> <translation id="5880748256563468367">Naar feed</translation> <translation id="5884076754568147479">Google kan je helpen taken sneller te voltooien en ontvangt daarom de URL's en content van sites waarop je de Assistent gebruikt en informatie die je via de Assistent indient</translation> +<translation id="5906513782029855931">Als de Google Assistent de URL's van sites weet, kan deze je helpen taken af te ronden. Je kunt de Assistent uitzetten in de instellingen van Chrome.</translation> <translation id="5916664084637901428">Aan</translation> <translation id="5919204609460789179">Update <ph name="PRODUCT_NAME" /> om de synchronisatie te starten</translation> <translation id="5937580074298050696"><ph name="AMOUNT" /> bespaard</translation>
diff --git a/chrome/browser/ui/android/strings/translations/android_chrome_strings_vi.xtb b/chrome/browser/ui/android/strings/translations/android_chrome_strings_vi.xtb index 99a87d9..820bc475 100644 --- a/chrome/browser/ui/android/strings/translations/android_chrome_strings_vi.xtb +++ b/chrome/browser/ui/android/strings/translations/android_chrome_strings_vi.xtb
@@ -132,6 +132,7 @@ <translation id="1807246157184219062">Sáng</translation> <translation id="1810845389119482123">Chưa hoàn tất quá trình thiết lập đồng bộ hóa ban đầu</translation> <translation id="1829244130665387512">Tìm trong trang</translation> +<translation id="1832459821645506983">Có, tôi đồng ý</translation> <translation id="1843805151597803366">Để có bản dịch chính xác hơn, hãy cho phép Google Tìm kiếm sử dụng trang hiện tại</translation> <translation id="1856325424225101786">Đặt lại Chế độ thu gọn?</translation> <translation id="1868024384445905608">Giờ đây, Chrome tải tệp xuống còn nhanh hơn nữa</translation> @@ -289,6 +290,7 @@ <translation id="2718846868787000099">Để hiển thị nội dung bằng ngôn ngữ bạn ưa dùng, những trang web bạn truy cập có thể thấy tùy chọn của bạn</translation> <translation id="2723001399770238859">âm thanh</translation> <translation id="2728754400939377704">Sắp xếp theo trang web</translation> +<translation id="2732063072010454421">Cải thiện trải nghiệm sử dụng giọng nói</translation> <translation id="2739256783402597439">2G</translation> <translation id="2744248271121720757">Hãy nhấn vào một từ để tìm kiếm ngay hoặc xem các hành động có liên quan</translation> <translation id="2760989362628427051">Bật giao diện tối khi Trình tiết kiệm pin hoặc giao diện tối trên thiết bị đang bật</translation> @@ -835,6 +837,7 @@ <translation id="587735546353481577">Để theo dõi một trang web, hãy truy cập vào trang web đó, mở trình đơn Chrome rồi nhấn vào Theo dõi.</translation> <translation id="5880748256563468367">Chuyển đến nguồn cấp dữ liệu</translation> <translation id="5884076754568147479">Để giúp bạn hoàn thành những việc cần làm, Google sẽ nhận URL và nội dung của các trang web mà bạn sử dụng Trợ lý, cũng như thông tin bạn gửi qua Trợ lý</translation> +<translation id="5906513782029855931">Bằng việc biết được URL của trang web, Trợ lý Google có thể giúp bạn hoàn tất một số công việc. Bạn có thể tắt Trợ lý trong phần cài đặt của Chrome.</translation> <translation id="5916664084637901428">Bật</translation> <translation id="5919204609460789179">Cập nhật <ph name="PRODUCT_NAME" /> để bắt đầu đồng bộ hóa</translation> <translation id="5937580074298050696">Đã tiết kiệm <ph name="AMOUNT" /></translation>
diff --git a/chrome/browser/ui/ash/quick_answers/quick_answers_controller_impl.cc b/chrome/browser/ui/ash/quick_answers/quick_answers_controller_impl.cc index 45d6f3d..6534091 100644 --- a/chrome/browser/ui/ash/quick_answers/quick_answers_controller_impl.cc +++ b/chrome/browser/ui/ash/quick_answers/quick_answers_controller_impl.cc
@@ -54,11 +54,17 @@ if (!ash::QuickAnswersState::Get()->is_eligible()) return false; + bool settings_enabled = ash::QuickAnswersState::Get()->settings_enabled(); + // Respect the managed settings. + if (ash::QuickAnswersState::Get()->IsSettingsEnforced()) + return settings_enabled; + + if (settings_enabled) + return true; + bool should_show_consent = ash::QuickAnswersState::Get()->consent_status() == ash::quick_answers::prefs::ConsentStatus::kUnknown; - bool settings_enabled = ash::QuickAnswersState::Get()->settings_enabled(); - - return should_show_consent || settings_enabled; + return should_show_consent; } } // namespace
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc index 8b0ff46..df872d6 100644 --- a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc +++ b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
@@ -106,12 +106,17 @@ PopupItemId::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_RE_SIGNIN, PopupItemId::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPT_IN_AND_GENERATE}; +// Convenienve wrapper to check if the feature to improve the suggestion UI is +// used. +bool UseImprovedSuggestionUi() { + return base::FeatureList::IsEnabled( + autofill::features::kAutofillVisualImprovementsForSuggestionUi); +} + int GetContentsVerticalPadding() { return ChromeLayoutProvider::Get()->GetDistanceMetric( - base::FeatureList::IsEnabled( - autofill::features::kAutofillVisualImprovementsForSuggestionUi) - ? DISTANCE_CONTENT_LIST_VERTICAL_SINGLE - : DISTANCE_CONTENT_LIST_VERTICAL_MULTI); + UseImprovedSuggestionUi() ? DISTANCE_CONTENT_LIST_VERTICAL_SINGLE + : DISTANCE_CONTENT_LIST_VERTICAL_MULTI); } int GetHorizontalMargin() { @@ -168,7 +173,7 @@ return ImageViewFromVectorIcon(kKeyIcon); if (icon_str == "clearIcon") - return ImageViewFromVectorIcon(omnibox::kClearIcon); + return ImageViewFromVectorIcon(kBackspaceIcon); if (icon_str == "globeIcon") return ImageViewFromVectorIcon(kGlobeIcon); @@ -791,8 +796,7 @@ continue; } - if (!base::FeatureList::IsEnabled( - features::kAutofillVisualImprovementsForSuggestionUi)) { + if (!UseImprovedSuggestionUi()) { label->SetEnabledColor(fg_color); continue; } @@ -823,10 +827,8 @@ .is_value_secondary) { std::unique_ptr<views::Label> label = CreateLabelWithStyleAndContext( text, views::style::CONTEXT_DIALOG_BODY_TEXT, - base::FeatureList::IsEnabled( - features::kAutofillVisualImprovementsForSuggestionUi) - ? views::style::STYLE_PRIMARY - : views::style::STYLE_SECONDARY); + UseImprovedSuggestionUi() ? views::style::STYLE_PRIMARY + : views::style::STYLE_SECONDARY); KeepLabel(label.get()); return label; } @@ -915,8 +917,7 @@ } gfx::Font::Weight AutofillPopupSuggestionView::GetPrimaryTextWeight() const { - return base::FeatureList::IsEnabled( - features::kAutofillVisualImprovementsForSuggestionUi) + return UseImprovedSuggestionUi() ? gfx::Font::Weight::NORMAL : views::TypographyProvider::MediumWeightForUI(); } @@ -1153,12 +1154,17 @@ } void AutofillPopupSeparatorView::RefreshStyle() { + if (UseImprovedSuggestionUi()) { + SetBackground(CreateBackground()); + } SchedulePaint(); } std::unique_ptr<views::Background> AutofillPopupSeparatorView::CreateBackground() { - return nullptr; + return UseImprovedSuggestionUi() + ? views::CreateSolidBackground(popup_view()->GetBackgroundColor()) + : nullptr; } AutofillPopupSeparatorView::AutofillPopupSeparatorView( @@ -1361,9 +1367,6 @@ std::vector<int> footer_item_line_numbers; footer_item_line_numbers.reserve(line_count); - bool use_visual_suggestion_ui_improvements = base::FeatureList::IsEnabled( - features::kAutofillVisualImprovementsForSuggestionUi); - // Convert a line number to a front end id. auto line_number_to_frontend_id = [&](int line_number) { return controller_->GetSuggestionAt(line_number).frontend_id; @@ -1388,7 +1391,7 @@ case PopupItemId::POPUP_ITEM_ID_SHOW_ACCOUNT_CARDS: case PopupItemId::POPUP_ITEM_ID_USE_VIRTUAL_CARD: // TODO(crbug.com/1274134): Clean up once improvements are launched. - if (use_visual_suggestion_ui_improvements) { + if (UseImprovedSuggestionUi()) { DCHECK(footer_item_line_numbers.empty()); rows_.push_back(AutofillPopupSuggestionView::Create( this, current_line_number, frontend_id,
diff --git a/chrome/browser/ui/views/chrome_views_delegate_fuchsia.cc b/chrome/browser/ui/views/chrome_views_delegate_fuchsia.cc index 7cacffc9..ba11d79 100644 --- a/chrome/browser/ui/views/chrome_views_delegate_fuchsia.cc +++ b/chrome/browser/ui/views/chrome_views_delegate_fuchsia.cc
@@ -4,10 +4,15 @@ #include "chrome/browser/ui/views/chrome_views_delegate.h" +#include "chrome/browser/ui/views/native_widget_factory.h" + views::NativeWidget* ChromeViewsDelegate::CreateNativeWidget( views::Widget::InitParams* params, views::internal::NativeWidgetDelegate* delegate) { - // TODO(crbug.com/1234748) - NOTIMPLEMENTED_LOG_ONCE(); - return nullptr; + NativeWidgetType native_widget_type = + (params->parent && params->type != views::Widget::InitParams::TYPE_MENU && + params->type != views::Widget::InitParams::TYPE_TOOLTIP) + ? NativeWidgetType::NATIVE_WIDGET_AURA + : NativeWidgetType::DESKTOP_NATIVE_WIDGET_AURA; + return ::CreateNativeWidget(native_widget_type, params, delegate); }
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc index a1a0be7..9ce291fc 100644 --- a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc +++ b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
@@ -10,7 +10,6 @@ #include "base/scoped_observation.h" #include "base/strings/utf_string_conversions.h" #include "base/test/bind.h" -#include "base/test/metrics/histogram_tester.h" #include "base/test/mock_callback.h" #include "base/test/scoped_feature_list.h" #include "base/time/time.h" @@ -18,7 +17,6 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/feature_engagement/tracker_factory.h" #include "chrome/browser/interstitials/chrome_settings_page_helper.h" -#include "chrome/browser/metrics/first_web_contents_profiler_base.h" #include "chrome/browser/policy/cloud/user_policy_signin_service.h" #include "chrome/browser/policy/cloud/user_policy_signin_service_factory.h" #include "chrome/browser/profiles/profile_attributes_entry.h" @@ -66,7 +64,6 @@ #include "components/signin/public/identity_manager/identity_manager.h" #include "components/signin/public/identity_manager/identity_test_utils.h" #include "components/signin/public/identity_manager/primary_account_mutator.h" -#include "components/startup_metric_utils/browser/startup_metric_utils.h" #include "components/sync/base/sync_prefs.h" #include "components/sync/driver/sync_driver_switches.h" #include "components/sync/driver/sync_service.h" @@ -987,8 +984,6 @@ } IN_PROC_BROWSER_TEST_F(ProfilePickerCreationFlowBrowserTest, OpenProfile) { - base::HistogramTester histogram_tester; - AvatarToolbarButton::SetIPHMinDelayAfterCreationForTesting(base::Seconds(0)); FeaturePromoControllerViews::BlockActiveWindowCheckForTesting(); ASSERT_EQ(1u, BrowserList::GetInstance()->size()); @@ -1007,43 +1002,6 @@ WaitForPickerClosed(); // IPH is shown. EXPECT_TRUE(ProfileSwitchPromoHasBeenShown(new_browser)); - - // FirstProfileTime.* histograms aren't recorded because the picker - // is opened from the menu. - EXPECT_THAT(histogram_tester.GetTotalCountsForPrefix( - "ProfilePicker.FirstProfileTime."), - testing::IsEmpty()); -} - -IN_PROC_BROWSER_TEST_F(ProfilePickerCreationFlowBrowserTest, - OpenProfileFromStartup) { - base::HistogramTester histogram_tester; - - // Create a second profile. - base::FilePath other_path = CreateNewProfileWithoutBrowser(); - - // Open the picker. - ProfilePicker::Show(ProfilePicker::EntryPoint::kOnStartup); - EXPECT_TRUE(ProfilePicker::IsOpen()); - WaitForLoadStop(GURL("chrome://profile-picker")); - - // Open the new profile. - OpenProfileFromPicker(other_path, /*open_settings=*/false); - - // Measurement of startup performance started. - - // Browser for the profile is displayed. - Browser* new_browser = BrowserAddedWaiter(2u).Wait(); - WaitForLoadStop(GURL("chrome://newtab/"), - new_browser->tab_strip_model()->GetActiveWebContents()); - EXPECT_EQ(new_browser->profile()->GetPath(), other_path); - WaitForPickerClosed(); - - histogram_tester.ExpectTotalCount( - "ProfilePicker.FirstProfileTime.FirstWebContentsNonEmptyPaint", 1); - histogram_tester.ExpectUniqueSample( - "ProfilePicker.FirstProfileTime.FirstWebContentsFinishReason", - metrics::StartupProfilingFinishReason::kDone, 1); } IN_PROC_BROWSER_TEST_F(ProfilePickerCreationFlowBrowserTest,
diff --git a/chrome/browser/ui/views/select_file_dialog_extension.cc b/chrome/browser/ui/views/select_file_dialog_extension.cc index 53edf37..59af2f40 100644 --- a/chrome/browser/ui/views/select_file_dialog_extension.cc +++ b/chrome/browser/ui/views/select_file_dialog_extension.cc
@@ -218,9 +218,8 @@ params->shadow_type = views::Widget::InitParams::ShadowType::kDefault; params->init_properties_container.SetProperty( chromeos::kFrameActiveColorKey, kFilePickerActiveTitleColor); - // Enabling the color below makes the Browser process to crash: - // params->init_properties_container.SetProperty(chromeos::kFrameInactiveColorKey, - // kFilePickerInactiveTitleColor); + params->init_properties_container.SetProperty( + chromeos::kFrameInactiveColorKey, kFilePickerInactiveTitleColor); } void GetMinimumDialogSize(gfx::Size* size) const override {
diff --git a/chrome/browser/ui/views/web_apps/web_app_url_handler_intent_picker_dialog_view.cc b/chrome/browser/ui/views/web_apps/web_app_url_handler_intent_picker_dialog_view.cc index fe9b6718..287a18e 100644 --- a/chrome/browser/ui/views/web_apps/web_app_url_handler_intent_picker_dialog_view.cc +++ b/chrome/browser/ui/views/web_apps/web_app_url_handler_intent_picker_dialog_view.cc
@@ -238,7 +238,6 @@ // size+1 for the browser entry. size_t total_buttons = launch_params_list_.size() + 1; hover_buttons_.reserve(total_buttons); - size_t button_index = hover_buttons_.size(); // Creates a view to hold the views for each app. auto scrollable_view_builder = @@ -259,7 +258,8 @@ hover_buttons.push_back(view); }, std::ref(hover_buttons_), total_buttons)), - button_index++); + 0); + size_t next_button_index = 1; for (const auto& launch_params : launch_params_list_) { Profile* profile = g_browser_process->profile_manager()->GetProfileByPath( @@ -281,7 +281,7 @@ IDS_URL_HANDLER_INTENT_PICKER_APP_TITLE, app_name, profile_name); - const size_t this_button_index = button_index++; + const size_t this_button_index = next_button_index++; // TODO(crbug.com/1072058): Make sure the UI is reasonable when // |app_title| is long. scrollable_view_builder.AddChildAt( @@ -289,10 +289,10 @@ std::make_unique<WebAppUrlHandlerHoverButton>( base::BindRepeating( &WebAppUrlHandlerIntentPickerView::SetSelectedAppIndex, - base::Unretained(this), button_index), + base::Unretained(this), this_button_index), launch_params, provider, app_title, registrar.GetAppStartUrl(launch_params.app_id))) - .SetTag(button_index) + .SetTag(this_button_index) .CustomConfigure(base::BindOnce( [](HoverButtons& hover_buttons, size_t this_button_index, size_t total_buttons, WebAppUrlHandlerHoverButton* view) {
diff --git a/chrome/browser/ui/web_applications/system_web_app_ui_utils.cc b/chrome/browser/ui/web_applications/system_web_app_ui_utils.cc index cc9a824..9b61c8a 100644 --- a/chrome/browser/ui/web_applications/system_web_app_ui_utils.cc +++ b/chrome/browser/ui/web_applications/system_web_app_ui_utils.cc
@@ -245,7 +245,8 @@ Browser* FindSystemWebAppBrowser(Profile* profile, SystemAppType app_type, - Browser::Type browser_type) { + Browser::Type browser_type, + const GURL& url) { // TODO(calamity): Determine whether, during startup, we need to wait for // app install and then provide a valid answer here. absl::optional<AppId> app_id = GetAppIdForSystemWebApp(profile, app_type); @@ -268,6 +269,15 @@ if (GetAppIdFromApplicationName(browser->app_name()) != app_id.value()) continue; + if (!url.is_empty()) { + // In case a URL is provided, only allow a browser which shows it. + TabStripModel* tab_strip = browser->tab_strip_model(); + content::WebContents* content = + tab_strip->GetWebContentsAt(tab_strip->active_index()); + if (!content->GetVisibleURL().EqualsIgnoringRef(url)) + continue; + } + if (browser->window()->IsActive()) { return browser; }
diff --git a/chrome/browser/ui/web_applications/system_web_app_ui_utils.h b/chrome/browser/ui/web_applications/system_web_app_ui_utils.h index 3c791cf..3f302b6 100644 --- a/chrome/browser/ui/web_applications/system_web_app_ui_utils.h +++ b/chrome/browser/ui/web_applications/system_web_app_ui_utils.h
@@ -96,12 +96,12 @@ const GURL& url, const apps::AppLaunchParams& params); -// Returns a browser that is hosting the given system app type and browser type, -// or nullptr if not found. -Browser* FindSystemWebAppBrowser( - Profile* profile, - SystemAppType app_type, - Browser::Type browser_type = Browser::TYPE_APP); +// Returns a browser that is hosting the given system |app_type|, +// |browser_type| and |url| (if not empty) or nullptr if not found. +Browser* FindSystemWebAppBrowser(Profile* profile, + SystemAppType app_type, + Browser::Type browser_type = Browser::TYPE_APP, + const GURL& url = GURL()); // Returns true if the |browser| is a system web app. bool IsSystemWebApp(Browser* browser);
diff --git a/chrome/browser/ui/webui/chromeos/bluetooth_shared_load_time_data_provider.cc b/chrome/browser/ui/webui/chromeos/bluetooth_shared_load_time_data_provider.cc index 616151ca..b7739d7 100644 --- a/chrome/browser/ui/webui/chromeos/bluetooth_shared_load_time_data_provider.cc +++ b/chrome/browser/ui/webui/chromeos/bluetooth_shared_load_time_data_provider.cc
@@ -45,6 +45,28 @@ {"bluetoothConfirmCodeMessage", IDS_BLUETOOTH_PAIRING_CONFIRM_CODE_MESSAGE}, {"bluetoothPairingEnterKeys", IDS_BLUETOOTH_PAIRING_ENTER_KEYS}, + {"bluetoothPairingDeviceItemA11YLabelUnknown", + IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_UNKNOWN}, + {"bluetoothPairingDeviceItemA11YLabelComputer", + IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_COMPUTER}, + {"bluetoothPairingDeviceItemA11YLabelPhone", + IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_PHONE}, + {"bluetoothPairingDeviceItemA11YLabelHeadset", + IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_HEADSET}, + {"bluetoothPairingDeviceItemA11YLabelVideoCamera", + IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_VIDEO_CAMERA}, + {"bluetoothPairingDeviceItemA11YLabelGameContoller", + IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_GAME_CONTROLLER}, + {"bluetoothPairingDeviceItemA11YLabelKeyboard", + IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_KEYBOARD}, + {"bluetoothPairingDeviceItemA11YLabelMouse", + IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_MOUSE}, + {"bluetoothPairingDeviceItemA11YLabelTablet", + IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_A11Y_LABEL_TABLET}, + {"bluetoothPairingDeviceItemSecondaryErrorA11YLabel", + IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_SECONDARY_ERROR_A11Y_LABEL}, + {"bluetoothPairingDeviceItemSecondaryPairingA11YLabel", + IDS_BLUETOOTH_PAIRINGS_DEVICE_ITEM_SECONDARY_PAIRING_A11Y_LABEL}, // Device connecting and pairing. // These ids are generated in JS using 'bluetooth_' + a value from // bluetoothPrivate.PairingEventType (see bluetooth_private.idl).
diff --git a/chrome/browser/ui/webui/nearby_internals/quick_pair/quick_pair_handler.cc b/chrome/browser/ui/webui/nearby_internals/quick_pair/quick_pair_handler.cc index 083428b8..98d267c 100644 --- a/chrome/browser/ui/webui/nearby_internals/quick_pair/quick_pair_handler.cc +++ b/chrome/browser/ui/webui/nearby_internals/quick_pair/quick_pair_handler.cc
@@ -6,12 +6,14 @@ #include <memory> +#include "ash/quick_pair/repository/fast_pair/fast_pair_image_decoder.h" #include "ash/quick_pair/ui/fast_pair/fast_pair_notification_controller.h" #include "base/bind.h" #include "base/callback_helpers.h" #include "base/i18n/time_formatting.h" +#include "base/strings/string_number_conversions.h" #include "base/values.h" -#include "ui/gfx/image/image.h" +#include "components/image_fetcher/core/image_fetcher.h" namespace { // Keys in the JSON representation of a log message @@ -23,6 +25,3740 @@ // Test device metadata for debug purposes const char16_t kTestDeviceName[] = u"Pixel Buds"; +const char16_t kTestEmail[] = u"testemail@gmail.com"; +const char kImageBytes[] = + "89504E470D0A1A0A0000000D4948445200000200000002000806000000F478D4FA00002000" + "49444154789CECBD0BB064C9591EF8E73955F7DEBE7D6FBFBBE73DA3796934A391460F24C4" + "C80203A1100821198CC59A084BC0C2CA6083F12EE6E1085E8E70ACC3188CBDB026C2EB7D38" + "300E7040ACBDCBDADE65D7115E160C1890401A4642A3D7489A999EEE19754F3FEEBD55E7E4" + "C69727FF537F7DF567F59DF774D7F967AA6FD5A973F2E4C9CCCAEFFB1FF9A70C32C820830C" + "32C820830C32C820830C32C820830C32C820830C32C820830C32C820830C32C820D78484A1" + "1B67F2C8A71F5938F64A90BDC99E9C3C7E4A8E1F3B2693C99E88541224480C5D075622D206" + "912851421489E98B56EA5A24B69584364AAC1A69E2582A9C5D3512A5EDCA892195A512A348" + "5D0709C3C8D8974CA7533977EE9CECECECC8C73EF631F9E217BF285555C93BDFF94EF9E33F" + "FE6379ECB1C7E4F6DB6F4F45E1DCA79F7E5A3EFAD18FCAADB7DE2AEF7BDFFBE4F1C71F97A7" + "9E7A4A3EFFF9CFCB850B17248420A74E9D92A669E4E8D1A3F2C4134FC8E9D3A7E5E1871F96" + "37BEF18D72F2E4C974EE1BDEF00679DBDBDE263FF7733F270F3CF0402A17D77FCDD77C8DFC" + "D2BFF82579F7D7BD5B5EF39A7BE4B77EFBFF933BEFB853FECA07FE8ABCE3CFBD437EEAA77E" + "4AD6D7D7BB478BE519E0FBBEFFFBE4F38F7E5EBEEDDBBE2DDDE7D4C993B2BBBB27870E1FAA" + "DBD81C90180E44917589711C828C44C248A2D451225EA320A18E22E32ACA8684B01EAAB026" + "318EDA285508614D704D8C18BAA3FCAA519B1823465F15BB21588710AA1863A5D58C8226EA" + "3EC588D18A0392067C8C6DDBC6D804A9DAEE57105A09D2A41F86C8248A34019F833431CA5E" + "109986101A748D88EC8AC84E8C71D71C6B4208F8A1E0F3043FC5FCDAC9AFBDDC8A2451DA88" + "07AD729DA3B4D3368D8B909FA46D1B09FA7DDB76C7F11B6E83C4B6C9C5A55F76777DF7E052" + "872A959DDA20C4D47D692E90EEFBDC24FDFBEEBB7C7E3A3B4A8C6D7755B0BFFB285515E606" + "04C614C636EA8DF1D8B698536A3970E0801C3E7C58363636169EFC6A91304C7049460B4706" + "196490579460B2C2E48B497A6F6F2F918DDDDDDD74FCA9A79F92BDC9442E5DBA24E7CF9D4F" + "E0B0B9B999E6F1F5F50D397EFC449AA8BFEBBBBE7BEB969B6FB94E440E8BC8C918E3B62400" + "4F9F8F89C8D118E3560801AFCDEFFFEBDFBFB9B3BB73E0D4A953E3ADEDADF510AA03EBEBEB" + "EB93E9644362DC8C3160F61F772835C340404E4748DB7418C4541442672789CCF0A8933C21" + "6728EB802CF8F00A96AAA096C86E3E296A31516F163A461BF8F2D8C36686CB7C9D9E3B7F81" + "010B9C30094176A3C4CB12653748B82C215E1609200E2009BB51E47210B910253E1302FECA" + "D351E297DA18CF57512E89C83312E40C3056449E9410CEF5F708A17F6EEFD10719E4859481" + "000C32C82B540054172F5E94271E7F226962787FE38D37CAA14387E4C4891309F4EF7FEDFD" + "727073F3C4AD6F7EF37531C6571D3B76F4D51FF8C0076F585F5B3B1182DCF286071EB87E67" + "77F7C0B77FF083274308874020401E5412C8C479A8896D943BEFBA33697E105C93B4C92AC8" + "74D2B8D6A14E09974EFB152DB8D74DBB7213AA19EBD2DC6D3B6DB5336BC5744EB40C201DCE" + "901D66562B2D2B468B9D316BCF8BE02F5189C33C6D89B3B773158B7A7EAE6210598B4160CD" + "D80EB602F38FA24CA4273B1DC03799D8589B9B5C0A226742159E91101E0F317C2E4A3C1D42" + "7852247E3604F984487834883C8D3242B016BB81220CF2FC642000830CF20A100003CCAC00" + "5B98F2A1F1C3547FDD75D7CB2DB7DC225FFFEEAF9763C78EDD70EBADB7DEDBB6CDDD9B9B07" + "DF72E9D2A557BDFFFDEFBF633299DC16AA50ADAFADCB74DAC868542777C3CEEE6E32D98EEA" + "5A269349BA07C0DDE2D34C139F0135801E5605250621C36A9B494007A232D3D713A8C5994A" + "1F6764A0FB7F06849D097A76E3A0D745055BC9E01C53B933600E4A2D5CE9EAD119CCBBF3DB" + "39134308B3F7312E0227DA255866A2CFA09609E32EE981DF7E8EB3CF735604F3A7EDC9C7EC" + "B9628C9B22726B365ABC56C4D42313851002DC119F0E413E1543F55991F0275555FD8984F0" + "07B03484059633C820FB9381000C32C8CB200A4200FA64BE3F7F3EBD87667FF3CD3743CB5F" + "BBE9A69BDE72ECD8B1AFAAEBFAAD31C6D7C618EFC8AAAD4C9BA900F051CE68344AE401AE01" + "404D339DF626F236DD67DE9E1CCC9B04453DAECEB4EDE4744E606D4CE031EBF700A6746A77" + "61ABFFF418D869D8A2F7EEB5F5D07F5413BF85AE9E28E42F549B0FD92560956A992B79319E" + "4135F79E7B90A540CFE96E127AF53EC65961BDFF5C665C221A0B46777EC85C20F68694CEB7" + "3F232B6A41F0FCCE7D9C40883D89E9BBAA8D4A68304FDF2D12EFD68701110B127642081FAF" + "42F5875555FD41A8C37F0A21FCA13E91987E1B64104F060230C8202F9128009C3D7B3699F1" + "277B7B09F48F1F3F1E6E7BD56D771C3F76FCCEADADED07D7D6C65F359D4EDF361A8D360052" + "C9DF9FE7F136B6D954AE409D7DE2BD59B955EC4EA0D27DDF01696FEE572559187166E269C9" + "32A753675FBD9AEE2DE8F6C4C15E95C56AF585C2430FFD57904210A312090E7253C0EEDC09" + "71C63562A0BB2D062904329904A1788199B69E4CFD217F8E73D18BB62DFCCADB5AA8B93F1A" + "4B4C0A9B942E2624C4B011253E20511E08217C4722225575BAAEEB7F5785EA3F84207F14AA" + "EA3321C839EABC41064932108041067991051339347498F6BFF4A52FC95BDFFA5679ED6B5F" + "3B3A70F0E0D7DE71FBED7FE1E0D6D67B44E466350F37D30E4060860F06D0240359CC66F479" + "908E3253F6E34C938D315B0166E7CCF9FDB3DA19252CC402F4256BD4785E792239BA5CEDE2" + "71A6FA2E9411E7D8869EA680DB5307FDC6DC934D16F3652B96590262BF0CB25095C53BC579" + "9A31FBAED7FB17207AC60308F8D56AA12E83301F65DF7D1DE60A0AF63973DB69F482D0F573" + "E585201AB03F8BBDC83687A639D54CA71F90507D00C72B9C5B55BF578F46BF5CD7F5FF1424" + "9C8F6A89F139C8202B240301186490174302CCFB512E5FBE9C4CFC98BC8F1C3922F7DD77DF" + "D7DE75D79DDF1EA3BC6F3A9D6ED775D5FBE7ADA4CF2003BDDFBE03072C1913F3BDF125F765" + "28C0F7BE663D1E0D31C8D22B850486628F19B0EA357B13603F033113FC9711B8E578013285" + "F744C0980C2C485B9F796F66EFC1743E784F49C95CE482A9EFDC33F2A32E100E5B91608214" + "0D60078D87080688437F3F5DE6D78706CC962FFA036EDE6C62C84495DD0CA15F2E5887BA73" + "031862D02D0DC452C34CF472FBB74DF3D6C964F2D6BAAE7FB6AEEBFFA3AEEBFFA1AAAA7F3D" + "9804061908C02083BC80A24BF6A69369CAD98040BE5B6FB9F5D8A9EB4E7DF0F0E1C3DF3B1E" + "8FEF823500A02F49616B7A50EE00B64D80DFC69996EFBE7A8D7FE6AB5759B018F4044066A0" + "3907DA0520E803D8C2DC1AF30485F91673650703AE7D6C9FC618CCB4EA6E7DBA0542537F83" + "B1C0BA369B2CBAE0C3FEEC19D0CA7CD5674AED6CED7C35738ACF1ECBC42BCC69E7EA56992B" + "3FCEEEDBBB29E64D0C33DAA17101B09634B30AD93ADAEB4A644064AE5E330250A5B2A7619A" + "DAA44E9FABA4E9879C6740FFD375FD5D7B478CCBAA699AF74808EFA9ABEAE9F178FCCFEABA" + "FEC721844717C8DF202B210301186490174830319F3F772E4DC45555CBFDF7DFFFE6EDEDED" + "1F3D7AF4E85F44A01E22F32FEF5CEE23CA015031AFEF0769D0B5FE9DE6D6662DBF0B1454B3" + "BF4463C63751F6D180523B17F4172DDAF59AB37EDFC50BB4F32443CB25E2A1E0DCF99F6302" + "D5649DC8DFC1E2D1836FABC970B2BF5A3579BBD6DE46C5E717562DB0A543B55C5D4227734B" + "F3E87B6B35E8030DBBEFB1ACB1AB8B6AD59DB11F8178F84E032767A04B7FABB0E06EE813EE" + "F4F78FB3043FD299E0C3DC4B7A109FDD6B163711953DCD0703CC48CB74E66A48C05F755601" + "7D06FB9A5907BAEF3BCB40F7CC4DD31C6D9AE607EBAAFAC1D178FC4F47F5E8272484C786B9" + "60B5642000830CF23C44275904EAC1BF7FF32D37CB9BDEF4E6076FB8E1FA5F9428AFABEA2A" + "F9F2E10A501372A320DF34D2B49DD69F401E24C0D5F6E735FD6835520D76EB03BE83B128CC" + "08464F2CDA6C61C8F1019AE12D591E40089A76B642A12BA00773D198C1D8CEE1D33C00CE8B" + "0548E9B5EF19B05AD086C032E21D8F6D9C273114F15F2A6F76D2CCA2B1B0167FE69B982D7D" + "D4F7546FEF1EF32B0B722FF4FEFAAE7CCDA900B01E55752201203B782939A9549BAFE6FDFD" + "B69EBD3B24DDA895A6C9568ED069FF557AD5B9BCFC1E75B6A423AF2288792C363B3BDF3DAD" + "EBEFAE47A35FA947A31FAA43F5390957B04E0C724DC840000619E4790800149AFDE681CD83" + "0F3EF8E07B4F9E3CF9A1B5B5B5AF0290E1BB944036836FD336E95893C1BE51E06DE34CFB4F" + "2A7FECCDEBD6D46F4128490604DC1F65C71CFEDF346DFA0C3047B99D9B21F6B9065466C440" + "5DDA8B207EA56356FB66D0650D5AE64033CE5DA3EF9326EE955511A03BF7E1F26DDDE69713" + "CE97D5BB4CCC776DB640584200C2B6402C48B8FEDA4E4D7F6D94BD3E8C60D63E6AC2AF72DE" + "8694F6377FEE00BD233D338B4235F77C2D522D3641A60DCE6916AC02897064D2A1F109215B" + "28A4AAD3586CF6F6BEB59A4EBFB56DDB5FDDD8D8F8E9F178FC9F31B6C6E3F1903AF71A9581" + "000C32C8F3104CBEDBDBDBDFBFB1B1FEF7902E17C07FE9F2A57E0DBA02B06ADAFAB7CDC09F" + "34740BC632AFED2B28866CBED6172666FBB2F798B905E67DCD6A29585CB627FDBDF4B86AAC" + "9241CD82BE7EC760EA6BC78B80CBE058FABC00D40EB0EE07983C12C3F5E2B2B50DEC7D6C9B" + "D83A949EDD7BB6B625926102396DA84020D7C1CCB40FAB4107E42007B51E5702817192F234" + "60C383A67717800C741607B532647211E60309B3A5E5FD93C9E4FD9B9B9BFFCB8913273EF4" + "D4534FED824C2E2361835C9D32108041067996A266DEB66DBFBCAAAA7F5385EAD44E5AABAF" + "E66224E381163E4D5AF89C368EF76DECAD02D6E7AEC0DD696B752A0DE763332864F89B4E26" + "E9359974A0DFC70964D1D502C5A56806BCA50054DEE792862F0E9096CAF1AE2D9DEB9D6749" + "0913949258C2622D0ED63A60CBB3C0CFF7E16B4A6DB08CA4F4310E64FDE0B2668195B1B744" + "A885C8D6A1CA1ABD9283919282519DC64FF77D9546641B95381A325177E30C7FAB60E31260" + "B198CADE6EFCE0A14387FF7255D7DF79F6CC997F814453D808C8ABF72057A70C046090419E" + "A5C4D88EDAB6FDF9A6693EA4016EBD7FDF68FC9D46DE24933C07F9CD27C4E902C7EA513759" + "03DC1153006B82E6EE4F80DFCC8201190C929657CF9B853D20936701EEACF57B80C6D68192" + "265C12CF64DFB7A963912801B107BCB6CEC9F49D81D496250592C22E843977C212D253AAB3" + "907B60597BDA6BC410017B8D3E0BC644720D5495ECE57215D435C620A5831E8D7AF3BF8E4D" + "04148220543D59989189D40F11592A2FAE6D6E1CF8A5F1F5D77FCFD9B367DF7FF9F2E52F36" + "FB70850C7275C840000619E459089651354DF3CB4DD36C6B4477CC267D4CC66C8E9F017F3B" + "17C0A781673AF1A6DCFD3BBB3DE0F7A0DFA7F5935994BABA050824D96C2CC6745D02660639" + "2968F51EC895DE73BDF83EE2100FAEAF770E9FE789779F677B3D1310AE4BE9DA52FB72FF94" + "CE6397823DCEE0CFF78D26C8B30933700F395FC01844603C4E2E83440646751A53DDE64EAD" + "041086A659702DE0FE3BBB3B321A8DDF7EE30D377EE1E92F3DFDDF9C3D7BF6670F1EDC9C6D" + "2B3DC8552B03011864907D48DBB6E3E974FA2B6D6CBFA9DB38A64A21F16DD6F2A700FDE92C" + "C80F26545D22D707946551D32CE673803C12056195004CACF8ACD7E8C49F0031486FA6656D" + "DE031C2B1EE82D730DECB76CB63678E0E6DDC7D3D04BF7F19EC77B3621106562C37E7D8FF0" + "78206FAF610057D9CF3378EDC1F7572B0597DF6BE42DED2F40CF26C65A303BD669EB2D807D" + "3AED5C05A3516F11C0DF2AD412318E337185F560548F7A570248EE743A49E3FCC8D1233F13" + "4278DBF9F3CF7C6B5D8FE2C1830717FA6490AB47060230C820579069D3BCAF994E7FA98DED" + "96266341A47E4AE88389117EFE14893FEFE79F99FABB08FEBA1A753BED614FFF5DF858F7E4" + "E2858BB2BBB7DB2F73B3266BD6E81508C480044FFE9EE6CFE06CFF9640589604FB59F14841" + "4902B926969D5F3A2EA61C0F944B51F81EA07BF7F2488FE782F0EA2304CC42606E41BCD4EE" + "4CAA3836C102BC2D4FC78B95746ED359A7A619FC351702DC4B788F08FF4402EA4AC6A371EF" + "5668EB56EA66E63E8055006B0E2F5FBA2C870F1DFA4B073636FEFCF9679EF9C1E974F2CF0F" + "6E6EC9683C40C9D528E55FD90ACA239F7EE415F9D008023B79FC941C3F762C659743B670CD" + "CB1E72EEF036675D0B79C73209ADE0371BDB4A02C0A86AA48963A97076D5E4AD55AB142D6C" + "939B604EAAEB204BE6DF9590385B36F7CF9AA6F94E11DD0226F626FE14859FDE4FFB20BFB4" + "8E9FA2BBD50F8B49177BFA43E3DFDDD99189EEDA9713D5D8495C5D0316E0D5BDC04BFB3CBF" + "B4F5E5AAC6A75A5F6F5520025102229665E06CC53BAF04A6A5F79E94EA2557F8CEAB8B3884" + "82BF5F26DEFDAC66EEDD939FD99E67415FC7180823C64E4B49A39820B2CF5FFB5BC791754B" + "A5F3355FC0A8EE5D03635C371EF54B43BB5507A3B4C5748DF2F298C27BE433D8D9B9FC701B" + "E3376C6D6D7D4A2D1857832C1B5FAB24036D1B64104726D3C9756DD3FE8710C2BD3A494393" + "D249D91280D680719B7DFD49E3875F753496E9DE44CE5F382FCF5C7C46762EEFF413710263" + "63D6B7C08C73100780C91F2F9EBC19344A206E3563251569A21F8F930F776D6D2DBD171369" + "CED77A206D85FDD55EA09BADD3952C01DE3D58BCB24BD77B6DE3C516789ABDB6F7B272BCFB" + "97CEF15C2D6234787509691CC85EDE31B264B1B0E5D9BE56A287BE461FE3A5FD2D964CC656" + "2A6CF654D7FDF2D27A52A7F34004527E894C3A46283F938AB4E574D5C8FAC6C66B628C8FEC" + "EDEDFDB5F5F5F5FFBEF47C83BC32652000830C42B2B7BBF7954DDBFC66555563CD0D9F02FC" + "DA695A8ED72801C8FEFF2E018F74EF73D6B7F1DA5A3A0FA98155EB57AD5DB5724EDBDA0502" + "EEA4495FD7F8AB89D7F3B55B33B0828EBE6752A020A22B0CF47C4CF41B1B1BE92F9678E1AF" + "5A1818D84B806D854DF025426235575902A42A1EF9F04CEF9EB0BFBC54B6A79DB3FBC4BB6F" + "49B8FDBCF77A0F68F8D82D122FED7FC9C480DD1BB69E96A0D8F23596047D8DF1876B9504A0" + "BFF1DADCDC4CFD9C486626A4D0F695688E9B71471842B72CB0FB21B412B3DB20B621ED79D1" + "A548965F984C26B7D675FD23F86E90AB43869E1A64102369695F6C7FB13753B73353EC0C94" + "75695F934DFEB3A43DE37107EC172F5C90F3E7CFA7E0BE3E414F9ED0AD368E497A925305DB" + "20C012F828307B016056BC631E58E1BE78E15E000790000003FE5629F86BFAACFDF61E502F" + "FBBCEC5A0B7EE23C97673960CDDD3B978FF3337A657AA6FB9275E44AF5B4961090C33367CE" + "24A0B6244FFDF55AB61248AFBDBCBFFCBC2003186318970076F43382F8F0528B538C93DE1A" + "811815F4BF5A105097BD38917E4789DEBD80DD09535D7F786F6FEF1D9B9B9BEFAAEBFAC242" + "630EF28A9381000C324896A669FED664B2F7F7FBCD5CB2DF14008D603F8DF2E735FD312778" + "19AF8DD3394F3DF554DA176096673FCE99649309B569D284AF1A3FEEA1E778BE5E15D64459" + "13B4DA7E69BDBB7D6FCF415D00464F3FFDB46C6D6DC9D1A34793A6A8F54D11E24BCA1403A6" + "B62EDEBD4B1AFB32733B3FB377BD1E673067C01607B44B9A7AA98ECB2C019ED5C0BE479BA2" + "DF3156D0DEBA0782B5DCF0312F4EC13E07131FDB56DA1E4A2E706FF435C8C0F6F6767AA1AF" + "533E81EC765A1B77A0AFC414DF231600BF05441C8D4DB069DA54A94E41850FEEEDED9D5E5F" + "5FFFBAAAAAFEE342E30DF28A129F12AFA80C41809D1414A56B46D2F40A60AAB296DD81FFCF" + "4EA793BF2939208FB5FECE673AED37C6D114BE9035F8D0639467B209172F05700DC2528DDF" + "6AFB9864853450ABF1A9947CF176E2F7AEF1009F01AECEBEDF40CB0BF1BC202BD00EA1291E" + "397224590834184D1C50F3A474DC5EB71FF3BA077CB28404F03981DC265EDB2EFBBE548FD2" + "FDBD003F31408C3172F6EC5979E69967E692FC30D873609D1D2B81DC3F9E95420C21B2C4C0" + "1239ED6BF4B3F635EE8B318A73B12450E71AC48B801CF6A417B90534F360E8B20BEAEF633C" + "1EFFD3F168FCBDC86DB5D0402FB378636915656805230301580D49936BDB48481BAF5448AF" + "FBBF4DDBE63D98C0305F03E8270CFED91FCF6005D32880FCEC99B372E1E285F928EB6C4AC5" + "2489EB30E99F3B776E6E49965024BE98C9DB6A839196827962C181C18CB56E8E11F0FCF6D6" + "EC0C97C0F1E3C7E5D8B1633D91B1EDE08907469E78E795AE293DFB32D9EF355E1FD8F62F01" + "BF576F8F38A8CB075A3F4CFE698748CA30280E09622DDEF3FBB355822D07768509BB9802C5" + "0D40D33F79F264EAEBCE3AB5874440E9FCCB3B3BB2B1BE9EBE5382AB4B09EDCE866953AA29" + "720A8C3EB3BEBEF6D610C2930B0DFE328A37B65651865630321080D5906EC7BD361180189B" + "7F3B9D4EBFAE037F35F943DB9FF451FEF86CB7D5D5490E131F401D93F9EEEE5EBFADAF35F7" + "E35C985AA1E96132B5E6690EF0F3C4D3F2AFE45FF6346A0FDC3DE0B25AA5029612011C3F7C" + "F8B0DC74D34DC932A011EA817CDA5EBD584A9ABE14C076BFE679AF8CD2B9DEF1521B7B6DC5" + "24C08AD716783DF9E493E90502C5A0EFE508F0CA60CDDEB6A15A1338E1928A3DAED605B620" + "68BF82F05D77DD7589E42A59C198C7780649808B08258D120918254B41D0658266E9615555" + "9F5A5F5F7F7B08E1F185067F9964D9D85C25196200065941E9F6436F9BE96FB46D93C01F24" + "0851FEC9D79FCCFF4A049A0CFE9DC9BF03FF5AD6D7D7929FFF89279EE8C13198B5D8982831" + "9102F84112C44474B3256119C079DAA115EF1AEF5869A5006B9336DB1C071CE29960C10000" + "9C3A75AA07073CA747029864A894088B474E2C31F12C1825B250BA9FD7E69ED581C983773F" + "AF5C5BBE8A0230DAAE238BBB0B7D63415AB76CE6FEE2B2F93BEDBB68B67DB6E327504C869E" + "C39600F429FE9E3E7D3AD5F9C61B6F9413274EF4B1017013E02FE2070E1D3A64568D54529B" + "3AABDBAB6DDA3B7677773EBDBEBEFE952154BF9FEAAF7932C24C0B8D6191900DF2E2CA4000" + "065931E9D2F9B46DFCF5B66DDFDD05FC611FF5695AD284DCE8487B9A3E67F0C75A69C951FE" + "29214A3D4AFEDB3367CFF64BF5ACAF1F2F803E08829AFB254FEC761D3F6B732A16AC1974EC" + "242F0E08A8D8EBEC7DB43E6AF22D018B38653439573CEEF7852F7C21919B9B6FBE39059069" + "6C009BA33D705561BFB78A3D6F59D0A12D5F4823F6C4BB8747406C7925B0F7347CEE475B3F" + "8C07002A3469AF8F2CC9C3332838B3282963F266C19CC78C5E63FB90EF6DEBAEE302561E90" + "954F7DEA5309EC6FBDF5D6740CCF0032A889ADB6B60EA65531D853A0EFCBBC7745557736CA" + "B66D377676767E6FBCB6F6BE3AD4FF06E7C30ED96D63DCE6D5B6AD0C14E0A59581000C2271" + "857E76D8D2BC6DDBFFB66DA7DF54A57CFE9534D389D9C427FBFD9B594E7E517F3F82FD4248" + "133922B75593D735FDEAEBC77798307B0DC844F5B3D8ED5E19EC3CB067A0F126790FD4B95C" + "7B8E050F31C0C7444025E43C066A0D808668AD01F63C2E8BBFF3F20678646199B6CD752E01" + "3A83B61069F0AC32A5CFA5F298A0A425A1172F264B110893055BAF2E259700EFC0A720CD4B" + "45BD7AAB687976AC8839CE65E13C4D1405CB0548CCEDB7DF9EDC40200118DB2008B8AE0B0C" + "6C3311C816B6D84A68677101F87E3299FC6BA9E5FBAAAAFA797C0E7927CDDCFB834FFA2596" + "A1BD8DAC620CC06A09DA277E7FDBC47F944C8F69339EA6D3F64DCA55CD90D63631AD88C024" + "8508787C0FF0B791DBEAEB4F0152972FA7EFBAF5D48B9A9D38E6790F58ECB9D60C2C4B3456" + "6B765D766DC9AC6CCBAB28FFBC14B46B2D072FF883A121C21AA0B10E25307FA1C50352264A" + "5E3DF83AEFB85756E95ACF958131F1D8638F2500554B9106565A97D0952C1D5E7F789616FB" + "59CBB22B3D3C8B0613475E31A02FAC0A4099B7DC724B227DF8AD68C22204898204D8A5AE7A" + "5DAA73B608C43E2871F4CF43153ED89DD359009AB048905F2C59AB365EB27BBD9265B000AC" + "B42C4E62D7AC60A289E13BDAA6F947DDE41672A4FFA4DFD4670FE00F933EFCFDED0C4001FE" + "00F52F7EF18B6942B7FE4D9DE8A0F1E3C5E67A0F6885B4FB6564808F7B9AA3D5A679192103" + "8A4712ECF160F2083079282DFFC3840FAB07AC012001B006F47BCE3BDA3F8BA749EF47D83F" + "6FEBC4EF4B24E1D9DCBB44BEC410245B271021003FA2FE55AC6FDEB67389183259632B9225" + "7EF6DE56ABB73127964894AC00DA6F29DB9FA90B401E80FF99CF7C26FD0EEEBCF3CEFC9CBB" + "A9EF75D9687AC6B4D7C03C798C4D9723BB73454C3F50C5FAF2683CFAAB89653F87FE1FE4F9" + "CB400056565609FCA1ED87AF6CA7EDFF98AC1EA1D3FC61EE87C97292B4FF491FECA7096F82" + "59E6079FB76A40F3CB9D24C503C0CCAB295075D2F426752130F68080AF13D2EAECB97682B7" + "69636589B5C1D6CF6A780CFA7DF3114960B0D57B43237CE491471218DC76DB6D732E010F30" + "3DA0BE927800CC40EE01BA250225CB07971D6949A57D5E260EF6B8B6214811088025423606" + "C42376F63C6E6BBE1F9BEB97F535D7D712017B5F7B1F2670699F80BA4E561EB834D0B7F7DC" + "738FACAF6FA4DFC605FC06C623D958DF48FB63543A6E60EA8F9D5BA003FACE74D9B6CD87F0" + "131C8DABBFB6F2BB8FBD4C321080151344DA860817C2AA98FF9389F1C6B699FEAF31761BF4" + "24ED346DDFDB99FF27D86C07DBFAA6C0B8362D65427C806E92F3F8E38F278D47274C357362" + "92C6040FE0D3CFACA1F35A7F3BB92F33C5B35F56CDC60C0A1CD5AD4BBBD865E0697B256DD3" + "D681EBC69BE37829824196544384D608EB094B299FC19534728F3478E0CCE299E74B96142E" + "DFEB1FBE8F6D433C1BAC417017011839A8CF23311ED07BC7ECFDD5C2648329C5047A8A4378" + "D82263C91EAF06B001834263029A3E825C1F7AE821B9F7DE7BD3CA809459F0DC79191DEB76" + "224CD763296CCCE4B1862BA0230186547CAFC8A81A8FEBEF09ABA596BC2264D8BA69D5A45B" + "81B332AF645D9C36BFDEB6F12826A068B659EDA2FED5E7DF252EC1F9B5F1EBC37F6B7DFE6A" + "F6C7679876A10579606627713BC92AE02EF3C9B2795A13B4D8EFD8CA60AFE534C5B63E368D" + "315F675F6C766633325F678FA3EDD0360007B41D48003FA318300AE483DECF31EF2504DA1E" + "F095EEC3E7717D4B75E7CF55DED807CF0FAB50C9C263DB8DD7FF47CADCE7F5078F05DB1FDC" + "2F7A4D696CD8F2EC314B3254ECBE16F0F9A37FD1CF380E1280B17AFEFCB96ED92CCAB3C427" + "0708260E209D212091A369F3572793E93F1CAC2F1CCAC20000200049444154002FBD0C1680" + "9511E5D7ABC3B1D3D2A2A6FD8518DB2F574D6696D6B7F3FBA775FFD8DC67AA8159A37ECB5C" + "803FB41C315BACEAF227987701FE4A06EC2469276ACFAC2C34A9B3F93A3AC15E1EF8F2E748" + "390638731C5B1EACF6184D309A5D6256AA8FBD1FFB9FD5250000FCF8C73F2EAF79CD6B52E4" + "B84D1CF4ACFAB1A0297BDF0B69F29EEBC13BEE95C9CFBAEC1ADB46BA0494AD259ECF9FAD17" + "0CDC6CAAF76238D4C26057062C7301A8666F357AAEA7D7EE915C4628072400FDFCA77FFAA7" + "F2BAD7BD2E9180CE1D7621F57983F253F063B7E246D24A1C8D51E8E624B806A6D3E6074632" + "DAAB47A31F0EA1498181438CFA8B2F03015809E980BF5A250290547FF9B118DBEF0D556716" + "9FE6A0BF14F13FD94B7E7F4C44B00424D01A01F8AB94D90CE00FDFBE4E7A200478C1A48BE5" + "6F1A27607768630DCA9AD5ED24EC99A14B444188349480C9D3061980BCFBB009D80280DDFC" + "8737026260D232AD360B908159F8A31FFDA8BCEA55AF4A91E31A1CE83DAB07B00C904C98F8" + "5CCF3C2F0500F78E79DFF179F6DE7A5C8915C00F99FE74BD3FBB856C9F78C42F902BC69238" + "6D6BBB24904983673560E056225C227581F20C045A6162AD00F80EA00FD203120077002C3E" + "F86C8302BB32BBFC0069FB60D403E02FEA12404AEEE90FB5516EDD580B7F39B7F442BF0CF2" + "C2CA40005640625AFC37494B03574152A21FA96F9CB6F58F62620998F0F2FEFD5D86BF894C" + "F6267D2C405A9E85A54B1534FCB5E4BB55F097BC8C0A9319345868FE3A497AD1D51E007911" + "DACB80DE0283B7FC4F886C7860E8B925848889773F9560FCC09E95C2232042C4428104EDF6" + "C94F7E32B53B88008EEBF2312900BFF71DBF5F461C4AE4C23BD793D27925D2A0CF04CD1F2F" + "6B55B16D6241573FB3C66DEF61CDF39C0BC07B3E7BACA50C837CBEED23EE6726203CDE3886" + "052400BF993FFBB33F4B2400E5210E021633ACA2696C79315B1CA2260CC26704E8C01D30FD" + "2FF6AAFACFC6E3FAC7631A228B6D3DC80B270301B8E605BF304C32ABB3EE15734B1B9B9F17" + "690F60026952647FB7A1CF346FEF9BB4181080B63387C22C89890A933734389D0801FC9AD6" + "5727766FA2666D78BE3E3393A93820296662B6665A0F68D86CCF665E15262936988BCFE7A8" + "7F4F63148768B01520526E7AFE1ECBC7F05C080E54C0F49E91C5D36EC501690679AF4DBCBE" + "B9D279A5F2F818AC431823BA511293A3482E1ACF2A63091A8F17250BA59C015EBD030573DA" + "71C1EBFD99309448A2D767F80EAB03409E610140C2201000C408683C4DF2FD2361491DBB60" + "40090B49C8AA2AC864AFF93191EAB7EAE9F8FF6CE3C0015E4C1908C0B52EF8F154ABA1F9AB" + "B451BEBB8D3165FAC312A46E1FFF36014E0FFE397F39B47E80E3DA7894CCB6A79F7C724EF3" + "C74B353BCFEC2F8E9997276F0658590226E24CE425F1AEF7C8452037049F1F1C1F7070B2CC" + "A9B0D99F2D131E7068F99FFBDCE7D25F90008D14B7E2DD8F4DE62500E26BAF4418EC7925EB" + "8D779CEFA1560E8C11CDF6C71AB3470682B37223520C8025613AFEF89C521A603BF6EC7DF8" + "59AC75CA5ECBA44EBFB31603DE4E1A9680CF7EF6B389045C7FFDF5C95D0662B475702B6DA5" + "1DEA98129349BD68E10F19EC51B5C9DEDE6F84B5EAFEAA958FA74042BFEB06799E3210806B" + "5962FEDDD4ABE44B0BB7B6D3F61F871C6DDC9AF4BE6AF2EFF2FC4FBB28E4BA5BF2D7E5F77F" + "5C2E5FBAD4039FE6F6D7495DB3B8092DC5B25A9590358027730B0E1EB07993B710305A136F" + "70A2DB8540479C54C15A770B0A76221702077BFF65266B3613B35689F7200138FFAEBBEE9A" + "AB9B7DD67DF5B4A39596409AA544244AA4C1BB972D03E4510343F53B3B5E38E53183AEED77" + "A131E42DE98CB404D38E15762D588261EFC1E3CB5BADE2051F5AF700BB0E42CE9D01970FFC" + "FF7881008010F4E33F2D45D67AA81BA003FF387BCED15EB3FBBF1FA8AAFB2B69771729F420" + "2F840C04E09A963C09AE4CDC1F342AF9EFB0A5394C89BAAB5F93FDFF9DF63FED27E5B47DA9" + "54B2B6BE963415982C757254B32582BA742D774B5B027B6BE23DD0F4BE635FB007460A209E" + "29DE0366152F6E80819F35D29269D99EE7AD74E0B62811156B4DC0DF471F7D348102120621" + "4F40C9E5B19F3EB7E201BB07BCFBD1F23D62C6D7E0B9402E411211F068EF517A263B86387B" + "63A9FE76ADBF37EEAC35C16EE3EC9569135D312915222B5A375E9668FBD912919877130421" + "FAC4273E210F3CF0403A8EB6818BC00B4CC4125CCD06086210F32641CDB4BD6BB70EBF7EE0" + "C0816FA89F85656C90FDCB4000AE4149CBDF92EF7BC51EBC95BF1DDBF6BD3AC17426FFCEEF" + "BFB7B7DBE72EC777DDDEE541D6D7D664E7F24E4AF6A381699ACB1C9316088050E4B307BEAC" + "717B1A9DBD462761BB44CF6A660CECDEBD8526633BB1737DBC6BC5090A64A2E211080FE84B" + "F7B4C785C00C0163A83348800298A75D5F499BB7E7B0A6EDB555A92D6C7D3978CEBB4ECFC5" + "38B1BE7F3EDF12206F05041325065F8F78D86BF53A754D79FD64CBB460CEE493978EDABAB2" + "C5C93E9F989510F81D6D6C6CA495002001AF7EF5AB1389468CCD786D3C37BEBB6B4C5E8A64" + "14E836EA48E4AA69DE7D7977F7D70FAC6F7C73470E067921652000D7A4E4FDB6A33F595E8B" + "1242B85762FB7743DE5D4C37F869DB6906FE6E72EC82FE46FD7AF798F73DD73DDA5583C267" + "CDFEC71AB73721736C009B8ED9FCEA69CAF6BD47323C2DD54EF4163C58B3F400870142C82C" + "6CCFE3B23CB0E76BF879F8D9D03F3015E35CAC0EE0BAA978EDCDEDE69D5FD2183DD0E7F62D" + "3D07DFA74B7C737E6EA73F71C092FBD02EE913438CF83919B8AD466EEB61DBC51241261CDC" + "96B65ECB96FE79CF64736BB09B07E782042065F0891327E4E4C993894C1F5D3B3AF79B8A71" + "96A124D549D41AD01D85256032D9FBA62A841F5F1FADFF1D898333E0859481005C83821FE7" + "AA756C1BDB9F862931A8E93F6BEC69B73F68FD6D77AC42D05FD5ED07B0B6B62E67CE3CD99B" + "FE7552D37DCE7552B5816A5663B71229584A9C09561C60F780590800EC44EC9974EDF576AD" + "36038F178C28049456F35390D2099EC98C5776A0E565AC99F33108F69B87D9F8A69B6EEA77" + "12F4840988D706FBD5F69F0D49F0EEA5E780286A5E08EF7A8F6CB12B898987056B6F9C7070" + "266BE136F6C2B6B7C67C70AC8A250EB65CFBFC9E8B8749AD98F1A965C39A864DB48E1D3B96" + "CA477B6D6CAC9B31D4767B04B4394780F0FDBB4D85F6F6F67E2A54F2DBD5487E33B67E9F0D" + "F2EC652000D790E4692399FF576B67EDF8BE28ED37A489284ABFC4AFB702A4E57E6DDAEC27" + "F9F64348EBFD2FEF5C9E5BEFAFBB9F41F3577780A7E932A8B1CFDD031F166F6996984870AB" + "29722016032E83862716743898CB6A750CE45C7F7B3FAE3F831ABB35F8592DC1823B00ED8F" + "C871988BEDBDECBDBDF71E4097DA616ED438FDE401A0D7970AB2007F98BAED73ABBB888994" + "1D379644B146AFF7B2B9F8ED382B913D4FBBE73C036CAEB7DA3893516B51B2A0CFAB024AE3" + "1802B33FD222830460A748FCB636D6D7CD83CA3CF0076B11E8F203C494B1BE91C9DEF4C7D6" + "E3FA6FC6C10AF082C94000AE3109D264F3D9AA3C700A1AFA212413A9F2E4DB47FBF711FF4D" + "4EF653F7131FFE6262D2BDFBED9EFE9ADFDF9A5BD984BD6CE294024960FF2A83A4900F9BCB" + "67CD90B54E5E8FCF5A259B7397DD8BCDC142206933030A91002120B5D75B00B2F7433F206D" + "3000E3E8D1A37324808501DAF3FB97888377DC2325563C0B02EE893A22F21F6386C158356D" + "6E17CF35A36DC2AB2B4AC244CD5A87B86F6C5C055B1A3C62A96DE045FC737F8B433E5ACA58" + "08812B002B3F8E1F3F9E9609EE242BC0817E596067F50FD22251590CF33E81EE6669A54ED3" + "345F39ADDBBFB9B1B6F60FDB6620012F840C9B015D4312D3BAB620A196D57985F64169E541" + "807F6CDBBCDCAF95695EE78F892226209B4D60E3B5F5B461C9F9735FEA275010005CABC0E3" + "69BF423E7FD6A0559669487CACA4C93210B116C6E01EC985C06BCA3D60F6BEB3666A2BADD9" + "1048EFA9B9143840B1A5BD112CF8B119DC5E0FB7CBC30F3F9CFA00A021649DD0978D606752" + "64DBCB6B672E8BAFE5EFBC63DABFEAFBE736B3E4D01201211264FBB934DE6CBB733D95EC5A" + "20F7C68DBD97057675EDF0F5152508624B811D07B6CEF6B32504BA9A05F901F07D5A2A193B" + "D59F0959CA08A85601CB8342F7FDDE64E767A74DF3A66A5475E8F55C5F8324192C00D78824" + "3D3842FFAF57E799435AE8FF0B8818C63C32356BFC91F31F098074BD3F3623010918D5D8AD" + "6D4F9E7CF28C4CB0FB5F5E6E87894897A3094DE273F7F3EAE02CBDB3136D24DF77A0A03AD6" + "ACB91CABC5B585CD77BC657C422484AD16A56B0299C1BD6585E268940A78E2EC1DC0448883" + "C624F70334EA8F7DEC63F2FAD7BF3EC50570647D4943F7FAA6D457E268F59E05A1741FB514" + "C18284FAD96438960C68DF58337E24B3BCB617CAE17EF2CCF47A1F7557457241713BD91502" + "F61A3D87FB8989AD7539F1F6C0429622B63AD8DF00347F04DB1E3972446EBEF966D9D9ED72" + "03CCEAA3BF8B6E7F8018663902FBE7417B4EA77269E7E2BFDD3AB8FD6A09D53987330DF22C" + "642000D7847466B380809A55FA4144F9CE36C637741347E7E787C6DFE4C03F8DCA47B6BF34" + "B9263FFF38F9FDD39A6D4C7839EA1F5A89AE04604DCC0354ABED7AFE5B36F97A206A01C5BB" + "269A18809652000B019A4EEE3CF9F2841F28939B389A2193150616FB9C9C09CE0B0C536177" + "876D07FBEC48C50C77C07DF7DDD75B666C9D640919F0FACFBBA747666409F0DBB6C07340FB" + "C7CBBB8F358BDB0C7CF63C0ECE63C268AD0E4C983C6B8F4DF663090793347B0E5B8E987C78" + "ED658905DF87AFB7CF8EF748A98D4DB6D415609FA58DCE8A09B56A6A444094F47B9D4E27A7" + "762EEFFCECE6C681FF7208087C7E3210806B407457AD50AF8EF60F994E9A1FE9E687D807FA" + "61995F9BB7F74D136B36FDA7F6C913F2A54B171734368D05B0E2015EA0A4370C446CDEF576" + "6ED3EB389A5B9C49D8037B5B3E4FD4962458A029590EF8D918704A00E001026BFC622C019E" + "15C0BEE77B21680CF10058432E1437C16DB21F12E0DD939FC32B4B881CE059401E4122E1AA" + "50F3B6D5CED9BAE1114ADB6E91E24CB8DF82C991605D0A9620723B7AA4520CD95080E6151B" + "5EBF703B5B421197042346B24C810080346169E05D77DE9948F7DA78AD2B2FCF61316FE5A5" + "41CC215BF772CBA5FF12619FEC7CE7B8AE7F75341AFDFBD2EA9641AE2C0301B816046EB395" + "FA0DA449E2BB45E2DD2938A8D7FE9BBCCFFFB49F243BD37F3799600242C4F6B973E7FBC954" + "D7FC5BEDDF0324150B1C1CC9CD006A357FFD6C3574CF1AE095A57568690D386B82E22CCDF2" + "96E77944870196490D03E3328B4609486C3B94AEB575C0E6418805408E00BB3CD00369BE8F" + "A7C597AE2BF531BF57D0B4DA3FC7458863F511024FBE9F05580F6CBDCF9E76DFE6552E7CDF" + "404B33ED38D0DF81987ED6B26C9A68DB5F969C94AC00625C6BB68E70EBC01570EAD4A914EC" + "D9ED06ACB9806352F383C90DD0BB01F01EE5EBA952C9E5BD9D9F5B1F6DDDDB62BB6F87F00D" + "72651908C0352021CC4F7ED7BE6052697F409F1B417F4DDED92F69FED06C744226F335966D" + "F164084DCE03074FBB679260CF1527A8ABE453156772B7E4A04444BC7AB6667D7720DFAB10" + "C0D8FAA8D8095A0C21F0EE596A132F96C0D3FA3937BE509C0413162C0F0468DC72CB2DA99F" + "3C3702D789018BFBC7EB2FEFBA40169E90D7FD9F3973662E3DB48DC217F2E7978818F747A4" + "E03B26726C11B0C00E17894D016CC1D96B1BEFF9983C05FADDF0673BCE99B8D9F1CC2B5F40" + "C21138091280E440E977AA75CE56BAB4B22757475D9AAD843990476E80493379CD68BAF7E3" + "9B1B1B7F67B0023C371908C035205129F34A489A643E1463BC2F2F0DEA803FFBFE61FEC73A" + "E1A4FD57DD463F62CC8F78D928725DF36F27627196EA49C1B4EC81A1984994275FF6937B40" + "EB81BFD0E4CB91DA2520B375B3A01BC9C5C0E0CBE5060A5CB465D83A7B6E007E2626310C84" + "F638347FAC0CC0FB1B6EB82181EB324D9F8F796DE27DF6CAB0E7A07EB8374CFF2091DCC796" + "B4554EAE06B51E88B3D4D323095EDDF4DC92062E64E28FE402F000BB4446BC8D8CBCBA2D1B" + "CFD1C95D11F3925BA4DE467F221E40FA768829503746BC1A430662CE07A00DD1050956D548" + "263B3B3FD98EAB5FABEAEA63433CC0B39781005CB502D0AF408DBB3DB65747422BCD8F88F1" + "31C6ECF79F36B31726123B596132B319DB742B5A5DF3EF6935E26888AC2D57B4AE5B48CBF2" + "CCE75ED95E909F9019D50A930DEB6BF7EEE59117ABBD5AFFAD9DB4F9DE9EA589E31E5823E4" + "7B71B91608BD760449FB933FF99344E29028089FB93E2520E776F63E0BF5996D3B4B6A401E" + "A1B98230A22EECDED16B2C787AC487356AAE87D75FDCA61C7CAAE5A8D6CF44C01BA342E3C8" + "DE0FCFC8A4D42302DE33D87BAB2B44835421B0E85CBA74599E7CF2744A139C9FDCD4C18EB7" + "F9BE0C71766E1D44A64D1B2E5CDCFD99CD839B5FD70E04E059CB4000AE5AE94266621D5666" + "B7BF64CE6FDB0F48135F15EA2A45FC777EF5B65F13AD094242FF4FD6FE9F399FFCFF3A4187" + "6CCEF500CD034B0F04BDC95027596BCEF74CBC365F3B9B2F79A22F69AD56F3F27680632DD1" + "8A670DB062C14DCBF2DAAA64C1B0C7BC951256D8F5C12675885A02101878F8F0E1D4777C0E" + "F7936D4FEE1F3E97CF11025D900E98FEF157C78F64C0B5FDC72E166FE50693468F7430C0F2" + "B1AA5A4CCDCCCBF9F4BDD6C72357D6FF6F0930F797570721B7185B0C2C91648289622E5CB8" + "98FA15BFCF30676DCAFD61C03E2F73EA0841C8731F56055468FFE9BB626C5E3D1AD59F185C" + "01CF4ECABFCA415EF9821F4CDB48882BF26A534EFF0F8901C0F48A9AF4A749EF313DF491FF" + "DD3E0109FCED1AFB6EA7C0C9527013870C58D3A767F2F4AE0BE4978F1439AD9F4B4BB384C0" + "94EFE14DF05C471B2D5E9ACCF9D91884ECF18A92F130F910A3FD72399EA9DFAB937D36DC0F" + "3BEE7DF8C31F4EB90240043CADDFD6C9FBDE6B53AE9F3DA6600DD33FEEAF3B450A817B9593" + "49A9E01C6BEAF700551CC262C706F799ED07AF2F3CEB8F1D4B0CB4E22495F27E07769C79E3" + "52A8AF3D32653FC7EC62401E05B4E95C5C4C027CF35E8B4AC5CC627A242F11C43E02D0FCF7" + "76F67EE4D9E4041AA493A12DAE6AF186F6B5FA82165B7D558CF12B1444F405DF7FB7FC0FEE" + "80389741AC1E8DD276BF172F5C9C9BDC01FEAC5D7BC0CBDAA0D0C4CAEF85F6BF1702150B04" + "6CEEB51A945707FB9D8AD5FEED73F0C4CE1B1A592999F94B00EA89DE233A3E707B4FBE9767" + "E9E07AA8006001181FF9C8471209D0F4CDCBC40378EE13AF7F2DB0823C2237015B4AB85F4B" + "84479FC59AE76D3B2959B0E383C99518F37E89B078A0EC914FFDDE8E09ABA9DBF66712665D" + "07F65EEC6E101AAB4C0A74BB6D2C09ACAA40406FCFCFCF625607746584FE778EFBEC4D26DF" + "319D366FEA6208E48AAF4172DB0DED70F549E857C48AC47EEDEC2ABCDA6FD7CE8A09FCBB09" + "AAC991D06972CAB67F3B3161A281A9B13559D13452DB8A17C9DEDFCF012D7100C68A3729B3" + "B9D69EB34C2B2F1DB3E06F275C9E7CADA99A57225800E3E72BB587922F0B60B6BCD273F1FB" + "12312999EAA1F983043CF4D0437D4027884009C86D1B94FACA3B4F010DD1FE30FD637C29E1" + "600B015B43F83B5B2E9308760778E5D86B2D00B3C6CD8481DD034C506CBF312961F2609F89" + "DBCD1BB76CE9B2045BCF457BC20A8078802AD81C26A6ACBEEF4337E3A981C09C924855DAC4" + "6BF747DB9454E8CAAF413A196200AE4249A6AF345936D7DCB32D9163ADB47F517FECDD0493" + "13FF98802B35FF4B9EE000FC69B7B6FC192F4CEABADB9F9809CC02295B03F83D6B3BDE39B6" + "7C15BE4F2938CB4EA8EC571633D9B339BD14D9EF95CBE7B10F9901AB641D891405CFA0E391" + "01062A06397BAD1830931C4406933C96086279E0A143871231602B4EE979AF742C641711C6" + "0DC01FCBD66C601FFBCDB59E9C0699CDE291E21B2CA87AB11E3C9E6C40A8B7C69EFB9CB7FE" + "154318EDF8E1B60AE426F0C6B64718EDFDAC9581098D8E35F42302734F3F795AEEBEEB6ED9" + "DBCB312D92FDFC28B78DBDCF5F64B657906463414F66B04A633AF996A6597BC7783CFA7F87" + "5880FDC94000AE460969C4AFD0F3A68D7EDE214DDCCEEFBBC43F9AFA372D039C26625057B3" + "218DB4A1D01261010866F253F0B7131D9BABED24C9D9FCC468348196C779A0CEE73341E0E8" + "7D0F94447C4DD0D3BE84007319D87AE482570194EA6213C5F0F3D8E0305B27AB717A1A64A4" + "A592362EC27E0FED11E663FC05A1DBDADA92EDEDEDB97CF79EE582DB83811FF745A01F5C0C" + "0027041BDA3A73F064A065796C4DB1E3C13EE7328B891724C884CC6BD7489927B5BEB6DEDC" + "2F9E958557A4781623B66484C292D6924543CB427D1F7FEC71B9E3F63B52405FDAEA3799FB" + "F3B955AE9B6AFE1A0BA0F101F63962C458F8C9AA3EF8B5C38A80FDC9E002B8CA24FD2CDA2B" + "FBB8AEA557B2FCC5F82D36F82F36DD64D6817F93DC01622288D38494D2FE5E9A031B5D2D60" + "27214F63E4095A4CC01E83A1CDF5CE939D4E945E4C00DF47C8C4AA13B6ADBFD5F8B92E961C" + "F071365DB3D6AED7EAA631961C3148B19667C1C4DED75A154A842250243AB7A35776CC6BC9" + "713EDC01B0F2C0940C4200ADDD02B16D772F58CE3E1788210823FCFD9A300A5AAA92108E39" + "D0F2D5B2A4E5E8F304C7BF6F9FDB120B6FC320FB0CF6B985C8D495C05C8810CA125268C79E" + "3DAF34B6BCB166856334984023D323C816AC2CE3F1A8D7FC17F29A04DD174044DF7559018D" + "A5ACAEB1C1D7D74CA7D3AFC5D6DF08102CBD06E964B0005C6D92002EAECED23F49B3C5C936" + "36DF84A43E0A88316F25AA39FF3171D46136D96042852607026027532F910C6BF29E862504" + "4AFCBD05687126583193634B9BBFD8F375FD35DF4FCCA45932ADDB09BAA2E57BACF9F3B9AC" + "5DDA3A5B409BEF96799709B7A77D0EB6B0B036C8ED68CBB3266D316006B33F8003AF63C78E" + "252200D7008E0358F05260F6E22394F0604C40D3C778C196C44A82942C0A1116AEBBB7EE5D" + "DBC1EB6B710245C558A8784CD873BCDD22BD36B2E35BEBB42C6DAF8D0FB1D602BD8697ADF2" + "B5156D5A659F93EBC3BF279036F41BFA30B72C763517A437C1AADE2AFBFF731440D7772232" + "5B0E18BA3931670B9CEE4DFEABB5D1FAFF2D8317E08A321080AB4C30D893DD6655486C67F2" + "7F8F4439889FBDFAFE634E01DC6BC43900D04ECE300DEB76B2D6442AB2A82D598DCA9E6327" + "3B3B117BA67E06573B41EBB6B1421A9A15D696664DE02FB3F3B42D2FDA7E19B0B2E6570279" + "5B0E9B875933F4DEDBF3F5B8D5FCD9CCCCF76482A2BBCBE1380800B699D5324004F00298AB" + "766E81B13211EFE8179CABBB47DAFAB149DB122FF577AB35C9B62B8F357E067BBDD7A7364E" + "8023EA3922DFB3A87030A51862C00496FB9BFB301AB781ED8352BF7179B6EEF63C260988B5" + "B8FBEEBBCD0A9E2ED22F4847EEE74CFF3927C0DC6601C965D0A69D3FF726D3F78F267B3F56" + "55E113A5DFDA209D0C04E02A136CF9BB4A99FF4237417CB3E4DF7A372165D378ECD6FD6B0A" + "D06E829999A97503199D7031D9DBE03FD6B43CCB80FDCCA66F7126627B5C8C96A9A65BDE1E" + "D6EEEEE6DDD7D3ACBD3A96AE13C7ADE0D5912767B66AB0EFDE2314DEE46FEFCB41864240E3" + "DD2F1813BD4784A0EDC3CA03128018006BB1909CD14EFB9C412FD29235BCD402C340CD4192" + "4CF0F4B35D6962576878417E9EE5449CB1C680CD6DEEB94E6C3DD9B220E40E699D259896C8" + "F0730B11604B2ED83AE28D0BB652A00FE1C2811506BB0336ED34FBF883A63BD341D493000D" + "0B4C45630E081A21985D83CDDE77ADAD6FFC50D30C046099F82AC720AF5869832E015C99FF" + "EE896D7C4F37D1C52E4808409AB5AFD94459759340D05DDBA6C9022066A2ED970A66B952D4" + "3B830F4FAE7692F3C054A504DA9EE61C1D93BD9DA8AD4582CBF1344D31B10B5E1DBC7A3020" + "5833BAAD8B57675B077B8E175466CFF502E7546C101D03A1822D0044FBD79AC36125E097FA" + "E5F15713FBE8F3B2EFDDBEEC39B6DFED3358CB8A98BEE76BC401490F2CD942C07D2C66391F" + "B75B3041A996B8D97EB6E50A813C5F638303955455B4ADB067F217223D7C3FF401622FD087" + "9D7F3E2468AA428EFDD7B11A666BFF3BF037C7257F07D256C10A34FD9EBDA63DD5C2D293D7" + "4BD9D7209D0C1680AB46F28F3DAC4E9761F29836D3BF90A2FBD33C305BFB1F0D0140CB5461" + "C666ABB4F67FAF270036FADF4E9C9EE6C3FEFF4051FEAC7DB14BC196C3FE5A3B7147DA70C5" + "4E94AC75DA89DED362852C1176E20F2651903749335873EC00D7D9962D0E995089144BE1B5" + "1D130FCF9DC0ED274673D7F301EAB002E0052B801812C181771E41D167B2899818C43DB0B6" + "BE726B35F1346671CCF842710D42267E3180CF2987EDF94CA83C52C9406E01985D473C9679" + "8C958830BB43B8FFBDE7D3F79A6F011B04A9663FBB47F7FB8E860BA8CBCFD4726617A86AFC" + "D6B7DA9DE60307D60EFC03040A0FE2CB6001B8CA64C5B47F4C18EFEDC12283BFE601E8F282" + "C4DE2428F9DFD82D079AD3E6550B664DD2FBACC2A0A76227339BDD4D9CC030D60485CCAF5C" + "2EC716B49450852778D60685266CABFDB196CD1AB5106831C950B160E19DE769B1569BB419" + "03F99EB68DC47139B0161B4D643CB4480571D644BDB667CD9BFB2A9865888122F26DBFF17E" + "FAF69C8AA2F8B95C0675B62A30D07240A6D786EC96B0FDC199FE8488B08D97F0DADC8E5B6F" + "0CF2F8F4FACCB3A6E09ECF3C73BE3B9683FBEC289A590262EFE68BA67CE9170E747300B20B" + "4EA6936F9E4CF79235905F83E4DFC3D00E5787D80530CFEF2557CDAB8DF1CEB68D0FA61F78" + "02FEB6D7B89ABC07003C0255989F50A0E9EB1EFF3A21E1186BFCCB26DA92DF9CB557D66A55" + "58A362D011026A2132525186390649FECCF7B7CFC1DAA417942586304493B6560CE9B0EF2D" + "412905135A52C5A0CACF2904A09E55C196CD7F6105409FC38FCC2E831231E136A84C04BC47" + "0AF4C5A480CFF1C649A094CDF69E7ABEED735B6F6E17CF45E569FDA571C163C39EABFDA962" + "FBC6BB47EC9372CD620DBC1D0955B8EFF53D566C9C3B773EFD4EBB73D4E4DFA700925984A0" + "69C3594DE78E545261FE786D5BC911195569D334FB1A64BED50679254BDAF9B71BB4A3BA7A" + "6EAF51957C63555A6233FFAA93DF0CE5D6FDAB4E1351F5B22D36C813C857EB0FBA4D1345F7" + "5DA7FDEB2417BBB631FE7F04FB4D267B73939F4E6ABC6449081898087864802764AB115990" + "28110E8E2E67C06153B107789EB665F31B08012F6B651E40F0E4ED0190A7B1B226DB52EE02" + "EE576E3B31DAB2773E83921734A9790110F96F098015FBB92A44AF3341BA92A58001BC445C" + "BCEBBD73F8BD47903C25CC42A80000200049444154C06752273496997C6A19EC42B0F789C6" + "6DC6F7E2F667526CC95FE9DE422E012CC354B7DD5CBB4535FD47B6FA770983D432C0AE8BB6" + "3914637CB7C679782B42565D861880AB4046E3B13C73E182ECEEEC3CE73CD60087039B9B72" + "60E3C0CC172E31AD9DDF9D74D1F2A36A36F96F1ED8482400D9B9120DA8BA9DB8B0F77ECCE6" + "F81753508F69DBBE5323FFD5D4DF4D286D6E87A04982406B7AD36017EDDFCC4D565E54B59D" + "8884264C169EB419404B802706C478E2E6F2BC09B2243C29CB923CED9102EFBCE7B0E3C403" + "112160B040A4FE730607CF14CCE4C793E8B8461880F8F934498FA679F6B640B6E5D9FEE3D8" + "034B763C8B803DC7AE8FD7FD2582890DB1D697D664E66392C85AB16D735B8768D6E47BE386" + "C713B7A5D5C0BD3E2EF50B9325B642B015A1A2244C622C4CB6EE9AB951DB0F2400591DCD8D" + "730040B7DE5F6C0E9454B4CE03DD6641FD912A245E30994EBEB1AAAA5F2EB984565D060270" + "150880184BDA2E5DBEE4FE38F72393E904BF40591BADA5B2429E54A0EDEFEE767E3265D969" + "7B4DAC9F4780D5689CB2EDD5C9F230928DB5F5F403C62E7B49F34E4BF29A173C2F511439DC" + "B6ED3B2B9D788CFF3FE67BB6F0E585CEEB871C40A80E26478080D596748D774923F2B44559" + "02C4A572FABAD37A79219F7BC9FFCEF76442C093B93813B010E1B0F7B665797F39FE401C8D" + "8FEB1F68AF772606CB820519D82DA83279E067B0CFA6EF016EE86BB80134DABF04901E11F4" + "DA5E0C49B09610BD97AD0B27DA59761FDB46254B4D4581A4B62DBCF6F4C6288F4B6F5C797F" + "B96DB9DDF977C3417D25C2C6C44125EDD171F9725ACE79F2E4C9D9395D29B33A4A5EF29F01" + "DF8CFAB46360BF555ABEEF7432F9D6F168ED47AB7AF49965EDB5AA321080AB407430AF556B" + "CFB9B2F82901EC01DA49ABAFF20414AA943C2349FAFD74A6760DB46BF6761379984CA7E9BA" + "031B1B2902FFE0C18332AAC78918601D6F6582BB9EAF6450F9EA18E528E68B3EF23F47FFC3" + "F72FB98EF36050F759DD844CEEAC2DEF87487940BF4C736280F2DAC2AEFBF74CC691A2CA6D" + "9DBD74B17C2DD7C74ED81C98A8A2F762CD92DBC0234B915C22CB9ECDBB279FE369BCD1C9A8" + "C71AB06AD489285FBA24070E1C58D893BFD4361E38DB7EB4417D1690B99EC109061532737B" + "9A2803306BEFDCDEFC3CF63B26B53C2EA3B3EE9FCBB7657059DC4F42F101F65C6BE9102203" + "9E1502713D7BBB7BF3759199D21FEC2A80EE2A11E17D028C6B20B90EDBD0B6D3AF1F8DEA7F" + "32EC02B828030118C4111324642682F4E34574EDA4910B172F26B08575E0D0D67632DB8D46" + "E33E6DE7B2C97F3FD2B6EDDB3B261F7ACB8405BF6E72D0F5BFB3BF36F14BA0287A6B6E55F1" + "804E1C8260AFB3E579E7B3E65D0A92E3F2BD88771159987CB9EE5E1D3D20B06D60EFC3FE75" + "CFC4CEF7B3F7E14D813CF2E3812D3F1B6BB9FC1CF61C0BC09680680C08C600AC00A57A78ED" + "E969C26CC5F0B4F240160906C352FF95C0D56B63AFBFADF66DAD305E9BF3F97C4FFD6C978C" + "AA795E9CE03D312E21BB44D15E6F0978C965C16460279377B5EC898439EBA262BDD8044176" + "EB005D2EA8274B9A13DE3BDE18FD131972032FC8400006D9B7C46C49A8F26E6C693BDEBD56" + "CE9D3F9F82AFB60E6EC9E1434752712003294F7F7C0E3FBA90F6FBFF73B309ACED363792B6" + "CF05A0568048C13F899490B99FD73DF7B771B4A092F6CE13B4B75E5BDF732A612B3C81DAFB" + "3338799A2A7FEF050A3299B0133783B5A7A9B30582DBC06B43D69EED7396C8A0B50278E673" + "4F136690638B860603C21280C872AF7D9709DFCFD6DD0BC6F3C89EC83C41E132ECF8B4D773" + "2E06DB7E16A0F57AB62679FD60499E67DDE0B1AAEDC92E17A1DF46AF10149605562628D65E" + "CB7D3F2BBF7B9EDD5D0D020CB3ECBFDD932DF4659803FA1C232476C554B7A4B06D9BAF6DF6" + "DA1B43085F2C8DC55595C5B0DB4106D9B774AB08A069E1078DB881DDC98E9C3E7B5ACE3E7D" + "36F9E4102B00077D14D9F7AB8DF1448CF2869CE7D3581414CC3A4280D1CBA672DDEC2718D3" + "AD10809780C003B68549C7B99635BECAAC32F026531BD5EED58B2753FEDE828F37D1F23AFD" + "8A561FB0A6CE0481C1C03E2783826705612D9F09959EA740C6AE0C6E6F4FBBE6E7E0F6C238" + "D0BA3109F334654FBBE676669268DBBFD45742FE712122C163C52B4B1CF0E5E7F09EC7F68D" + "AD27AF926032E8DD8B9F9BC92BF787906BC0D685EBD9B5A31280ECBEEB9FCB949F6981D5F6" + "F56F67F157F57FF6052C966DDB8EA7D3F6AB24D412A54AAF41F2581CDA6190E72F9D169E2C" + "03F548F6F676E5F4934FC899A7CEA4950B587EB8361AC90800A3CB0E0B2F9C1324BE354ADC" + "E87D7EBAF5771BCD8A9FD0CF009531CDEAE63F328B25989BACBC0993BF130205069C92962A" + "B2A8ED3280DAE37602B4E72D5B512034E1F2337975562905F979D7B246CB6545F2E796EA28" + "0EE0DA7BD8679642F21C6E6B2ED75B59807ED7DC0FCBEAC5AE86525DF9AFD73FCF664CD9CF" + "1EA87BF5F4C68A474EEC5F760D786D6823F62B5A3160DBB675924A95C89E1D1F252B5520F7" + "910656CEEA986D8EC1EE071066F89EDFE43BE77F67FE00DB1B6D6CFEBC2E155E998DD4F621" + "830B60901754D2D2C27A94F65107183F75EE4CCA2770E4D061397870AB0B2C6C96A4E64C0B" + "0BDAAFD48F96FDB73901D02CFB9FB90C603F6DE672943380F164E44D5E15E5366770672D88" + "27D68A92E95813B70539AB6597409AE3164A64C2BBBF07629E59DE3EA37D3E7671709B4921" + "5E80D30E0B999FD965622D261E387960653FF379B60FEDDE0F25A0B5E383AD361C33C2A4C4" + "D3DCBD34CEBCF993065B466779AA183264FBC6F687FDFDE8F7BCBC8FC94D74DC1ADE9860B2" + "60019A7F0FB6CFB8FF6C39D6F5C45625FB7E56BF56AA5077897D432EB38D334D3FF182ECFA" + "8BD26F099CEA699606E8B9987FDAA6F93A894DA8AA10C521ACAB2A830560901747A2E48442" + "A314D8F3C49927E50B8F7D01B9FD6534AABB5506CEABE926A837879C01CC6ADD497242A0F4" + "796EFBDF2A95AD4BB32CB0DA89CF9BECEC84C7016562C0AA7292B294CA65B3AF251B6C7215" + "C77C2E0404228BDABC37995BE0F5B47CBE0F0327130171FA802D01F63B4EF3CB1A2347C47B" + "DAA3CA323260EBC16DAF20AC04C0B35070B9B66FF8FE814CFD6CF6E771E4BDE7EF6CDD191C" + "BD71C984C3B39088D36662C6AF1D3BFA99CBE171BA6CBCD83ED3EF99F8E939DE7B261055CE" + "E2896DBE454DFF51C79848FE30FB33D7AD790C19AB802610CAF7BC753299BCA56D9AE50AC8" + "8AC940000679D1447F9FC82580D713A74FCBE7BFF005397FFE1959874B003F526843D096F4" + "D534882EBC4FFA35C09D2819C84EC1746C1E10A23464F2556B036B814213174F747D898EAF" + "5CC59B08B94C5ECFCE0066C50309FB1C7CAE37F197AC025E59C1F8651994F8198548049316" + "AFCE5EBD4A6DC6E770391E7162001202BC604CD6FB2101FC1C5E5DF7F3BD67A160F2E411B4" + "521D4A1A76A9DF2C39E0F818BEC6DEC7920FBDC6C68394EAE01159761BF16FCA239EF6DA3E" + "3051DBA837DB936D3FA83230037DBD537F4A7E938E37F12BEA264835E07F2F030118E42590" + "987E881BEB1B69D9E0D3E7BF24172F5F4286231923AF0032F8D49584BA92585537B6126F08" + "7D4CAFCC850FC6BCFB5F7FD880C09476DFB393953533B3D95A85352B7BBCBF6561126D9D9C" + "F856D3B2E7EBFA746FB580A7D907D2403DC08C8E4FDE03647B8D10F89426EE60F2DF47B3F9" + "8E1400D1B67F4569976D1D3DC2E3FD65A0928229D9B6B53E4F2986A1D41EDE7B0FB085B6E1" + "E531C744C6FAD82B27077F8938789607B51A705B7AA0CA75F4C693F7DC7683234D9F1BC912" + "E6956BEBEF591AF83DEFAB206615CD1C98F7D9408D647D60CE1528C60520A68098D610BD4D" + "D66B91B52115B0CA40000679C9043F60B805D64663B978F9B29C3E7326AD1C585B5B97508D" + "24D429BBE0D1A01B7F5BF39F9AFC68CD6F3FD9678DBBFFAAA01DDB14B19593AED47E579A98" + "BDBFDE64C85BBD8A33B9B3ABA054BEC8BC66CBDFD96B0369CCDE779297CC5566C740CF3DC2" + "ED21942ED89AFE3DE0F2A2BFB93CAE9FF762D2C61A35D7D53E57C902E081BDD7070CAC1EC0" + "07DA2488417B194096DAB9D43E7CCCBA10B84DB84F8496A97A755229AD52E03A3099E3711D" + "1C52CAA4C59222EEA9A8983ECF08FA0331837E8A0508661180CC070D376DF36511E3D449C6" + "B4AAB238FB0C32C88B281D88878E045CBC289FFBFCA369EDEFA8C60FB895B6696F98FDF267" + "7F924FB769538EFF645A6FE3BC05C0047DE9C4C2266E3B41D9C9C94EEC0C289ED9D55E6B85" + "27BA862C123AD1B5CE66390C985E0C010B47507BE5316832D9F180C7D645C5D3E06D197861" + "ED3D967121153372BADBF7F84E5F9AAB81818AEB6BFBC00A6FBF6B4951209338EF02C9E668" + "718896D7BFDCB6E204EB8903C25E791E69094B6230BC71109D00C2E804FA95EA1F9D0C8E5C" + "FFD236C71EA9F3DC0DDCBE5E4C8D7D1F3891D7C2D3E77EEA0D8261EEABE40A88641150AF41" + "9A23E25DED64FA9A21087026C32A80415E16C14F102B05767677E4F49927D3264587B68FE0" + "977A3F92FC54ADF4C97E5236C2AA92F1B81689A3CEDF9F8379305960B3242C3704D8789AA7" + "9D50BDFDD0C54C5A36B90A83A0D0C46E275CCF84EF693E1CF9CF0066FDAC4C428231F53300" + "7801837C1F7B7F91C5E43025D0B0D702BC01E23A51A3BD34EF3EFA73737373CE7AC265E31A" + "94A1FB35D83AA01C94A1E5D97A58A2A6C778CB6221A0B340C40059221BDC865E9F689D79AF" + "012120B77D6A37076262CA04D1237B16443D626AC19AC96C296190AD271F67525151C09E8A" + "1D4B5A0726DB96A0EA7BCF9593EA8AB4E4B167F6FDFBA8E67D630880E61FE69A6A1628ACD7" + "CF362DC3D58D4C24BE715CD50F2F34F08ACA4000067959A5CE3B10229BE064DAC8B1A3475E" + "BB0E974017399027913C51F66986DB1429DC6DFBDB6993E7F2E485CD4430C1607F0221F0F2" + "34609145CD4F8C566527E952D29B924667CBE1FB799A286BBD0CE06C6A55A928156C09C8C5" + "F1C57A960EDB66D0E2A1C14B266CDBDBDB72E38D37A6BD200E1F3E9CB2EDE13D801FEFD1EE" + "0CCC168075AF06FC850548CB3F7FFE7CFA8B3CFE7A1CF7071940D9705778E4A66462B7CFC0" + "1AABD7DFF63B8F4C71B93C3E14D87857440B9A9E966DBF632B86A7D15F890878844EEFCB4B" + "5155ECAE843CAED98DE5F50113338EC5E076B36D6D9F238DE3688376BBF37AF37FD0CF8B04" + "29CEBDC789EDECC2FC657AF2A67D7355C77FB950C08ACA40000679792481C15E8A01D8DA3A" + "F8EAEDADED6F1D8D46EF19D5A3B7A649C7FEE02D38255250A78D7F000E2AC78E1D931B6EB8" + "419E7EFA693973E64C7A7DE94B5F4AA0A4E731905B9FA8677E5DF65E08C8F91A7B0FD600C5" + "0117155E5EC862C9026B9A914CFA56BC09DC8AD61F1AFA850B171240637F87E3C78FA7DDD9" + "AEBBEEBAD4C6478F1EEDC9D5731105F492E0FEE8B773E7CEA5BEC40B9FB54ED8E407D73321" + "62426581D5BA1B1834C5216BDC8F628881B6BB47123C2B81E73BE7F1B3ACAC5830A77B63C3" + "7B3EFBB76415E373B97E3CA6B8DD99AC5A8B822D5F64917CD9BE42FF5673E9AA5BD1D5409D" + "DB3FE6B5FDC1F8FFB3AA10F37B94D7D9064C9470772D9608B6B1BDAFAE8718009581000CF2" + "92097ECA30EBC384BCBEB6363EB47DE83B373737FFFAE681CDFBED84D2FB6CCD1CC75AB415" + "9D4400F6200178C1C4FCA94F7D4A3EFDE94F271001684083B59AB24D456B27769E70ED677B" + "7F369D725D3D133CD79BFDAA56AB128758A8781AB667DD10D33E1EC1D1EFA18143EB3E74E8" + "90BCEA55AF92DB6FBF5D6EBBEDB6A4E5BF94024DFFC48913E9A582F172F6EC5979ECB1C7E4" + "F4E9D3A9AE78E15C9014900AAF7DB9AD2399C03D42C7E06FBFB78491EFC71A3E133EDB6FB6" + "3FD81DC3E709B90DB80C1DC7DE58E33122852D8BAD2585EBBAECAF3826FC2BF5C3B26B4272" + "5754E9D51DC8D1FB6D8AE0EFD2031BAB40BFFB6F7F64DE1230BF1AA05B89D4B6F1EE493BAC" + "02501908C0202FAEE41F2826A94B972F417B5C3B7CE8D0DF387CE8C88F545538A6F7F64CB4" + "76E2E2EFEC357A9ECDFC063270DF7DF7C91D77DC9188C0273FF94979EAA9A792B95A77895B" + "06ACACD578BED99259D333C572E0983880C3CFACC7F81A21400BB43260D93389D9375F4DEE" + "D0E815F4EFBCF3CEA51AFACB21206E4AEC20B0063CFEF8E3F2C4134F2462006B01FA14D601" + "9C6B577A582991311E675E9F709F717643CED868CDEA1C1FA252B224D8F73616C21213DE81" + "4FCBB0F56052C0E359CFE7A0586F6741AFAD18C8AD366FDF07E316E0F810FE0D20A1970DF6" + "D3AC9F31AF06EA7627D5B40086E4F61554AB7F949911A0630A1D01686FBD7CE9D27522F2C4" + "C2C3ADA00C046090174DF08383AF1E66FE8DF5F5EB6EBCE1C6EF3D79FCC40F8FC7E36443F6" + "40DF4AC9C4CBD7B006D7E6DDE0701C80002270EBADB7CAC73FFE71F9C217BE90000444A014" + "B02766422B698ABCEC8D4DC325F2C00481AD0D9C498D27497E5ECF772B34515BE0C7F9708F" + "E07B98F6EFBFFFFEF43A72E4C842FBBE52055AFF5D77DD955E301B7FEE739F4BD601F439C8" + "80E473345F814DEDEC594BB85F4AD61C9B548A2D35A53EF134F7CA49E75BEA7B7BCC5AAA3C" + "0B954728ED751C68EAD55F1C2B81F7DB60299DE359B3F8B8D651834A914A5CB2A53FB6F979" + "A4C3F156CC5AFF857A58C37F7E17661901BBD5C561EDE9A79EBE7D20009D0C046090175CF4" + "470893F278BC76FCBA93D7FDCBEDADAD77EA7D584366294D4CDE792AA589458900B4DA37BF" + "F9CDF2DAD7BE56FEE00FFE20590510D00682600304BD32ADF064CC13BD070856738B4E6097" + "951271E03A3079F0EA5CD1BE00B080E07C5845DEF4A637C94D37DDB450F6D526D0FC61B5C0" + "0B02ABC0A38F3E9A5C05B070A08F55D8CC2ECEB8E1BFACA5DBFEF6DA5CDB5D37A50A1485AF" + "E772E01D8F5B0BC25C6F1E774200CC7BF7B3C6EE8D532BCB7E9FDE7126B5DC56A1B0F43650" + "C0606AAB949B42413F2C06FCCDE50908EA25B05F77DFF4E983BBE581310505E6D50855B857" + "44FED3C2C3ADA00C0460901754F0930493DFDBDB95A347AFFFF6A3878FFC625DD7731ABF37" + "B1B0781393D044ED7DC7E5EB7B901125026F7FFBDB5340DB473EF2913451C31AA07E54D5EC" + "ED7DBC7BB1E6C540C1DAB71070B329D533BBEA396C5560F3B338A4493F430B86C50391F530" + "F17FF9977FB95C7FFDF50BCF73AD088215F1420CC8673FFBD94406102FA04B0C3D00130231" + "CF02E08D2BDBE7EA5651B1267AEF3E422E00FECEDECF736994B46A4FB36722E9FD3EBC3651" + "F148295B06985CF0CBB6199BFF6D39E3512D559D631E52AE8F36837EE8357CDDF6D70609C6" + "68F47F6329B0A1826D8A3119CBEEEEE5DB161E724565200083BC2092A71B994C27B2B6BE76" + "C7F163C77FEDD0A1436F9002305BF126DED2F7DE397C8C5D02F6388010F101F7DE7B6F5ACE" + "F6BBBFFBBB29481026E32B695A3CD17A805CAA036B915C67050C1001DE514FEFA324C55EEF" + "6974D12C4983260CD2F39EF7BC2799CC5745D0C7F7DC734F8A6DF8E217BF98AC1FD6F5E311" + "274FA3E67157B202F018E66B978D212F0E80FBD4B31288B3D1958E1BEFDC12C1F09E9DAF89" + "148FC065785600EF37689FC95B2E8AD53D95AEF8EF82F65296CF1054DFD782661B05E50266" + "4440CDFE3816358AA0F329B46D83CCA327171E6445652000833C6FC18F37E5E14786BFF1DA" + "4FDC78FD8D3FB96CB2B162274C0FDCBDEF4AE759E1FB6B6E008022001FBE62803E5C020F3F" + "FC703A06D01087B0B01665419CB5216F122CD597CDB00C4A1EC87BDA179F8367453E04901D" + "F8F7DFF18E77F4CFB66A823E87E5033120887D403FC33A80F6E06443CB88A877DCD3623D40" + "B45622FBDEE60CB0E4C003630FD06D7D38BF038F130F84BD3124CE6F3538C97FD8FAC56DE2" + "B5931DDF36BF80BD3EA5A7CED614FE7DCC951BE96D2609F902B78D425E495055F5ED3248D7" + "DE43330CF27C656767175AE6AD278E1DFB77EBEBEBF0AFF93F5A23CB007DD977FB159DA034" + "DF3D4CFD08007CF2C927D3C40B933022E011390E9FF8C73EF6B1E426003094D67E7B75F396" + "5FD9F79C78A5E4F7E5B4ACD15939E0B919B82C7C8F0879ACD77FF7BBDF2D37DF7CF330BE33" + "01846BE0D4A953690CC022009001416000B6C264CB0337FD6B130195805F9CA04C6F0920F7" + "AFCD20E98DA192258001D982AD37966C1D3C2B849734488874302160933F7FB6F5C256E1E9" + "7EB847BF374036EBDB65B7B33B74AB04D24661F6C88C08F44A46572189B1990586ACB80C04" + "6090E724214757C3E4BF7570EB5DA74E9EFCD5D16874A83451AAEC17ECF9BDA78189A3C588" + "D13000FE08028409181A3F001EE6704D66A3A96D630E8C43CE006887F015DBF23D4DA994B5" + "CD8ADD0B40A5140058F2D37A20CFD6059D50A1F1E379DFF8C637CA830F3E38F71C83CCDA12" + "2400F90D600D00090411405B79D695D298E3BFAC11DB7800D6E8D952C0C7BD7AD8F71EE9B4" + "0492C76EA0E57E3695B18E512F28517F47EC92B0A0ED8D59A1150BF67CB69AF0785E5FCB56" + "B87EE95F9BCCF9FD73E273EF0AB06600F5F2CF1202CD4BF719BF8F0307360F2C74EA8ACA40" + "000679D6829F1A803384B07DC3A9EB7F637B7BFB1D523053AA9426543BB99408007FF62665" + "3B212AF001D0A1F503F491D14E93C6C0176CF70DC0B5DB5BDB7DCE002601563C33294F9AFA" + "9EB52DCF42C09FEDA4CBCFC7A4C05A044070F0FEBDEF7DEF3511DDFF6289B62B0820822131" + "8E35F360C841939C00481C22E8114FEE1F712C3E42897DBCE44225B2290E2154E1F161C71E" + "5BB058F3662D5CCFD3659481F65DB0E75A10E7FB5485ED80ED7DE7098B207E2857A0DBF6B3" + "C9DB7F0783F9FD3A00DD193411841C28D876075AD335693541BAAEED948267F69E7B2ACB6B" + "4C060230C8BE457F837B933DD9D838F0E0F5D75DFF7FD575BDE901B28A07DC9182AA8492DD" + "78D77AC70205625569C3A0715AFEF5D0430F252087091CE0AFFB064033C339AAFDEB44346D" + "A6C91D007FF1673EF39954064082B52E8FB0785A9BD5BC8422F84B417E1E91F0B437DB1698" + "D0900C072407E00FEBC6B52C57B2302D130B90DA8E180BC887007208D700FA9D13452D13AE" + "873726F4B30DE6E43165970A5AD780B7F3211309252E9102459938D8E3DC86FCACBCB18F10" + "F11403FE4244A074CC5A00BCDF0D9200F52E0081E65F7529FD9DDC0D316A2E002D87720186" + "CE2510C40601A62FD0460301C832108041F62DF891217FFFF6D6F67F7DE2F8899F110790AD" + "B0D6EF4DA6960C7864C12BCF230DD0D831C160E917B478987811E0876398D459DB80AF1FC7" + "E016B01323480002C64002602ED434B31E112801046B729EE624460B62F2B38C4C70201540" + "0BBE6D807FC96A71B50B8F316EFF121958F69D5EAB608C7EBFE5965B521E01C40768EC882D" + "8BAF5F04B0C5E5A01EE07AE491C78D470CF9793D97901748EA9DE391583144410ADB2D7BC1" + "800CEA156508F43E7BED8136D77D26628EF06FA3F96DC4940A7061CBDFFE19ECB2BF6C2968" + "678575CF316D709FB10C92642000835C5982EEC73F95EB4E9DFAB9ED835B7F4352A04EEC19" + "B80A4F2C9E29DBFB2B0E607AE02A8E391D600E6DFFA31FFD68BFDE1D5A1D8EA996EF8946C6" + "33098036AD24C0FA873D40F126656E0B7E36EF99F53B6F7F010F30700EC01FF5C412BF12D0" + "BD92A5D4BFCBC46BBB52391E4163AD57AFD72C74700B800C2090525D411E587AEFB95E766C" + "E8F8E1604FC9DA7669BB600E22B5655B6B92E71A607269C797055F2BBCE6DF827C29C19010" + "987B6391EFC7D601D40B04606D6D7D56E73CEF44ABDBB7B93D678D3DBB477FCC26109A5DDB" + "590D4000EA2138268B3F330E3288114C8EF8211E3B7AF47F06F8F72654FA9DB389DF7E6E69" + "2732294CE66236C62995A57F312101C411E4F73BBFF33B696278C31BDE904CFE20021E6073" + "7D71BD6603943C316132C67A718DA08725802743AF5C7B3CD2BA69EF7CA189904DB6522043" + "38064D15D9EFBEF11BBFB158F6AA8B07FE1E59D0F647BF63ACC37A8478108C0104095AFF7C" + "A90C21C0F7FA31D05238AB69DBFDF2ADA9DC2E97630DDCC68A788181D1F8E1F5189361D6DE" + "C500BFFDCE6AEDBA9F7F2898F66D9DB82EDC0E564000C6A3715AFB9FF477254291E6875EE3" + "B7BFABCEEF1F75C740E90FCF24B955D08ED54000B20C1680418A9282FDBAF5B8B7DF74C34D" + "BFB6BEBEFE46D636583C6D2852D4FB95088037C1B2D95797EB610D3F7CE0D08491D807E67E" + "68FDA5A4257C1FBCA0F1C5BC57BD35BD62673C90000412AA86E895E9593C78E9175F238565" + "58250DD512053C2FD6F77FF5577FF542D9AF54F1FA749970DB95AC29DEF7DE67B9421D6C9F" + "80F08114228910DA1AC9946025B2BB493221638260C19CAD47256DD91EB751F9ACFD7BE019" + "29F0CF5A1DB87ED6F2C06390DBCD5AC62CA9558DDD736FD89747243C32A296B7D1789492F5" + "201EA069A72908B0EA5700C4942408017F9DF131248F405AF6A75B04C74C1EEC0281D84701" + "A84B61B10157540602308823DD8F0993DED6F6F65F3A72E8F0AFF232219E6CF9AF77CC4E32" + "A56BAD78D7E02F3433446D23952F26658021262368FD9E46A3C25A8A9DD850265607A8B6AF" + "F7D4004258195433B21368A45CED254DD19A4EBDAC806C05B0D76B7D703F24B301D1792583" + "BF07D6CBA4644EF7C8E473F91CC82CEE95A5A2DFAB5B08DB12234010BB496A60A807F87CBD" + "ED43CF24EFF57BA435FC9C9FE04A2487AFF59ECD5A123840957F1FEC0EB0CF63A3FBEDEF54" + "08E4F5BEEC76E057B73CEF80ACAFADCBCEEE0EF2F5771600BCAA3AEFFE27DDB6C019C1B144" + "B06D733B67FC57ABA4E60E90BC82008FDA9FB3B0C1C0EACAE2281964C5A5FB214FA7C90CFE" + "B78E1F3DB600FE563C53BDF71263DA8FC61FEA9D1B4D94B63D07F5005063F7B7DFFBBDDF4B" + "F9FC5FFFFAD7A7F3303997CC925EBDED79D612C0932E343EDC07D1F520023C697A939F3739" + "DB7B7913B38AA7316AB958B70EF3F4BBDEF5AE85325E09C244CD7B568F18F277A5F3BDF7DE" + "77A53296D5CBEB07F435E20000FA77DF7D77D250E112F0EECFE558D0B7CBE11804ED78F2C6" + "6BC9E42F446682134CEA95B7ECBDFDDEBA1EF89EF679B45E7A5C970E46CA01E0B52F93A8F5" + "B457C32C621F3B898A0278DE1320E4D8A35425BBD4326BFF69E39FACF077E5186B80FE0D03" + "EEA90C0D31C8BCC498D8F8A1EDEDBF7BE2F889BF2F068C553CAD5C9CC94285C15DF6714C08" + "FC75331704FA615BDFD7BCE635695206F0C33CCF13E87EC502B3120C3193AAFE45F2181004" + "BD977D6E9E10BD49CFD64B7DA8DC9EF6B3D60B04049BD940F387DB03E0FF4A8BF667ED7A59" + "FB07C75D62CB61605D6615F0CE17676C79E3D31BA7B68E62FA012400E3EFD5AF7E7522824A" + "02968DB79225CA6AD2212FDFB3DF79CFEF11020FC8F57A94E9593D827127D860406B15B071" + "486D4B0000200049444154EF3D325D8AE8B77D6B0904BEB7310376ECDBEBF1777D63632E83" + "DF2C1628B7496606315902E6B7596E5B7D3F3BBF57F8751C4857B7A66D16D71CAFA80C2E80" + "418C749BF96C6D6DFDC0A1ED437F5B6CC08D13E423579884AF748EFDCCA64DFB5E9706FDFE" + "EFFF7E02FC2FFBB22F4B60887CF7AC593D17D1894B530443D3833BC04E90380E12005740C9" + "05C0665D4B106C56406B46B5EFEDB303746086C676B60848C37EFD58DD000BC02B5D3C70F5" + "B4649B70C7B6999DD8F73B563CCB94375E4B6394EBC6E52809C0864AF88B716089188375E9" + "79C55817EC773CE6793CD873EDF936329FCBB6E77279A500D5528E7E5B3EBF67526273082C" + "B342D8636905C0FADA9CE5AF1B1F92FDFDA916D9E43FDB18A8AB4BB604E47FBA32E36CF540" + "4F00F4616496A671C565200083641F592BD3C90440F313470E1FF949A12035712623FBD94E" + "689CACA644063C4B823D86F7D0C831F9FED11FFD519A58B09D2DCA8716560ACA2B1DF3267C" + "061000350807347DEB56D02025C4042002DF6A6E7285493B3ABEFF7E12ADAB64D2847B01CF" + "A9F707D9C0FA7E64F54330A2E62378A54909684BC7B9DD3D2D9FDBE84A7D593AD7AB83251C" + "1E79E036B6E7AB0BE8B6DB6E4BFD899C13E8178E0DE16B2DE85A5FBF829C1D6396147ACFED" + "C588B0B5AA6451B2CFCB0435507C82E7F20BCE324215ABC9DBF34BD6038E29403BC2FFAF44" + "1926FFEEBD3152A39CB8F89B4D1683BE3AD6E4AF3600993BAFD19B0C321080959720C9DF2F" + "12AB63C78EFDAB43DB87BE5908FCBD493792A9DE1E630DCB9B9C4B6588014C002E22F01180" + "05308426AC19FD3C93BB07903C817913AB5D77AD1A3DEE8D79428302F53C68E038862044AD" + "83D59A6CB93CA97A91DD972F5D4E9A3E401E2666948FA0331000100158222E5DBE2487C7AF" + "3CCDBF64D5F1FA7D19187B84C97B5F9265E794CAF60881777EA080395D128BD521002D8C4D" + "DD77C21257BDA632EBFA85C91F2D09E4BAA87896225B96D5C039CF0093032ECBABB39065A1" + "2A2C81B5E398B57A761558AB003F03DA14443FEDD6399D26C8C66F0F66FDAA0AF3A0AE4102" + "39D14FD09C00DABE6AB5547781860AE443797F90C9C24059511908C08A4BD642368E1D3DF6" + "DB9B9B9B6F64D3A44A49932F1DB79FBDBFE24CB60ABEF8910280E1EB47563F64F483260C30" + "64CDC4D3D89689672AD609C92EF18A796580AE0DB75A13820211A9BCBBB3DBA78D65ED4A1C" + "6D2D66FF2CCA83FB02029FF27DF7DD9796318204A02E2004B03EE07971EDE1238717EAFD72" + "8AD76FDE7B4FBC7359A3B379E7B5DD2CC9E47169AFF580B624B65F18284B6528C8A27F9034" + "08E3E391471E5900712DE74A41A33C964B75F1EAE17DCFA4C2037BDB5E4CF0D93560BFF362" + "06C42102963094A2FFC5B82BF04ADB338F46B29BF7E8809B3E6D041432F1C81BFC2CEBDF68" + "CDFC41FB7506FEF81656860B172EEC1486C4CAC940005658F2C4BA75EAE4C9FFBCB6B6764F" + "09FCAD7813B82C01FED2779E99520112BED50F7FF8C329D31D4CFE0046D5B8791229D5D54E" + "5C1E602CBB564C90148808C0DA5E87E3478F1C4D817976C21602350600582E50164803D2CE" + "82D460499F269CD10D69AC2502310075B5E8EA7829653FA0CEE0C5EDED4DD8F65A0B3C2040" + "6A6EC771B4D7786DDCFB7D93BBCAEC17EFF5AB772F4FBCF39681B4CC7E3769D929FA0FEF61" + "09D02582B61D82139DCFF7E1FA32B8734A5E7E466FE589B50E784B0FAD8580E30802651914" + "32D9F33D35B0CF5A0BC404C7B2D95FC86580631B070ECCF6F88BDD2640FD3349A7DA9B1924" + "6F004463AA53FF17B60AB6E98491CD746363632000590602B08212CC247668FBD0FFA3E0CF" + "263E156F72E0EFD81AC07F8526399E14F159F3F923D80F20802D6D41081005EF6D4EB2DFF7" + "DEC4C8F5098E9F14DA9D12128D39D0E757D046DD3893204F9A00339C078080991FE08F54C5" + "F81E2082E57D6C9EC5BD713F0D807CB984B5510F3085809EDB96C7832D476329B0231F5EE8" + "772F60126D8176867B0456134DD10B22B08C742C138FA432E87A65D96744FF81C8A18FB1FB" + "A42E251507A0C50484B2C9BD64D112C705E081B047783D6D5FC50B5CB500CDE0AFE57920CE" + "DFEDE765FB378D73FD0DA5FCFF4D7205686DD14255F2DFCF58408B40C054EFACDDB7D94A10" + "BAFC01B9A744370E8839BBE0743AC1D8198200B30C046005256D92D146D93CB0F92BEBEBEB" + "6F5906FE56EC24CEEFBD631E607884007F15FC91DC079FBFE22BBE224D949860792B52AEA3" + "9D884AE794BE632D8FEB8BB60108039878D2847500F5F32C2721FB3661C6C779007E98F911" + "CBA0C76D9BB39544D3112F7B8E175B9691396E3F3EE6B5259703F0473B3CF6D8630940D1CE" + "B078004435B84E33F3E105C205AB0B5E3807497A707E6B7676F4EEE7890792ACB92FBBDE3E" + "0BEA85C0408C11C4ACD87E8B4BCCEF2C9549E4E31155EB87F7EAC1F1067C3E9BF4F9BDF71B" + "DB8FD9DF9EC76584424C808A12FF14783BE95CF33836CB0098BCF8890484B980BE6C15C887" + "D296BFFDDE247A3C66E04F1E815406FAE691473E355800B20C0460C5043F0800D0F6F6A15F" + "D93C70E0FD16FC3DF1B42421E05FF6BD3865F07B0024267868FE98F8DFF296B7F466600BFE" + "5CC7B0CF58006F32F726409660A29E0138002B5B1E8E614281766F770DD4BAE3B9EEBDF7DE" + "04FC9A51105AAE380064DB0BE7A13C94FF724A499B2F1DE3E7F18EABE836C6584E87258EB0" + "8AE099BBE0AFC52D9E353012DFA31F6035414228B4D10D37DCD0E7682899DB4BF5F43E7BCF" + "B6CC5AA40983B04410E318A983D1F742C44268AC7956232B571A9F2A76FB604B0238EEC096" + "6597B07A01B5917CFED6AF2FCEEF8BC15E0C49E0DF6834B106682FB4D5FAFA9A4CA6790540" + "D3745AFC48AD10A10FE4333DD2AFF6CF618229E83FC4F9347F9D75A0ED959EB62BEEA98546" + "5C511908C0352E76E269BB0D7C0E6C6F6DFFFBCD0307DEC1E0EF99FEC501F392C6EF5DEB81" + "887D8F1F3F343A04FC21B84E2751BB57BF270CFEA5F354BCEFBD89D1D3AED04E6999D2FA7A" + "9AE82D004003457D013EBB7BBBB2737947AEBFE17AB9E9C69B12E8E3856B00FC5CB6A7616B" + "9F20EEE1E5929206EFF59FF7F94AC701FE8885C0B6BBD09C15BCD18E25512D1FF5C29841FB" + "8068814460E73EF40DAC2B285BCF5B561F6F5C2EBB86DD011C65AF3109F7DC734F3A8E6753" + "F70D8F31D6D0F93768F3ED8B63E1F2FA87EBE381BAF7DC5673B7E4A9E402F0CCFB9EF65FD1" + "8642AE45211BF9671938F7A40A954CD58A91B57C5D05B0408854CB17111E6A310700C6EC3A" + "E8B20776D73D75F6A94F2F74F48ACA4000AE71E92782CEA75D1D3C78F0B7EAAA7E93F5B30A" + "4D12256012870078C4C08ABDCE961FF21A7F984C91DD0F267224BAE1DDD7544A1A0797B95F" + "59984C0A6DA0A2AE00808B4EF651D72FAFAF27800781C18A855B6EBE254D6AEADFB765786D" + "6C5F0041900A94FB724A89B0ED573CE0D4CF683F0024E22010FD8DCF42D778CBDDF42FDA11" + "E08FCFF0BF8360C192001F3CAC2D3029A31D9F0D89B17DE18D3DEF7C16DC53E33CFEF00FFF" + "B0DF54887F4F1E787BD6347B7FBBBCCF6E1D1C9D4454DA76ED927D046CD9DE1A7EFB1D033B" + "5F27E637C9E77B8441CC6F0D4403BF95A6C9593FB3C62ED95A19B3493FE4DDFE179F479708" + "9A2D80352D005602B4F3A9FF71CDEECECEE98586585119088091517D6D35077E245BDB5B69" + "9DFF68771700F51F2DF8ABD81FE595B47AEFC5DF79F5E0F7F8D123910A34FFD7BDEE756949" + "15A2E0D9DFCF529A489E8B2C4E268B1228925F9304E975202CD048B10D31C048E3022CF07B" + "EDE9B5A34ED83077BF94E2F5D932F13450AFAF85888E6AB7681B757170347F9553C7F2F5D6" + "FC6C2D259AAC093BF761D508DC0258A36F49C0B379268F547ACFA962C989E4150C782E1091" + "4F7CE2130BCFA0E496356E1D036CC21702729B2C48DFDBB6F14CFBD14952653F97AC20ACF9" + "7B11FD5EBB71CE00B6D4E977D6FF3F994C1370B79203003BD49FADF15712E0CC25F953EF0A" + "E8EBDCE93DC9F4AF16879DCB97E4E2E54B0F2F74E48ACA4000AE6589924C6A30AD1D3D72F4" + "5F1DDC3CF8760BFEFCC3F5B47CFE6CC1CA4E90ACDD7893A61E03C029F83FF0C0034973861F" + "DD0B72122228DE71166FD2F68E79D7F1F9F61A9DB0002C3061C3DC0CFF33CCFCB066008C74" + "299F10F8D936E0F6D5B6D4B801CE32F862CBB2B679B61AF4B263B63D74E5846D2B057FAB89" + "0793A8C9F68D0541B59C202810C4022E012CCFB3DBDDEE4718F04BA6FB2B09C601C605C608" + "021C3B0DB799ABB790D99EDD01162C8508903863958F71801E3F93BDD6D3D83D60F7AEE1A5" + "7E4C109669FF18EF487C85B1A0161D803FB6201FD533FF7FD2E453478494EBBF7707048DFE" + "EF909EFBA74F08348B254CB93BA693E6330B0FB5A23210806B58F0B3E940E5E00F1E3CB8F5" + "2D6D0E8609B2F8A3F6C05E4843F0B4FC9259DB1EB393094CBE9FFFFCE753F214ECE4870900" + "13A5CDA6E74DBCDE24B74C3C73BE3789335873BD85C80026724C6EF04103F8416674999FD5" + "D2BC36F5DAD0122ABC5E4AEDDF6B0B0FB0F7FB79D9B92A6A3DB1BBC659D010EA77B620D8B2" + "398113FA01FD8276445C095C0CA5F1E489BDE795DA420C80F33931078222BE01E302562268" + "B9DE33B0F5CD1BB74C1AB48E36B5301329FB1CB67F036D3D6C9FC53E63E93B7619D8FA9740" + "DF8B47D03B6DAC6FE8C34A48B932A63DF0C7BCCCAF370564937E17FDDFCD63E979707E1B67" + "D1FF21F6A0AF7B99C464E1AD656777777777E7F2A30B9DB9A2B2B87E6A906B469A16EBD8C7" + "1F3CBC7DE8A7BB8D34DA1EFC2D23F7805B68373E05283DE6992FAD4666CFD572A1093DFAE8" + "A3F2D0430FC99D77DED983BF382069CBB6E24DC4563C82E0694B57123B79A9E956C11FEBD0" + "61660699D1DD08B9BDBC97D7861AF58E3230A1A3CC175B4A6DB8ACDD3D29B5E5F3E923065F" + "3B2E342F824DBB6BCDE462B44A9CAB4B48BDB165C1DEFBCE7B268F0097B4645824504FC4B5" + "683FB3295C854DE65E5D5833B7D75897897D5F5D2185AFA7FD0B69EF7C1FFDCE6AFE1EF0F3" + "733211906C055ADF584FC979F499F13E187FFEACCDBB6BA29AF973805F9499A9BF6BB2FC5D" + "9F1028F7574A32360619FBCCB967CE0D310059060B8091E0AC27BFDA447F70BBBB3B72F4C8" + "B11F3E76F4E8DF53CD5585271A9E0023EDD5CF66D4D2C4E9FDD5087AFCD801FC5822854039" + "04BA69AA5B9E54C59984974DCECFBA9F9DC0B4D2840B4D0E4082FAA3CE6B69CFF24EDBF440" + "CA6BD32BBD24EFFE0757C80BF17CCBC42359DEE765DF79E77AC7BC73F07C180F4A9A546C90" + "9B3716745F0464DC43AC05B47BB499646B82BE87CB0B631DAE145D16E88D1D4F1BDFCFF394" + "2C08815606E03D347FDDC511018AA893A79947F2FDB3B58BEB1FC9A7EF69FA56ACBF3E9A25" + "7D9E25808989E7CF0F4E3CC032AB80D0EF4AB2D5060A412200C9E72F49FB6F264DB741963E" + "735EFD9F4CFFEAE5D7ED016387FAFD5B992539EBC98069A3D1A896F3E7CE7D643C7E656DA7" + "FD72CA40008C780965AE46D99BECC194FC210BFE6C62F426752F62DF7E2F737B742F1281E0" + "6CAC83C919133E22FD619645C01F26739BE087CB103341F3E4B64C9E2B78DAE7D58951CDFA" + "2000D028F10A393BA06D0B5B86D72EA563FC3D349F976BDDBF3716D81CEE9D57BA7ED9B198" + "4DD79AC14F453FC302C2641563083B30C29F0EC1CA11F4932EB154CD147D9596DEC5D9DA78" + "94AB1603EF1997D5D7CA32978498B1CFEF513FC42368A643252662347F2F3E202CD9D84788" + "34D8B4BD7A5F5EC6D7D226549E55803F0B918DD239B60D544AA4C23E1BFA0A846DD24CA44A" + "63622FA5EDAD44498705F585D1949F2F984C7F790340F3BD9201948BB6FFD4A7FF7FF6DE3C" + "EAB2EBAA0FDCF77DF354A34A9235CB48B63C626C83CD641C4FACA43B4D67F522BDE86441D2" + "9D4056FE617213C80A7835B0685861B4C922A48319C26463B0DD74023618D9B2846D820149" + "D664CB9AA7AA5259AA5155DF704FAFDFB9679FF33BFBEDF3BEAF249525B9DEAEF5EA7BEF0E" + "E79E7BEEB9FBB7E773EFAD377DF2A6B1E77BBED25400209A7F8E53AF9E29E1A53F76FC98CC" + "CFCDFF8F171DB8E83FB6C05F1C4DD0065A592B80075ADE77DEA6455C6EBBEDB698AF0D9F3F" + "FAA86575BD72A41EF0DB88E667A2254F3A5F011E6E092DF18BE233AC4D723B9EC5E36C347F" + "1D63081BC8404120E1B9A060CCEA763CBC71F0B4666B9A3F9B36F97CCDD5D752CB2A1400C0" + "B5A0928E35040200FF7DF7DD175783C4F340A0255C4900575803D4DC6E4DDA81322B5AC180" + "ADBEB68EE371F1C0DA8E9BBA782004605ED988FE8E5689B491FA1E085B6B818DC667E0E736" + "58EB5781C1BE679E4BC013105A667FDE67CFF1E6420ED20C43EADF7AAA0498ABFC4929ECD3" + "E58C802E07FD0D5B7AE943896A524120F908D2580C6E81B870D3DD5FBCF9D0E1A90740691A" + "03F0154428A5B930BFF0DA0B0F1CF86349CC4726805E4B93B5BEEAE0F8F45BDF1904C1BCBF" + "F0852F64B3BF6A69CC285BED8861CEF63B1FE71DB31D792653000952C9C0A8A1F123921BF7" + "80EDAD7EB6B6F1187A1F8D29D072C75A0AF9D9266F2C6507C28A3DCE5A7E26B56941D86B9B" + "8300F537BE03DC390B422BC561C5440448E2D9407384591D421AB249666667E28249101ED4" + "1A808F8EE94EC07FBBB9E381B2775F6C02D76D1054E00A80DB02C22F83A727500801BD0560" + "BD9697EE67FB6785066D9FD7B4F000DC0A0DFC914684BF8D29F0C09F85225DDB21F2A86EB0" + "00215D39DEA39AEE7B4DEE1B2C00835F3FC4C0BEE19926B7809DB761A8F817F43C91386790" + "7EFAF9BB3FFFB94905A7CE379A5A0088B204FA02A4C4A0972FBEF0A20FCFCDCE55D5D0F845" + "6C316D0B061EC87980C164F3E511E90FED0DE57025A547D9687FBD16076BD976AD39DAFE3D" + "1BB24C779456E9D3DC7DFC464D0200BFFAF93DE0E33E7A96013B7E42C28E0DFEC375CE95F6" + "CF7436029205A349DABE7D06767CACE540357F1D0705698C0104303C0798F7212C627CB4AC" + "2EE68F2400D1DC7F58021E7EE8E19877CFEB3530E8B1D6DDEA7B6B7FEBB71D130FC0793B40" + "0702000A2069C0E7C8D4EEE7F162C1C506E2F1BBC0F7CAFDF26A0388E105ECBBF7AE6F8F17" + "23E0D8FDADEF764C34460302DBC6262A7ECE0CEF1A9E13FA2A43B47F97B4FCA02BFF01EED5" + "EC8FF5017ACD1228818174B174BDC112B0B2BC2C77DC79E76D274F9CBC17C2E3673EF399B1" + "FB3E1F692A00102148E4854830802177766579E517E7E7E72FB3C555C4D1DA5AA02EC672C0" + "71011ED0795A0BC01F265B686730DB8201A9AF3638A6FDE0986AAD69530CC37E26428010B3" + "D2FAFCD02CC19410E1DF53A5394F00B2E3D5129EC4C90CC0B351F0EF530D79AD757F2EA805" + "DC93C803244BF65930B540D16E83100060E467894048088D181794F6E5859874AD8040E998" + "48B58310F0C57BBE28575E71656CD35A99BCFE7BDBBD7B6ACD397BDF3A9F6C854D3D06F30C" + "311E4853442C8C5A396CCA5E4B00F6C6D082370B05320190ED71F61A2DEDDD2B04B4DDC792" + "5E1BCBFFC67BC57B205DB45EE6B14D0BFF49D2E0C99E34243177F47CD4E44F970A863F0D3C" + "25BEEB7F034B12DEF1DFFFFDDF7766C4F9475301806876F4021C8E0E80BD29FBF6EC7DEFD2" + "D2D23F5706E981A30525D6C0C5C401B0C95FCC0BC50C8B8502F5F923DA1F3E7FA4FA69FD7C" + "6B8AE5F42DDDC7A0EF693F814C9E96F9DA7BF4980F5F3BA45C6DB8273066600A5A96D68E93" + "385ABE384280A7F97B1A3F7FC707EE8667BBF46F0B945BDB3CEDD6DEAB0798DE39769B771D" + "1D4F9881351E40E70F2C30983F281405A1EC924B5E341620893985E707827F1DC77FE1EE2F" + "C815975F11CFD10A81B6FFADFEB47E334D9A5356E8F18406DC238A15C1CDC10183D6C2A5E7" + "79F9FF3630D0DE13D7066805FC891122AC86CFC7B43E6C21F0048396101852FADFE28246FF" + "8F64E3CCBAAC6F6ECA7C740329F80FA6FE9805806BC426062B4020BCCFDFD3FE2155D0BE7F" + "215A1A1E7AE8A18F607C3855F27CA7A90040F442740160D9CC95E5A51F05F82B688BF3D27B" + "003EE963FDF12C2488897456468217FBCE3BEF8C66DC6BAFBDB6D2FCF5380655CBA0ACA936" + "181FA7C7543D41471A8024C4480110882CC73160CAD0C8B8C6BF27F888037A16E8F9B7023D" + "FBFB751BF6C3D40DF0D7223193FA7D36B453806BEDDB4E73F7CE69B5E7090142F3A74B51FE" + "3A2E312072763606F7617C50E35F4B07ABD918CF0EEE02B80954E386B560E6F199E8768255" + "00FB3D5FAFA7617BC7B0E6BC9D5069E786077A92E2196001403C00DE11DC8F15C6EDB52D79" + "057A26090E5E3B16E4BDE0432FFEA095EAE7F5D7EBBB50FA1F52FD36D687D53EE3730A9AE3" + "DF0FA1690AE82905A0CF3F539E7F5E21B016094232FBEB5FECC74A83274F9E0C7FFEE77FF6" + "C9FBEE7B4076EF7EEE16DA7ABED15400205A59591EDBF67C25355FF77DF8F63DBBF7FCB802" + "8D92D50C2681BD052F4F8BB54C8EC11F2F31183334363036ACE8D7A51C7AABA928797ED98E" + "96DF951D30D69D3067DB3E08FE7E008B46F9875486D71B2725DB176FFC842C270CF61EF8C3" + "ED80FB84F061E335762204B4C6A445DEBDD87626ED6FFDF6F6B500D01ECBF7AAE67D7CD432" + "82BA0BAF7CC52BE589279F90C38F1F8E63A6E3AC71013AFE986B184B04043EF0E00372E5E5" + "57CAE2D26295B6E9DDAF777F769F15A25BCFC882B827F4A8B549D78AE0B6BCF9AC7FAD45AC" + "77EA26D83442AF8F363090FBD602722B74B0B6EF9DEFB5A1E332B808E7A54F4BFF6EA6D2BF" + "55F642E8A3D63F44FB4BCAED4BEF9D6E9721D0AF63F0973EAF1D305C6FB8CFB5D555B9ED8E" + "3BFEEEE0A1C30FECD9BBE7CB5E6AFBF94CD391207AA19886BA1410331B665FB9B2BCF27E21" + "BF3D530BAC2C60B5B6F3B9761BBFD41AED0FDF2D564263F0F73411BE0E5B0794F87A9E19D7" + "63B01EB3E36DDA0EA2B0D14FF4F9C00517C866026566B45E5FBCBF6C71E1003F35F35B2140" + "FFC22A02D0BFEEBAEBF239362ADEBB1F8F3C70F5FA6BBF7BCFB1255C79E7B668A7828B18D0" + "E0D434056D1D4768F35C8701CF125A23E618A7B3615C77ADED42264C5AFFDD7F962DF06FED" + "0B8E56EE01AC1D43BE47DD8E7E23DE03F783804068C39DA921609F8D67EA67ED1F63A06366" + "AD0256C0B0FDB2C0CFE758E067B281857ADD492E8090CCFFB3B110D45634FFAFAF23BE0359" + "01E4EA53DC37C180522AFCEA800F59005DC8BFF50FF3B3A5E565B9F3F6DB6F78E2C891681D" + "F29EFBF94A530180E885E20240518BCD8DCDF93DBB76FF115EC40DD36F8F295990B700EFF9" + "FCC5A96B9FFB9098123431146601A8C2E76FC19F1940CBCFCF4CBC759C572485B5193E8789" + "1919347F0426820143F36713B1BDCF497F3DA1493F0CFEFADBEE03E3476604345C8C1BFE7A" + "D1D816B0BCE7E0090AADBE7BE7B48EE94D5EBBD73ED376C0E99DE35D538C1F5B92706B055C" + "3BB718FCD4B4AE2E9DEDC6C7135A260940F6BB07FA93C6158467AE56003B4E2DF0B54200B7" + "69E78F35ED774EDC803DDE9E374940B0FD924686000B2C781E1000628D7EF4633404470EED" + "14F02EE7A580BF1826385802CAD2BE250AA00B1A1BA025826B21E0C4C913F2E4D1631FBDEC" + "F22B64EFBEFD32A542530180E8855008082F07349DE5C5A5F72D2C2C5C6B4BAA5A86EBF9F2" + "9939F526BFDFFB88619ACA50E0BBC6CA6BD0FEE1B385195703FE42435BF7C0BD333E47715C" + "049E866A41DFFEE6BF60B6588408E00F53B1469FDBFBB2F76AAFCFA01F26F8F9ADF6AFC185" + "085683F081C035B84B7652FFDF8E616BBB770C6F9F0462DB7D6F51EB98569B2DC1C36AD436" + "2DD4132C18F434AD2E24CB81385A6B8B5A73B5753F767BE7141AF20403BD4FCC3D0800988B" + "5CC990C132385ABC15F6D81DE065CD0801BA07E2C1A9F2C7E7EDF4E30952DC9EBE27484F46" + "195E15CCA0FDE33BF8463D549CDE2709E0250B09C3B2C07D1601FA8EB5FF20DCD8DAEA9A3C" + "F8C0838FDD72CB2D1F9B9B9F93A74E3F35F64CCE679A0A00449B2F000B00225AE7E7E6BF7F" + "6565E57FB6E554953CF09FA4F14F1200C480BE6E03F823FD0A35CE9183AD0BE330D3F5B433" + "66722C048464CA54B25A3EFB40F9180F14C56827F0F7C34A01D085FFB555C7BFC5B45B4292" + "FAAAB71302D40A00F703CE43512458230004D08860E256D3768B997AF7E96DF3FE8A03609E" + "C9D9A3D6764B16445B7DF2FAE369CCBCDF13286D3B16845BA0D922EFFA3B39AF2538D8FBE5" + "FEF5A90C2E0400AC6F0000B4CF431C7FBF156C95AC39DE0A0FE200BCD03BC8E3EBF9F5F9C3" + "C77A9ABFB564709FE6171772CD7FACCC87204006F6A019000AF81DE7F60FC67F80FF901D90" + "620046B61050F1FD6331B4A5E52539F2F891FF777E76F6CCFEFD178D3DBFF39DA60200D153" + "694191E72B0D4CA27BC3DADADACF8BF392F1364F5B6D7DAC55C00345DE06BF254CD71A71AD" + "B9DA9E361A1AF9CA16DC4769C53D5B9CA465EAF78885066552D0FA1F7AF8A198238EE86B35" + "FB7B9A25F7CFBB77160418FC39AF9F83D8AC2080CA75580B01808F0C04496970BA380EBE5B" + "6014630DB17DB6DF27019105B99636EED1A47DB67DEB3EB05AACBDF6766DDBF6BC00520BF6" + "761E3E1DF2C6D36BDB0A21DEF5EC731896E95ECEE0EF09625608E1635AC28077BC27585AF3" + "BD5D3B401C60E7ED5610B0EF3BFF8EC27D5C136436AEF70FDA88EFCC5614047AB3A67F50B7" + "BEBA0322F6F795FF3F823C09087DE8354C60B80FF4A31B6245FEF8FFFBE30FDC71D75D31BD" + "D47B36E7334D0500A2B5B5E76F7A485CDB7F7D7D717979F9F7BDC554AA631DA62F8D803FBB" + "5F1CA6CCBFC1B4005E9FFFFCE7E5F2CB2F8FC20057F8E3E319B815DCADC6C456002FC25948" + "08600060E6E48126E881071E8802006213907EA579E39EB6E7DDAF1D2B9BCB6FFDFC56EBE7" + "ED1823084AB096200620A47B80E5C45EDF03317B9FDE6F7184053180E1CD0DAF1D6F5B8B2C" + "507A6661DB17EF58AF0F9D09641307F4BC71F3EE7712B5FAD3FAEEB53DE99E850404498581" + "20F0E17D823548EB40D8BE778EDFDF6AF55628B69A3A1FEB59043CC0F70AFD8811E6EC3E6B" + "E5538A168FC5C5781C16BD42E53FAC56BAB5B92533F3B3296F5F44A3FCBA94E6975701AA9E" + "4D2A07942E91630242FD1EE3EFAEB53558FEEEFFEF7FFDD77F76FCC409D97C643C50FA7CA7" + "A900407466FDF96B01C0845E5A5C7AEFC2DCC255286AE13104718AD758A0F7B459FB11197F" + "99420AAE820F1BB9FE580F1F9AAC2DB8220D462C0E23E4C8E1406E8A16F08B615C62181233" + "3014F881D91F653F01FE56F39706036F8D13033B6BFC56D3F7347FB403D0475544F87E3186" + "30632ECCCE46A1404BC3723FBC716BF5D51B777B1E0B565E9BDE35EC336B1DBB53D06CDD97" + "77BEF79C3DDAAEDFF69ADE31E28C9DEDCB4EB6F333B1B1375EDBA354355397C5B6D1FE76EE" + "DB71F0040E2B30705B5E1BDEBBE48DD74E0409719EA524EBC26C42245C8800002000494441" + "54565AD2C23F6786E0BF3E99ED07BF3E15F45384971210D8A74A01229C092014FC57DC005B" + "5BBDACEDDA257FF6E77FFEDF1617E6E5BA97BEB672117DE6539F1ABBC7F391A60200D18609" + "A87B3E5017F3FDD765616EE17B961697BE0312B4528BA18AD400D6027D3EAE051AFA17DA0A" + "346804FC21880DFE4B5DD884C9321EABBD4DDA6FFB22A652A01886C3A0CFFD00F823360116" + "0A98FD3DCDBF759FC168FD3D55ECB3E97C1EE0F371BA4FB334501F017DD1DFD0FAF0D1C586" + "C400CB24506AF5DF0325EFF9D86731E97CDEA67F5BDA9ED776EB3AB6EF62043A260F945AD7" + "B6E77940EFED6B8D15F7D74BC9B3020BBB2CEC7BC8EDAADBC796C89E345EF63EECB56DE4FF" + "76C7E9362FF2DFFB8803FA3C96DE7C41F09FE82240B3B3D1D58AEAA573F34B5118181A102D" + "E49773FD3AADFC17422AFF3BF674452ADED5E763101B70FCD871F9DBBFFBDBDF4776D79347" + "8F8ECD8D294D05808A96179F9BF5D85BD4A5BCE7C5F985D7AC2CAFFC474EDF13471BB0C065" + "4DFA5620B0DB6D5BFA3D2EDAB1B1114BFC02F851AF5DD7F31787413103EBFBF1E54F5BD499" + "B5008231617AE98062C008B10948F583BF4F179469019565C8C144F7F36728BA54C6D59AFA" + "BDE03F3D0E2E13803FE225E092D0C570A0FDE93D681BF6195826EEDD8327F8B5CE9904EEDE" + "F1F6B972BB93DAB6DBBCEB58D068514B88B0FDF4DA17236C7A7DF3DAF3C6D013CE269DEF7D" + "E7FE6804BC9643B6A6784FD8B080EE05E58931E18B016D0FD8ED3EEF7CDB8EC8B86066DF57" + "E4FA038CFB34B731C7D7CF9C19567F4C4B014BAAEDDFE5EC7F8DF4E7311CEAF91717C1E01E" + "E0458092F21FE30160F57BE8E187EEFBEBFFFED91BD6D737E4F1C347644AE3341500886C3E" + "FD734D7809CE6CACEFDEBDB6EB2333287E42D5EAEC8BA9E469D0A111F52F06F4C4303B057F" + "30AA9B6FBE39FAAB01AAAAF95BE6DE12025AFBF85E98F1687FAC2B8099220B13FA17C08FCC" + "04802D565E53F017675CECB630C1CFEFA5F4B16540E331F838AD6FAFDAFD4B5FFAD2F81B7D" + "C25F30FDBC1C2A0900BA2C6E6BACECB3F53451A1780E4BAD31F0E6917D46DE76BBDF3EFBED" + "8EB3A0EA090CDE352D59E1D2B639C9CAD1BAEF563FED3D297194BF383137DC9EBE8FBA2C2E" + "E689370E7C3F765C5A3CA005DADE6FBBAFE5FBD7F3BC7A0376CCCB7877B1C00F68332D017C" + "FAA9A786F897B979E9B7FA218A5F8C7A4FBE80400B0369945FBAFB242C14F3BF8E2D8A0CA1" + "34F4FBDFFFFE8FDD78E32765EF9E3D636334A581A60200D1F3AD44240061D7EADAAF2E2E2C" + "5EC8E0AF64B5310FEC5BE0EF7DC4808C8EC7E73EF7B9F81DEBE3EB92AC2DA6DA02021BA8E4" + "0908A1110CD862747C6DA4FA212B016B10689EBFB56A28D9E047067F05622EE06303FE6CEA" + "9F8D07500100EDC21D81C03FB8231007A0657F319EBAFE005FDFA6847900CDBF6D811C2B14" + "7863E5EDB300CACFA775CE766D7BDFEDBD788245EBDC9DCCB767224478F724E639D87DAD7E" + "07C7B2E66508A890ED8D079BF3EDFD789630693CAF9D80BF07FAAD77CF1B73A6E137FA3F80" + "3BD2F100D498EB6710FC38373B44ED4B42F818E13F2A35FD73F0DFB86C402327BA3A60BCD7" + "F4416A20C60619379FBCE186DFC1F7BDFBF68D8DFB975211A6F39DA60200D14E19C397AB2F" + "A3D1E81F2C2C2CFCAFF10522C66281341853BED56AAD6060CFF3DAD2E224007F9C03F33534" + "7F254F63F49832EF6B0901426BBCDBED4A3606403F609E78D9353011A67FEB53B7F7C7BFBD" + "E8FD2D5A7296F7AB56EF6500042A5DCB420202BCB06EBD6A79D88EFB04F8779436A6EDEBC2" + "402D30F580C5DEA78D27B0E7B7889F93472D20E6EB32007973C1EB430BB43C00F6E69647DE" + "F8EDA42F62EED36BC3EB8B37B7783C5BCF52E7C3500CC7B7ECF1F7968FDF1E6BB57A3E7F92" + "B6EF090AB65DAF5FBC6DB83F55E1FB41E39F999153A74F0D057C20BCF4AAE57743DEFF6800" + "FD4EDB55ADBECB8ABF8C1C6131045AAE3CBD17FBF6EC01DFFACC85175E78FDBFF9A11F1AEB" + "23E8FFFEE99F76B79F6F3415008838C0EEB92600C2EACADA0FE065E06A7F1ED3B1E0EFFDF6" + "0200AD3020C458005877DC7147F4F5238D0E15CB2CD36981BEC740BD73EC3D71BF3C4D8853" + "093B5A450EB10948478496AD6BC7DBBED8DFBDC9DBB7F9FCB6842F0702B2E66F63035448C0" + "B1C78F1F8F51FFB000E03BB41E165C58309354279E5724F4FAED6D6BFDB5CF96C90358EF3A" + "DEB9DB1D3369FC3DD0F0AED13B8571BCE35A6D6D77AC77EFB69F0CE276DEDA31B3DFED7B68" + "AFCDCFBE4BB13E5A12DB5E5F0CA0EB362B0C88234C71EAADF7DDB6DDFAED8D4F6BFC86FE8C" + "22F86F25104780350AFF80AF44D37F3CB08BB9FC9D46F8C5DC7EC4058CB20010AB00EABE78" + "BB4391A0C0732CFE0E5118409D81E59515B9E5965BFEF3A1C38FCBFCC2E2D8F84FA9D05400" + "209A9F5B18DBF6E5A681196C2272F67B16E6E7DFAAE0EF49FC93405FB55806278F395AE625" + "A9D0CFDD77DF9DD7F457BFB5C7705ADF3D4DCD63A0FCDB5A08F4989EEA02F0777C106077FC" + "C47179F5AB5E9D81D992BDC7DEE4EE7BA0DF8AE8E7B1F43202F05B811C7D43D53F644DC0FC" + "0FC6D7A7857FB496030B6118676C43AC853EF7AEB14CB2BD1F3BD6DEF79D1EB3DDB3E363D8" + "E5D0D28EED395628B47DB060E7CD7D8FBCEB9EEDB9DE3CF5FA38695C3D8160D2BE2E95D58E" + "96ABAD3EAE68285470C70AC4C1A408B62C672C0C70F11EFD3E49DB6F69FF9EB06FFBA66B39" + "F4492B9FE946D1F72FF1BD9D89401D17FDED0607FF50D67770170C96FC52F44763FFD205A4" + "2CFF5BE20142BA0E84820B0F1C907BEEB9E7B15FFEE5FFF0DB4F9D3A2577DE71C7D8339A52" + "A1A90040F47C08024C2FD435BBD6D67ED1320C7B9C0581D030F95BF09FF45DABFC3DFEF8E3" + "D174ADC0B85DD0DF249AA4FD8B6184D68CECFDD5F600BA08FA5B5D598DA0A9568AEDEED133" + "EDE303068C8F0DE4D33164C1432B177A667F6D0B5A3FB47F4940A9001FD7434FE773DFB04F" + "0B06796334E9AF1D47160C3C10B39A6267B22C3C46EFF5C7F6A905C05EDF2D7942A16DA7D5" + "86BD1F4BDEF649F7B4D3DF766E59AB8E770C9FAF7380D301392E842D5E3A069CB2E7094E16" + "C0D5B2C09F49697F628430CF3AE03DA3FC1BF705E136FAE387C5C1B6363762312054FFD363" + "2A2F40CCFB0BA5BE7FF42274830410BA9C06185400888241A816FDC15822F7FFFFF9CFBFF6" + "F38F3CFAC869D400F19EFB940A4D0500228D587DAEA84BD1B20BF3F3BF3EEA468B5CF0C7BE" + "685E301F03BE35FF8BA381D8ED00262C50832A7F007F902EB92A0D06DFDAEE317E8F415BA6" + "6E850071985197CCFF1052A0596B7C025B3DB81F42000B70D65C7C05728DC446A12336C3AA" + "96AE1A3DAE8135E9117370F4E8D168C687895FDB552100ED23F80F400E268463F5BA81D63C" + "B0D60A5C17D740AAA52720586B8007325EBB361EC2D655E07D3DA5615ACB837DAEF65C3140" + "EDCD019900F4670BC0F61E2D5079C282ED871DBFAE1103D16A437FF3787AEFA698F7CFBE9F" + "8815C15F04B062EC55588450DB35A2EFAD6583BF6F57D37F12E8B780DF3E7B6F4CB07D0396" + "8C785F212DF9BB1E63002499EE8735FD93295F8AF61FB5FB84F45A034073FBC2500F980202" + "4332FBA70F7CFFFBF6C26D79DFEFFCCE6FFFEC81FD17E472C2536AD35400209A19CD8C6DFB" + "721218C0FCFCFC772F2E2E7E93178FC08CD33213D55443C3DFEF9DC7DB0156D058114C8768" + "7F80945DD3DF4AFE2DE1C40380ED8401CBACAD5061BFA37F871F3F3C984C676662AC827501" + "A8D60D26DAA7055800F260B2006E543284E500E00FE187ABF1B5081A3A0400AC8288D80304" + "49E25CB485EB80D961E54114227AD39BDE146B2668F4BF6A745C779DEFBF4B2BC54108401E" + "33076D8A31B75B21C70A76DE336021829F8327D469FB2D606D09732DAB43EBB98B03A8965A" + "428837BF6C9B1EE0DBEDDEFD78828CD766EB7D122318B010668302B56F985B98574861D512" + "D1982F98DB6CB2B7F73209F83D57410BE83D6160D2185B42FB58500D4ACC60EEEF62CE3F80" + "7A6E61AE5AF04A15FC4E6B00E498C14120406C40D80A294D70700B84E14B760FC42A82A9F8" + "0FB2A4E06AFBDDDFF9DD773FF2E8A301A9C07DE0D28253F2682A00109D593F33B6EDCB497D" + "E8D7D6E6D77ED23278A596F6B013D0B78C48F7E3A5D4C57C6EBDF5D698E70FF0813060357F" + "4FD3F7C863E816007A93CF6F81DF6B83098C0495BE2028017815E4B5EE3E3EB0120080B154" + "315601445540DCDB9CB3EC7320D780EDB312C603608F0FF28CBFFAABBF3A0A011FFAD08762" + "85445C0B9A7F9FB226B0E63FC6355A103636B3FFBF4B415F9ED00302C38715C0031AFB0C5B" + "D61DFBDD6EF39E8D1841CD6AC8ADEA7F2D61C31B43EF5ABCCF1336EC9C6881B13DCF9267F5" + "F0FA60DB6E090ED6B2A273C80A69DEFB68AFB59562478EA68A7598B3986F98AF6A19D24C01" + "3B7E2DA09EA4E95B61C09EE73D233BAEF53319C5D8A533A9E476D4E661C9583F23A399D95C" + "D16F10823B5AB44744ECF3EC745F8C0A1CB6F7A5EE7F30630901E3A28B2F925B6FFDDCC3EF" + "79CF7BFEC3DCCC8C3C89F5369C3930A59AA60200D14E34C07345783116E717FFEDDCDCDC01" + "5EE8C7D3A43CF0575206D43B297F4A0CBACA540064D0622100C01CC9EBB17BABEF8546AD72" + "268FB9DAEF1EC3E57B08A4B52A69DF21A480699E3C71524E3D752AFE06C04283D2DC7B6805" + "F6B972F09DD7B71671D11F1DBB97BFFCE5B1C8CFCFFEECCFCA273EF10979C94B5E12850368" + "FCE8CBE958F6744BD637D6730120F4870500269C03E1050C1FCFC3AEB2D87AA61E30890151" + "8F5A20D8B2EAD8EB6D07C66200CA3B4F1AA0EC09C1ADEBB4DAF5C81BAB49820A93376FECFB" + "68DBB5FB8223C40B9D07D7960AAC21B9E6D47DE459D26402A84B23DD4F1C01819F8527E8B7" + "C628C4803E64B16CE4FBC61CC73C8EEF0978C9D6501510203EACFE17E24983853FB51B2483" + "7F5037401F8AB0900EB03C034AC0DE3DFBE43DBFF49E9F3B78E8D0FA65975D1A6B0F4C697B" + "9A0A004433CF6121A09910AE5C5C587C274CDADECB3D09FC6DA01F6BB22D8DA34F55C80062" + "B7DC724BFC8DF5F2756112CB6CECF79D5804826339B04CD00316AB1D7B3E559848618A4710" + "E04632FFBDF295AF8C3E77B83038325A8BF2B498BA473B395683FDE056F8C11FFCC1C8F034" + "6052CDB6100034462050FA55CBBFAE0461060280F70CBD71F4F6C90461C0D36AADC6EF6998" + "7AECA460C149CF5CC634C7F14044DB96F79DEFC97EB77D6D516B7FEBBEB85D7E8F785E4A7A" + "FFEC7BEA7DB85D3E0E73077306C220E60DAF14A8E42DEE1328BEC4D3FEBD8F3845855AEFB6" + "37A66AD5DA48C2B1A435FA75CE2FCC2F44EB466C17FEFE8CF55DFA9E409D57F59352E74F02" + "0B00C30183501032F85F7CD1C572F7DD5FB8EFB7FECB6FBD67657965C81C086DBE34A54253" + "0180A9C110CE350D2FD1DC4F75D2CD6EF69B1573B0E0CF8C82817E4CA368580CB85D68A308" + "F80348415BE6423F4A1EF0DB7DDE5F0FA8EC5FDBA749F7C6017E6090F80DCDE8F5AF7FBDBC" + "E635AF89E98ACC2801CC2D062F8699B68EF380D1DBAFF9FEDFF33DDF23EF7EF7BB63A020E2" + "0CC08CBF944C91EAFFE72A87DE424AF880A142108380837675312325CFFFEF69FAFC9BC1D5" + "6EB76D330888F880CAE6740BC89E20224600F18EB5D7690912ADE7B6DDB1AD7DE23C6B3B86" + "F6DEEDBD78EF9A07FA3A8FF9FC9E6A4D0034A1F9EF4BD5EB34A54EB343F0CE862448F66659" + "6C31DABBADE7EF590876F2D713B8B4EF48598CFD4B41B8F11A41CA12E1F1F83EAFEA5B0955" + "7D4FD5FF535080CED1EA3A321C15C2D8CA7FA36EB054BEE797DFF3C367D6D7375F7CCD8BC7" + "AC341E3DF2E823DB1E733ED054002072F8C297E19A789967BE717E6EEE7FE38A7F422FDB24" + "9F3FFF9EC47894F4E580068DE57211B0067FB5A6BF79DA9E34342DEF5E3C8D9E8941DDF6D5" + "D62EE0C046F58FE23B2C15C85240BF711F7A9D9699D4F6C5038049E4997DED77F40D960704" + "FEFDD55FFD5574A580092A786B9061A034AF60AC291650508701E7754E64F9244D947F8BC3" + "C45BDF3DCDCF0A03F6F9B325A3352FECD8B59E4F6B9BBD4EAB5FE2F8F9BD36BD6B5820B7DF" + "F537C7AE78EF259F6B9F199FD375454BD6B90E411CCF1B6E255895307720D4E21DC53ECC1F" + "6CD7F784054A760170C19F96C9DF8EA167ED11E79DE6BFF13AD2C9F193275201ABE1BA6AFA" + "1F8497AD741D5DC047DFA5E1DA3D69F61234BCAFACF217AF173458B08FEE03BD3E620E2EBD" + "F432042EFFDDFBDFF7BEF7215EE258E21153DA194D0500A2FE39F01B6142CFCC8CFEBE3498" + "B6F7B1FE4466FA9E39D87E079381967ADF7DF745CD5FF3DDCF86910B3107AB15D863789F65" + "86AD8F823F98201820AE81F5F4E16F4780945ED3D3F4ED3DB7C8DBE76DB3DBBDEBE9B8C312" + "81788A189B90564CC44705952E05008AC390797C347E40D3027961A39655481C933F038E77" + "BE0794FAD70BFAF3DC005E9B9D59D9D16BDFB66941CB031D3BEEDE5CB5F7EA09582D21C4B3" + "6CD877CAC6DED8A04C7E2EF6789EFB5C780ACF177304E00FFF3F9E3DB47D3C7F5882200870" + "864BCB9CCFE3D1F2FD0713D7E399FAED387B34EA4672F2D449D9583F9384912EA7D82E2D2E" + "0DF7DFA55C7E190DB8DE0F68DE510EBF86EA0F29814911EB14FC07A1A0EFF5772AFA932C03" + "18B33FF8833FF8F9AD2DAC00B8BBD9D729F934150088161716C7B69D4BC28BB6BEB13E3F1A" + "8DFE1933714B2D21C0FAFE5BDAA01048E9BAFE48F78396AA40C3E641A59679D07E6F99B365" + "42DFB9C08E5D6D4F57CD03F0A35D68FAAF7AD5AB22F00B05E37982C7A431F4FAD63A66D2F9" + "ADF6C0AC61BED5E03D30426843ECCB67506DADE9C06D42508300200664BDE0350BF4169876" + "E226689DEF8144702C11BAAFB5E68305606F4CBDF65AC7D97169593BBCE7C7E75BCB015BC3" + "B86D6FC9662B885981CC0A0576AE2388154006E1515D3E782F2144E2F983345347FB65E791" + "35FB076315E8262C1D6C85A196B0C8F70C81E4A924A0CEC4762516FF51C1A5CB91FC5262F7" + "13D6E7E57CA5AFF2FAF3FF41C74BE5873E5B0AF43BB26ABEEA9AAF928F7FFCE31F7BEF7BDF" + "FB5FB00F4B6D4FE9EC682A0010655F542235519D3A75B2D2909F2D0213585A5CFAE78B0B8B" + "97AA59B8A511B6988DBEA09E499D5FDA9E52D090BB0E5322FC8C9CEE270422FCDDD3A8C430" + "8A16B078020B6B4076211E3039F8CDC144A0ED4323C2023F9282EEF8397820583D4FC705E2" + "01418B5AFB5AEDA1EF60E01002901608E0071357EDDF33D3B7C61284310000E8B3D295183D" + "A1CF0A7FAD7D1E987A82944C00040605061C21AB800643764E611DCFCC6CFBD2D2D0BD67E1" + "69ED56D0F0EEC5FBEB8D19833FBB00ECBBE8CD79BB9F1791D2BA11AB6BAB71212B35FBE37D" + "04B0C6F2D17D9FB57F0D325537802DE7CB60CFEFB467F6F7E69D5D85B3356E336915CB13C7" + "8EE7B1E84633311347EB5C20386F14A3FE7571BF94D31FA4ACE11F86D5FC74653F7EA69677" + "95ED831B606979298EC74FFFF4CFC4D57E5656D7F2C24053DA394D0500A25B6EBDA5FAAD13" + "0F2F67F42F1D3B469AC1D8E9674DA8F4B7B4B0F82F3A19674AE298135B4CA63705633C891D" + "2F2600E5B6DB6E8BBF01AA0834F352FCC4C940B05A83EDAB774DCB04BDDAF92A0468753D68" + "3AA89F8F1C7B582884805F0C43F0FAD21248BCFE7A141A6671EFB70716BB77EF8E0200042B" + "8036183C183DF79D99B5D71E7FC733436D0108161A156E0520FBEC278D830790DEFED618D8" + "F520AC50C1BF2D48054743B7E4B56BFBEA0915ADF1F3EEDB1302BC31E3B1E3CC949E0A4E79" + "73BD05FEFA5B8B5301D8F11E6AC0A83E5BBC075A0F00CF9CFB8C687B5D488ADF49CF0A2006" + "FCBDDF769CB67BC7E3F3EF43144CA3CB14A67FC4336CAC4740C77C8D3C72D0EF8BEF5F08E3" + "3BA9360615AA2409052190E0D00F65814389FE47A5C1EB5E769DFCE66FFCE6CFDD7EFB6D7F" + "F3D2EBAE1B9B0BDBD15D77DE79D6E77C25D2540020BAF9965BCB0F94B43C73465621953F75" + "260A002FBDEEA5715A6E6E229F7B4E9692663714F1381317BA8860853505B6B11624C9FE1F" + "2E2C2CBC1E39E20CD4629891652C2DE66299167F07B0422B05E823475E4B8C0A314571B405" + "8F3CA66125F5DE44EE5B4D5F3F007E7C90C6F78DDFF88DD1DC8F3C7A49FE7D66B44C5650B2" + "C7B4BE7B6DF1760F5C3CE0F280553F30D9ABC6A6EB0C88010E71842CAFDF60F68825807913" + "63C3CB0FDBE36B4DC917043C30F6C623189FBCBD86ED3F6F9BA4BD7BAE04319ABA4D31B442" + "89BD173B86F63EBCEDD63D2254FE98B57CA59D003EEFB716395E2302EF220246C1537445CB" + "3E45FAE3FDC4DFCE54F1534BC07A5AED727E7E61A88E67AC743630B0F53E7BEF3E0B156C55" + "D1FBD0B6619940E53D98FEBB988AB725A7CF9C4109F3D4DFA4D927333EBE0E4BF9EABD0C80" + "9E7A52C62DD6F84FB101F0FBC7FB4B79025AF16F7D5D5E7CF5D5F2898FDFF0E97FF7A33FFA" + "4E08C6EB6605D029ED9CA60200D1DC1C0D075E0498E89797E5E0C1C7E4861B6E90A3C78ECA" + "8B2E7E519CB3989C471E7F3C4EDC580A761535E137A2D9175AE01954A74BA5743D0223BF60" + "FF05FF4C4B7E7A9A0933161B40D462349E0080FEDD7FFFFDB1CCE8B5D75E3BB6BA9F47DB31" + "0B254F10E94D143F2F98A342806A3900FE37BFF9CD31871FDA8324537A6B553F314CD8DB67" + "B7B5DAF1B6B5CEDFE9F5C0A0C0D8712FB83FFCD5D5FDC0EC1928A0F5B58A2931E3C7F98887" + "C05FB5DC4C027E6F7CBC3E7BC72879028F38F382B575BBC640677CE6DEB99E30C94192F6DD" + "B04249EF5492F40406CFCDE13D5FEBA6E16DFC9BBFB7C05F92E0A06B49602E40B8058FC05C" + "50733908A00AAB51303E7DBD0FB5E0E118AC328940416D435D7BAA218BB1020899F75B0298" + "1D53EFB9CFCECCC8D163C7A28032D24261A93CF6ECCC6C84F221E77F249AF0DF49F1F90F4D" + "052DEF9FB0BFACF0971F479004FE21833FB60D817EBB6005E9DFF5AE1FFB2EF0D895E5E5F8" + "6E4CE9E9D1540020AA5E0E30A854877A6575354EBC13C74FC8A9D593B2B2B62A5FB8EB0BF2" + "C10FFE61F4470154BFEEEBDE28070F3D26575C79659CC8175D78A15CBABA26A7CF9C76AFB3" + "B1B1715927DDB7698118A69656E181BBC7C4849893320D80BF17F12F8E16268621D8BED9BF" + "C1983C2DF8EB423931E0E9E4C998DE06F3F8DBDEF6B6E8E3579326AFE6E75DD36AFA2D46EE" + "D176C76CB76D27DFFBB4A80B841A3068A459AA4914F7AF1500D9626235660B667A0C84B847" + "1E7924FA7FF13D32E1B4609007D6ADDFDE3E4BB63DAB197AA06181D782B5E72E68017567E2" + "0CECF12C68D8763CC1488CA0E08DB31EE3CD314FC06E090CEC92D32A901AC3A16E9C90B23C" + "42AA660973BAA6FCD97E6B1F5450D0678F45BB741D0B9C870FAED3A5F4424FF3F7AC2D4C93" + "E6D1FCDCBC1C3F31B8E96650DA37FAFD51E80AC2C04866E76623F8A7334B09FEAEACD7AF16" + "806EE844AE02A8457DCA75FB6825E88422FE8752E93110F8C77FE227BEFFF6DB6FFFFC9EDD" + "7BE25A20537AFA3415007640CA30E6E6E765767E2E3220BC702B2B43BD76BC947801F042C2" + "D4F5D9CFFEB5ECDBB34F5EFDD5AF1E5E8CCDA114AC120264D656777DEBAED5B59956EEBAC7" + "C83CF0F73E7A8C9AECBEF8C52F46E603D323828BAC76200E7360B20C93FBD5027E36F54300" + "00F043528720F5F55FFFF5B1780F3421357D7A40B49D49DB8ED34E7E7BC7D8ED93DA6C3148" + "FD0DA68F3A05881BC1DA0ACAB0256983CC9CD9F46C01CF3E7F000640E09E7BEE8942D36C0A" + "C412B20E79A0D972177863E09DEF6D6F0987BCCF03586F0E59C0F7FAC6C2835A08B84F9E15" + "857F5B812B90702DE6B9DBF16A013E8FAD9DFF5AFB0196407C00CC6AFDD17B81A008308D51" + "F46935CA49E3A4E30A1E838F5E43050D7C30CF2078E23DE758111E07DB9E15883C61117D43" + "BADF97BEF444047B0074AC72194DEF41E617E687F14C5A7DE045FCF3B6726F31A21FE58047" + "92FDFEF14FDF5775FEFB04FCD80EFE89A0E01B3EF189FFFA9F7EF557DF2D29EB664ACF8CA6" + "02C033A058D8254AC28589E145C70BFFF891C763953D48BABB76EF92DDBB77E50561F072AE" + "2CADBCA315FCE731246B76F49893185002F820DD0FCC02A6476536963932B598BB773D667C" + "36B84F7D9E60749ACAF68637BC2106F7E17B0BF8BD7BB2F7E59DE37D6F6D9BF47BD23EEF58" + "4F20C1BDA16601A737EAF2C30A5E5D8AD466FFEAA47EE33C084C3015430840CA180B13DEF3" + "F7E6866D7B12F07AFDB0DA79CB0FEF99FBAD7B409CF9E6B906AC70DC35FCF3B60FDE3D3180" + "8BF3DCECB8B58402DDC6CF57CDFCD0F611D8A7667A80B316FCC1BB00E0D7C23EEAF6F29E9F" + "EDB72A1EBA2895BD978DF58DB88E00AE0D214005443B869E05C0B33A480A42558B03171A5A" + "3FB31E1599E8F7D7733B494BFBD2FD8816FAED72509FD602809C305C77281454B47D5DEA37" + "F62C9AFEF7EFDD2F8F3D7AF0E80FFFC8BFFD3FB01563FC4CFCFEEB4F1685EC7CA6A900F02C" + "93C6AF424A5F5D5D93A34F3C298F1F3A2CBB56D7E4C5570D652A373637E6432F6FD2D50783" + "63926C81BCC7DCBD0FC0022668BCB8A88FAF00240D6DBFC51878BF657EBD49E963E0079383" + "D60F0DE88D6F7C63CCE3D77CF6ED80DFFBEDF56B27FB76BAFDD96C134C186E21045B22F052" + "C1C11673115A808A058356BB3806E3092140DD281863EFB96D07FC4A0CCA769FF5B78BF35C" + "3C61D15B27C0BB8E07F662E6A777AC6EB3D7E99CC247AD6765EFC59B779E6F5FEF05CF733D" + "AD7CA715FA340304F35C97D3C6F301D8431086B95FD3FC14F85B7DB3BFF51CF5FBB32523A4" + "F52EA0894B8A27C08258EC72B263DBBA0E8F45CCF57FEAA99885928F484BEF2283295B2D42" + "01FF08F5EC16925E4632CAD5FEF3B542C882832E10A4F101BDC632A4943F9C73D5D557C94F" + "FEC44FFEEC030FDC7F701129808E6B754A674F5301E09CD2601E3B7EF2B83CF8D08343204B" + "D892DDBB76BF697565F5E2ADADDA7F38C90C270E736A6D87768097166BD25F1963124A5EB6" + "BD9638DA81050E667C0CFE56E3D7C2256078887286A91FC2079823CE0133B13E64FDEBB939" + "C4614ECFC6F7496DB5B6D9E33D80D1EFB87F30E06FFEE66F8E0B2D615CF0014365AD2CA428" + "7F306A0F6CBDEBE319026810D489EF001BAF3E803D8F81D2BB5F3BFE6C766FB5E9CD21312E" + "093173B8B5F08C34040EEFBB1ED35A51D17B265D632D84D6FBE6813F97EE05F801ECF181B6" + "8D774E833D616903E0E383EFFA7E6820DF76560A1E07003E3E98271A34C8E3A02E07F409FD" + "C0BB0601001F048C7A9ABD9DBFDE7CC6F520C41FF9D29198CFAFCAC3F09E9F195BA0A8D3A8" + "7FADF6A7CF2B45F46B905F1035F927DB401F52911FFF39C0AAF1CA57BD52DEFBDEF7FEFABB" + "DFF3EE9FC4BECDF58DB1673EA5A7475301E01C1326312264F1E223201099046BAB6BDFE795" + "59B51A8907F85EC43DBF346AB283E91F3AFD389F00002000494441540C00CC434DFF7C1D71" + "18AAF7028AC94068E5F2C3BF8FEBC2F70D6D1F1AB0066BE1FA1E2058E66BAF2B0D66DEFA7E" + "B6E73D936D1E63E5B184000697C7F5D75F1FAD3070C1E0D9D89509430A08D348EEED08638E" + "63C1EC21E0619CF15D53C7EC1CB1636ABF7B00CFF7C39ABB9D3B9E30C73E7A0ED6D3F96E23" + "FC2DD976BDFD1D650AB4DE23FEBEDDFCB6F39CF7A9A6AFA00FE10B42173E1ACDDFA7B5FC61" + "F951D0C7F3401F35D34301B335079934B21FE7EA5FD5FA394D580B67614E41F8D08A9338E6" + "CCE933D945D47A3FBCDFF8603EA13AE1E1C38FC7C57ECABC1AD6DB989F9B1D73E9E428FE10" + "4A6A9FE6F227DFBE6E2C417F34D626F21F32C4C69975B9E6DA6BE4E69B6FF9EC8FBDEB5DFF" + "3B4E5765E299120BE2E7334D05802F03218065617341565756DFB9B8B0F8EF1151CBCCC6F3" + "A90632B1EB761B0F20E645568DE1E69B6F8E417F6056C8B1E76222B203E0E7EB8786A95FFD" + "9ED0F8C1F060EEFE9AAFF99AB8480F18108E5150B27DF5B42ED98641F1766F7FEBBBB7AFC5" + "14BD73261DEB5D53FF6A59E077BCE31DF2C77FFCC7793537AD09A0A4C17C81D2025BC15B7A" + "CF5A0D0ECF00019E2A0468C1212FB8CBBB77AFDF7C1C5FD36AE7B64DFB5DC837DF99C03D4F" + "EBF7EED58E817D47C4B15C4CBA27FE6B330484E6BCD5F4F11EE17D02C062DC356B45635C60" + "E6C747415F853A16BA99AC2019520C08CE5133BF24B3BF057EED27040EB5FCA06F5A6D12FD" + "881681D0676191DF3776C978638DE57B714F070F1DC41A25A54E5FE8A3DF1F190048F38B26" + "FB2EADD99F815F9F55D0C8BF580C8856FACDC715E00FA5CC2F09041B1B9B72E0C203709B9C" + "F8E11FFE37FF0B72FDF7EEDBFFACADD7A26377BED35400388784E87F30897D7BF7BD69FFBE" + "FDBF351A8DAE54A6E045672BC84FD2802C40F339604E083C040300206B99DF9629D7B667DB" + "B6C0AF7FD13E801FDF919683A87444E8824969FD7BEBB36D01BE07AADEB6497FC5010C0F04" + "265D63A7C77AA0EA9D8F718715E06BBFF66B2348231E035AA38DCE56E0D731D7CC0D6F6D06" + "250554B40701EFEEBBEF966BAEB9260293B76850674AD77AF7E18D9B776FDB01BFB518D871" + "B3E3E409A362DE096BBEF79E51EBB7771F763E2A986B9C0CC014D92AACE9C7F2B6C91A80F9" + "AD457B00B82AD429887B64AFD9A520504DE3C3BBAB817B6A2552E0E7F7484DFE380EFDD4D5" + "2255308410A8CF5A8312D95562C7839FD7507AFA68AC7B12AF3BEAD2023E43F5C299187FD0" + "C956AF71059DC23655F9D3BCBF02EAF533E972511FB60004750B0489858556569665F79E3D" + "F2FDDFF77DFFE8F6DB6FBF3F66229C38EE8CEC949E09F9B3754A4F9B201D6F6E6C4473FFFC" + "C2FCFE8B572FFEAD8585857F80971DCC0326350DCEB18CD07B31C598E03D26D653B118E4FB" + "23025D25DCED80CA3206067FAEDEA78C47172781A67FDD75D7451FFF3305FEB3E9E376FB26" + "9D6FA9B5BD75CE76FDB7BF9599BFFDED6F970F7CE003D144ACEB1A8813F3C1E0AFCCDFAEDA" + "C6F304ED43FBC33341B021840000109E931532BC7B9934769380D9DBE71D27665EB360EB69" + "FE5E7FBCE3EC311EC0DBE7252696458355D54C0F2045F43CB47D003E7E2B01542168C1AAA3" + "9ABEAE42A966FAD698F0F5F558CDDB57F057ED5DB305F45816AAD8FDA699461A84C7D608AD" + "0FC1152827CDDB3E551844BA3282950F3F7E4466E766A2DF1F57479E7DE45B33A358F9AFD7" + "143FDC6718CAFD8A8A02989F2190A6DF65014032E0F7B9D25F34FB4B528492D000F047BAF5" + "E5975F21EF7AD7BBFEF59FFEE99FFEF9E2D2929B25F34C480338CF779A0A00CF32A130C6C2" + "E2027CBEDFB77BD7EE5F0033D600B0C8D053400D0B001EC3606665C15FCF65AD116D60795F" + "8DFED515FE3CA66AAF65819FC15F23FA75511B800C7CFCC87597F422B156DB02FE563FB6DB" + "D6EAB73DA665DAE6FDDE39BCCDD3886D5BDE6F6F9F023AC60ED1FADFFAADDF2A1FFBD8C7E2" + "6F808C1EC3265A0DF2F2404448006066082100DA2A82BE60FD417AA0AE24C70012C8122064" + "6DF2FA2E8E00EA8D95B55479DABF67CDB2CF6BD2B3F3C83BB6354F743E73012C8DB950D3BE" + "7ED44F8F31D5887D80BE7ED455C3D5F7EC35F5BA2CCC69A0A782BE9EAFD602F44D8B06D920" + "3FB614E23C5809D405A1EFA76619688681F2035B51D33EDF906286F0F7D1471E89DAFFECEC" + "5CF6E06FF52181FF8CCC8C6632F80F16FEC174DF49978BFD8858333F058386DAC4AF11FF71" + "75BF748ECE9F03072E908F7EF4233FF3810F7CE057245568DD6E4E4CE9E9D15400781648F3" + "F9512CE3D2C54B5F71CD8BAFF993A74E9DBA1C4C59A57D056DD5FEAD7FDF4AFB0CCACCCCB8" + "BEBEFE85C68215FE34DF1FCC4B850C6900B0BD86057F5D871CFD05D8BFE4252F89DA2B729B" + "D157301D5E92D73331DB6B8BC3A8BD6D3B057D260B66DBB5EBB531497BDDC9F1DEB16803F3" + "00C1916F79CB5B6249699856D756D7E23847ED2BF98B756E789600067EBE5705628019840B" + "0801B000A93BC0DEBFF75C3C616927F766DBF6B473252F98D0B352D876BC763DE1A805F892" + "72D9A131E33D51ED5E23F7B56D5D7D4FD3F6B47CAFB6A1DABBBD27EE933E437E9EFA5B9F21" + "B7C3A9B39222EF3D015A8505F419C702EC7591297E0F39321FBF792D0D2FE6418B543DF2F0" + "2382946468DE92CE85268EBA252A50F4F93E69353FD5EF43DA9153F948EB4F8581FADE281C" + "494010CD028062D3F7316BE98FFEF00F7FF5C77FE2277E78697951762FED927CE1293DEB34" + "15009E2975834FECCCFABA5C7DD5D5DFF9E22BAFFECDC78F1C89200C8D595FC0ACE9917FCE" + "032DA197A477D2EE8289C6C74B8C8562E06F86F6A775E235CAD5329416F0E7B5C9534013B4" + "4A68FA68137E7E051F0D7452B2C02F0E689CCDBE9D1EEBFDB6C74E0224AF8D4980EF81249F" + "E73D4B3E07E00C2100A9919FFEF4A7A3691563ACCF4117855186CB024066C2A674B0186D1B" + "5A2CEA03DC7EFBED31262346739F3C59090E9E25C402AB8D19F102F0B82FADE7E33D07CFCA" + "E29DC7FBBCE3791B979A46FB9A0E07B0C798E0BB9ACCF57C00A7023EE634833EBB5F729DFD" + "8686CF60CFF5FD3BAA70C8424148163C067E6D9F7F6B5B1A1488E3D157F015751568D0A75A" + "194ACC87E4545C2C1A3454CC0B790EA925E1C9A34FCAA38F3C1AE7E2DCFC5CE44D43006F2A" + "5D8D3EE8F34AEBF8F7FA3D9AF7932B20A5FE65ED3F037F890D50908F66FF9815407501C290" + "5D00EB62E8FBBFF8D0873FFCAF240624CEBB551EA7F4ECD15400781A3404616D6690C54B79" + "C98B5EF423575D71E54F3DF4F0439109C795BB92F94C415619B96A27DE8237E2B800585860" + "D0467B600A77DD7557D4FCD54FCFA9511EE0F36F9CA32644DC175C0800FE97BDEC65D954CD" + "BE49EEA3FDEB316A71C0B3759E779CFDBEDDBED67176BB0571EFF8D6F99EA0E0091B1C8487" + "BF1002103301A6FE577FF599384F9029C0CF93E78982494F8BED4C5AEA550B05A15DD41F40" + "8C06808F8B05B1D66AEFB537F5F2957A937522E6B979C2C2A4316C090242D70A8E1B44FBA8" + "9A7348163508DA184708C308DAD3F51200723A2E98C3D6ACAF1AB25E0B6DE11C4F28D5F167" + "CD5E3F76295E7BDC88D6D9D7BEDBB160E1413F2AA820B6474DFC42A98F5C998FDB3B79F294" + "ECDFBF2F6AD3871F7F3C46CEAB7549C704267F542BC50AA6B373254B288E498C0B98CDEBF8" + "C776FB50E2FC00FE9A1E2042DABDE48D452090BC4EBFEE1F0AFFF4390600B9FEC864B9E596" + "9B3FFD8BBFF84B6F7DF8914752C60CF8A3CF23A7F4ECD05400781AA40B7C6045ACA34F3E29" + "DFF22DDFF2DD2FBEFAEA9FC22A5DD0C4552351BF9FFAEC94C16AA4B6170720DBACFCC7DFC1" + "3490EF0F66870F1885462B7B1604DEA61A05FA0AD040243F1806160C52CB0518A6F5E17ACC" + "DDDBE6EDDFEEAF77CCA476F9770B50BC6D6234E749C7B5F67B02C44EAE8D803DB852DEF296" + "B7C6744D6407A8B6AA1600FEB04B809F452B4B00BFF13C3117D03E2C017121AB1327DC9C79" + "DBD79D0A5E3C065E9609CF6B36FF7B0284777D7D3FB89CB26AAE182FCC51D5ECD5B4CF5515" + "F18E411002D0034435168723ECAD599FB570B6C2B076CF42980A05D6C4CFC19B2C68B33B81" + "AD03ECE6516145330DF0D1225E56D0887DC738A6B6D48201CDFFCA2BAF18AC1700D9940E08" + "1E71EAE42979ECD06372F2F809995F588C6ABD16EAD57A0773310EA02B1A7C5DD63F6BED69" + "C68DA7F7E9EF500B019A12A8E7F7299D1182EAADB7DE7AEBBFFC97DFFDCD7199F2786F7372" + "665AF0E79CD35400781A04A672D14517CB4B5F7A1D9600BEEE9BBEE94DBFAAC53FC0789551" + "A95957CBC02A03D6CA5D2DCDAA3779C9CC2CD9F48FA03FF8E961A207B393E457B4E7F4A950" + "897EC004713E8008FE62003F02D52499F8A1A9324D0A16B37DF78EF1987BEB98D6F749A035" + "E9BB778ED5CC3DDAC9F6D677254F33C65F3C3300D7377CC337C4623E88E04786800A720C36" + "56CBB481802D2144DD0BB004C08DA36B41A840D1D2E0BD6DDEFDF2B69D3CA3CE2C0DACE771" + "3C0C7FD4FCAD15F654BBC707EF9646CE4B5A3E1AC2AAFAC5F15B3F6CB9D2B16CF58FC7DAFE" + "E58FF5F1F3FDD8F73434B23BF8FA786721A0B1A0A2560255226C9FB58D6851D8D89075F084" + "C5A528C06335CA214B41624032EA90C0C48F3976F0E0A108C60B691972057AC40074291530" + "041367126FA92B6E7805F1F87B88EACFEEA3B474AF1D132BF8C567DF6FC9CBAE7B99DC7EC7" + "ED9FFBDEEFFBDE6F387DFAF4A6F64B26CCED293D7B3415009E06A944FFBAD7BE56D676ADFD" + "086460BCBC60BA604C78D1C0A83478475300F1D2E26F5C3B7B5472B3C561A4AD8F9A8801F8" + "F0FDE39A1AA5CF3E5C057D0D125A4D4B1A435382991F4203FE82B1E21868A656ABDD29004C" + "FAEEDDDFA4F3B6DBD7DADE3ACEDB3F09B0CE76BB07987C9C2704E0F9E9AA8CF07B029CE1C6" + "81F00820530B93FA7F5963F480C463945A1D0EF3005622000C043DF51DB386DABA27BBCD73" + "73ECC44A60B560B57CA9EF5AA3E4352F5E0BEE606E6A7EBC8E9D822484284D7583D0AA606F" + "4DFA36708FDD2656C8E2AA7B425AB6ADC6C7B10F2AE0EB7DF3312C6C3060AB4BCD7E38FEA0" + "554F40EF6133D6E31F140BC439E0FD461AE3FCDC5C9C5B00F621E570480B45354A0D4A1E8D" + "E6A2891E3C08C17E5B6461884BFAEA33CCBE7E11915001BB2425BFD3687E5B5B210B0221D7" + "F34FAD0C91FF7D2F975E76A97CF2C64FDEF0CE77BEF3ED1B1B1BEB78EEEBCE2A86533A77D4" + "9E65531AA32EBD7C0AE4F3F3F3ABCB4BCBFFF04CCA2905C34235BCCF7CE633119CF162621B" + "6B0BEC6BE440BDD65F4F8A461B58116E14D7E33E9D73FE95B9A35DBCFC007C049E41238066" + "003F2918AC5E1BE745667196A06FB77BC06701DFDBEF7D9FD4A6B77F12EDE41899006E3BBD" + "9ED7D7EDEE5D0104A08FE781E591F13CC0A41F7DF4D1F817FB400072CD166073B11502AC55" + "43850DCC43CC17B8A710DB81390900652160270193DEFE602C4D1C54CAA0A855EEC0E4751D" + "7B05F9C585C5E887E692C8BAB68446E7AB46AF162C0E88E531E1A03D2B3479817BD6D4EFF9" + "F5F53E39F8564898E0E722A42070AA9EF65B3FD64AC7A0EF595FB45D7561C4BA05072E90B5" + "B55D83B5716E4E36305E4F9D8EF9FA1A84D77533713E812FA8FF3FDE8F0CE67744E147BEA4" + "56A104F429CAA4E4F18BD4057FB44F14D52F747EE159C399BDE6FFF743D0207CFE1FFDB38F" + "FEEA3B7FF09DFF0A73010AC9238F3C3276CF533AB734150076489AFEA24C686161E1D5CBCB" + "CBFFA9EBBABDCA08B04F97BD05D84208803540CD99AAC9A849CF16A3F0048160FCF7385735" + "456CC35F659C60AED0F2F6EFDF9F9724D5C54A543800C87050981800F0FA2306B4BC633CF0" + "6B1DBBDD3EEFF7A43E78C79D2DB5C6C06BCFF6C3EB93174CE78D27E686821B9E21522EF181" + "C63E986C0F46EB0C9E33A77B613EB5B4527B3D6CC7BC84D5E8539FFA54F4B94228C43CE0D5" + "E2ACE582D35483C91AB1C7AA36AF560B7C14E8D5B5A1EF80BAC3846AEDE3A3816E6CBE67B0" + "17025D3D9FF3E62DD85BADDE0A0136629F89EF5589DBF484031650585051B79CB6659F999D" + "5B3C1755809094CEB807858A5657650DD6C6F985B82ADF564AD9036AA3525F48CA0A7CFF28" + "3C86F350C657C81513AB1E4219A1ECA4BC9C6F57FA2242610021450B74695E8754D52F0907" + "A11F77070D6D872204F4412EBEE862B9EDB6DB3EFCF18F7F3C46FB2366C5CBA299D2B9A7A9" + "00B003EA52DE7B8C1C5E5BFFC6E5E5E55F5C5D5D7DBD466873A016981898DDEB5EF7BA68DE" + "0503072387C6A51AB732D056352A0BFA42EB81E32FCC791AED0C1F3E72F3F112E972A49A2F" + "AC1A9446F88B632EB6C0E781A7F7527BB41DE0B5DA150356938EF5F64DDAEE1D77B6018396" + "5A8282D76EEB58EF183CDB98C609663F3717330650D2596B32E0A38BCDD895E66C7B0CEA2C" + "6CA00D2C5004411173279A93C9846D81B6332971985B2A7C70ED7AFC55C057B3BE826C48CB" + "D5EA4A790C92EAEFD6887C36E173609DBDBF16987B7F799B7D26DEBBA6C7B4C03E24BF3DEE" + "817DF60AF4F63E3AA7AAE32452A101B4B8B020AB7BF7E680C7C518B8D745E08F963FADC84B" + "6BF12B64433B5F8C950617A29B704EE672A05F1C139B62978AF5749DD0827D21AFDF3F1CD3" + "27AD5F2D0256D34FF32DD4697E7057AC2C2FCBE5575C21BFF11BBFF12B37DE78E3BFBEEAEA" + "AB278CC294BE1C3415007640605A175C70E0AAABAFBAFAB7F7ECDEF38DD1779618809246E5" + "CFA475C0F1A2411040143618ADE6D78371E33C2DD4A28C82ABFBB1695135142D210A429BD0" + "F0D53FAAD1E21EE08B93C7ED698A9E16C8D402E8A70BF8AD6D3B696392C0723602CD249A74" + "8C375E1EF8EFF49A63CF87AAD1A9C68F804DB87334B603CF19F347AD3A0C425CC446AFAB26" + "690D0E842000D33A848C403E73052A05F0EC0307D012B05BEB039BC8B55EBEBE23FCD17E71" + "560C7F38E0CD82672B48CFAB9DC042108F2F5BD5EC3574A95E7B3C5B29D82AC19ABD6711D9" + "4EF863A14BC725A6352E2C0CF1444B8B58402C0238003DAEC3119586948BAFF727A5063F5A" + "1CA94089E5A667E76469697988114A0723C25EF757E7674D9EFAD8AB7B274B0405D8FB9CCD" + "2F215903D20DC52A825A1408FDDEBD6777AC3770FDF5D7FFD00D37DCF0EFB17DD7AEB5B131" + "99D29797A6024083A2A96C732B9A5F5FF18A577CFF252FBAE4E7510E1335FE599267AD412D" + "01CA94B47087325898E835B7DE328DC290F4C52E79C7425A8B3216D53E780538A5ED40DA32" + "467B3E9307B03BFDED819EB76DD2BEB3151ABCDF1EB5407CD2F9DEBD79E778FDD94E40F02C" + "33AA3D73DEBBA43980B90490B02670EF598FCC7A020AF01A0CE8C51678FD51E0E6D2B59A52" + "6A9787661F385FBBA30039AB95B3C66FC1DE02BD151CEC18B3666FEFDDFAF85928F00416D6" + "EE5B6D7AEF8FF7BC8371A5806692FB6E79792982F5D212623E86A245D0F44F23FF3FD4BC61" + "684B71BBCBDFB91B1DB92DC02B96979663E0DFD8FCE4F43CB347858A90858102ECC3BEDEB1" + "0690A0B5B5155D92A05FFE955FFE1F1E3F7CF8BFC1DF0FE1736B6B6AF67FAE692A0010F14B" + "8C89BB15B6E4DA175FFB63575C71E5FFB59EB47A665A96D9321361C6C0DA06477033E3AE4D" + "F4E30174E2803C33BEB3B94766929EC63C09D4BC6B6D679E9FB4EF999AF4C501BE9D9E7336" + "7D399BE36D7FBCFE79636ECFB1C7F1770528BB7F52901B5F4F0345D5A210C87DE3D5A1B051" + "FCBC8FCDDD4242EAC8D4CCB7F3DF0BBE63B3BB37E69E39DE9BA762D2FEF8DE78453DD6E63D" + "A0B76E012B506D47DAC6E6D66604CC214E62212EAE83EF310812311231D0733697E0CD659C" + "2BD3BE24001EC5BFA3BCF01ECFCF7ACC906AB7BCBC12AD0843E651C871FD3D1D9DB5F7BC00" + "B050619F41C317AF963F7DD76A7F7ACF18A703175E08F7C37D1FFAD087DFF6B77FFBB75F7C" + "E31BDF98E7DB949E7B9A0A0044C78F1ECB3FA0B97FDDD7BDE1E75EF5CA57FE80A6C8B1E630" + "A2EA5E1E380E4C2490D42E55FA13A71A596D46C90A082D20928629DA031E6EDBB740F8656D" + "F93A1E604903C83C6A1DD73AAF25A46C07B23BA149E704E3329964319874FDD6336BEDF7B4" + "6F7B3DEFFAFA9D3575EE9B2D0A65BF071373E2098A568BEF52EE386FE777C4CBA7F7821795" + "7A53B39EC7D402AF4D87D47EF2FD33B073054E76B979C2190B29ADE7EA51A00246B8AD99D9" + "2130726D6158B37F09E99D2908328E43378A200DD05F4F817963F363C8B6CF297994983746" + "1D9F9404C5D5D5155958588C6BECCF4008D016533D9FB8ECAFB6D76F9976D9C76F82FB7459" + "DF6401E8536D7FDC3BEE13F148D75F7FFDEF7EF6B39FFD27885DC0EF29F03FBF682A00101D" + "4B0570E05FBDF8E28B5F7ECD577DD50F207846C15F4919AA6A386C9E15D274943C66CBD1D7" + "424CC782BEC7245BC7F27EEFBBDD67A9332BBC79006BCDA616F83C50F7DAF318C14E808F01" + "D93B7612ED94F9B4FAE98185B7DDFBDEEAB33786ADBE6C771EF7C36AAFADBE7BF34DD2DA15" + "9E906A8557ABC9778E2BC17BAEF69DF0E6ECC8AC8ED8516642A0F534D82FCFBE796BA9B0F7" + "3DA98FADB1B7FB39E5B14BB13A007AFDA09EFD7CCA7CE85204BE2A077D8F80BC62DAEF82CE" + "71F5E99700BCFCFE51947EDD178C17DD5F484A475F169C8A4B0577B3556A1FDAAA04FE5084" + "8B9002F96C405FD0C34209FAD3EFB83FD41741D0DFEFBFEF7DFFE2FA8F7DECD75EF29297CA" + "AEDDBBC6FA3CA5E79EA60200D1D2F2B006382A675D75F5D5FF08A63AF8F13530C9027B4711" + "CA5CF94B88E15B2B81D5623C93A6DDEF69BFE2306FDB266FDF0ECCCF86019E0D60B58EE3EB" + "7A407F36C7EC945AC2C84EEE4BB639DF1BCF565B93F67BDB265D63D279EC5FF7C0CE82B507" + "DCDE5CF2BE7B7E70CF92E45DDFBAD4F8BDD19C79D6E45BAE083EDF136CBC39EE91F76CF37B" + "DC27C1AA93AA7091A638E2AF664248E20BEA72901C6B97FAD125ED5BFBDE4902FD2E5AF907" + "1920BDABA32EB90242AEBC17E750959B4FF3215D0B8177DAB7E3C7869461B5F27B169EF21C" + "A8C08F08017FC8297FBCC42F9ECBC2FC424C2FBDEBCE3B3FFF471FFCE0DF3F7CF8F03DBB77" + "EF897C558309A7F4FCA2A90040F4B55FFB75F10726F3BEBDFBAE3B79F244D63A94C1052AC3" + "69199935ADDADC5F254F2BF7008ECD8F1EF355B2C7717B1E3353EA9D1AEDAD733CB0F1841D" + "8FBCF62C75C6F5C06DB780C4BBBEBD7FDEA7CFC8BBB7AE51B3DE02BED777719E8198E7EC5D" + "C79ED312C4BCFBB6029FD5D0B703703E57B6112EF81EBC3EE87CB74284BD9610D804530698" + "B5F74900DF1A7FBEE6D9F8E8BDFB0B49F3EE43B9F6CCCC6CD2A4E76461610078802A400F45" + "8CD48C8EC06168F7674E9F196AE876A352412FFDD7A588BE10B57FDE89DF694E20C2BECB86" + "8054B96F941B51FF7C97FECFC31CD46230380BB010D0FCFCB07682C621D871CCF7DD979C7E" + "8DFCABD7F0D7F50186BAFE6A0540BBABAB6BD1B5F1A94F7DEADF7FF0831FFC216499208515" + "0A54EB994DE9B9A7A90040B49526EAD6F0125CA81357999017C0A48CC7D6190F2688CA064A" + "794C541C862986217BCC7BD2774B9E063BE9F856DF2669B1DEB9DEBDD8DFADB62CF0D8EF67" + "4B676B55685DCBEB8BB6D53B8B28E96F3B4FC4990FDE585980E673BCB2B3766CED3D78CF9D" + "B75953BFEEF7F2E985E6BC0579CE08B0A6F99606EFD124C1E26C29241FF6608A0F1981E183" + "C7225F5A99508B1BC5DA06F373B18CF74CAAD81728EDB10C2005ED85524837C1743E664CC8" + "EA0AE016453E4849F4A705783AA9840A6D26B7D90DD74EABF5A6C575867549868A7C34BE0A" + "F222C5129185151688FACA2D20E9D942B080C9FFA9A79EDAFC8DDFFC8D77DC7BCFBDD7A3F2" + "64166E1BCF724ACF0F9A0A0044274E1C8F7EACF8CAEDDD776D34BA91C6C6EB75B3C6258629" + "7BDAC876160091715066A0F2B4330F78EC76EF3A3BDDEEB53DC6B81AE79C2D3D175AC2A47B" + "F1049CD6B84E6AAF75BE77DDEDF6EFE4386B09F2FA68BFDB6DDABECE775B68C80AC6817CE1" + "5600B0C0EE5D9FEF6B52DF9F0EE53E248D3EC4E8798D59406123D43898891AFC686624F373" + "0B311A7F10E86763653DB51C0D15F43653F685D4A67CB3645EA7E67C3E4641BBEB7265D178" + "662CAA97E64E28CDA6BDC90AA049FE22A39096E31DCB10E0C12C5F100F80223CD2158D5EAF" + "D007A9403AB0F95F034285AE2F21662ACCCECC44E03F72E4C8E64D37DDF46BF73F70FFBFBB" + "EFDEFB1EC7B6B892E889E3454A99D2F396A60200D1F11327065FD6C2C2BEB9B9B98BF465F1" + "CCC0FADDAE70A6B49DFF55A9B58DAF6BFDABDEF1F6FB24CDD2D2A47DAD633DE63D091C7742" + "3B31812B9DCDB13BA1B3E9BFA7F97BE4CD9D9D5A313CCB89ECE079EAFE16106F07CC9E26EE" + "FDF584871679F7E9CDFB6742D5FDCA803DA36EC8B18F608E824629FB402B19A24C6E047F23" + "C8C774B61877A085BE148A1378ABA55DCAB532CEABCB49CFC88174DDE00DC0B3EBA50AEC8B" + "E7E52A7C5DD6CABB7221F5FEC776BC110F54ACAF0B525911B6422FCB2B2B8320B3D9674140" + "A3F9B4BF1C4F90C781D2FEFA244841C3C7B6DB6EBBEDBF7EF4A31FFDAEC3870F1FD1E26453" + "7A61D1540020DAB7674F04F4858585178D46A365EB2FE617D6D3B45AE6D696B6B61D592669" + "AFE599435BDFBDFD3BD97EAEC80304AB759FAD06BE53B29695569BADED3B39C6D3AABDE35B" + "E77B6D78648FF1C0DC036CEFBCA743DE1C3C1794FBDDF725F29C5C12436AE14CCCAFCF6B00" + "24D0E785B8BA04DB2199B507015E81BCCBD5F22A541FE04F4612115C7AB2B28B2E855B39FA" + "D5045F2A3B0EE3DC0FD72FA6003A85CCFC6A09487DEDA40403186C977CAA120B886AA98915" + "0647D1BDB1155D9952D2F7A23FDF98FF93155423FB25C6366CCAE2E292ECDDB7571E7BECB1" + "FB3FF5A94FFDD39B6FBEF9461CA7AB8A5AB7D7949EFF341500882EBFFCB2F8238470A956F5" + "B3B4538DDA131E5AC16CD260F0DE36BEBED7070E2CF3FAEF91D78ED707EB0AB0DB5AE4DDC7" + "B926FB8CCE651F9E0DCBC7D91CD7125A5AA0EEFD9DE4467836C9EB678B5858C97E7904C477" + "29F68617F7C177007F04FA4193E7857D8A256408CAC3F2B9417DE295A65D8D60CEB6CFEE76" + "1D1A44D4A7D2399D9AF68364604E6250F2E127DF7F28C576321E937FBEBE7A8DE66A3DB0D4" + "65559F520259FD37E09FF945B4228E24603C642699F9B58A5FAC0B1C03FBB438809AFD2124" + "61ACF75F70819C3C794AFEE44FFEE4671F7CF0C1FF136E105802B4E7C1B54B4CE9F94E5301" + "80E892175D3E309AAEBBECE0C147E5E8B16371F18D49C5405ACCD303E8962FDD6BBF05602D" + "CD52C99626DE09B5EEC1D3D2B73BE67CA4B31D839D1EEF8DB73DDF33DD7BFB269D772EA875" + "ADDC4F3679CB10A83643458278BD0105FC19137CABD4A792B3B1B646C2C374C55A9157ADB7" + "9B74DF35F8273C8CEE0405DEDC76B627940B7459532FE01C721840CA224A4240D94E7EFF54" + "3C2CF7AEE3B6CB87DC830000200049444154CB817CCD4669006AB38F4294C4BA0009FE5319" + "DFE20948E97DC92D10839E6746B26BF7EEB8EF8E3BEF7CDF273EFEF1EFBDFDB6DB0E62B553" + "2C46F6D8638FA59448F7EA537A01D05400203AFAC413B122D79E7D7B2EBEE28A2BE33ADAC7" + "8E1D97F5F5D395A9511ACC99B5796FBF77AC776E9B39159AA4BDB54CDBDE368FBC63BCEB48" + "E3D87341ADEB3F9BF4744DE1CF17B3E74EAC49DEBEB3A302902A6CB62C0F628A086971216F" + "719F62C6EFA2295FF3DE3320A500BEB08520B48D5C9F5E015F684B57AD631F2BDDD49A3759" + "DA3D3BFA60A697725E5C594FCDFDA2D17759CBCFD1F695554568DF68F0CB8F527752B11FCD" + "EB1F0E52E12264D7809AE18350CE7FF2F16B2AA1BA2068168C3DCD3E84644549D92942A0AF" + "CF2A661E0E7114588B0026FDA79E3A2D77DD75D707EFB8E3F6771E3B7EFC1EAC6ABA6FFF7E" + "999B9F77AD99537AE1D15400209A9B9D19EA6BF7FD016CBDE4924B65CF9E61419FADADCD18" + "01AC35C43997DBA604CAB3A0E53131F3D67444A549D7F1DAF6B63D93F35ADB5F88F442BB97" + "49CFDEB30C785AB93DD69EC3041F72CC458FC270270BB3F3F17799FB920168782770DCECE0" + "7FC6EFE8874EDA3B45B0DB5805F89B0B4055D66D63362F2BDAF1BD686E7DA0D4BBA1A2DE60" + "EA962C0C4B69981E7DF4D1F7EA4F2F2E833C269D06ED4936F7E71882ACF8AB2B80AC0400E2" + "A135F30C486130FEFCD2C92E05F7E98098D80B7545E467504C1FB80EDC249D8C6473734BBA" + "39CDE71FB200E026C1B3DAB56B775C61F481071F387EE79D77FDC19123477EE6E0C1839F7F" + "F2C927E4F2CB2F9763478FC9934F3CB12305674A2F0C9A0A0044330B0BD1C736373F7F0D7C" + "86602E28F681085A358BC52541D737647DE34CACDDBD7E663DE5D70E66C83ED5C2EEC35666" + "3E36708F7F7381218F3C063E7D01CF0DB59EC173DA27110281B42D056F49353F5224773F5E" + "66974DDA762E668DDBA4B646733BF9D4E1630780F39C9D9919FCCA59E8ADA0472BDC51FE38" + "DE8DB04529ED49E34DE56E156CB3893E2F4053DF3C1BDFEB7119F6845C3237A4B45E3D96AD" + "02A1E4E3DBD612C6AACC20D5FBA926FE529B3F645D3E79FC3B32FF4BD9241AC1DFA57B5333" + "0159223AE1A0C150AC14A108345DAA10D87581D6EED71A00DCF1A1555403441D030801C86C" + "C073D3F981BA06BBF7ED8D073EFCF0C3F7FDE55FDEF453870E1DFEF599D999CD03FB2F88F5" + "FBCFAC9F292BFE4DE92B8AA60200D1E9A74EC79762717EFEC2680AD8A2853DF00F5ACFC242" + "CCA9ED52282D72626334715AF10B95C0B8A2995A0B82A91BCEE5816D2114AE2008068A6533" + "39BDAB15793DD24A61DDF8BE9D909A19B96089560EB31ADE4035736E5DF35C00AB1737210D" + "C0A48EE4C86D491A2D1FE95502947C5FA1D9AC4799893BB9DA9E6B86AFDB99B4D1A0E09D4C" + "CF71C1983033E48477B6046F97CDC316D48BF039007FCC7DEF06B3BB648174D8DF75C55CCE" + "E36AB575688F6C4606B8C55F3DF9E1F5FFDE68DA3C0EB90A5D71DDF3C2F4C5DC5FB4FB120F" + "97B6C571E9F3F1D1EA1E52709B94F4B8106ABFBD681339B84EF2EA7783072198F108791C55" + "6849434F610765895EC901839ACAA7E984814E28881DC83DC075044497DBAFF659E1A58C71" + "0C5644F071E863C5C285C5C558E61CAE182C3D8CEA7DB06EDEFBC57BAEFFF4673EF393F7DE" + "77DF5F1C7AEC31B9EAC557CB85175D189726D6788D297D65D2540020DAB5BA3A98CB46A3FD" + "C1968C4D363FB802542050A08DB9C5B3B3B2D02D3441501298F75BA9C4A856D78AD5C88496" + "E3AC0BACA87021E402E8D2A222928090AB0C76265551B7556576FB30A4358D661257EA5D53" + "AABA1B744116DE3E7E6F45430AC484252DA4545C235218A86A6C7A26315851E013E591A16A" + "03CF6168C7A94AA7C24A280C75C4969704CC10D6BC604D4941695D6AD716809214553D0C5D" + "D17B5300694E53EB52853E4D351BC67B78BEBC4C2DB4EBE8A71DD9BAEF52B5AD5AED70CF64" + "6256418673C1CD3DB1B9395039D82A302FAF58599F53D79B67F96ADCB5903BD273BA1B698F" + "DC149BF4B345C0EBF3403C47F3A8F77C96A6AF75491BC739458AC963247945DD727F18DA3E" + "BDE7DD10E9CF73B39CABD68AAAB28F8411C51028F227C01F09E7F67709E08B899E05842840" + "D12EBD8474F5276FCF5606BA7E25880CE308EB0DCCFBBBD676C9BE7D7B11DF74FA965B6EFD" + "9DFBEEBBFF970E1F3E74EBC14387A262031FFFFCDC7CD6F89B8586A6F41541530180E8C453" + "A7F0822EEC9A5BDDA58CB798DB49E2EF52E52E295AB906D788D12A233C25CD5C2D7433DD8C" + "CC765DAE1FFE7C252FD0B0A56117164B7ECBC49C46A3BCB2C9D879A4738D6DEB3AAB759356" + "B6D9A7EA692332C24AEE6F253C7492C1BCEAB389602E7EE190A5848E23C635C29B5D30A100" + "936E1A04A4722EFBAE038D8DFE566D3547C7877A295E5E8845FDB62ADD60135C4F19AAA9F9" + "50D4F0FADA8DEF1D83AC4A4954398E94D0AAFF55DC4B067BFA1EEAE7CBD7DCDE8A53D030E7" + "D117DC2D1A75EA215B0E8AFC11EA39855F5BE94EBA520068A4D1F4FDA03907AD0A9AFDEBC5" + "D5A1F7A711FB43605E97B19CD3F10205FDC58C0229C241D1F4B3EDA6CC47BA5A1757072AD5" + "F8421234AAB94E8FA6365884E80278E2892FF5377CF2868F6F6D6CFEFABDF7DDFB07A74E3D" + "750626FEC1CCBF1E5D05AC884CE92B9FA602001196CB9C9999D92BABAB7BD4F44DC65C8ADC" + "4D184105353A16E2B95102BE6C4D533FE61607F48C334226FB92DB9DA5604861CAE32B8515" + "E6AEBA7039A62B0C8703A43A66C665911146D8A040E9F4B14B0BA478FE4396093ABAC7F123" + "256B65B5B540AD0D6158E35C9F97AA8663A677D2E0C6F60EA4A5A035884BF26D8664622E0B" + "A30C16805E47260A240A8263561735CD92C65A34EC1A50B9C321EFA2AA730ACA41FDD8650E" + "A8DE5A37630A53E9FF054FA4E8CCC9B49EE73FEBD7A6CD3268D551F5DC29FDCDC273A85B9B" + "2C04149FBBE475F1E9BC347787B1EDEB3ECA80CC7836BD8E4BAFC2BB0272EDF609BAD25E7E" + "3B34D2BE7E5E45D8279F7D1246D89C5F2C01EA6629703FCA7980B50B27F75FF98D3E60B53A" + "9220CCDA7F57B5A07D282372F1452FFAD127BFF4E44FDDF49737C9CAEAAA5C7BCD35D1CC6F" + "8F9BD2F94353018068B4B529B3B3339776DD68AED28494AD86905FC08EF8534D66A3774C36" + "E1D57EC849E449F8FC7DDCCF4CDEC1AAB3AC31B1D66DFC88D47FD2C16AE69DF705DE34361A" + "3E75056C698953CBC03354A7AA64EA8F07B68E3A3E57082886FAE7A5DE4A20BF6901BCDA56" + "3A4AF6E471E04F6A6002FFCA2FA14850A43B057FD29AF3371383503DAC4A7B1F170C449760" + "A57338C08F2D0BBD986D559B4518650D31D071AAE95BD37F2570E42130667B29E3A0401B97" + "B4D527D0D7F3AC7C37AF4D2EA14B4288AE99AF5EFD2C6884829146B0E4762B81A51BE5F3F4" + "F841A6ACC747442D292323E4F31C49E03FEAAAF3AA61CBF579433E9BDD5A4A8E37AAB22A16" + "6BE4D0D228FFEE4B9962B658A44BC2D7FFDAAF79CDBE5FFC855F901B3E7983FCC55FFC85FC" + "F5673F2B7BF7EE930307F6B3FF6D4AE7114D0500A230443EEF1FB0A07E1914884A8471DA51" + "D276F391A6D55CF4A33E2C6490F6D45EDDD4901F32D0E52312B874BA4A8828D81077CBB5C4" + "C75FF4F12DED3B126146387EBED7E7F16D2133CF71E327DD67E2F439829D02A88A765C40B6" + "0A05C880C62BA4D5415E1D59658A10A35610D6361964CBCDE8786640EA54B029F3255A0654" + "E326C41B3383933B4018B0E946F91C7B9ED873A9746C1503900E2CAE041AE7A82D9B7EE56B" + "0C00DCD737523F55BE6606FBF2BF154A3ADE1BCCB2D45904A4BAF52416EA33EEF5D9E9BC08" + "1C8037BE32A3182B09BF49F19B5A09D20454F3BB2BF0E7F9AB8338E251AE82F9B46F2EC86B" + "3FAA17A9308D6A9CD2B586EA7E65195F9545ABF14B8723F0EFF12347AE464ADFFFF46DDF26" + "6F7AD39BE4431FFEB0DCF0C91BE5D1471E96A74E9F8E3102533ABF682A00101D3EF4B8ECDD" + "BB7715652F37FB2DD200C74D7DC99317A9F13EA7638B1992B77515588DC3A8CF5EE9702E19" + "2EC5323158170AFB5184D345442AF0CF0D785773A412B377221966396674576ED775D96CAA" + "5DC9A94F2A62A824A0428C743540DB342D5199288D4E4740C3C15B5AF025FB71D42C1C281D" + "4D99B8F18DD2B845CB45DF9347578AC58125B93CAC7D8D9F5D2D18E41AF121544CDC7ED721" + "CCD03E263488EAAFAAA656D690FA1C85DA71F7018B9B2A34E8593C7C655226EB44D6D80B68" + "E567959E6765D4C8F75744DF9E6D37E958CEB5AF66687699F429A052DFB3BAECF158C1AEA0" + "9ABF06FFA5B991DFB1224476524A082BE0D6D3550572066FE614D996163908BF659DB917CE" + "BAC92392AF53FA114C0D8112A8583A86A0D3BEEFF7DE7BFF0372FF830FC9CACA8AFCBDB7BE" + "45BEE6B5AF95BFBCE926F9BDDFFB3D79F0810765796971C725C4A7F4C2A7A9004074D1C517" + "2255665FBFD5334C0F0784925A953664693F5266F4C1B7E355E27D0D76CC243B020F210D82" + "B8D17044950A684CD3F9CC6265088C14A28CCBDA02086C798FA3F9149D7D5C48604DA4B8CB" + "E9F850520A839A3DD5BD4255D5F2B60CF8D4BE19783DBC307A4EE7CBEA7A194A65AC1CAC35" + "76874280584CBEA133DABC6ABD6AD5A1A03DF53F5307EB6961DA101BBC57C50C24301AD3FC" + "4375EF638240C66715282A845725B636CFE7DD654B4ED43302429EAF5A9F409FBDF1C9EB0D" + "56961437BD32240B4DBE0B9A3BC632C1F21CB9BC7AAD2910327CE720CF184C98DE9FA14A1E" + "0BC25D65D163F792A4F74883F8F231A9035DE025BF439EDF62D4071DF060F84415505963BA" + "CAA7564AA07789DFD3F17713D91D737373CBFBF7EECD5939479F7852961617E53BBEE33BE4" + "D5AF7EB57CE4231F91F7BFFFFDD23DFCB0ECDEB3A7996A3BA5AF1C9A0A0044A85A363FB770" + "20BE8429864CFD74AE8F9D737494A7198D20A6A0118312350F7724B9136833BBABF615642C" + "5A41285C41B566052B36A957BE5CD248ADA0A12031468E7F701C2AB8BF65AC589BCF10CA43" + "598218F2E08D07141AAD552483609D671FF29D94A1320BA374F46C2A70AB2D279AAA55B2B5" + "29929C0244BB8ED75F1842E974B88A00620506FAAB2E021377513DDFCAFD40CBB766C0CF8D" + "65E02C0172746C575F3BBB25F2DFA2796661C4717B0C9B52581D0B8B24C48CCD13FD936BDD" + "77D571E3025851B32BED9F6C315DEA4601CB40A7F64386C8888E216130F663A4B13CE36E3D" + "95CEB3454833764269AB88844273B19E892A44EA3C528B55555780C0BEA3730B75C63790BA" + "576511D1BD9112A2D7413D92F9F9F9DD575C71F99886BFB5B921DFFC4DDF246F79CB5BE4AD" + "6F7DABFCE9473E2277DD79A7AC9F3E1D1701F2D264A7F4954153018008657E171717F7D57E" + "6493C39BAB713144F177638ECB2AAEEEB6A0CFA70577179F9BAF64A28DC634B7B19803C508" + "8302F9F850AB1D56750B86435922ED63DC6230AEE9E05E4662C6A63AA330B10ACCF301A302" + "ACE9F83A30B208341D5D277FCFE6842E6BC54185290D6ECB434C4218016F2705583350684C" + "40C65B062FF6F1A77DA5A6AC226E0573ECEBAF45C31AFCEB3C7F1559A4EA6F08B58057051B" + "96C6EA780716308C5C9AAD23EC62C9809A045C4AC6C8DB4D7B6342422D419854437E1E8CD5" + "1D05897659FBEF4201797D15BB3181D658ED48880FA330C603CA59433B23955A55EBEFCABC" + "C9D62C1ADF1CC867DD564E244C9EDBEAA0D1E585BB7A5C4A2B9D99534558188DBA7DA74E9E" + "5C08219CB1A07EEFBDF744CBC09BBFE54DF286377C9DDC78E38DF2377FF33772E79D77C9A1" + "C38764766E368FF394BE72682A0010EDDEB31B52F245B1F25EDC9C63CAC91457236BFDFAA5" + "176FC4FCA5BC84B5E64B0C430C3F324D56E71223CA00C31A06A71D169DB50208EE56DE178A" + "89BCD917EDB7EAC9150FEBEAB6C78480BA2D15A554B3662B46E6D465B0C6D4AB6826EFBACC" + "ACB5D67AA9954E1A7D2548046A8E2D37B9950C68E5068B9FB84BEC386860622873226B5EB9" + "946C1D741952AC40EA5C85A92C1C44ED350C059A746B96571C0B40D9AFCFBDCE0E28739735" + "F502F6F9C9A9993B0B30EDE0C22E9BCA93F014CA1B931F935A135898D0BFB92AA00DAEEBAA" + "6307B96F94059B316B5C76C7904582626EB280C6C29BBEA3C53950C6A12BD680E8A74FCFB6" + "CF11F7A563D9CD242A68D22241A2F5294A9D81D887D1E0121A9150995310B9B69F5EA37A67" + "CC0BA5F72429F87744AEB52C69A57E0C42C7DE43070FEF3D73E6CC63080AF4E8A1071F96D9" + "B93979C9B52F916BBFEA5AF9C2DD77CB830F3E2077DDF579B9F596CFC9E9A74EC5BE6A21AB" + "29BDB0C99F05E729CD0EAB91EDAEA467654666F52EE3922777804D0B20C021100B2922BFEB" + "1804581B2B5B02F910EBC0A012499FB5138EA322F65DEB3C7E519E1AF8C7557D125786DF2C" + "2C5813E518F8172EDFD1482AD32E8691A27E1174558242BD872EC75E121A421687721F02B5" + "49821657AF138A040F9AE35D69A7B5469FE331AA3E29409A61262D9860BC6CE39800A31907" + "CD2EC80FA1F4CB660D642B85022D81ADFE2E51FF4239003AF3C6A641D9A7B24C5EA9AEABFB" + "600B07E5580B923AF20426E98EC68FFBAECFA27201D433606821C5328CBA02E4B551210CFE" + "FACE1475224B85AE10D07721C9946672E5294215BEF29C4875FAF3FBDB8D090FB555431FCC" + "689CF564F9539FE7288FC7488B1A493DE907F9B9E44BE0DFD690093177D18B2EDEBBB9B9F9" + "D876817E21550F7CDDEB5F276F7EF3B7C897BEF42579CD6B5F237FF8810FC8238F3C12B30A" + "C25035D586C74EE90544530180E8E8978E6229E0C5B9E5F952D94F2981835FBCCFDDC82A8C" + "01CE9077DBC3EB2603018B3DB88057A06D21E35961A885B706423BF7AA355336DBAB6E4CD2" + "F2C7DAA02C0A351D733D056352C9710CEA4DE00B93E9BE5CAE68725D66AE46EB4BE758C1AD" + "CECBA728F0AC95710F8B9E5E4EF307C166257422554DFA9E04BC8E73F749E8E381CE404C80" + "DFE5C24506D1498AAC8203ED333219047CC59E5C2BFA504A5A2901125B9CB8767F968AE8C2" + "BDB9AFACA59AE7548D2103DC50D79E6FA14C059E431DBD0BC5A553AC6675719D404FB60EFA" + "650066035420281EB68C92065EB958F2F88808B90C4B4FBB9C0DC0F34C3376AA61A2DFD1F8" + "45425367DE85E1F6BA2C04E4972A88ECDAB5B6E04CD78974E2D40959DBB34BBEEB3BBF4BFE" + "F1B77FBB7CFA339F96EBAFFF84DCF0898FCBF1E327E21A03171E38D02CAB3DA5E72F4D0500" + "A699C83B7629A3E50033D64F264DF1BA263D9DA366E2B4BD0A2150F3B9A369F1C5323B0C0C" + "6E642ACCBED74A7E2054ADD38E449C4B56D1F313FAE1C91263F75D403F0F4BB284B0D97788" + "9E2FBE4D6DD7285C89B98DA72ED6DDA83936334E0FB0876B765445AE00808E652584E5C5EB" + "684C7341A26225B059092C38F415305A4D9F05C5F2BB4B568440C03844B34B0E0C947C85A2" + "D1ABA669DD05E5F905D22E152742797A5C37226624F439382EFEA24A88054AED18D07D90D4" + "1AFAFADAFC0485DE2540ED20A050E9E62CA84929D99C4E1E2A6F536D0CCA2829425E573D6B" + "1634BBFCE0855C0E21A7FE728129498B270DEE02239892CF3F1B0AAAF38C252BF52F54426D" + "A06A843A1925AF2C680366D90A985D5E29E170343B92271E7B72F9E4C993D272014CA2FBFB" + "FB656969495EFB9AD7C9B52FBE56BEF6B5AF978F7EF42372E4C811595F3F230F3DFC505C60" + "086B0A8CA2DB6612A79CD2F381A60200110A618C666676674DA632D9D71A40D614CC1CF705" + "84F2128B48652A2EA53E1BC440290A46956E362EAC04D6B444AC5692C993065483769204F9" + "B4AA31F28FE76602317B5537AB31281A78ED12E0DC7E65CE5DCD3CF31F0D78AAF5731D7165" + "9AB6FA5EC99E900C70958615EA6713AAB67338963AFA0BB8911EC9635774DE6260EFA88FDA" + "7731428348F0B5F7EAC1B1F93D5913F2C1B50FBEDC29053E524A64D1AEB9EFF4EC74B8D3FC" + "CA961AD120D251D5674A8EA87DF255D0A28CCDB5FAFD498BEC562EB66A77168AB2EF3E94BB" + "E0D8D62A63C0BCA455853D9EBBF46E9511AFDBAFDF677B071E9FA8AFE56917759C43B158C0" + "75310C3B2F15DC25A1ACB69A702C421F5D9C233975F2D4EE2F1D3E220B8B0BF274E8F8D163" + "F2D8238FCADCDCBCBCFC652F9757BCE215B184FA93479F949B6EBA49BE78CF17E5B6CF7D0E" + "8B0DC5EDE8C2FAFA866C2C6CBA8B889D6BE2C5BC7851B3290D34150088E6E6E6465DD7EDE2" + "5C72250EDE676D703890DEDCE2AC240D988BFEA480A98EE1B2BCB2632AB51540B816BFCA28" + "4529A8FB95FBEEE5B98F1F97258CCCE969FBD8C9F579CCB032EEE7012B1B5850908E0E1B63" + "7CA5E00FB7CCA6E70C5EDC6F128D34827E100202ADF0566AB6873CA45D9653D4D45DDD7ECE" + "2C488C5E17CA513FACA9B79F5530AE806002EB243F7319AFD3503DA7501D53187D0163BE2C" + "0B71397E0157EECB9807D27AAB71928298269AA1BE2D1E9BB443D7FED76D16646B6B48B1BE" + "D43C99DFA5027EDCD74ADC630B08C7E95007F298E77922B99D6E34AA7E0BAF24D905D601CA" + "BB9BDA1C568CECA8AE00BD80A1CB2E074B5928EB747EDB70CBAE7E394820ADE78906A39662" + "6545D8969231905AC49A1C175F72F1EE3DFBF6C9CCCCE418809D50B47A7423595C5E924B2E" + "BB54DEF8C637CAD1A347E5B39FFD5BB9E38EDBA2450099041887A74E9D92E3C78FC9A14387" + "22203FFCF0C372E96597C5ABE09CF5F5F5988575AE686E7EFE9CB5FD42A6A90040F4E49347" + "771D3870C19E12956BB45D8EEEEF2ACEE08024E90CC6794E7A3229B19E25803510D5F4BB2A" + "45AED37C7E0517ED572010F7C09FAD89D526D24CE8DE0B5B722C0363F7CEA752F05D066C4F" + "60AA8733E8D8D0F0D36DE7EF55CA995093AC388D9540AE0309432A8293A3B8B3BB2664062F" + "062424A88E459A6B71B5D278A5858A48FBD6712963174A5C405AF4884BE8B09CD967106520" + "ADEB477419105514221FBE5840A5FB2A0D8F3DD09229426256A7FD29F7D2D16D310EF7C3BA" + "BD298556BB41FE7102B53C73748EEA787B9247CE78EB6A4DBCB28D97096ADD5B5E66410679" + "9662026BDD239251B840589FE2FEBBCC1F428AF92F734D474F6B820C86933C5E5525CC7C24" + "3D88DACA50BB28CD4B683296A0812F2C2CEC5D5C5C94679D82C8B1E3C7A265E16D6F7F8BBC" + "FD1D6F8D573878F060B40EE0DAB7DD769BF47D90BBBFF0053978F090ACADAD22ED5A2EBBF4" + "32595E598EC7F626B5F199109EEDB163C7E4DBFFF1B7CBBDF7DC2B37DD78A3CC2F3C3DCBC7" + "572A4D0500A2F9B9D95511592AFCAC120128A7D9A6AA7128BF950A0AE597BB46BAC46F8BEE" + "14AA173A949EA8445FAD71CE95C0EA403BBAC4B815800D0941EFD643710E44F48F299AE578" + "0A5F67D74150ED5AB7652DBCA04B559885FDA8D97AA2775ECCAF1A8D1D485B56B06170640D" + "B302BE3CACB5C0C2A161A1A3A0BAAE16BEA881F2E473D95BB285EBEA731953A877C95E3D86" + "89D4736F4309E0AB01508504F6CE5771011AB7C02E01C70E55652104D2904DD0E1D02625FE" + "A73F7D5F9E617E0279E2D555F80A1981984AF5EAFB52B9F6B587242D95EB15ADDFF6AD6C97" + "6A9C3A675E56D38752ED4752E24238307018AB51313CA8B5224D9EBC900FBD33752500D5F0" + "5500229FFF1846DAF1ABB7E11E171616E4B1438FED3A74F830D29D6D03CF2E21CC6166144B" + "0E2F2C2F464BC15BDFFEB67889377EC31BA3391E99056F7BC7DBB1005BF5EE3E5B14D232C8" + "175D7840FEE93FF9CE737BBF2F509A0A00442BAB2BF35DD78DB29F94B36B8C299ADFB52AA0" + "8F999AF21A1364E404B257546D3666C4A2D450A01B8164E5B324F3AF64C80C55639DD66A09" + "3D312156C8789BB2B6C2306944C6C6857694810AACD91770E521ABD65D5780A7EDFA3C3200" + "76A5F962C7E832D0D5C259A850A3D40FE0FA29543B5E23ED6DE1365DE12EA159083C2E8A8F" + "AC754A598BBED29A09C548F31E5B35B03ABCF464985764E931C83D36C5F218A936D9E53AF8" + "6ABEC84F256BF803A8772633622C6ABFB7A92204D42C3CAAE09444147D6205235974298BF9" + "94795F0A1055AE9B2AEEA4B4D0057A6EFA8C2A59A0EE9BD608D001D77D05B043CAB9CF6255" + "6C6A80FAF2BE74941D58BD061DCF527E3461EC9DCA72964E5F15A0C7F588F167CDD7D4671A" + "64CFE6C6464CDF3BD704D7FB93EBEBF92A870F1DAA6A08CCCDCECAD5575D155D02101C47A3" + "67BFB600E2BAE076809B614AE334150088FABE1FC462B62167CA2832FC9FC57066F2D66FCE" + "7EBE5001A7F2FAAE6366BF9D041C8A793AF3BE7241D664A4624532C6AC99B964C65AC17CBE" + "2481A0BDF7D496C7798A2A465654BB121AF52D5B378A265F0902A53B651C8ABE9B40B05482" + "532DBDC40CE8109754C17233A5EDEA8A2CFC70D439D52E08A475B23FDD3CB68250F50429C7" + "06A3F53724000E12ACF45B05D586393D9F5BC500943A12D523533022F4A1A96C024C33BA8C" + "919AFD475AC847B87B815F9FFC28B23D24D0D3D538017A56A31C244AD72714AC023DCB1DD1" + "8824137C67E63B69F7237D3975CCFA54807254C62B670C8CD2BA0246082ED616BDAA1695A2" + "DF74EDCC7AECF856DF5303A324DC74A5DD314983BE6F6E6DC9D2E2D2F281FD079E5616C0B9" + "A03E165DC3BF99B1F5B69E0D029FF872083B2F549A0A0044DD686669F8456F5CE5032C227D" + "D1BFD26E02BC7C3A33026DAED2AE49036C50FD4E2B6870F7AC79B21B637E5E5A1F29CB7AD0" + "D0765F40B4B06A13F8E820FEB80652AAE509B1DF3A586B7CC872467F2EF51BAAB614927340" + "DC98862FF91CE5E4E5BAA1DE4D39E2D5F324212154A33F7C07B0E57E6449ACD8844B6C8206" + "AA71C11FC7B5C18263357A9C9DC0A99CC318F65296AD1E73F954DA7669A3D340BD1CC74093" + "C95C8ABB54C09A04179E2676427154BE5049DC8E2C13A1AA14CC3D2816191709A56450D4AA" + "7C7D4F695711FCF8BD166A7CEC0D8B424B25AA91F0530A7A75D5D870A97021F0E7F9D3497D" + "C3D55A16D6B293DFBB2C91D092D634A71C21CCB310442DBB1BCDCDCECE9D3795FC6667E622" + "2FF15C97539A0A0015F55B5B2B1D7C636C321C7B9312131ED5CCA89516A854A52875212B33" + "63DAA2D47C74AC397609180571E00D0AB47A3C591EBA72DCC0116A861D8F19156018BBB6F0" + "A0C818E31C3F6E9CF9B12B256B950E03CE5B68B960031125609104148D0C1F70465B2B7DE9" + "1200D5B0A95A59285A28971CE8A9E852155C671E97A78057298A8C9604523C5E6C69089441" + "50DF7865B9D17BE8280A3F1F38164C50CF9FAC5DE73E9515146D16407115D8F3551808E6B6" + "0C3893F0AA41AF3C171C29289DA682DBB8C69CB3083818B47A15531F48F850318AD7F3E8A8" + "232C30AB4BC1D603E1EF212F16C4081C067381612255643E5B9F4211EC924121833EBB2834" + "8E437251A37116552C0D26C0B61719CDCE2CCC2FCE8F2D08F4954AF34BF332333BA5463B4D" + "00002000494441549305E529D5341500884E9E38B97B79652557011C2FD6C3ECB388DDFC22" + "B316E969CACA5018E42CEF6F09ABB54651E927F99A217B5385104C12F32D418263FD222B45" + "15FC504E2FB714EC7D5981605C4010B60288BA6FC759570CB00EC4BC8C8A1302314EF5A967" + "8D5A39BF8DC1A801A8E375EA3B5A5950B122D4C3107855BF2EAF359B050FD6F6AB804302B5" + "0AC669FC4B5A571169FA7CB7465CE8245F3B775014FCC65D37F919E8F85436F37AFC82AE80" + "3D22537C1D50605C57E4EE308F9D8B12E5391E34432554724995C150D4DEEADEB280C7E396" + "DB1D9F6FECA22A8FBEB4D3552DD4F3825D61E53707A636DEE920958549D30BF5BDC98B09A6" + "4936EAEA472134D5F5152BC36584C040CFCED1F48BB062DEBFB8F4F168657565EDBCA9D8B7" + "BABC2273B373D2F75B63FBA63415002A9A9F9D5D2EEA55577BE8B2BAAA2A34713DAAF2C7F9" + "E59233076BBD4C829857B3330CA950059A6486F7C04748C7348D540CB15C964CE4BC8773A1" + "B326C9CA7F2839919EB0205AB92CEB94B54094D31989B9AA601452CBE43715CD3050FB40AE" + "1A580AE78AFAA64357003F670494EFC5B2100A2E12EA17057CCC085F9833B5532916E959F4" + "63235D0B6BB546375E34C88A51F94CE323AD9F73FDE4EB7E073AA48CB942442D6B85F1F003" + "1DC4AC41D33A16EEC1E572751B65A961B6C1D4F115B560935D5354EE390B275972AE85F03C" + "6F34ED74C462553D2C6372AE0A98EAC6D17767ECB83451F3F5394E42DF751DE3513144701F" + "BB2E0B061C255004645EA593C324A5C4D398DBB222771103822CAFAECAFDF7DFB7FCC94FDE" + "10D3EFCE07216034EA64756D4DD6D6768DED9BD25400A868667E7EB94E3D3207F88A6D6572" + "2C3EEC724AF6418F9D1B88A1711BDC74C7884F6DD4296F92796561317501146D2914F8E680" + "2CC6F6B13EDAEDD44B6F8CBA7A4CF88B323E3E4DFBAD7D35FADC58A05D61A6A5A470C6FC8E" + "16BDD1BB36A096B5D60A978DC5A10BD5F5735B79719FAE04C5698D815C9CA8BE0F15586AC7" + "430DF359E94D9A684F92050B39553D0332A3B36C50DA678DDF9835E2CA8466BF79E4F6C116" + "B712D51730C28D7B4EA74571B88D7A0A053BC5EC4424B7949ACA799285A0DBBB5A9D26A9AE" + "332D97FE537C80FE095D5E186A883F1995EB74651E9A57B2B4AF426E722DE97CD5199545BF" + "149B90FB54DF3205BBD276C834D95C95B6A5FBD5388BF86C51A848FB1C3AD9DCDC402AE0DA" + "25975C12D3E3CE070100B10E175E78E174F5C2064D0500A2D9F9D9E5C069CC564570026D2D" + "59B37679B11902EA22374C2EEF656D9FB4E881E9100872DA970DB1A6AB8FB40FA455641160" + "4CFD34AACB1871FE9E986BAAC9B8CB5DF29AA94AFD8A917714E8593BACC6A0CB96837C7D2A" + "485347489788F97CFFD932D2512D01B342A1F023A5004102767EBA2245892D8B0B96BCF5EA" + "DEB35721E49CEF5EB32272CE7891CEF81A9CB6C8BEEB8ECE0922D575AB6122E064DB916B95" + "50C053FD7D2C6E711CFCF3588954E31905502A98C4439CB32BF44151F060AE58382AA97F46" + "46601B416AD4AAEE3AAFC8CF5F816911CA3320EB73EE876B079D7BEC5FD767A91719B1BB80" + "D22FC687A812C2B22B221FE28DABA42584D2697DB9A7DC0F8D11E8CB7B846A7B2BCB2B8B2F" + "7FD9ABCE1B17C0D2D2725C1B629DD211A754682A0010CD8C468B25BA586A17C08417A69213" + "482395FC7ED719E2E359BCDB91329AC241357FDB5A1B6C0474C1F59AC9D75AA41856CCE4F7" + "D3DE418E8EE6F8803A58DA70FB2CB554017C9D61C43AC039952A6FAA03F34AD05346AC7C6E" + "59E29785A25227BFB0F1909E8FBD5306E0FA1E829A99497ECA233D8E6EE9AB1A7CFB2CA7D5" + "350CB8BBA14A2DE400B5FCBC6D7609F5BCD41DE00742F755654E9467691B2BAEA67A98ADFE" + "1E28C8CE8EE19875642C46918F1B4FA7D3FBAF5FAEA2F9D7E342604CDF8ADB4BCF55E11290" + "AA8B0E69FD0875BD95F89B2CDF73DF726361A8069078471566D7555D49E77595409FDD1DE5" + "AB79FDCA4CCDFC461596EC8EA89F2B7715568C7E6B63F1D4C913E74D1060E8B7646E7EAE0A" + "DA9E52A1A900C014C292973267448131F264830AC394714829055A95959D44CC044C15B08E" + "4CCF420C3A8388FA0E59A312CD16E85C46BFA3611ABB3F2986CDB1DB2106CCEB008846DE53" + "60A2A65171CA966ECF88412D925050F9B4F3954BC5BB4A3ECBD9185C1380AC0319D30AAA77" + "C4A38BA657E204B20BBA124042F691C7F3FA320E35E04A65430849ABAB65B4AE0A082BC35C" + "CECB2A7F96463A5321B1142ECA58116AF0ECA4F22255639D3B5BDDA841639A85C2A0DD9592" + "CA63C251463CAEDC58F6B1364F8752FF8CB041732A5B38BAAE58BE3AA9DE090DEE2BD93269" + "6EE68ABFA4A16753FED8442F634BA3938B03B13C4BAF8D3E9F9245E4B46B5673F4A509A94B" + "11D3630AD5B56411AB020EB519BEF209D5084723121AA754D15400203A7DFACCCAEEDD0BB1" + "60C640E5550DDB080192CF60AB818CF9162BE26D1D31ACAEE612CC623B161E949315F64FFD" + "D0DED4CCB6C4235815AF2888818E1E3BAEE0FD84F3983D3154756307B020E1065CE9FD730A" + "5A568154F32F8BA2D8108E5CFEB50B79DC028F43BE993A7FBB807E9757D8E30C10E2F5A4D6" + "EAB8F24A79216BCDF96130B0FFFFECBD69932449921DA6EA995559353D077BB800C8DD95A5" + "5028F800E1FFFF29C4072C787C8108B0D74CCFF4559519AE940837D5F79E9A4535A6201421" + "32DD76A72B32C2DD6E537D7A1ADB0A98EFE101AAA7830600A2AA97161CD0A03384010C2AFA" + "814D0EBA90CC00757D39969E768C281C187C651DECEFE27AC56FDB271D53415B3454EA146D" + "309D92112DC17E3109A2CC39A5B44AE0026C6384F255DDDB70686D5AAB5E171DDCCA5249F3" + "06DD00EF6906B0D820791CA058CB305DDCA9D0672E16ED5DBFBBECFBD516FEE12D45015C33" + "011A3B9A9E45CA0900A8BC7FFFFEF1B28390CA11E966BC7B07C899625931032F0201E62025" + "5AB542B3290AB8221432194853EB364E5C64316926494EFCCC24E02DFEFAE59F3ADB672951" + "C59F75F6B298E645B21BBAAE4B583A5BE5B3CAA0793270FE35349273DBC3D1CECA851271F2" + "33B1ED367F1F5101C2A6BBA0CC4C521C369B2C4DE689BE26C5D4C853BD67014CD6C8EFFA08" + "F7F3AE477083BFC1024C26B87111A418F0D08214B14D67584A74346D79ED9F686ADA96399A" + "94C1DDCFA00915CDD10FB2B3C0D17271A6532355D382F3EA5B8E2D43F88E2F05DCC895D81A" + "F24A535D5FDCC025A20515ACA716C2167B9E7134BBC418452DB499A6179F9EDE3F3E9AFB8B" + "BD81F2FEFDE3BC3FCE52E504005422E24149204BAFD63EE3773E84B87236A5DBF9DE809468" + "5521DE42F11A37281926D5DEE3429DA4D90930344D2C87D225C51BE16E4E36CD6C7F4A1A83" + "AE281822C99993A9B4F9BA5D0F9BFD2FC9DDE859F1AF9F8BDCF14EB3359815AEAF2547B11C" + "2E53DC680CAFE4E28DE2DD9D7E21624C1EDC6C463126F67B4042273B3187B871A866480332" + "63D314B0485D110777A4196612D566A6BB1D2688BA246980AB9CBF7BA9A26BF159B4F745F7" + "8921CB9D174BF0B42EAC61E917F9145834A88D0E1C844808E98E1F66943C6B39319E1349EF" + "1558BD7DD88D66A8BE079EE0734B19FA642260F6C8A9AB85A1FDE1847FCB87C5B09745C311" + "EA28EB9C0383D7DB83EE283069EBF1E1C13E3D3FBFFF3FFEFDBF7FF7E9D3A797FFBFA403FE" + "FFB25C2F3D3A6E3F5C9F99B75E4E0040C5DD1FBBC3549DDD86C2B73B926B1D682000480206" + "69D51B30E8DEBEEE3E7DD70FF5F122D4B6E5ECD664CA64B5F50DDB48BB94B628ECB478D0D5" + "2EE7F0386C6E67FA15E399C39E9A9D35E7ABF9041879C843D21A734D14376DFDC6F6EFE207" + "B085BB49761AC9D19FF3DC994CD0F7853E04EC79AD0F27FC49E60BEFFDC465942B30FB41BE" + "004B274EF9BECBF6A46519E0A38457C75C3023C5CA58F5DB4B321E1D9214C27D9FD1F8C979" + "AE3B31D63E9EE6B30188C9FC41A3AD58F9853FCB8E5B7BD9C9BF58E384B593ED3BCD1E407C" + "45AA8C866348FF7C8CFB35D2557F5E26957E21925F049AAD72D8CC05CAC80B9F9D5B2753A1" + "CC9D5E3E958F5D4D9B9BFBFB7FFBBFFDDBF711F1D35B30035C99FFBB77EFED613B59DDAA9C" + "B34225221E59D229C2E190B80108BABCDB3EA5C03D547C5ED21324DF2F1D3F72EB1AEFD32F" + "E4C407F53509A70B4061D655A64E0C617E560AC5B7133F5D3078050B7D3CDE0CA2131CA088" + "04300E038872BE0236891C6B058C021A55D53F393D1687132C5513252978871643723964C2" + "2104C60D8548DAFCC156342E7F9E99219E5334018D7315A4D1520ED350EAEF4A0A4CA18FA5" + "85D024FBF77A45926C420BB779AB3010E17D4DD22C3B3CB2868A3B4E66748EC4B9FEBBBA64" + "B09EE5505046149E7E00B48F9A3367F6C7A9426831D41C97E6B6D8123CA7B6074015512CA2" + "8A3A720854AA6E5C2694B70BB18B6AAD7B2A17364E3E009AB42510F090444332AD222DF8CD" + "F3FF72B9BCFBC77FFC8777CFD71B01DF406CFCF50AE4EBFFF69E45EB2CB77202002ADBB63D" + "A93739712AC2EDDEA848D3F015038EEEDD5B07BBDF8AA7A5D14593468478B26D16EA7C11EF" + "4BBA3353EAAD122397953962EAEEF4C5CCD09925F5B02E6322CC4AD554AD93F458124D5250" + "22D2298D26918F22D644F8C9BE6F29F8161E239021391578A83A1FA95AAF70316158DE98D3" + "18239B4DC69AEDCC95AA511ADF2DAB20CF2198A384F709C59F197AED658F11330E0401D0B5" + "EB7B043E8EE92149BFECD332498319A20F05A37A02230129B3F28492DF617EF337D1040D29" + "B7221C7A98237616CE5B4FDD3D806CABD74CF8EE8C12338CCE8D460B7A1019A95F67B0F51F" + "2F17A0E034DF0286848FCB21D7C5827EA3221FF06499A3B65FFDE69BEDB25FDE4424C05503" + "703503BC15A7C7BFB49C0040CB134813E9D7BCA92B1BA7232D5E1DC052DDB14AD38980AC38" + "28556F4408D72423A5E226C5F60B5BCC98DB0930A86C779353A0327F341C9084BBF44FCC5A" + "7A8BC958D467323FCCD0828825341D0E464899FA305C628E06E9901401350F99950E1A8D2B" + "B3CD303592B6E845666A3E3CC923C8A933730E88A2007B20FD0D92A18230A36BB1609441CF" + "69DD3A6D5C32031DFCE1002E2A3262BC1CD84D68794BF30365976B790EA44C2969750F76A0" + "890BA9AC39CF99E4EBE7AC8DECEF21D104AE0EA03543EC4720B93378F36D35B7B8EE1A9C3F" + "99F36E99DBA7DD05DCB676B628F70894792128124150099E73C71EB3050130CA0ED89D038D" + "FAAFA38410E3EEEFDF3D3DDCEE3B79034CF1FDBBA7DB5D0067599713005009B377B3FC3433" + "FC396C898E7E25ADE9928795E4379DE83BCD986BDD70660B91E22643FA6A0C023E8C88AAFE" + "3D95DEA1498A59CF4912C8B2E793A43E8F7B10AC247E418E4C9CD92ED5ACDCE90240E95CA7" + "CE547ACF40541DF57EE03D8F4C7493AADBF48DD070B6F2BA376DC78C255D307A8881F3A88D" + "D63DA5F4BA8CA82D086B9645321470523BA44028EEFEE73E9B008F69F9092CE5DFD0A868C4" + "009EEB4C1E60945AAE8D07888D7B25240B1E7592595A0F4194088FA9A446265B7739A312D2" + "573CFD98490F64A02C3343CBDFC1F3575940716B96D5C6CD5AD8FE40671BCEBD26E62F778C" + "FBB63D766AA28DB96A9B1C6BC6FEDEFC7AFFFEF6DD777FD8DE9A09E00C035C9713005079DC" + "1EDEF3ED66428CBA5D30CBC4D0E95812732EA18B92F7248D702105BD301304316607292326" + "2ADDE85A0BFE7AF15B3DE14C747BF9920683B2C531E92B458A125A6309B944A99809239B20" + "C817E3088173BDD29662C5CB2B60DC9687BAD8C3422547CE2A87786C30E5E25F0B7A724BE3" + "6BE9559F9DDC27ECD5A6F920E87BCE1FF35EF5F8671F0498219A5229E79A24D4629EA54198" + "99B574AC05E815F809BFB3810CD27A810A8637FA1CC6C31B2494A91288BBDE8BA0409ABA6A" + "98C7FD76DB9DD3F818E819EED8E5E6CAD474248C11C0D8A2744A20A7A3B83C05ECC4CF3701" + "8A5C6E759F8459D0678C31B3D7E5F9BFFEB53B5C15FBAAAD0A9BD7F697FDAAF078F877FFEE" + "7F7FDC2642F6BACB7917C0BA9C00804A583C643AD0BB5CB9ABE4BA10DEDF2B1AC48C9AE8DE" + "2FD0E42E35B333A09144863E350FB1567549554CA10437D0C53B4692DD246541BAE3B663D1" + "E73E5F1A55E0448D131C2CB21F8D1F35490C7F8ED60CD4B33175D5E929958DD979B086CE73" + "D9D6D65D6FE973DE229CAFA624569DE3027112563FE77380B6030CACDEF7260513630D0112" + "8D292FC5FFD42C688865D4EADE2F37BF86AC83D791A32A7A61DF08D3E75CB7067DEF2529CB" + "FA7BE6FA0B0217E478D8FC459C2034CF61C4ACA94A10D45304F3A69B9C0B794FA7839FE17E" + "04951D18F4223C55C25BE9386089A5D11BDAC057AA6DB866C5BBF6EB3FFE87FFF0F8FCF9B3" + "6D6F480370B99CD701AFCA0900A884D923CB3D33F7EF1C409F0DBA21142403D9EA8AD04F8C" + "73CDFC4D2406306BB6C16B085FC5450975579B7E471DCA69A66EC035BE3D4B0E5EF432ABFC" + "27A0540E553E493D20C8813EAB384C202163C0AD181E5F962480A0D9DF8DB505960C3A1458" + "C9D0356301D64505EA1BC3EC422F33E6C83E210F806A238C723190329F2A9095F27605A0AC" + "60E8D2914C7E98BCD3D9B18389C65D68CD3CA309F694CC67AD06E69D742CEAB7391736D330" + "F032F8434CA76D0926B04A58375F34CCC010DAA23DD8F7806A143BBB4FE1BBDA85C1C0537A" + "F7F6DB718D5FED298655A417A8B6EB748EB56240257D22C0E11DA6D5F3D026BD7BFFFEC1B7" + "ED4DDC07F0743A017EB19C00804AC4FEC1FDA108880AB72CE29B1C59B9A8C64B5698847240" + "7F30F04ECAD8CEDB7EB9FD57A4191F2A700606A40F7696BC8A6372E8E0AA07341F32CAAD40" + "0D66A081074998A2F514D162864F594ABCBE4B2968D620281DD7F92D898CB537CAA6317F8D" + "93C2762BAC187CB08006E72860C9153676C500041E285F41AA0CB0275892E50A24D441E603" + "5A85A0214731D3086AAFA934BA822BD8D4D09CE9EAAFF673C47AEF48929B0264BC07A75784" + "396DCE7E18A1E0213BAC385414609C680BFB7D53534A56E36EBCE2403C0422E97C15A2B444" + "52AD44B439E6A4450A4662B83D78071EB76B9A09C753EAE0727B65A93F01E3E605BCD91701" + "DD8636E8EFFE97BF7B7765FEAB08A0D756B6BC01E2BC0C68594E0040657FB9BC7F787AB297" + "E78B4AB6C687DB161A006635B37D8E2A41485E6A02DA21EC12A370CDD1124B5F887D766803" + "26F14CF4CB9364B82AAC42E567B527D1F9521B2E872225814DFF0726BE4995E7C442469809" + "845413DE149B25C995C18F1B5DA4C339EF730C0D71559818CF01694EB86D0658A2BE0EF47D" + "AE8C52330B40BBA367684BCA2C7A0AA0603F0A17A45038C2691F66640A766263D6C5135B7E" + "8C3B7B07E614B669CF20A66B33120CED86FEE40E0B43BF15014431B992E62982A0BF85C694" + "21A6645D19F8C06A8F9A46E440DE0A7068FABE400FDC276D02FA0046CDEF468EFB9A60C887" + "6A27B0D76B0FF76C9D6D19CAA1B6D61F58781B7702FCD33FFCD3BB7DBFBC09A9F84804F4EE" + "96ADF32C73390100157F7878D877881392E56BC10C9318C5F4494B1156269CE06AEBE2145E" + "46123F89A44DBD87F4BED9D42F31FA48BBE94AA5DAB4A7ABB141EE5529187590C4B2610AB2" + "2E496E548C7C7C43EA59043B406E2DA9B1220316BD754C44B47E393BD0B5C881620E4DBCEC" + "700DEB1304CC8615DC015AD027404461B646AAF57CD64D9D0B7A0A60C253B42083B72520CA" + "BD16C5640B068A2968517602187CDF822DF025B55F4C79D2AAD4DBF5A96B4D9C4C388A31C8" + "FB8EAB499F0A06C9D504EBC9C945D6912DB2924671A449F539355E21617700EF0080D9BE38" + "FECB9E963484128A38F9C32CAE53967D98794478DEB75A283A17A42130CC9DDF9676DF2EB1" + "F75A5E65B98E733B9300DD2D2700A0F2B03DDCE2638DD466EC99CC5201DBE6E53630D114D4" + "23A2CA5D08F513218EE8040577C6972313D9FF98C083134C439402C978EEC6464D2769998B" + "88E760C2ECB4C8D3128D58D7B3AA723723BBBD690E744E80A38C28FB60457C4B1672305E96" + "E069B24B1B83CC722D1A3FC3074540A619AB3ABC80478DA8221D286BE0B41810E571D19D2E" + "628E97D5EF320719BDC020D108D0B6D5FFE206C96ECBCD77A39EBDA9F66B9D5DE575A82504" + "9818AD8D2513AC7DB1D046B036E1DA9F2D1DF122BFAA1D140540A611D52454B480F31AE5B8" + "94E16EE4EB92C778629C0C3AF23C9A436B92EBE1FD793D7BA51868587B7204E6FB37382A20" + "5F4EA7CD40C6D2D4805DBFFFDD6F7FB7DD12192D51DCEB2ABFFAF8B1E6E92C733901001537" + "7B6046CA8C4798B8F88E2F848FA9DE8C257721C8F5F2E2207AFF40CE83C99E326E3D1B0FEE" + "0FFD7098068E3E6F8DB92C99D14AD2BD33C6EEC4B7A6BC0BB7E7E8CFB6FBEE396CD0E89633" + "622A3ABE7BF38AFCF7A2D1E90C9C469C121AFC05185C9067BFA14F6E50CFA68ADCBD04FB31" + "471CFB0F2401D3F16061FBBC91A6844FF96E5D78D3C211684CC51764DAA3818CBE0F522391" + "4CCD0AC0DA82F90731BE7A3F25E305E8E2778EB903139B7724DBD4FBBD02099A42F6426A76" + "BC1F0761A06B8E5BA6A2DC7B02FD318030A4E4CDF5F56A73CCDB74EF463EAB2E41F9D0042E" + "6835D8D4D5AFB817FF191EC7ED45F82B5CDBBABC5C1ECDDF8654FCF272B1C7C73304F05E39" + "01009597CBE5E1C3BB27BBA6C95C80F5764005A28FC3DDA4A57150A17E230E22C862510AD2" + "2B4148E2E544A86C241801E3629227B233318B796C4B293F947FF7E7F3EF1428C0FCE6BAA4" + "3DA28A652F16E98A184C693EDA33249D263145181FA51416299D3157802843675FD27B4AAD" + "EA7100E86416256997D41D3AEF95CC37A5C2E1718E690051AF77368EA7476DBB194068746D" + "043E4D0C8FA4631F973989E75C500BAE9C2521846A1AB4738C57B06DD1CF3E0E861B1562C8" + "C04E183C65334C73CFCAC1B5B40E56EB5E511FD47E3144FADDA82FF5B9F63D32020685E5E5" + "D183CB0D79E96FBA97529260C99D597A1BC21244E3DA6B9C1541141EBA07D23722D74F3419" + "F180C57CED256E2180A9D93D8B9613005071B76D0FE28C64774C89B4989E0AAFC41A7AF4C0" + "F10F10FFF170DD07D2FB40E0A11382821175BB98112BF292207B5D41E3717AC69916AD983F" + "7D3DFFCACE56037454063F4EAF6ADA5695E089EBDF629ED391B0D2FA2663C13CA7F4A3129A" + "32163129D02C6D04062CDAAFE92B905A213265688FC1C877615C0109D8ADE61EF1E1CA4801" + "5494853BCF4B3973BA48BC9C9D50986BDB13B729DFC79A2590A88DD236B5855C7B2C6A8479" + "8BCCAFCE8F8E6D7DF574DFF57DC96931F672FAE388496911DA07F4679CA937EB96844A7966" + "02E770CD0AC1A0F3BC7B6A1B6EFB6693EDEB94D80BFBDE0554800D3B81D8A3CF5DC333817F" + "D248948B620331307FD1A123E47EADE17A05F0DFFFFDDF6FFFF2873FD8D3FBF7D3A85F5BB9" + "3A007EF3CD376F22E4F16BCA0900A86CDBF620E142948AF776CEF80EE095AD5F88C9C0DE6C" + "8F8C4C02D29DD2C0847BC814F7A1DACD07D231B0B53CF705A8A27FC7B4B4EB0C96A5273E32" + "4E5A92FDAAA07E70685623B4B28E871EA48CE87D180B567ACD6CA5EB752F074A26B73D0CEC" + "1828181B4B4B35F7ECF99FE380D02F60016A69966E4D72FB9784E651B601056C6C43BFB38E" + "5DC6977FE627B924B0CAF0B279E19D1618BDD2866660C2BAA0C84572054A4E690BCAD94F52" + "14431333850FD2A6AB3D509A82EEC482F155CF297A0166BD185919305760F93763198D1B8B" + "8A2894F95A6E33C7798D0D478119740B8795F3413DBB7EEED9FAFAD9E561D79C64AE89A8DE" + "E2590F7B797EB6BFFDDBBFDDFEFAAFFF7AA9A57B6DE50A787EF7BBDFD9EF7FFFFB573FD6AF" + "292700A0E2E60F4C388425D5A9BDC3C5B248EE7A48B14CDC505FBE337DA8679C1EE6882EE9" + "137BB033339A85C939BD2C35B796868E38E4F0B8037A42099AC1C29112EBE42B111AAD602E" + "BEE0D2B122E6EC9D4F7903F29DA8A908AA4FD922D4C64E39F20D8E82F3F4F7E4BCE06A4559" + "BB892024CCF0E8FB4834535237EAFDA247FD9D1F65496B918BBD0246B49408D05EDCF7FCB8" + "B1C5616086495F592377F156352DB8321CF5D9F0A1CEDF0B3910834F4D467E939DE6D0CFF4" + "71C8B87E42A2A1DC9DD688B72D259FEA1EB61CEF6F50F98837BEF37C14A6A64C896858C2F1" + "B20F1BED5B4E2BDEB07185097AD37AB1A6C391342AE89D9C365C4016A26DB86A383FFEEA9B" + "F7D7D4B8EB844AAFAB5C350046B4F12C5A4E00C04542D50C08BA01766B1B6AA5EA8E110A85" + "78689236BEB019411B9BAAB8548C2AB570BC3913507A442AD743AFE8604D0E0E2BB617411E" + "448DC188F737B82E9AA7629300044C34EB33031A0A4193EA06F565A1D5685D8A280EE0328D" + "9435DF24618E4074D4519EF7B3A42C3339287B18DC0902DD1CDEE42BE919DF088C2084D330" + "DC629D581B022ED07B290E72CBB532927603FC861C4FB90FB514C964C011A73DB047A6D2CD" + "F7911CC9584A5FF68A121CF16FB42FF83E888C90A96B82CB8C77FCBB67E4DC3893DBD82790" + "EA8FB16C4E6D545B80E441BE3C198ADBA3156EE0E8FAFDC306509077355826F0027689F16E" + "DE6900CD931368516D452A1C6A4FE77D19D1FB7EBCF3FCFCF3E3F3CBB4895E65B9EC8FF6AB" + "0FDF9C3E0077CA0900A8EC7B3C3E5E9131E5589DF89BCFBF882DBD39A525B980DDDDA7F7B9" + "80482841CD572A6C495480AA3C2EA6455DCEAA9431AC097A2FDE7A7854CA042C662988F3A9" + "9710489A00EDD431474409416FC7D862AD5636CA88084B0331080248CC5326AF7AEF0C9358" + "B4E715AE145B6FBA8ECABE86D31DF97D04C5A20B960825EAAAD549C9583BCFCA8AF2A21734" + "C2497FACCC230088B846BACF95579D2EE351E0C57F862C275F7073CB25309CDFBC2F498D4B" + "A71BA681F9A8112C9B208299EE3D9C4D4D9C651CB6CBA686DC0B5BEE3F48DE3E4042FAA26C" + "860BA92A3745AA07D23F676B627D021C8746035075AB754B20C1F749E03D1D2C83DEA200A4" + "1D0BEF3374ACD7C7A78FDB55327E0B4C312F013A2F035A971300708938F8EECEA74F4500F8" + "288D03BC7D4112A6DBD37C108D2F79C9733BF991ACAB079322CA803C370C2E48C263E9983C" + "993B59E84085641CF9AF88382562F812D4F429D1988111739D237325DE6893B4C8C918694C" + "023A98B3B6DCE7DEC0124BD75634364A723AD62B6ABDB51F9AFB20C7D627B57C1416402BEB" + "DBA3773B48DB90CC37F43DAEAD8D5336CEE06825686672A002434D2BC2C02AD773A7EAAC35" + "3C1E48253933D89A8E0D496CF3BF09F26446725ED962C0FB6730381B8E9537A7B979B4052C" + "83FAC460B8762669C2EA7443BF4F93BD13BC86D45E7B99C1EF48C15B51289992B784025C56" + "A453EED6B3D41EF7082CE843CDCDBC07ADC69A51129202708CF15089FFC7BFFF3FFD0F7FFC" + "E32D47FE6B2F571F806FBFFDD6FEFCA73FBFFAB17E4D39EA388BFF00002000494441540100" + "1577F7BC3A364F4D3F87DD9E3D7153A3EFC9E1A85E21E16E82F4F72A0B207B304088C3C7BD" + "E541DCBEF70DF6736D37592DD890B79785FDB06AB73A83E7C529AAE5EA07D1C29799575F89" + "304185489B2D66913DF4BB148CB870B491D2286089958D5EACF7290D27A36C7D52E6971223" + "184297A037CBB4B6B32A9E6BCAFDE0199E97F9032CEDF0210023B83F71BF3E2E883C607B06" + "ED21DE8FD94E4A9D586CAC256BA2788F50564A7E4BFBD30E94A4188C3A79B306A025346A7D" + "CD7F3751CB2BF32F2D92692AEAEAD978A162F76BCF02A81678EFFC794CBED20B726F8D8D9E" + "8373E0C6218454E9C4FE695C7525F16A1B3826A6D2700B4A76BBC4CBC3CBE5B33DEC532BAF" + "AE5C235E2EFBF3F2FC9DE50400527C5085E0A31B74A698872D1DE250705E551616956B169F" + "A8A4FC540428991539CDB1AA2F48A50D8F60E217D335A7209D8A4732D908FB2D58B5318317" + "63B4339728DA43120A331D720263553CF795B308765F86F17D79A0878BC62123025202A651" + "4E33A157EB021454C8D5BE973FC64E77F897744D5EFF3959D022049966D0086B3FEABBAEB5" + "00BA98A6974783E53912031DFE81EC5F802809009B0415A48F28A7386D4FF0023A280E7FE5" + "6CB9BA3068EC832B61DE9BCD9C70D4B24401B23B0FE55908DAFBF49B8FAB3AB1CF780026FB" + "4F802A66B4D202E76B691A0234D643E05C593AE7D1E4055D3EC497FC74F2C23E02D04448EF" + "2D117E501B880E4079787C67EFDEBDB7C7C777D314BEB672D5005CC7795FE3FAB6CB0900A4" + "C4769CB2C566517164FAB90A4BEB257D0DE232886A7863B82B6236354799FFD8C62B364C4A" + "80C2529E00073CCF1255A6E93D6808EE2CAF27E49638C4B62BB85110511327EE0C2E3F31C2" + "EA87B4C00BE73C28666E35263323BB35314BD20E548FA6042E680D7F533A6001032604DA44" + "D58AA78A504BBE1DAFBBF2058291144BEC5739212795E1B6D833BECD5B01A8A1A9887CD647" + "BE1F6A07D7F1127C5844D961557BE447B5282FD47CCD2A8906B2E167B194D46AFF3710D07A" + "25DA39EAF7D6331752C401EE1E68A18C03B01D5FA4E39E4AD866A491A928FF387204F09E24" + "E17EBFE6E0F7ED703CCCB98C7123A391E6A103F668CA121E688D9E0581049B473861AEEFCB" + "CB8BFDF5BFFAD7F63FFFFEAF2660F01ACB1500FCE6DBDFD9C70F1F5EFD58BFA69C00408A6F" + "48297BE770AC0E0D3BEDA4D4CC126B4A3644E105D14F18A031CF62360669BD27BEB1765318" + "0107AD993410A361EA66750E24DD8954539F3A81CAA702AA7D6E551CC98490D197623255C2" + "69996BDFC921B00FAF7DD16FEA13298D4302C7585932874680C20F690D6F4D6D47F6DD9A97" + "E62D9FEBD6257E4BC94FC200E19BE04629845D33FC323FCD76C1EC2105EA8AF33D08D91632" + "1B41808FBA5AB6AE96A650D60430B9301819693968CFFA0240013380C9197FCF25197982B8" + "114A582F04EDA1CC84C9E03B01EFD0DA64B2ACAB67C2E14730772EE72A9D37D53E1FB2E7EB" + "D9FC3FCE6DB00D59A2C5E2DF6EFB33D510C6749673DE1DEBEF4764802ABE42CD1206D4868B" + "8B08A0C70140BEF9F537FE569C00B7C783C53D3E9EAC6E55CE59E19202441283BC78440A8B" + "CAA32CC28F453DD754D622914EAC8149AB15B74198954ABDBD6BBD4F0B790A7D24A2DDBDDF" + "E1DBCD60A6D5D107BDB3FC42D22F3D5712642548514D85380A16A820A97892FA6D9A9F0236" + "E553C06DCF12A98CBD4041FA4CE4BF2A560A9FAD9F6A81481DCE1A1A62391E9AB2993EB2A3" + "693E6BE948FA05274E97B9B4D20E80617123467B9952113B987F90A73A802A766F5671EB26" + "79FA9703E422A3213A8DF9A9B7BA5363D052911F484E89EC4BCAB90F703B00D618D3115AB7" + "A96F43BDB4559B871342861D6E340787C6277739C24C69E62562C72AFD6F88E6C7DB40181D" + "E919625F3E76EE755A428D8575015EA2D518DDFDFCFCB2BDECFB4C3F5E61791771737CDCDF" + "C058BFA69C00800A05300DA9C81940CF12E7B204D5A44C2B9AFA570A112EB5B3533A5AA6A7" + "81764AA5CFC4C04038F0471314EBA719827CE9CFAA8F10405E60E3D37B219DF0BAAB809F21" + "E65F730DDD3FBC9BE9CE7F031D15D571BD3F98BF8C3BEA3730A9067C06B82B7E9FE68908D1" + "2888F01890DC65F824D18729EEC85CED7BAA0D76ADF658BBCC6190B9FC0914D991D405F547" + "9997883314238CD242F911E65A4321E750EE4125EE01C83AD6B80128578613D3F4E02F8163" + "B51149CFD4925495A6A59B860220430A01775491E704F698D226701F2BA285350BA6CF3023" + "ADC880C24DF57B399E7ADD2538C211B9BF90F20B7C75A4CC3E889DD1F7B0D5CA84899956F7" + "0DAF69DF63DFE2E56D30C48B5F8E6440270058961300ACCA8AE1B538F789F88CC2617B40FA" + "511EB96041BDFEAAA0FE8690E6902152AA6529335F1B99C632BC0CF51128F86F3D0733651C" + "F3B17EACEB461A6E4075E1A416350237ACBE674908896A7484E9C4C537C89978EAE7E7CEFC" + "1148413E15BAE8E4ACD6B4268DD98F3F055A95035E916E00B789AF024ADED4B600485E7362" + "83E11FFC86987FF15805407B011538B6D52AF8173687F39C8A578938A6465E4D08671219D7" + "F1CF26F324FBA0B73F3D44497DF29425EF2E20B1892FC7B451E93B312DD07E0513A7D54C07" + "41673D0B01E08885596E9A65A50D94AADA7ACA6F02B2C5D8693E3A68AF8BC0167DC3DA8EEF" + "AE4BB0CDA1ABAFB1C463D43E39CB5C4E00B02CE3701EAEF083C00EA6DB6CF7BD7489C1C671" + "2CE9B750FAA250A55000506C719E65A111448E533262260D1421D2167E84E423C0817A6F9D" + "98DC292C8DFB82CBCFA43893B28402A7F1BC5CA55B8C2EFBC1A3805777C599339012A6B760" + "2C2957BB35837BDAC58920278FA57CFE476EFFEC27385EA092EA7232615EBFE3ABBD77EA98" + "8A62A841D25ECE67687AE9A0E522B574CEB531A31EFDBEE66358EDE5EA49C6B8D3DC814F67" + "DA63A4D03D62E1B1E371A5724AC7414025C45FA403B2DAB7D4A12992A5EA65D5D95C2AC914" + "D9EA69116A8E2A4225A5F912BE793EBDE84169A7260756D77AF9D0B62C3FD100B0CA0290F0" + "1334766747DE16568A822E49087A707B032180B7F256C6F995E50400BD900ABDF2D5D7C164" + "D9E7BFB63ECEE79D605CAF15E56767B9395577649F4EB1A713C7FCB02961387E0CF9496567" + "F6569F06307D634450BB7CAF52BCB29668EF979005D1BC5227A7635B11C3647ABEA8D3F503" + "ABF79339207A606DF0A839A1BAA674CCE3E992ACD99E42C41852F7F1FB4E511BDCDE718362" + "326356F9A8147FAB833D482DDBE10DA4B5BB8111EF652ECAFB0F080E4D6088267C20BA296B" + "9F4C9E8BE41A144102FC0990595834DF25C028E98C6B47615446CC59C6EC7446782F8B531F" + "9D2BBEB8AA86EBD827ACD2CF47C6F3FBCD9780D6CB875F803CC7E763F4C9F37AA1AD1D2932" + "7B358D960F33025F9AC47102EA13C05A1DEC73A7666AEBECF781D26B2BE189CCDFC470FFE2" + "7202002A4804647228F127E5DEEFA96E1B9FC48537A9F666C71CC37B86C3D91477552A3941" + "49BB902280FA49DA6A6F33DBFBDA73D08728C496A88C3371628A5FF392840D8CB5C2131D63" + "75E622092E722CF5BD1A6B21E5A9BD1E2C1273C70AFC9C1917672ED8F68B23B4A43CEB1282" + "0B6C92E86A028F38FD7C80D41DEDCF118636B4141C1EE0401B79F784C5F02BC8592BC044D7" + "1393DD9F41AD760FCE8B6C3E5917DE0BD45E6124C70EAABDB0B32BFC7C4155EE8B5ADF5047" + "4CD2893BDE5298DE3032EFDFEE50E764300FBA18888633183DC9E8E4A177DB9B9CE82F376A" + "4B76953DE144444EF90BE0AA909EFCE33C4812C123A454C663D9162271A42FBC786F491F7E" + "EAFEBF584E004065DFF787EBBDD11A3BDE527C34A8DF04096148C70301352539734D82CC9C" + "73452ACB7CF74A58A3115022900622B4967813C990D44240079CF34E088056265C0E6374A9" + "BC6CEF1AC9DFC0948691F1BDFFDC474D7F4C0CA4A12CEFE39F785993A2AB1A242702B7A09A" + "BAA77F7330CCBED49A77D3C0E2DDEACE4EFE0DE5E038F65182BF92F2A93BA376C8E4DDE473" + "301A56FBABADFC70106CEE7116F4C9C34AC1CF93AA7673D572B0FB6BB02E8C2EE009424D9E" + "1236EDBDD20CDD3A8C287AA8D6731FD16488D32652F18AA3DEF83F71D025CD41ED559EDFD1" + "06D2048CFCFFB1913663427C35563E763AD3593FDA4D20C678FAAEA820D62A4A423147632C" + "0C72AFB32CE7E92C554E0040851375550993E332A9083B332F89958042BB9273B52597C2BB" + "EAD387C313C93B2D798A3A9269DCD02CF196E8D3189C17036174D36FB393B18BB401222E80" + "82D5A12CD937A6317D9F4C93AF0096DBE70C84BF889ECF2686749A1BCC147491BCF469768C" + "C043F6EEF0C5D86933A07DCC49835B6EB8FF36789DA099E15C03C6771570B2A75495D318D0" + "40BFA4283BD6172A3F53A069C0C6E54ED11C054C3836A6838C3039203C5F9E31F73C2341A0" + "90233A9AB3A54C9E01FCDCFE6E89820500B7CF05248C40D138BF9430EA765361CE8B8756DF" + "0EBE6602C4436E0A406298E9361916A9FB739FD689C30C6DC29FFB7E02F01010DAA6AD5F49" + "CD257CFEEEB596D58548674139010095700A519303E80BEA3451D5BBBF96C43A113894E54F" + "4C6B1AD739CE700BFD1143AD1269D20FA8ED9AB804C72F7421EF2EF35F1448302522B58719" + "1A2433A73870AE9B96235AC7529296C42CDD4B3C8EFBF8CB313079314BA932E7DD898F44AF" + "E22BC3BFA0E2F317A0B19624120356AE7FCC287FCE8F0C4E663ACD8E692289E74B3709DFF4" + "453267D45080F0282D2DE66FAF3988B60FB8C7AA5F486D4281D345589F31F391FB2D5805BE" + "C3B1CF3019D07A61330579E763B916172479F64D65C2DBA554E0F2749B21ED13A00664F45C" + "49F14E7B25F7094FB18079B6E833D0C2699D8E8CFCBAB5F3CE193043FBD58A7BF8BDDF5E5B" + "D9F2BE943732DEBFB49C00400A282E6EDD9B437B607F840433314426762C951719FA0BBB55" + "1A3D665041DF530E70FABE9AE56E2C3DBA9A1416ED37B23A4ABD53A19CFC5C8F106EA843EB" + "463F62FE2CF179667F2BC6E12D2D314D52F49BFE340DB1A50A3B524A0371D04FCCA8F1DCED" + "BF1BC5934F170781292740B326AFF7E9170CC65DA8B90931778445C779E3350EC983600F5E" + "CBE1877819BEA5A436479E675AE655144503C02459AF7203A82989C2179B4F8830434AD9BB" + "2CCD77D2BC8FB01B9C72AB50822D2E7AAC091404183AE79F18D129EC6C8AA18A07B116F654" + "2CA005B480B4C313BC6C8883684151AB3CFFD3E88E67F7EDCDC8C5FBCEF7519EA597130050" + "81C34FA6D8BCE2EC0C73226FE29885DAFADCC4820A919AD4A1F7982D97A332A121C9641A77" + "8F5E1149633E49A9135C6983B0F6A40ED8A7C12AA12DC93C2B75D453297003154D790823E7" + "3B198057BB1AE6477661F8E90D61F868144C9A98579F2B061FF59F59042752DD401D389D98" + "9FBB23600B6D5C2DBF93F42D8B90F5D35C1F0030867621A6CA28DA6ECCF52EBE283A448DEF" + "E7792D7F0C91F4799EDCB479AD7FE67D51EF082CE9E72094F1170096F902C7AE8BA680BDC0" + "9CAF7FA47F0FA9EC055852C31CB5931AB6E80B95C02BF44C6428A1283C7A9C3F0674F826E4" + "3375C61AD81E6D5DD779938AC73F99C830FA58660811E426FADACB6EAF3FDDF17F4B390100" + "952BD3981C79493AB505A998CEB4F27F133ABB207073C94B5B1C76EE4A3766C455947D8030" + "E3FB8929D798BC08D5B20B3C0714EF3C0D58AE5E2529AB0826854765A29AE60F5119D844F0" + "46CF45B2AD280048C43C76C8426364A4F64FC0C1C610489906A99716C967C8B6304328BA0A" + "CA18989EDDECA0C9906704E9E98608305F6816281360F932A0976131011D6182D58FEC28B5" + "C9A6A9698DB74232C03B94E4277318E49A93E3263819A73C3EEABDA6E4DD33B4AD3BECF1BE" + "68CC1F801621B493752957AEB4365EFB51D6B1C3AEDC4B38F0A4B5F2D9D410478E10EC1400" + "91EA6FBE8B5B8CC62B41E1802E4B50950872CC75DEC5C1931F8799A48331A1106309FF420D" + "E47FC7E5EDE83ABEAE9C00800A349FAE042316C4B1317E6609B36C8730B7E3EB58D48BFA45" + "EA49E65ACE7E0BE645E498091B8812883684375CDA135350114FCA2C73E1A3420C75F11255" + "41637C9088E5738AD6E41437AB7EC1FCF379992D76A8ABC9A14B84A8432C2CDDE8B9C11B1D" + "1EE7AC51686977B32FA133EFB21BA2ED0D5A85F2282D8E31EAC97ADBB5BDA4D7F7917C68C6" + "82A3877253A0C3194A444184FA89442915664442DA1588F1FB885830D2F2546ABE851DBE8E" + "CEF85E12F2509E8B3257C0C1F3000DB49C12B14066110233E9F72087251D44B76DBC97E70D" + "6C55A4F6F1C7B40D2727803CA9AA49C8FD215D3002F5B923BCEDF502FD197931130B85556C" + "8AE0B5961389AADF4839F9FF97CB0900B8AC44FBFB7C717C60E6C58E4B83794E679A0812D7" + "DD31C3D40E984699214ABFEA62739D2B5D556C249D344E5CA18B31AB605D2B21DFEAA2593B" + "919D0434997B84EDD84E8C3C89A213E36027B024C420FE41C380E539FF56094F2F47617573" + "0A4FAA8180EDB9CF63F09A84E9875E59019A4A1F0886CA0971449A8344ACA16F0358543E89" + "1481AD8043ED93F1BEF755AFA5E57A33A9942641A42DD2C2C84AD41FCDBB6C675917C37AF3" + "C4D55A7026C01A0A33739CA56290F523404686DD653B1D045F7F3B2E0474DA2FA4CD11F080" + "E451BCBB6BEFC77C7E2B8870CA897100A6D85CBE3762F661513704328F4FB345260C93E883" + "B6FD6ACF97B6C60B182738E38D10D30567AFB7BCA5B17E4D3901C0A2C408C6A92292FE6243" + "851EDEF160FD84E41CC478D682EDF83C71DA83D0A64D56947A40229C6CA8DE6AC101531859" + "979C481B30A537153CA14E4CA803097552F5BAEF1CA900464614B424CB6CB7C87C13ED9C81" + "0FD9FC35B94FD60B2659522ACF7F5DB0CE4C4597A4340D34892BC1D60C9A80ECB74A6D701C" + "9C3C361A4397AEED0D99F0BC541402D507BDBF9A334241114016FC15264691E082C22ECBA6" + "5A98072F4A440AB5E3CDF482F9A0D33406CD370A961F49E60B9015DE342B644AF71DC7D374" + "F017123A3AF55BC34C73AF1D5DAC5D4AEBEF052E58A30020C1CA9E04B9645A6C372F92EA41" + "347BE55B30017DECA194FE6BED8CB40302D1DE463915005F2E2700E0C25270962282A60734" + "3F25AD553A24EF2733AC4C730B0C2155B316C1A2DD17EE15439C55ADAE884D825A2AE46A62" + "E2F8DA8558FD0A6914AFCFEF979C4A49EADC4D987F8EBF88204939397EA831738C0C32160E" + "90CAD529069AC9670B67CBC56BDF3577C42F90CBF9FBE9D96672288D40F72D70EA4A85F1A5" + "CB97D6AF2D90466030CB4C3900FB063403EABF40BF8F0D3A254D4A6046CE7AB8F35E8F0AFB" + "DA4F1A9660867B7CB76776BB1DF737B82948B6406A690F64353C5ADA8706C02A5D319D8869" + "A560B2DF0B4CDC3E6CA49DC9903167A0C1E74CE70567A2A3FA76366E63F5DA1241570A43B3" + "A042C336FE5BA7CA75D57853E17968D300B2071DA07981A1EEF517F5EA3A4B2F2700E0228E" + "47CC80FAA1A3EF495AB5C6BBF3F79206C62F3E1E54E79F452AC04161BD572E1206D13CEE7A" + "63FCD6E563CAB816740BD06C732F52F58B6935EFFFAA8828A51E4F49A76112CF1026D7F9AB" + "A1B3D83A3B4314E04A86DEE3C245EAE5A98D52E6929D7EE6292909AE4531AE731B8C226A4D" + "A2E09421971FB741AAE63DFA5827CC8259F58C7AA055962447EA2392296DF7D4248C776E76" + "F54A30059F98AC57962AB0276F6F92545B10A11243619DC278FF8F7C01E9A34120A9340414" + "D228E7A5A4ED8CE088029CE978C8CE7B2545D74EE468D5DDB6C146E396EF9F4F7DCB0F6022" + "A48BC7FECDB5A0200600C2F5FD6DE2BB7AC36058FA0B44451E6D099CC907187D88362703F6" + "56D650F6C9A1FD6FB1AF77D1EB2B7E46017CB19C00408AD795B075AA9270044BA92A25CEAC" + "8FBDBDCD5235077EE1EDE0DA24B5F4FA4ADAE9126A674C77AA11E91D5DB08642A8B52227F8" + "9925B524AE56AA119D8985FB046732B4464C5962AFEF527D4C6AEDBAD98F6CE5964CA4FA03" + "F5AB99DAF3639A476D1F23A630360EE76BE9819D9FE7DF2D193E42E278FD63848566D9A5BE" + "E4AD5ED2B3A23B9B3EFBF08FE8B90DDA6ED13783B8FC10946E9236F9848A7DDE1AE891054E" + "F57341DFC924A532F57ECC4DE25E025F1D30A603A2F77DD245E77CA3CEA8D315C3F3C170AA" + "BFECFD3DF56E8D4D7B965B68B6F9D3C6F7A84B86AC7A7A7D66A308025A338A2A90BE938324" + "0F958F343BA926DDA914CBE55B43F3F346CA5B1AEBD794533FB2282427D321ECCF257399D8" + "DC2094F9BEE1B9A98EFEDEFA2B664B084522E250D8C48B9B45EB338FA98D405B6E5161AAA1" + "A037B201A0832E284B9D929E7548445DA674B9C296FD1DA0E2056D0D9A13E098302369AD99" + "079CEAE679A8AFBCC2E98E6E8031641FE5FE05158269B0F5FA98223888B1F62556EB226BD6" + "D2D256AB2BB0E79558A9DEF5D55B8AD6CAFC2235F170021DA69A3A93CECEA4049D76FB5E10" + "0697BE8715E038A46EA915793472421B5E8D605352B1CEE3E57DB7A07E3B315B1D0947D550" + "94438E89AE624E739233804B3F0CD74B78B0BE78164E938BD999B626CDB9B7E356EBCF3728" + "3A9C013DE8E241D2B27D99089DE58D955303C025D26B94894477CE69EF4CE789D486A9824B" + "4C709F45AE2A5229CC582A66EF6AAA3589DB969A0C1A07A5812D8CA2B5AF8429EB3FB187FE" + "0A4EB8BC61C833CFBF0C67A849B04D709070A6443E105F33105BED6F0008753539E5B53FFE" + "84F9449FED6AF6545767FF106A65D08380D30F55B1AA66672998AD07078F46F483D89977AF" + "7BDFF6C174A83335581736AAA5C051F12A8233ECF3205A8E6CC1EB2AE35B530EE6080D0885" + "0486AAFB3989138743C6185BEDC1042A34965AE7DAFB1CDF0EFFFC6DA3352C930F062200B6" + "24FC23AC301539BC1FB253872A1F7E09053F99043096927D1CF5AE0920D5ED5A7DE59C1F5B" + "7B6EF4C903691926984466981448840A450B41BE73C65F6539F1CE17CB09005A496B1C52D4" + "1EDE4A49027ADCF08A09E64737F22D62463639D0AF8141BF8FBEECA523939EAA289379DA5C" + "17791BF76EA2B1F97BFE0A4A072F492B16AFB15940E68C2645E2E4597AEAB7021A0315BE8A" + "39256ABA97BFB4A420D659F7A4424F46D26E499B96810799CC6E522937C7B5763FC0E48B47" + "57FD8A1B01ADCD2D3FFD686347CBD235A9963101E54F6053C54A1EA72D2943C208E150213E" + "14E42C181DC88929067B77D9707ACF2E401E664BDF29464976772EE2509A0FB0477D999078" + "1629C18F742768BF626DD3D1E0C6D837804E893420FF13A7B390F371BB75946801722739F6" + "A812890680749618B462AC785FB7E88A00BCCEF296C6FA35E504005C6E6ACBE1794BCCC8E9" + "C89536A0AEAD25B040DFB3445CBA0016C7161BB30BB4E5EC27B4408943A9C82B0FFE1CF2C5" + "39E9FD4E8362BA94F0BA11859004D8B724FB0B0034FA3488664AAC6CB26049AB7A5553464C" + "3A9D2B938891CF82F3EF4E54D449AA9D049DA036F93B7E04F36445342169C74E549E45F5D0" + "B92E33C1CC7315A0D5EFA476A08C7EE933C18E7B99DCC7296EBF9870D63552FED290DA0A11" + "7F2414925A19B55639012F0699681B79294C7C1114307154C880D97B0AF631810C99B3E0F1" + "F77D87E8066C1FAFF456660C5E33A2A687E3429B241136026056BEF371D364C4C6F4613C01" + "32422A7FD2427066C11181E1A97DE4FC08D5315A3FD739B7B6C4C0213099C8565B9CDAD75A" + "BAEFC659B49C00404AB1401CD770108DFAAFB8008F37BA28DC98131F44E1E81031BBA35EB6" + "CF2AEFA003CCD25F31E4543730818B05F33711599A144F7F74D7E751B7C42D97F812C2C343" + "5EC724A8F40A220BC90C9972F27A565C9F4B77239434D66F713B88A924BA297B73EB8FFCA9" + "0C88D5DA7D82A67C0A554F144EE39CFE1AB8904E9407B34E4691FDA810C9DBD4EE2317404E" + "2B0104D3FA791FF47E01D471D220D7F1E51A4E2BDEFC1A6AE3EDB417D172B7A1F3DC58D3A2" + "B0E4DCD97F58FA0978C3CB514192E2F7C031F8068DD96D8A1D7E7793473FC1D9029919A5E0" + "7CA64625C254949D9623233BFF954941C10CD3861C6BCE3ABF5BE038CF8269FBBC523A8179" + "DE104E78FB67E31CDDAFBC6C343D6799CA0900B84C829B934A39BF62D9432506A92A19B55B" + "991512C9C3599AC4D4A0FF793BD7A482843D0FF656D87167456F5741D3403086A9F7449034" + "441FD3C2327549385ABDC86901473EC47D130B65A971CC33085C8048B3ED5FD4C051DA07B4" + "8739E57648B580EEA6C734DFD6937C92B0410C17F94C833B28EACC286BE910374FD32E4890" + "B502558F4E24D043F509371F82E5E5F374EB61FE5A55F2EE4E309722BE2EE0118E0664E211" + "D505F6D9A766E1FC5ADFAB3A9C9D05354416C978E074B7D5A1AC7D238C5A3543BA91014C3C" + "F75A6662BCFDBCC909E8D748F315C196F6FCE2D9B82C4C921161A6F45D3AC3729F409A11CA" + "CC6833BDA957F796B8CA69B790AB6A03F59C02C878974DF4E09596958AE42C55CE28002E53" + "D6881054AE3F45FB6615A00DE9B588542503224E11AB2A981189785884380FF2CCC07B5FD7" + "5FF52759602B9AE5FDB96990AD561A410F9F3348C0A981F06A86EE8FE7B90D9B40574A9722" + "650E10C2202502CF39478D7582BD642284C0168C05A22B184D8EB3FABA506B3398001F08D1" + "5E549F3C432DD177F556E75584148C011BD663D2E430261DEF3AAB22727E1518A757BD8C8A" + "187FF11667F0437B7D626F5EE61CD92F095016E012736D04848DFC0368BCB4DE9EEB338048" + "30234D0C9469915BBFEB2E8562A1F0B84792230E55A479067C1534CE661B6F9FD2B3DFC959" + "B1C6EC54A7F3E88610C07E0504DAC811F00C8E3FCBAD9C1A002A11F1734F1AD3A57B6FDFE3" + "4CB10A9A5E23A9BD984C8934B368DE048FE3A323456AA545255B346314F1ECAEAFE7D8F719" + "348067047F21EFDD990B216E4A8349DEA3ECBF2B1041B668E77A54424B693239A94AD94E04" + "3A9915886D3209BC5F53388D0B6A7295CD8121D8A9921DFF8852EF3B811A5AA3B4B52F72EC" + "47EE0BCE5D5004BC271F92166BEE8479B592EBC1A600280700766E4D5D0820F4EA74C9C9DE" + "CF3E3126EDD4FAF5200EE258C888480C9C6EBA0B01E506099BD24F77DF03209D2E1DE7E720" + "A739C67C0077936C5020391936220BBC4C1D04F2AAAE9D3483F3790FAA3746F8E0316EC7D1" + "A1C9C377D0AA29D0827F4C82EFE797E70F97FDB248FAF5FACA657FB40F1F3ED87E629E6539" + "010095D8F71FA72F475939F318D14C274111294BC7DF8D7D80072E384FD5C15201D3662FED" + "C174935A9830C1AA2B89C2425D490F81D91585EE545F32C4103157A9B0FE2A13059C0865EE" + "CABE895BEF18D810FB164F6C78F6A65CA5B7F75926DAC9587470ED268DAE0B03AAB2077749" + "BA39FEE1262448AD1398AC65E7C03D0686E86C49DF30EE57E1A889634A48FDCFE30BFD8A9F" + "48C73B9B18256D87E1BD5E4C2D254C79F60000200049444154F0C8840D6F7BA331BE95528C" + "B65F99986AFF50FB144629CCBB4C109968089C106192E4685735134B8F919020B500051439" + "690F9DBFB2D7BB0E20C6ADBED3965AA1F871DDA4681CC8E3B2B677F6839EA575AB681EF611" + "201349ED779AFCAD5D8EB5F9C337BECD80E63596077F1CF37787EEBDF172020029FED0BF81" + "335112B723F4E7284A0C628404394952B9F544DABBB3171B7B6C0F920D53A4CF6496779CD2" + "A4ABC810C674A9D3352382CCD7BBE225F489A508A997E850AAB3D99CC2767B0B9A5FAAB708" + "38453858697039091001AB54EF1613F152D1B265D952B064CD037082303BF1A3C87EF37292" + "B9416EBF5B951EA161CA28FD96029808FCBE63FD2A81ABF7D7274059A0B1AEFC6D5A084ECE" + "2483D73E29B8B0C1E8B5EDE237032824CB2A27CA7B6DF0F189043506A89CE072CA70C8734C" + "8C90EA522D12B9E49BF392D7335B0321D57E820BEF8E83CC9535D570FDDC9F2F403CF6E0CD" + "A9B0F910713F427D0C786F76EC866499AD5172668C1112BB3D6C6F461C7E781C247D5A8CB3" + "D809005A0105218943C3FACC384E8A29AA4D84D98AFE350613EBA41EF73EE7DBE5113FA412" + "DC174FD9EA985171E421116B78C8DF3F17085D34B1EF2A2E110A0E8133C5C7E20C140A5834" + "5843EAAA6F9104DE200505C5B7A7C296E7BB24298E50885A2A8BA84C8CD58DA4A68EC92990" + "31E507805476984FAF6066A394BFD8071D8481471CB7221AD989993FE6FCECD92F0E77ACC4" + "010C4AD8EDCF0B6065CBF817CF5C99CD0D4FD4654393BBE1845BC298A964DF67931232DC8D" + "BDBDE3F76256C14041DB61B381995823B0D3443B94BDDDAAE27A3F01435510465BA9D8A9D3" + "86C8FCFFD817333345BF01400A38B4ABB3A9851A4FBA8ADE40D996A7F6C12AE483C1032F48" + "1D3FA7F11936739E3123D03C50FD51ED3E40F2D8639370F17ACBA9F8FF723901C0AAB46B4A" + "A1AA66DBFB7C862A22C0F16E112B3722B779605120FFCACB0DD523AA001EDEA6DA092335A0" + "1B0104A00249EAC2A0C4F1811DAAD8DBBC3B934D99014BA34AE27161A9C1D89278D58D6A14" + "F14C732659CD60F928260F7506E6EE184BE6094887C2CD143E589BC75CBF54CCEFAC0016E9" + "1AAD050087ACA2D11AD26D7E430B51572337A1BCBA7DEBD84EEB0B46E6040464AD5BEBB91E" + "B384CF8049EF0E4850400813261FF12B216E1EB4EE8C9B18F4BACBCC01BC59AD95EBAF6D30" + "8A0434A913AF08A3A3B66E947723C1C8B6A53F01690BC61C1DBC93247502C4958993A4728D" + "042226AE155046413A6F990CA17D1FD40FCC441B7B20AC76ABA5A5FB151CF90404DC35F0F6" + "AACB64C63C0B971300FC42F12EADB0A86ADE2E096A7545660143BC72F0A393C8D52B102F9F" + "E573E0B583842E3902983112CB38C987B66E8704FF4964AB7A9BBA75A683961D039DDBD4BB" + "7DBCED4D4A3FE871B2068EE75FDBD671F14B5E139B7C0A8CE168750758A8B1C2596FE38805" + "856D8DB17A499733999953001FCD6C240E237C6B9F081531E3ECBB43A4193E86E38FD9E430" + "25CFA9503C021E995B41F4E746482BB54E5E5A86E034863C3607738652A58D81187049CC09" + "0A73DD78EC59FB7E275110BC4C21DD1F1175EDD9666727E083FF8E14C0350F706634B91D90" + "40C63833E16E3E6D7833B1F1F7A5A5116E8BDF446146E6B1E9984A1E04F653A2CA72FC63CC" + "6F29F4EB0C73FB7239E7A7159166B2B89E25F9C0826263929ED269D28C1884258888B46A3B" + "CFD68F21AA7B1F947675A18BF4832A2A0F78616BD393FF5525D8074035AE83602561224783" + "F263683CAB40CCB828C8936E517296C66AEB82A09296A14A07E8D146D8A62A4BCC11053486" + "BEDE08F9526FF66A9BF113C77CD7772488D7D431778A6A37080019C1850227CDF9B2800FCD" + "8386491A813F230ED3579BE77244B090D4EB666B86575804A19753D002CF1526AE3445EE00" + "451698E4045F32E268DB7732D72DC07BCD0165E293FD000892DF3A317F38A3D25B4C2CE412" + "246F4053F406358F9D6E54A97B497CF821251199D0B59A13C5A7C4E476CC7B4D9DE56D9653" + "03D08A3060A2CBC1AA6D967E423DD7F5B7E3DF92C28A7FDD3F85C26F3881CAD437F69CA717" + "1615AA80170B74A343E65292751261E27A3E515717827D48511BBAE6D50310E32609A6D45D" + "D399BD7496299B1456CC0E4CA5CBE6050BE4779DA8ECB395B6215858A7D9CA04514DCA9FA4" + "784896FC1D204AD077705C443F688E4A03C13FE53CD038DB15BC7892A542060570A69BB42A" + "112255CA165E3C0BED018325A77AE63650D500B3DD6CD1C05A6982F2110A3575016FEB022D" + "01A215C4E5677CCE78974A20D430363BFC79E64FB82617A2E444796A91AD90A31168532D3A" + "00F395DA2057B769034EA005DE59C7ABB89A3816FAA6D75AF62F6D86B39C00804B125894FB" + "C8B9A203C6019334F17277C038925F03BDD9D61829ED33B14DFADDA30288044C9A046F843A" + "59E55A5D2952B111E5F156AF64EE33CAD71F45C0EA4A64A7FCF242B4E8AAD4B118DDE69BD2" + "1233045CD90C1381958D1F904109632FCA140540302AF3984D0077181B44F5C572B4C725C5" + "AE78BCD76C6AFE16EA1A2E23C228FB88678F81E3D7BDC600D025FD6FF75674815E6A149036" + "9ECF7D6BB805A9D62E9952BEB5C86459EDD65C920CEDE46C28BE3964EAE84EF696FBCA0A34" + "EAEF8EEAC6CBFC39EDF8656BAF7D3BE63D6DEED4DF7017107F0F48600E8DCEE9D87316F04F" + "608D5A452919997A40B3F8BD284DC0765783F3DACA3607769D85CA0900A8B87BA3E3DE8E2B" + "4B822C953B08A8511820E9E4906DCD4BAAFC254C8030BCE6894D5EC84284219EE10B765473" + "104866A159EB9768423D4D894E8CA43C3C988C8488522931C9D59E8C9C1542C5EF6675257D" + "7AD5110904E8D6391F9D2BC9BAF8B92ABD3B3E8234DEFF20264BF86AC9E78355EDB0696726" + "C0A600A8B69CEBEB826F42C7044A244D17E2A467A66EF54DB1303BD5FEE9EF133614C9BBB4" + "5131BFE330C520EC327FD2E44B004198E082758BF9326F7B6E4F319ECE60862092D6801688" + "9E3DEAD9289A448161F6C7698DF86230D42DA1AADB98B07436DC4C4049F1F276FC3A23E6FB" + "0CCA1C5234A0268357961A30F58F61404F4E9097CBC5F691A4EAF597EB5C7C5803F4B39C00" + "80CBC3B6EDFBAE8123D0CC85D80B8D4138912CA13F22542B012E0FDF3BFB32E850D7776CFF" + "67463A498CA845184B79B953B709A474C2397FA3AAF83D1A91632FE7F47F10700036CCC231" + "A4769210E55ADFE6A4D6C3A10C9C941DB76CFA3C0007E19025732EA62293775FFC2DC24BEF" + "2C41864AE562AF2F67C580445F626E3ECFC6DD21FE86DDAD33D3052F30CD18A3575863CD73" + "50645D49B788CAC8FB1A742769A4049825AD01E9545868EF592A79BFF5E3218E8DC455B185" + "21A1C33C9640F578E461A8FEAF99E19C355BB44FF2BE4BCB90D2AA834263D3FC9334600FB1" + "D9D75E2BD57F82603E07C72F9B2DCE48F1705CBF0D4AA2991CF9A08AC6AC5C22B03ED7D1ED" + "FB1E97CBCB9B00002EF372965E4E0040252262F3CD9A0C722B93A7AD8BE27AC92C8F17E87C" + "3261CC8C69FDE13AEA8B0800F105509154A4C9A9CA24173B9321D0EE43A49E3512ECE41645" + "B12B998DA738ED243519AAEEEA57FE9BE783B504B7BF93018AC45322CE4A77A1527D081926" + "FF05C755B5050258DEBEA35199183F4B93FC3B6A6048987F6DE306BB1A0ADF11C143CC480D" + "313734C6979DE1FCFD5DAB900C2401D02263A01BAE6FB65219A37EEFDE6534A71AF717267B" + "7578C7F74DDE306309F22CB45A2CC6C1F52A4C1B1F03DD91E4396DFE46376F57EF667E891A" + "7F02CE1918E77E8D76598F9B3729DEE95FAF76B3A68C3FC99C00C6697E6B2FB868FC3A95F0" + "8A2E42536C424AE122F7CEE649A5C8847135034C07FEF5151F59DBDE82AEE36BCA09007A69" + "97F488A73B317C61D47736980AADF3819EDF99BFB152A922CEBE32D8994DA9712735404A2B" + "BDFEA2DD4E97AE68BB3E793477EE08EFF043206555AB13A19F3A045FBB92803336DDC9ECAC" + "6A7ECC07D5D6FA8DF0425285720225F2A4670F69C9B3CF7C55EAB985E8C3AEDAA6A3588E80" + "0676FAD3B9CF7989A5E35EBB43C00026214663494888C69C577F690D5893215BB2A93F9CBF" + "5BED9BA9C35A613A73AEFC23829EEE22BF614DEA717614ADD195685776762BB5B9E6BD706C" + "3468062814A383F04CC3EDBDBDDC8BDB74583A468726A50D2DDBCEFD5D40A0C0C254334FD9" + "4C53722D5AE2A24CA885B550743B0397D759249FC759A672028056E050E76AACBB15BEE884" + "FFDBEA486ABC793915091374957AA632792EC103BF187966D3FB82D464C5F0626E30A60FAD" + "5D64D22B5EACB9CD8684485A8419E38C3E20ED61926E0D883A88D74E4CA342A81D95AB6D3F" + "FBC462257EEC990E8BB7D11C1408909E84A6EB0DE8796A0CBB95BF4275A60023181EAA6629" + "F4F8017ED82D8F4060FFB8AB7A9CFD3E76E7B9981DFC7251662DC562814CB08F455FC79608" + "88751C1D78545D7C8115F39EB64F0A5FD055D1CCECB2AEE3F11D6B5D1A298AFC681900D36C" + "9195DDBBBA1B6051A57B36FBA9CA3DC31CBD3DC76005634D5F9EE3916D640564AEEE34D598" + "871E5D34C11F5ADA2DF03DC251698DD241D6FDB87978563FBECE929BD0FBF9388B9D00602E" + "A0AB83C12D08C6F1F3FD0440C58BA26D3CD253DF65FE4604AFB4EE094838698A552F852B4A" + "BD2B4636F3F97B2039DAF8265527FD161C16986AD2FABDAB81E1CD8C1CE54AD783A44F1648" + "737EF8DA5B601BA73038C493CB5C13A3B13E5D2AA74FB6F99AAE9ABB35732D47B49DD4E509" + "A88459F1A4E3874C63BC8FEF2BC1CEEDD1234BA0074BD61CB5B0B79587640C8DC2304664BD" + "05AE665D4D24C3A1AB9AFB33D6B065D9D629FB651898738DD8796B8480105AAEB94C878798" + "A56372AB65875ADFB36107E3EF7707D811D1D72EF9A1BC01ED9CB91E82493CE0400A1FF7FA" + "A76DBFFC5E0AD8D099227007538C8BB620CF1DCE2A4B173CC1447B768FD8BF3232E9BFB372" + "1B676DB6B3F47202002D13272D8260CAF36647FF287B13EA5066976AD90566987837339AE8" + "36BF922CF4AD892E32332BA9ADD9FAA5235E4FFAF4FBDC6BF0F726D64DCF71DA5875BCEA19" + "DB528AF3616F150740D18AA8F48E70C9FC81D32537AEABD5349B2F454E54DF50876A3CC801" + "F3A6C1605011409333BFBAD38E4A75D6244BEE1FDF0520E39AAE989E3605C9EF3D4D6F13D0" + "274039D755A69FFC5D40A1DBBCB3B10FEB7DCB2B199CC004ED6BF99DCC03AC05C8B9B97DBF" + "DFCEA2A6BC0E59E7F296A7505BD16E10C3D0CBB310FE4B95A39F03F07AFD30D0443DB1D198" + "46EE00AB17DB50B0B778B8D9A14C079DA609160D0A80F0F9CD36B6080E5278CD657BE0F93C" + "4B2F2700581625587CE8400C08610FE99C7F62801096CC8F13AB700319DA34FE9E2D00425C" + "D551AB9799E012FB1AF5ADF2C4E71FC7879DD5A1CB4E933984FC13C44E9B1A0B96CA06922A" + "C958A4DBFC0C90502459A434E03438103627BE3289CCCC50D4DDA6CBC80C5B1C200942ADB4" + "A70254F6047AC96D825230AF1A366A94DB778DFDCF1AAA29F8E25B600E26E0C3F3C06DFBED" + "C2F4FA580E83345D613C588043E6C1B5B615BED9F312602D7395AACF1C3EDBD6495B857FCB" + "BCC3E918E6BCF475AF3D4A6F33BECB39DAB6DA2031D0BA6F0B475FE7DA0914D419D5BE6CE9" + "34C9B6FA0AEBF4A633C87192344F40073774F26AC0FF414D6688228831C6C7C7C73D9D205F" + "7B79F778B0B86DE5B77196130070B94955DB22364C65FD5B114FFAFA0E5448885902821680" + "9E7FAB043A3535AA48E28B386B2366A4F212D7C5B67396A01684B08F2F50C75C20E368BFC1" + "4D4BB2E1E424D94B76EE0BCC8158432B1880E6C96A8AE5EF1804139699E6C038AB4764785D" + "9BCF20C9DD44027752B68B6BC56086A5C560B0B298E414D887C2A2245D5E1E749B587EADC7" + "4E1D56F38231732E89562FDEB93DB6A1ED451755D28DEAC1346F26DB64B0FAB148081A9415" + "991C12D34491F3726B6B679F9183D5EE9C4F8FC27113AC21094E4EC2B6E823317ED3BDE834" + "D755794B93B1D7FD00047C57C70435C952DC7DD4283951431BF8930906ABF8950E748A75FB" + "6EDBECF3A74FF1F2F2DC3496AFB33C3E3EDAFBA70F76B99CF702AECA0900A85CADB6934355" + "93F87B1890FCBC384F759889DB80D093D8D254E12CF11B11A80856770F522E525D68751DCC" + "DCA3F47D3CDC955F28F5CEEDEADA6183667BA684EE651812B2FC39AB61C53B9EA421B92BDE" + "558A2FE99B52A7F210E8729B15F3C25C212282B32DB27240FCCB595340D2EF6A7659D8CCF4" + "A40CCC70777E4AF42EAEFE3EAE0A2EF5386925BABD3FC7346B8A345E3FD5DC3B338B690320" + "86BDEFD305A6A259F262C6A333B44F9A16A2CD12B403ABFDA70181865527D0612A391B4DD7" + "98079F3ECDFB9EC17530C367F65B4DA4B4EDF4C4581F1E34F9271CE3DBB4C6721654C0C77D" + "CB7E5502ADAACC10B91300FFA55F08B787C7CDFEE587EFEDCFDF7F5FD2F16B2E5700E00F9B" + "3D3F3FBFFAB17E4D39010095388C87CD6B9B0FF48A40E6CF2DAD68313F307995C40D0F92E3" + "21FB58B3E459170B4D8478F6EE57BABCA4D27745917BCCBFECF245CAC01D9D0DE325D1ABCA" + "BE9130023E86FCEE81B9D65C099C900075C8853215CAA74C86A78ABD0F66E685B0BE4AA642" + "6B098CA0E02C4A7A6F2A04CB70814C574B3F97344E19F2E8963D1FF6FDEE1AD0897BA9AA03" + "4F4DC392920C72ACE0F025C0ACC7C2578AD509778ADC19C00720A7698C37E62ACBC975DAB7" + "A47589B6A859CD008E3B9F050780CBB59750DEB6B6B537E95CA7D77EF6BF22C95720BF815B" + "1BE3CD10530B92FEF90CE45E254CC6408255FC001B946F83427489FFAB8186684D1EAFAB46" + "E5CAF83F3E7DB08787D79F26F70A003E3C7DB06D5B48676739010097D8177E4F598AA14F1C" + "7EFCDDC41567D2373E575EFA688472D629B0ACC176D27CC6CB5E1B75CE998ACA3896835A68" + "31BE507ABE03957791D046EC9BED596B4C2267836F9D33360D640BAEE9E3EEF92F54FEF57C" + "B411C498BEE13AD27E1ED4BF45785DF169BF6593D36E843EC8661E7AD2C77DFF211EFEB834" + "271B4993865C065420626297F26FCD5EA9B5C7C85B9F592B52FB8881C76064C1BC73392606" + "57CD664EE1794D89A0008473F4D78A116C0B95FECB10C08E92C2C8790FA07883A4C5278BC1" + "F2B33AC7BE1F317C3D47403F3F302B3492C1CCDAA12541100383723E43860816CF703FABBB" + "2E808FF86C70F84182C5B0A7F74FFBF6F0281A8DD75A6E00E0C3DB003B5F534E0040258C8C" + "EBBDF039224958638BC10C85E919187414C142181BCADCB6480E64A8EEF654D8F58DECFCE8" + "F3CCF0FA98F247FC214CDDFB2F4A7CF99640382B352D023BC391246A64F754A8E393EADF28" + "17424944633D24A360311DB63333396D896F1A2311ED82BC953D4C71B0DB16096484028832" + "D9501E62841A62FE19A2A4535EE1821A82262E9AB40AA2A2E0B04A2C7F707DBCFCFA01FDB4" + "28E022AF784CC0099EEAA9AFE67DEB752672DF6618A2C956E470BDD917E3A660D9DA7E5E2D" + "F1C8DA99B700DA48915CFD4DE9583C0D72A75FFD2CB60AB54BFF7F91C6B3A16D5B1D610542" + "9821A117B5769E70652BC0CCC3CB55064ED42BB9B3F68A6420D274684B76FBF8F1E3FECDA6" + "5112AFB5BC7BF7EE36B21300ACCB0900A8C4BEC715152B490FA13085BE8B7B36A6414C12EE" + "62D1C59EC969A84BE1B343571021542918F1D4A4226E23587ABA55B21D7A983DF3CD45252C" + "442B714F5126F668D6010990B8F14C557FC2064F632CEE04666170B027BB7B1BD358231E3C" + "A45F66DE1813A6030C4843C894F7A2DECE0A9193209291B085862E8F09A9B7313F7A9FC716" + "DABC9A813A53BEF3CB972340F0EE31EB3B31B9E9B1B93166655DBD0D48733C535F78D72568" + "B444315B33B54F90494A36D1E0C59CAE39F41C9984FE610E3C93E5B0BA981309259022E0E0" + "CE20B886B4D6226444E030176E043AFB7971854A635FA43F40F49A7539D80F88D1C7D0DEBC" + "BC5C2CEC32BDF71ACB955E3CBE7B77DE0570A79C00808A6FBE73D855112FE24075DF770FD5" + "6B75E10EF180334ECC2CE3DEFB690F2C07A7641E4555BD9E2B6528535C22A62CB9F6469B22" + "54EAEEFD128294C4CAF5B9626E1CBDC4147864D1CB0C6DC59C792E25528180463910A24700" + "4AF0DA2A55B5E4BFEFE3BFC3F918044C13367BBEEB8FAC79C175ACBC1AB244EE62FB2FED46" + "CD6926B0616DD160521C19D295190C12C504D0C6D4FD5D0CF7E01FE5481864633FA31BAC89" + "415FA5A23683B3DA1F21A24737B70242DCD7DCFB36ED6106DB48492DF73C8C5A0AC03849D4" + "3DB2B5F07C0BC8E3BCFC13DFA527477A3F373D33FC4239C08E350D0AFF33AAB2004513FFD9" + "8D0166B2C566CC54C31D918C317FBEBC5CAE5EF177939CBDA2727570FDE6FAEF7E4601ACCA" + "0900A83C3C3C5E385C04C703041831BF2909B73CE5EDB0AA3ABC7943572B8B43DC6D8B1122" + "1D4D0962D2C99054D7C2FA84009B30516E910950EF97122B482820A82ECFA10D25E04E92B2" + "8B06826E9BA38B7098C96BEBDC338448661B1AFBAED713CFA387AAB99B288A294B342583AB" + "BE8CFAB0D77DEECD618FA773A757775D93A31AA754D0DC479D576B8E7C3D99919A83347A40" + "F76580D1F461B5ED0A7C1695F8AAF6D24A4FE223F67083264822208CF67B8B049131B1835F" + "DE04D8F6757005B7AF864BDF82F771DBB7B9D8308773D86FC02390CEDB966383FA02EB5740" + "C6CAF190536D53F365AA48E65D20A36909742081756EA08BF7C9E3F6F0F2F0064200AFE571" + "A8FEDF82BFC3D794130050B9DD06386C63490E913F6292D1D7DF2FA40356D57ADD22C8CF71" + "1F9220C02BBD78116911389CCE8A2892A3D534BA2E95775BF12C716488510F433291AB5A5B" + "4332F54ED01A20B264DBA52919B5F21DEB4D8BA1043FA520B4318BC2EA580616692092C4C0" + "664F7E464DFA2FBCF4C79AEEEA6E98AC9B6FA0C37C44992FA86B90EC59D2AFED33C9EF531F" + "D994008687366FFF7A14C038F6D2DE65D0F13C9C15BDF747FA11B206E201235CC8E749DCE3" + "BED7ADC37BBD404DB913901F89D1FA2EDC6A867F29F631696032174E6E1DEBEF91FF4E826B" + "EC19876F010DEFB677373A1B79B1D0D4393077EBED374121353921F349F3DCFE12B0203E48" + "7EB389FFDFFFD7FFB3FFE10F7FB0F7EFDF4FD3FEDACAD509F0DB6FBFB51F7FFCE9D58FF56B" + "CA0900A8ECFB7EB96E188DB5CFFCDF791CC7F70E4D80782A672189339D7B4AB2B005C53D6A" + "9D4413967EC0F8B90256836F4B27ADAA9D790367129BFD18470B60CE22FBCBBDE8A06BEC03" + "203E1195EF9CA8BD33E3B746C0D5C1AD3B31F29CA69A3CD7E07034242DC06058C2B88079EA" + "4FF422EEAC8D722AB629C798809E7D4E73CD2B4FC610B56F21A185F92CC046500C39CC2404" + "020ACDD09EDD5B1C62A9F0836649FD030EEB04C2D18E79DE173B2BB315BA008599B137A06B" + "AA65E94F1EFB9EB4108EF929270B9F73F8F3B99045367520151FCF955F0069AD8E2BC2DB2F" + "191598CDC478CED176528B0A83AC73B1E99E606D3EF7C33527009B4C30DF346FEC5A34E629" + "85893C9BFB7EB16F7EFDF186EC1FDF481E806F7FFF3FD8E3BB93D5ADCA392B542EFBE5A601" + "487B519180F2469E2F95523C4E0567783CC017D54C4F8F3287036647A0526DED47A3480B12" + "3DA90438474FEF8A2F9E1F0DA564AFAA4C530662203C92ED8FC31F9B96C28B78F5E042EE54" + "335917AF21A2C8FE1A9259AE735F9BEA2BFB31F74BB228CE4CCACAED407B1BDC08D5BD2E2E" + "1EFA47A53BCDB96186C7200F1EE31281504C41986B933A57BA7B363730406DFB2D31054466" + "9E0B8E559749108D0E182EC0A5798250004327C6CFF92F625AC3EE1763755E3767F0873D57" + "F393A08DD5C33C26BA0930997566CECBDFA09D72310F8ADD221C8E838E15CABA8DFF3B1DEE" + "EC664F3214C8BD411BC03992A1458FE477CF2F17FB37FFEADF5C1EFEA7872FECC9D7536E51" + "009BDF4201CF3297130050F1B017FEBB189C98115B14C0282B769EE94C0124F46126AED3BB" + "E42C644500C90B996F055C7AA453C99CB52CC14E1F74E0657ECC7F172A0222470001068623" + "7A91A6EA87A923A699087A85F4A02569A774688DD88987BB789EAF6746832238C320F77B7A" + "6D089F90EA606610168C3E59536174CF77D1DCEB1A05810C767403ABCECC8B51FC1EE3E335" + "EF0361C0B802A44E2E8139518E892DFC400EA8FB4EE0106B23112B4DDBA4FE0BE362219662" + "DD71D9D0F8DCB762EE040ED15CE60820269BEF68FE026AD7ACCD898B56EB989A5070451AAF" + "52FCF3D5BF4447DA0736F7A32AAA87E1F642DF484EC26D0979ACE3BDCF9F9F2F666F2333DE" + "35E2E1E3371F2B43E959B49C0080CAE3BBC7E7AB8A2C0BB3315800FC0EC19CE5F78D886C4A" + "214DC6327E811D82C01729E7C0D424CC0C29912A3D07C12E32DE08E1BD029BBD9568ED040C" + "780AB662852052601A518490BDC8D9B3DF281EBE6AA28C78C64349F345D1DD41DE5621F939" + "046F0E816DF2F15560EEBF00A9A6D513D306809E8412F2C53EA449CAE5828386C904FB4EBD" + "88062C2264DE7D20B7BC4658341C34F7876684F23BB0BD98992AA79AAD3A7B18617ADED386" + "A06B9E3133141553366D384AF675AE421A0A3F0E95DE9760461718F9B87889AA4AF57E3ABD" + "D136BDB57A0BF38F3A4BA8C786FA9FCE4AB65713EBF4654D11E6AFA5C6486878F333EA1484" + "9BA12A7194753E67AF9C4E1F86D6202B1E0BFBB06DF6CFFFFCCF3FFDFCD34F6F2613E05FC5" + "5F9DA980EF94130050D9C33F975044875E585BC1F3460058DD38FEEAC400CC7FC12899274F" + "C8801CF138918EA9EACF3A93A38A4060BB88D0A4DD99AE54AC5FF03B242A89F0EAA4996859" + "EA3BEEA810C2E41BDE54C52DE6B97C20069A084223F058CF3E213B5ABFC88743F53AA3AC69" + "E0C4394B299E9F9FA548665016267D4D6F7E91F583E669D403064FF72B0C861821AB21EBB3" + "842ECEFD187966C224299174203B910B1468D76B2DC6FE13E33381C13A023A7FB829B338B8" + "5A2758A8A6E683EAC4B3BCDE143E9A7E08A40170AADC2B548E7D5320A2FBB8B33FEF00C0BE" + "831DBFC031A7001F6771337E96EBA7FB3C961EFC500120B53803149DA8185A1A80A010F70F" + "D024B4B53D6E97EDDD836D6F0100BC7F672F76B19F3F7F9A7E3BCB0900A4B8C7CB94356FD6" + "0BE2EB45BEEEF1EBF1FF4523C8EE2D8E801399E61A86B4C4EFDA8D19E0BADCCEBCBAF8748F" + "A31BDE67C2DF1F9D1205A548E345B445D14019F92A509208582C2A572F7D489EC5D4495350" + "8C7874F890D006288A20D19078163164F6A3406B4DAB41EBCB65DA17778A17519EDFE1BECF" + "EB447D6900B0FA3F98A000AE565F69E929E744D05673E9475B0FF993C309FBCAF5C921261D" + "B447AAFDE88FD3B7FA6C7D45E0433509D245BABF60E16B005E5A3309134D77A6F57AFE56C3" + "66A57BC32952B06F746B9F9A36629C7D9FB582E3D0A43967EA243D0BFC98C0712B5AE0F58B" + "17AD61441AB0F00000200049444154241822AC605C97978BFD8FDFFEFEF2B0BD0D1F80A7A7" + "0FB7C93AEF025897130048F14B97DC4A1D57522AE3814E160C683D3F92440666795F05AF7A" + "05BA7C078641F0E12EC9561DDE48481F66CB8A2B2DCF7D99CA4A9C77CC571401F7FA813DDE" + "8F796C1E954E81891C65406A6EE90BC5A833A34B9DAB800A565AE433166807E26297CB8FF7" + "178472C59068708001F4A048CED5D7347960BF98EFF48EE9F8178E9015CCE7D6B40B297223" + "EF7CBD58806996F2CBEB5EFC1FA92732A6E35B54738F61439B93CF33F3ECAA8C79EE17353A" + "FD82A11E2629DAD737897C61BAC85FB3DF969A1F61DD5EFD93E4400D1A40428739AB120FA5" + "73DEF86EA3044FCEB904327DF290E8D3A195C3650F099F80C74E42866C725E28724076B397" + "E797971775777AB5E5BA16EF9F9EDEC458BFA69C00808A9BBF543CBD289F17A96FC5A90D45" + "F9EAA887245AA3EF57A57F0BD33030BFA8536DE500B6B71E30F9440898F7F8A33E0E0607A5" + "2FE53A534A07252EF8E354CFD0180014293977F28D38685E88164198FF6D9C3B7A1060E800" + "6C30BE86FE39C001883E32EACDBB4198E8148E88B038BDF23694A948C63CF4314433D16784" + "FB10358008685D6E539AFE01999868E2A4606AE2154E7B5A3612F5F30062246E0E272A7217" + "68AFB1B4CCB025FB17F28E042654EA3E022CD54BEC075E6AF4D20EC958767FC049335319E7" + "CB7C45B1B3841E72D51F9C06C788027B7F6B127BAAD9055F6C381F9D50F054C74DA3B7D10C" + "E23F98D14DDA49C0E3421F6843D51CA9635176F9BBEFBE7BB94280B720155F731DFCE637BF" + "5982F8B39C0040CAE5F2F2F2F8F884B491C5C408A9579999E6F170A2F3C0C117D5E4E29D7C" + "35C9677425C120056C27EEE155C23FC090D9D9ECF84084B4E5CC67AF25ED65263DB161DD04" + "BB9EF0C3643249468EBF83BECF770EA646779B9374C7765CF4870008115FF5A66F6D320D00" + "66D015205BEABC3EBA5635F6F66849FA2B862C190EA39854859E0EA7BCB469E74CC9BC26DA" + "4B80C1B6FDDA0F3D7C510106FB84C089AF46566186CE0B31365A307A910905ECCC9D7A5CA6" + "334FBDA554AB6A06C3F2433520AE2DAD4D478F6B296E63D918202AD3AFB87849A645793A52" + "BCE63DB8984537C601414C966D67635EAA4F0344C81D0B71F80C64BD95F39FE69B7B12FC91" + "7E6B082BE8D223C62C4F4F4FFB353CEE2DA4027E7AFF644F1F9EBE4075DF7639010015777F" + "5639C992CDD1F5BE4A153A0C801D909C802A7399883D13112D74DFB5EBA99E2CEF6730E989" + "B1516D9D09A624998465C60C7EA7AEF17C6A0EBAE643E602C4B4242CE75BED12340088205C" + "522DFC18874D5275E63BAF34BB5560DB15B57A818C31772C99D344749F04443F64FB2AA1E6" + "BC566F1D0C128E9F33500B6A4B731950144136589EF0401B999867C5E0E75E8D6AF6A8FAB0" + "77288C8FF68BD37E1007D2FBF888806A361A376BC6FA71CE8B41DFE981900D0A13D1B1EED7" + "C43B3B995060D602A345B7B06F6B2DE95E8F7CCD65FC2C5027930EF4C537D937EB12B5179D" + "B56502E8C827801D7EA9359E9718634F4D80CC570D7CECA9FD7868DB7032DEBF7BFCBCECEA" + "2B2C4F4FEFECF1E1F14B72D79B2E2700A0E2E69F417D35F106CB364E2AC119028C221C7CA8" + "7E07210A39A8FCC2E45E85920C35D38D325B71681E601E800C262AEEE20E51CC70E5922037" + "FB6567DBDFEC4615C9B4BBD304038394F007E7770CAB3A077346A64DA6F9A37E252F2CE9B8" + "0CBFFA0E8F3B2255C093389FAD97031F06C661782B46D6A735E841802C78CDB767C979127D" + "27ADF81EBA825535F6A9118010A6471D0BEE4F8B7AE87BCF597B21EFB7D123D045A4CEF2E7" + "88C698EBB580177EAF303F71FB96EBC052F6A135A9843CDDD4C2EE7704BA70DDAE67FC2A16" + "A5E61531FFB5B7E9E6C1024766D44E189F0493EFC82BDF5923969F88B688F62BC10839DC56" + "D6CBD97190A7B07ACE676BB4F1FDF7DF7FDEF7B77119D0A74F9F90ADF32C5339010095CF2F" + "9F3F3F3DBDB7979728465FA5DDAD2DA981D7679EDE255B5DACABAC93DBDE0F92D0BA9AB740" + "40844A53C4E0347F38ECE26CB7570BEA51361799438748C4D1482233F3467A433B530C456D" + "A3C7740E3647F9DA8BE98C67250DAC48B749B0292D721F53A03FF7ED8190D7D968511E154B" + "E08088087C3D482D8D3555DA5C7FBE9E927F39E11163E5E43F63B0C2E923BFAB7C0109FC54" + "937148ACEB38841BECA11C00057683A46606648539C3C81DC38CF688917A3F74071478BE13" + "B088F3626DEFF6F96F8E7C32FF998B60E27174E2C6BCBBA8E35B444FE7B3B45FCB74C06037" + "BC01DAD68149702086CF8E7E04F1791099086CE30967CD4D8D9FA6CD55CBF0FBDFFFD5CBE6" + "B69AF957573E5CA300B6C53A9CE5564E0040E5FDE3BB97CBBE4BB63DD8F142A89CF0E92EED" + "4BF21E3867A92376CFF2D5249EAABA252229E2A8170D1543258679FBB0DB54E7464C8DC3E5" + "C220DD04F7A91C8FBA5395321B75EC87E31A4B35C70B5E5226D49E3C0DA35EB2FDFBC4BC9A" + "84235C86ECD4668DD2DD277B3019003D89F43D3120B40B9302994AAA9F7BD3FA34665E6040" + "3DF1CBB62F6B4CFD258094FE13D51E8F35B756A47F81664C62B0817ABBE8DE0C2D9ECC4E41" + "A9F9FD6977DAC7CADCE7CE14A8E4339340A9EBE7792F94731C361CFE1E9A8DBCB18F32FB61" + "6F77349F6B9AFD4EC7448ED0198E7C221898869D6E0A2CEA895B13DBED02A172E623100140" + "8D845E16212683EA2F4DE75E3A865CAF6132D9E3F20FFFF48F373AF7166EC83B9D00BF5C4E" + "0040E5F9F9E5E5DDD561E4A539017246AFE9AD5EF80930B210152BA3F5D0433E2380229609" + "0682E2F7951FC5CCC4168253B5520C43B3058AFD9DDFE9DEFE8918987B14ABF01A7B7ED7B3" + "25D46FC518A222D340D729FD6A8D2B011588B77569528DF53C9DF7214028B19D42D056CCBF" + "31F39B309E4C99C465C4E553252B75858FA03EAA9B5D48D99BBF243B0ECFA3D8C3CA1628CC" + "620ED163AC84FE4433732D3651DDF9C08C1F9A07F6B3605EC35118D97F4BD0C33E1D95475F" + "E7B9CC707526B127245DAF71E738626530E68D9F53ED474D4449E4060F20025C87C6EAB884" + "EBE6F95F7B92018F132EA0FDC2A5FA0D70923E34C8ED40E74DF013E624238EBAE6C08140F7" + "0F1F9E2EFB1E6F422A7EFFFEC93E9C4E8077CB0900A86C0FDBE5A64A0DB6FD19C5061F0572" + "3149328DD1A5D72F4B3A5DE539B359077169A8BE2418795BB7F53DC676F7FBF4A427E6BC3A" + "284A545770456F6573229C754F7B3E4997ACE4DFC6495190B3969CB5C00AAB9321B239C096" + "99A0FD628C2DEC4ED68F9E2EE99839D7527A705CDA432AFA54F717EBBE3165756F9CFC1374" + "B2D1EF7A68893C5A8E7D000C919849231077EA91A515C546141349A9D724CF00B252821153" + "FF68A7944E444C5A2A6C23AC0F591F21F56FD4AA136899F3639878D1B37689BCFC3BA0EF77" + "FF5702FFADE670614FC09AD34FE5F84A9C5AAED40ED2336C2E5A83796360518009A3819740" + "156DEF4B39E6618FCB65BFE5C67F0300202E9BC5E56DE43CF89A7202002A7BEC97620C1EA5" + "22E30C62AA0E1CC539155A4600C90320BFAE4A46FC7EB424CCDFF8503B204439578935596F" + "5E4B41A754F03323638234A11BFE130F8A1C3F49102BBF243789A1AE4A0DF64CB6B8A40FC0" + "D449BAE8A46CEA96C4D5D4B6CA4276F6ABF7871101AD10560A3912D84C52D5045F951390BA" + "C60539A8746E6BCA7CD7A008B1E62F830566EA0E09B11827EF9D69ED97D677597ED8E113C4" + "7533CC9CC4A89C000B28E08D0211BC30B9172264AFD08628415CFAEB36A975057C36BBF8A1" + "85C36D96695AA9A8146BE62A3A63D51FD76D569895D5FE915904BD708B9C19FA4F02F005AB" + "2649DF2BF575CD7B6DA9D0BD5ADFD39E4B7A5311497679787CF7E26F4403F0F0F8640F8FEF" + "A7EFCF7294130070D9E373B8172182C68D6546FC2347772246041B00CF2735386ACAAA5D6A" + "50EF32284BF39FE54D78062A51D2F6528A1DD53486CB123FFFD7820918BAAED91155A55B5D" + "31526182AE136587F467DEEA08963221515A9A0148E45D69D58D2572327088905F5F34DD40" + "73C8635E0EF0854B99D2B723064A3B24E4CEC4499235B2F16301441AE6DC0145F4FDC84CB1" + "A7E4CC925FD798B4A50780DAEB67A7776533D024CC3B08F90E729FACE458628FBA16C58849" + "35CFFE01E302A5C95930C142316EE27FCDD720D515D926A669DCCB9FBD5BF9037052FD5A1B" + "1B21851BC1A8068A33E35F5DE38B79E0B9F0A4102C504420418F7C9FE39AC376786F9B9C3F" + "D5A65D2FFF797979F9F48FFFE51F3E5D2E97379108E87A19D0E74FDF4EDF9F65CCCF390F28" + "0F0F8F3FC6659F1DD71A2108C9193609CDCAE227F1F18E983C113828E5830EB2D1A5272C5B" + "719CBA7A7FDB24B7AD4AD9103956792A4C7884C53430D4C4A494DEC94CC0127DF178B1E99B" + "30EB62CE42DC3996FF10C9196330071081B83146F98A25C7BA657130BA1D63AEF4BB5D9A0E" + "1340E214C6DF55FA4CD8A37042C83AB13BA1AC6A1CED0B8849A7C172F85B2CBBCFCCDC69CE" + "13007670BBDE39532D833FF27EBFAEE2C898481759699B8054BA30210CB2785F7ECE75E23D" + "598EA779E3DEB08F732E25EAAFA6C426505973E514BA978991384C506D09A901D9B27F1976" + "E8E9B71FD35E479E0F3225F2BE9094D9D06E30CCEEF36404096A568FEF3FFBE3C3F38DBCBD" + "854C801F3FD80F3FFF643FFDFCD3F4DB594E0020657BD83E25B1B0925883003720F91CC2A7" + "4C9333DF315903F36B62347D167B6112B1D44464A2108AF9972CAA5DDAFBC25F5C20C1ACDF" + "17C93F55A78BE7E5B122D024E5B00318018F3E7F151B5F36E3D1AEA42F6E6D6D0741EFECD2" + "44F3425492CC36D50450023175F439EFAB97A885CD9569A64360BE9ADA98BADD167D090F9D" + "E45D3A42EB40A392ED12ED77BD04C884279023A0470113019AD4628C3B0DA26B9704CCA006" + "764A3B4AFA486C48559B261FE9E1AE6A72CEE7C0D5B1883F124A787B008E720474DCADF3D0" + "9D9A874B8A9EC98C72B8A61AF68C00100D9984CEF4D9A8919756E77686373CD51306CA1956" + "ED017A45FB98B55F8A7C473D04B26DBBE54DD8DC3FFDAF7FF7779FAFCCFF2D78C67FF8F8D1" + "3E7FFE6497CB9DBBC2DF783901009597CBCBCF57955197AE4B1D594C0F4CE446FFADE5F216" + "E90999EE8EBFBBCBB5962284D9E6207A4EC402D7C426111A2DFB3AEC6F12AE96ED139C11A9" + "8488530EC1004018AA70D8D3D420116FE776F23DCE3B40540FF398921C3141D618D0BC2A59" + "F3FA67F2C417E269143FBD268CECB4C67978A00217150C49681454498CA9096CC7BFDB507B" + "17A0389C0DC5B171E193600191DF5BDDF81644B0A4E3384214839807BCF3F37FABF9683A01" + "4A4E550E7CB6F63D89548D8FC1E4D295FFC04D6B22FA1F19BB55F2A47623A5D1860D9AA84C" + "7A35FA0DF979431B7167B39B3A0C46F47DDDCE0D3BE789FA9E6EF3236DC32612FB4834B451" + "3BEC407BCD4618228A288890D519276B00CE0419FB257EFEEE4F7FFE8296EF75952BE37F7E" + "793E6F03BC534E004025F6F8D94CD59F45848974A82251B38629A3777A1BB77129B1D1228C" + "44E899CAD5EC0D10F9E0BA4AE681F71C99F5F16D03D16E8886051E17620AC22CFD0C307F74" + "4F130115F1AFAFF4B6B8B2A5D75C2F7C1E6C969A4136A90EF611CBC219E1E86D2416822A97" + "3183DE5F0350C163613345FA95A4CDDE5B9FCB04505A0262DFABF14A6FF9C63FD500606C3E" + "F2FB1F6FEC16B4CFA29A8D3D089C3184E1B9EE4C7D9C8C4898C1E094D71F3E1EAD97951A79" + "8FFDAEE36A4E1432E9F1DE4492286FEF86693EFE4AD5CDF34759FEC210080067D520FBBF5E" + "D0435D6BE62ED2521069B0115D143127A9B9A57876BA98A8FA480EBF0472BCB415782158E5" + "715B91ED9607E0D9F64F9F5F9EA7365F6B797CFE6CFB7E5992C5B39C0040CAF3F3CB0F1F3F" + "6E37D258A46F4203E0A641F7816781A021C1F285E89D09D52FEECA1009A90888A8FA88F94E" + "B22B7DF30B0C449BEDCFA1B3CE63E276EECC13DBF92B3B9D6B02997A90C4E272AA2BC84549" + "790887054983CCC4D82F42E7499999B3E6213B40698A8D9C40BB2F85F8F631232F40E0B230" + "219F5BBF7C48E81946489A8EBED43A6DF0BC4FE6372D1F010993796CBD18B1FF0504C604CF" + "971AB5AD9592FF001FCBB9AFB038CA0F1C88AEABFEAC9B187331AF27246C44EA94F09C60A3" + "9991D26350657DFA9459046D48EEC9EC9DF65AED4948DAB297C69CCDCC7D080B9309D0AB8D" + "9B6620E8EC13C0E170DAB101862B2199239A7091CB7705169B6F3F7EF3CDAFFB0679B5E5D7" + "BFFEB57DFAFCE94C0474A79C0080CB163FA83E2E0F1D189EB3939883F86461E71DF10827FA" + "632431DD2DE330D7B675965042D80C984A27D271DC173E49E0F70A33764A04C32FB226C0C0" + "CCA40DCA9B0E3528A435A701B1145D66D5024BB3E4996D696A5CCC69F23A52BA43824F06C9" + "F575C270AB63D7818EBA214877D9B5958C7127E9D7AC839AA1C21641BF0106E2DBFCB330EE" + "6A82D77D5C1424DB41F7C634EE7CCF2875F11735002E73E9C19087231F1200B719232C951A" + "2078DFEBDF18415EAAD33D107CAE3B01E24EA6219192E94E8E8C0800FE53DF14B6FDD0F043" + "1AA3610D27C4D9C1D0685FA5DF03C05ED28A8D45790625845AE00A0347551009DE4651CF6D" + "0FEFECD3A71F7FF8C77FFE177B78232AF11FBEFFC1B6C7CDB68787E9B7B39C0040CAD3FBA7" + "EF830E144BDD66ACD6A3AF4B1317A25FFF92862D2526D1F2F6E75BB6BD20627A7B2D8986DC" + "03DF8A041C7C2904D101700AA8B0C77D777C24F650E95399510459240E692628831B187456" + "3FBE23F4846442CCAC48D23180219690A1F9E5D4CBB3E73BC42A064620F6D43B84D9A5873D" + "CD1C780FF976F0257DF44CB4CC8B29EDEFB509C6DF944AB8BAD4FB5E5F126321A0691219E2" + "C84DB02A3C81946B0218683118B6BF1BB20D0685309A08DCE8E7A4012B10D9B5311B8636BC" + "F6E44CB6904C2EC18DE7DC1247EE37EE89C98BEE0660662B2D0C00B1513C90F6016D736E7E" + "DA7662E2AAD977CC475FDF9AEBFC8B6802CD2CBD92A0170EBBB1C7F7F1F262F15618E2BEDB" + "0F7FFEC1AE618F6799CB0900A8B86D3F96877E94E078148940039300584FBB611091847442" + "C21C39FAA1EE2A44185DAB9AD4E23111E7F6421B1D3FC76C5B2F7D419AD5DE99194068EA55" + "2431D2E868F01796E652E24C29853DD4BD3198EC750B7BEB7398121CF500EBD066859DEA78" + "FEEEE5DEA10AB49E01E784C9AB2EA63BFA07F5EF88731FFBA30684044FFA9EF6AAE63068E0" + "53071B735BF0F2D54C94F68AB9ECF83EB853B94E64E20028A5688F64B8DDB784E75ED00E58" + "7BEC0908296D4E4663E4BB62D36769995929656E206ECBFB3EB5155DED5E3B7A80D92D3329" + "EC4362DFF27A66CA4DC108AE45C248944B8227F11860847498CC763E53ECA3E17D0D09A001" + "C75C7300D8FBA7F73FFCCDDFFCCD649A78ADE5B7BFFDADFDE7FFF29FEDE5E5F94D8CF72F2D" + "2700A0F2F2FCFCE3E3E383D22696FA2B1A80E857B7CF175D1C9C241D96C48B5B19A41496FC" + "CD4A22357A67660C46F7003029D09030F00765DCCE6362C9AF8521890DB28F2099E320662E" + "4488582CA708A87F572A66E5075DCDDB34DEE55BD0E3D95BF76AC6BC5D88B4A7DA3BA2DDFE" + "A7CE7FDA2024B8687F33D7EF10CD8C18A63CA0DA8CAE15B879AD3B220222BDF819EB149218" + "CF04C78B283402B08CE35EFD554648F5742C26BE734A67BAAE361525BDE47B91F3977DCBEF" + "E44E003072EC1F6C881860409C34DB9CE5DF73440BF66F0D3552EA37D2A621222101463605" + "499BA2610850D80248DCFE2865C19CFF4FCF5F0CB73D05C3467E14398F0511266D072E9382" + "A6E1D6C04FE13B8DFE7597DD2ECDADFB2C5C4E00C0C5FD730A0E7B119CAB436E4A011AB12E" + "926377181C1C0426396FEFAC0A08BEB909932D5F82512F52AD124356A157D5ECD21AE7E3CF" + "8793E091942E52900216F4C7AB010F483E0002D937765422C5421150386CADA476CA867A57" + "C02DC7AB194EE83C5437559AF2462A928717D3A614B2320753E9F08C5A8F6E1F00A0102629" + "EFE737FB20EAFA7DF94FD6FCE88E8B5A83B5935DC13A56009473A1AA2FC06FE7C433C53765" + "CE0E29B98F95F1854AC4D6E02DCF9DA6BE8E9D6CE1C5BC93CF0DC99C45E049CBC093903E01" + "5BE1BB04348573AA1D96BA710B634510DCC201B9726FFF1B7F1120315E07C35302220CE199" + "FD3C0B527768F5721C578DC5D516FED3CF9F3FFFFCD34F6F262CEE16F9F07C8601DE2B2700" + "A0F2FCF2F2F9C3FE14877E8FC9E786CFE116CD1EA8A97021421D877713491274730505BCFD" + "45AC6C50BA2272249D69C453673A7369D6CFE3AD85E39185CD7713B4FE15B96E59D68A9901" + "1FD44D65921E75A2F5487A24DA83C9E92FD40C30E643ECA0B31618E1518BF04371A59854E3" + "31C18A29A5BF81910755C27F9764B6B43370281F49C1A6E35005046CEAD2AE91AA85EF87B8" + "F9BA3660C37F936F01FC1330078961B0979B8ADC8F30BEECF3EDCE1953F4C62C51987EDB66" + "5D85ADF76BF010696552F1C64B4361712A75C7EDA22680420027681DD27CD16E1A2481DBC7" + "4545BC5F0112185CA94623E4FAE1FC0EC05976568F262033DE245A24082F95C558C73DEC71" + "7BF8F9E9FDD354DF6B2DD7B13E3E3CDE01EA67390100957D7FF9C1CC7E0EF78F57CAE5E92C" + "44FE7D72D89A6D6F7C09CD411DFA16036C9D02D2AB06627148FA6ADBCCDF396D2C34035677" + "D1DF67DBBF5058D45E110956E38A946F65CB17065C01E2A4656066536ACC4640859D711604" + "96BEF9490223C42427699AC180FEA1FF72995401901AF98B34C3746B40C8B3008053763DBA" + "44671AD788196FBD1EDF83E00BD3EB9025255DED480DAD46718BC31FEB57371936CE6B3C1E" + "F87EA4B321C24513F8CDDE8C392D0C62586A768F92642B3C715102DB91B42002C52BB77F69" + "1B022784F717DBE6AF956E75BE7BEB9109FF1780DBE51340CAA88F003640DA8E1C05ADFF66" + "64CAC8F51E20BAE811A125F8F760DDAE63BF6A007EF8F1D39FFEFCDD9F6E39F2DF42D92F17" + "FBF9E79F4F0DC09D7202002ADFFCEA57DF87C79F3DE26349A29EDEBE8C0018B5A304250F81" + "AF003946B114F265BA813F4A8DA7F64E90F110E27A5FBBA0A54B6131881C5FD56BABBA480A" + "F3F635C323910CAB311A74753F487D1A043052E24B26027A36B141619CE3D70267783125B2" + "B591FACEF7A6EB54E60A4C50F33588755D29F5ED141EC722B029F7E2AFB9ADDB6B8D892718" + "72BADC27909147C610BDBEF16DB72127B84A4D85629ED65F8C1C5A98B11F58B1B47318EB48" + "AB9CA1991DE019AD664E46326E713CADA9656D59531DD49CE6D8B4DF091CB64DF73AA02A03" + "7A5ACF04F8C3F10F4F079D53D4756C474E0C845398D32A5DA8C131BDF0DACBF97DD2054410" + "A839B08EC27EBB10E8BB0F1F3E5ED39EDB6B2FD735797CF7DE9E3FFFF1D58FF56BCB0900A8" + "5C2EFBA78707FBDE36FBD71633A1EFCAB6957A3CCF6CC8D32CE1D083AD74D42F0F338850DE" + "B7880CE04E37179894CC5B3726619F184204AB3117CFF6F77A0214F1B6EEF67C274CC144BC" + "4D1313346ECF31469904F3CA3D90AA7D615EDE4113CF1A4B50DC96E99E30F071D4032635D5" + "10AB4C8414E2E9FCCCA1B68DC1C40F0938FF18C0497865CBA960E34EFB7D47F0456857FAD8" + "A6BF635E87ECE26DEF4BE85FF2F59E88083111C796D8A176A8DF8DA4F2A13529338397C321" + "1453DBC871D133FEF9F0DE09D11099F11951FB1D98F1B5CDBDFC600A66138E64605B3D2730" + "1C940D34A316E41CB5A4E1EC7409B7D9919892E50DEBEB403D89F9195BFC7DEDDB65DFEDFD" + "E3BB3FBDFFED3B7B0BE5BA8EBFFAF8D1FEF8C73FDCEE4138CB5C4E0040E5D3CF9FAEAAB13F" + "3D5C6D46C37BB4DCFEC8014F997B53D525CB27A9B7D81A33B02E511879F2062A28E26849A7" + "5A685668DB5A10D68646F21F2F02075A45120F8918E5A847520D3D0921A911A328A21F883C" + "48E65A89025A940419F659A6BF7D4336DA9A0FB1FF6A3D5E13ADC33FBEE654B7479EF63D99" + "AA4C187100D1EDEB47012AB66E2F677DE787C6A536898C4AF26EF5A423259C087D66B6C2F0" + "6C0205CB7ED57F35565DEAED7590EAB9F6409A208A7BA704CE2F3BEADFB5531CD960110D44" + "53553BEF1D808692C633A6BEEA8821593B726FF4292B1F1ED6434CBA2F3E96A3A7E91BD4BD" + "FA116A28E030A02DC87DC7861E395FE2B74068C04995423E3A535C415B33BF25027AB07FF9" + "E377DFFDF0C30F6FC604F0F1C347FBFCFC7CBB0AF92C73390100954FCFCFF671DFFFECC5B8" + "4991E79CF023FF25263DA9084D8993D0C185081D52F11471C0525E791B3760B1A2F5BE439A" + "E0268A6DE66F2242E33B682021CD14D34FE638086D1159E933FECEB96009D84743E5FC5620" + "81C1C8787610D00A770CD8CDAD98086CC7AA1540B89A74AF56B2854B0967A3A7F99F019A34" + "0B1F2ABF25E01992073BF7499442D9C0554229CD8E78E147690C829826E687E15113DD5B41" + "A422181070984F7D34EBFB2BD1ACCBF464E7E183C1A16C58A70A1829D539DB3D128875C73B" + "38BCE28ADEEC6F8672126C4C9F12BADA228EFB961A708216C209F8021ED6CDC0C4A2371C10" + "D775E7CFE50253F679930719486DC089F22B7574F825618F4048A06E1BA9100A4A1F30E1E5" + "72F9E3CB7EB9393FBEFAE266CF979729A3E459504E0040E59A37FAE1E1E14FB85DAE1F577C" + "CE5357F7C82CD500298D0C22DB844829AEDF42EA25924BF1EE2BC29E5EC5E54C37A54CED32" + "150F48C49BD1DF861C5A1C948405060853708C788D37EDC2876985D84ED94521EFCFBE0880" + "2C4CF867261634FE622AB960D186C07CCB71591E9B24A6891EFA591F0E6F392DC81E0C6997" + "3535C5962BD4A08BEE39EE481C2465EA49698BA0416089DE6446AFE604008CDC1B7B2C7783" + "646494EFF3B768FB553A070D4431FFE69F6146A02D2B93A65CF7039D4506BC3BAFB9B49E35" + "D78372E73E7B1C08F764241FD148007BDD53D4830C3B2C3602EE72A6134C1C7FA79A9F2F0F" + "8A8201FD34E4D672440ED4B46988725F079CA3AB09E062BFFBED6FFEF09B5F7FF32618E2D5" + "F1EFAAE9F84FFFE9C7C9E1F62C4739010095778FB7C3F76312EE528FB3B45F977900E15B92" + "9BC04146163FE8E061B36DD983EE149072103F71F431A178CD116E014C9AFC9F2C7C372256" + "356632450C4917EA7C63168C3A93D9B3E7573E43A085C935CCB744C229DC514C1ED6DFCF39" + "26D697F3F3FFB2F76631B664D975D83E7133F3E59BABAA6BEA6AF6A481A65B2D523F064D81" + "9464FB8B80F4A11F0BFA15017F18300C81A0048820204BB00159FEB0448B143F6CC1B004D3" + "063DD0B24C11A269901607D9B09ADDCD5964574FD555D5556FCE7CF972B8671B1171F6DA6B" + "EF1359739788CC38E8AE97F7DE8813678AB3D61E4F08C76300E67AD2464BF766E48EF6F656" + "3F4777E4044740644C98B769012CE337AD752974BF0462002F8144258C0CE9895615000020" + "0049444154B97648351E5C041F85F65B547833F02C069D3B98C1E37F5EE363BC3FD785758B" + "61D6504394FAA5D559BD2DC98FA2CB8C89CA94D63C79C80749DAEB2A12AAC7D2B7B9719867" + "A93D4AD4252620987F4F5EFC62EFC9F461C002718AC0A19A0DDC53B8A252BFB1AFE002EA4D" + "1258F2D0D8FB7A65FFCA93EEA20B5A46B5FFF8FFB73B49F3329795005079F8E840AE5FBF7E" + "6F77F7EAC49623E824957CB2121642007300E293F342785177F7729941890ED609D23D0194" + "48004D07D6F3F3DA860D268831E40E479230633A3CFE0D448454B904F259CA288154D1A66C" + "87FFE470BB540F45C47B0343AF22A847605C28B8B4399AE58E9B806BC407A903FBDA7C7892" + "ACCE8E86C33067EE3350209F864A60CAB1F7F3A3E9B4003EF6D984FAEE48E108F6C1212D40" + "621B5BF2CB0B24414AD7D759054DA627E6B35126463D22BD4201EB3351348FA34F1AB30670" + "58A1D49DF3A4D9486CA2248ED6C207A34C5A719B91BEC6EA44163B01BD2F257F6B033360CD" + "A8693E82EFC27C52E830A8F0BAD3853E9B346FE42D9A1DFB16171A83511A1ECD51AFBFF6FA" + "E19812F83284C58DE375656F6F3E60690D035C2C2B01A0F2F0E123D9DDD9BD73E3FAF5E9F0" + "8890A9AB63D86539F7FDC8E48DECB394CF0E70EDB396BC61C64DC09F443F5B5D1C6F2F1277" + "D776B1259DE92A4CD5118DE984086EDFBCD1051F682745EAFBA3629313A8FE991C84BFA1D2" + "2C1853216FF03EFB9FAB6D8B6667B95E6DBD543C3CD10993CF621A1406D4B2F08C84F7313D" + "2D27C9A1A37611CFAF5D25A4B876A347F1F5334511B6F3F295E797C464E48508D90E63C72A" + "F9A7B8DEA87464AC5F557CF6BCF72D5B36F82C80894C187E4E7DA80B43C78BC3EAF0E75684" + "D852C481E53A201250405A8C907ADFC2C42E69E1EC1C7E8A7230939B875A3201EC690F03BB" + "110C73B09CBF1AC21D183622D619B00AC2572568083C911091F23057FC84A91C5EBD7AF5DE" + "4804CE234D17A98C63B8BBB32B078787AB06E09CB212002AB76E5C97DD9DCD5D386E513AD5" + "4CB4B1C7167E99E3C6E31B41BB27834507CABD94826F108F4D26862E608DEF638D40C17D2E" + "B364682F544F5FC236623E007D738384C939E2A3C6829CC4A81F4A3D3229195EE13618B887" + "E0D3BE37C0B136D2A8320CB86A3B4E4867F6A6C8864EAF407815BCFAF1BD7A3F306CD9D9CE" + "6E497A8385DF844E0D34FF8660E6A06C550E7CDC565E212C69FB0A81932239F0A5010CEDEA" + "BAADFC81DB41EF0D7EEC2318541CD47361F8662D93573D3B521AED3A87CBE14A88EC544A03" + "7F6107C310CA476365755AAEFDC0D8FD89EEFF9318252F8412EF999E5DB5590D967210F463" + "B180FFA12D2DC4F1DEEDDB4F3D1C264D543FC617AD8CFDDCDBDB93BBF7EEAE6180E7949500" + "50B976EDFA9820E39BBAB05858192062CEBFB429C05D5E5BE66DB65B07E45A7C4F63E9AF30" + "6737681DCC56692C2439C7C54DBB749BA5086FF4C98188488E49C79CE824A7427658498E76" + "007ACA54D8110D4E5BEA9BB30BD104BB4D25504253D95FA0531774CFC27D43B40B023812AA" + "999367F001E0FE2BF4B54162E679201EE6CE71D662B58C93E2C1EF9A446F16F059930FF017" + "7660F07ED9BA186611DAA78129919B9944A40F2D1477E2E399CB9C60284D3B21737FD80973" + "C9068B26578FB2CC0337F7B5A515B6443F84C7B06E61EC72EF08BF99448C67FF83601280AB" + "DF13DEA7B076E3CA8F92B4C6DFE9BEF04BF174E205E44BA0B940824146FACCB1F9652D9A9E" + "AC183FCCF850C63C27771E1C3CAC97C9217E74EC1E865ED45ACB5C560240E5C1C38772F5EA" + "D5BB57AF5E9D9398A74DDF24D268CB6E1720656F49F1FC4912CE76CD2489286D40F9951638" + "179A6DD0D160AEDB25DCB8DD6A575F0015497F98B09E7EA8BE9DC4810BD2093F23AA59397D" + "718CF3768252DB78D8C142213581F8B8DBCE1C246C8900D231011E1137207B5D9D54C4B4C0" + "06C57FD240C2E23D3EF5ED335F83247D31639DCDA7454B58D553C4568B9B0F6614619B7FFB" + "9DC6098F6BCF83A68409C9F8549F58A9BC28AA0F68111F7C1A7E27685CA9391E92A6C23AA9" + "BA706D221598629BECE984466D2600CEA047F32E7E1897BD43410BA6DEDF38CF0ECE1EB550" + "30BFBC14407395B40513C12A18678A16F6F614091440B565162DFCBB789FFA57959698B314" + "3F623933F242DA87601E785036E7FB4C5CC45286348F6B09652500541E1F3D1A4FFE7B24F2" + "14A14993BAB1AB30BB76110409390259B08D737E46360178E6302AE1E5D720B595A6E6751B" + "6FDBFA60235C58E94C5C5895991FCCCF4D12207B71770E52CE58E6AB8354ED15CE125CF231" + "608EC45532B2853EB8173D4B9576484CF687B77B6322243BD12FEE0EA6B0CF03E7B89E54DE" + "8C2B1A6988030939EF4576E292B7BAD41E1E91D70CF21D1448C5DC3FA729A581B876EB1040" + "0BDEA8E9F738DE794DD02B4100CD05819D6D8CE87713F975213FB1CDAC012A13B4D00CBC09" + "CBA1134C48242E2A8F848FFE09F63966A4547FCEF4F7400FA2BC1083BF1BA5F83A34138231" + "2E8E1898A7921BECFA017F3D8D6ACC65A0F644B3898696C6E21337DE33FA346D36C3C39BB7" + "6E2FEF1317B08C26802B57AEC49C206B096525005476367BB23DAB77B6DBD14B76E3D9CAF8" + "04409632C4C1AFD097F6AA67DB60D1D283548AE9CF52776187A6B04D1B1131956DB78DFB65" + "A9AD0175C3464F3B3E498B11F0BD93B6C9520FA152B534A55C57E017445EEC0F1CCBCB6A68" + "F67BE03C08FEB4D8C69286C24C27064EDCA6341751764F21704B3B48CB678F8E589F30CF71" + "D612FC931B7BDF6688E0E88313952009DA67B60B182CE48D0F3F73B89EC634C825CE8F88FB" + "2ECC18AE610EBC6A0D04D14999505F4B4B88E484C4C2F69ABA84FA9F47CBCD09FEABBF1B19" + "3443DB422218F28731A2293ECF21D470304D54756F7E9594B09F8096280388611BB8CCBB15" + "897FEC3D8E6D9E4DF66D0F69927C45D6BF6832B081D652BAB193361723189E9D9EBD767A78" + "74793400AAB279FAA92903E25A96CB4A00A88C2932777777BFA92AC7A5942BB56D4E03CEF6" + "A6921C8382946500A5F1B6CC43BBBCF2A849DCD33AA9905D7AF3A43FF95ED42F9970885722" + "04EE8974D865D838019AFC7BB29D9A245A48D959480A341B79E62726F52DB5C9F897488C68" + "8094E7FA6E3451DDF6CC9D296D93ADD5F3380409550B2477D650F70DB13FCCEC42000CBE18" + "B3DA6174410CD8FF9F8E78EEBCEE893985438D48CA4FA976E3C14434F4DCD5D097881901DC" + "8B62ECA4D9E37D5C7AA2E91A7B6A5399ED0A4BA4C5E911138E1246273F078E94F63B99E3BA" + "36512D85E2E8A7643DE9DDCC6F32879E829FA9D7410BBCFD6D0E84B68E391916BD20D38FC3" + "346D95F6103CADCEF6FAD96F437D8D0B4D37693AD8BB276ADFE268943911D0EBA7F5F29C8D" + "AFEAC9BAD6B25C560240E50F7FFA0F8D1FC6A3A3EED75A5F88CC7C06DBC18C7C512CC66636" + "9B01680385C7124995A1B0A8EAA2820BEB78BDB151BBF39E785BB4012132F441BE4A2F4081" + "AC72FE8B611B968B2D1E4F6E7B1E491EB41F424DDD80CB0F6BC97EFE365E6A4D8746C31E62" + "9A84A5EC798E3131D701131E260DEC0C261D714A42393EA7390B58452A906046F0CAEC3A00" + "3C778140DF9BECBE1C9555E58E9BB40E9C580507C5EE26C15CD0402F488185C6897C16827D" + "B95D63D2668698926700ECAD619612809BDADF57A82E77240C7E0C6D5DEC2E2E87D6007CDD" + "226816DE0B036FFED27C232C773047BE04AB7E1A0B3EC23B7C6D1EFD35BCEBB656420E0426" + "13D4D679889D9042EA4F4A89ECBF30AEA7CDB0797D7F7FFF2DDFFC0B558AC8666767C154B5" + "162B2B01A0D2F2469F6C76771E94525EF053CE28FB1FEBF0D26620B40116E4C54FAA67DC91" + "A516F3E68DD24948C70BE9DD6D8C7636F8F4DB506450960972A17BA9749B6150310A2422BB" + "2E9829F846003DF531FFD5807531735EB1B164F9D87F87AF037C0A3400DA743A9C9DCF1E9C" + "CD5CD3B03028FD8649E35D28DF0E04F212A5F6541571860C0C2418A30EF3F43370AFE4E8C8" + "9553DB2B9915B20F62688993477823D0D1C9C84F80904C57F5B356C5C06F1E92C49492F4DD" + "45522C0158FBCBF2451451720D50BAC7C718D8AC6964A1A9626A49C4A3D06996D22D740AE3" + "B3DB7A3F81851764FE0DEF88D9EF9BE35DA9F34141ED77854690E83C25FB29C17350DCD65F" + "32514F8DE8A271C210E222CA81F0A6D4E19C9B2E60D16E29AE2595950050393E7932C58BEE" + "5FB9F27A91F2EDD32F93D83FE04F6CAAD99ECED2007FE6F85F20C8826DBDDD814DA2A9F821" + "49706815F021494CC9C7A05FFB2A7937E34FD8741B3A73B63ED66076AD6E95706853388C84" + "E2B55C8D6F5224AB38E7EC2E16D2BE788887D543E026E6D91D5208E7917560B13F30854BFD" + "629B7E3BAA071A5CD648A44C456A0392D68A817D679229ED64BCC81CA276813984C6B6CD76" + "0D9FA388803EFE4595A23878545C4AE6BB42D4039F2605506CE33970B440CA25500D879BE3" + "622264F31AAEA18BD68570CC6F08252D34FF8AF722F698C7C068C110C1DE08DE6080EF2382" + "5555A81E25873F9B458A3480D39F337B00BCBF149C4990977D2401E10D0D6C5AA985F49422" + "DDCC2E7D5386F2C6D0697E2E70291E91B296E5B212002EB5886E653C31EB1B57064F919AED" + "FDE11532DBA22375A77AF6D73582C5A2685122E30F27DCD1BD5A5CF2C76605312A92932893" + "9DBF01640223AC2FA8E2DEEDF85111D32C418569D510B850E6C24219FF5A675CA26DE30729" + "D5CC0D909E6C3C4C028E49827864F94BFC44B68070164072988BA95AA525CF71895135CE87" + "847B636394100E99F1C8D6508C1A80DC20C76F9C4E7C4D6301293439D8A7F5E652B0C671D3" + "96DA571DD4B863063926E1DAFC221A8F423BB15AEC4023ACBD34481A35186097210A22B31E" + "EE16BD4785FE4D927B697DB3D308F9965284082349C9C80D50BC7F64C19B892E67FE33495D" + "7C9D34BFFDC2B97ABB57DD4900E6A5ADF5498B05A19D731F2C90E1B2ACF1732215C6FC1B97" + "0EFF2F5387DF4359090095C3A3C3E9ECE8BDFD2B5FBD79F366FAD5DF600D422B0150B03B96" + "70D7B23BDEF9D638328DFABDEA92916D304AF1E3EE7C180FD0E9302AD42D4BBB5357724483" + "A43D39122593981A28942C7FB39F02B92A92ADD825C09828283C9CFFC99D2C0E5CF82A24A4" + "4920DDC9C0F689C2E96CC3ADFD754B75867633C3090FD060F208B4049A901E94F1D1D89F91" + "9ADA3DA055EDC900B465DF9901B8C6562712955AE4DFD8C700C64E00391DB18824D2E3638B" + "F5BCD06C4FB59DC8B3BF0C732D6D0116491BBECD9B01A479EF2BBFC3C5495D5A994C6ACC77" + "200496B3A56C887E3B43A0F1B4B6C2ABB6A4C3D7F04C5CD901995203FA7D8429C1D4BFA13C" + "2E455ECB4FBBC8A57353594B57560240E5E6AD9BD32140BBBB3BF7B667DBF9074AF0035F00" + "42D6E9C534C9883603BCEC901A161663D22CB8BC256D93CD278ED943E35E621BA04BB66FB3" + "EA53121EAB9722A5DB757C6C6B6878800316AB5C53EB128D7B4E934A0E8D774F6706EC4E5D" + "9C9C09D9A98B9ACB7B73370A11936872A84F03870CC6E1F62962E7CAE0DC497304DCCD819F" + "692433B1D1A44150D294985E9D089901AB49A219F4421AA364CECE1903E368F80154FD6016" + "272ED86535B4DBA46EA55B1ACB9BD7465507C0628FA174D50B26288C5221BA46476D33C772" + "47BF4244A3B51B61FD5E4F158FCAC921BFFC253406037110336DA9930C654D00CC5365CA98" + "58A8726DFB859D1060EF2F121B365FA220DD634F811A8C5EFFE269C3C91151B53C2943B934" + "27014E253B75AEA52B2B01A0524FABD46D95EDB07D3D2F9CA2382704763D67D9A525E7B3B7" + "AEBD84222C1674256F329077A0432789B981ABDBC535B40D5BEDC273BA12ECF9544FFB8C26" + "391AD39907F1F433420AEAAEC7EDB3D40BBE130DB190FA43EA60310222B683B50D8F894102" + "1BB2CD771225F58D66357C1ACE19BE25BF0A5CBBECCC0115B9FD4BA7F107A260E365A701BA" + "6DDB77F04299243311E1719F41289E0AE98F8A76F3D099624E88FE374D16013CDDE46A9FF6" + "BF8E25043297DBE4E6221CB1B8E0DF12503FFA98F03A04296BBF201AC6DBCBF67F8E428953" + "523B7F1F241DCAD13901B87DED3A1916376B147F73B43131CE4B2074DBE47244C0353BB69A" + "A0EF84AFC0B5B3F8019189FCDB75C36618F7B583C387878F17FD6A2E60197DB976F7F6E4EA" + "D5EBAB19E02DCA4A00A8ECECEC4C31B2C366F8B2C73CBB54EE9ABB28B55B781CCB4F7EF2DB" + "BC190D11BBDA55B1AA50BFED6E9076E355200106FE5005F0AEBEF477145425D51B25C4B9FF" + "53229176E84D89DB3215DF288113C5247B9296826452A02DF036C5330DF810A46E00B93B84" + "8C3E1425F728B6181BAA3D8B50ABE9030288C68843AF477D6CA2B42F0E6CE45048BCAAFD1B" + "BC11C01C72CE000A0FF007687CD6EC812E48030B0024805196294BFEB110D96B77F689FAB1" + "EEA1EEE791EEB2324A23B10B0E592CA6C6AB83A60B6BBD14A457464B4C53C539B5827F0A85" + "28669BBC49D02DD747F021686DF354BE42F1FE3E34B8D6C6C51CFAA021E4BE12B89BD0A0EE" + "371197391BCA24443360BED535579296870FF1D4AF87C7C727AD7DCB6FF0452A2301E8D7E0" + "5A72590900157BF9B767DB97E7233387203599938F4A7EE9491A140F17B43D35E04A57A2A4" + "43EFBEEFCDB657B73041E4590F752EC38F04D92AFD5CDEE22B4E6462BF85CDABB581A5AA70" + "121F030877CAD593BED959B8964694E5F075748F812B2169980BDE2CE977F660C766AF144E" + "B930015CB11AA9E334C2D44D101DCA6FC05A8B380C0B9312095A6959E1EC03DBF87D5C5C79" + "5E1830B93706307884C27CC244C3343FD104C3FDA4A44D616EED1DA18C40617D692FD527C4" + "72A29B433CDDAF2000AADA4C59FB385785D59D134319CAB63B8C9406BF00AE3CBDE76AA18B" + "12BEF3761B890D4E01ED9131BE1F2B13EBDA1256318916BAC7C7AF281977A232C1E7BCF57F" + "D46A6E36C3FD679E7D4A2E4B292DE9D77A0AE05B9795005079FCE4685A309BCDE6CE8D5B37" + "1E15919B86C04B922FABE36D4B084EF2E1F302350F9B5E8FC9D830A06224299BAB2AE74BC7" + "B2F4D4CEF7A0BF5225491B1A374793A88A1457A562BB8D46543FCF5E0188A5D03394920611" + "5E16126ACC0CE117F4AA13287D3549CE3C179D04EA24A49BA238C8804D773E4BFE07F66C6C" + "E8957EE4B1CD33E2110011BE95D6503E034289482482D1FDAD7EC88FDDD74010BE02CE7325" + "A00F124F7106468A7E0CC4ACFD6EB1FDE40C1B260A6423AD5B24C0889BB6FB75B03A9E2207" + "8830D9FC4013616B2178EEDBD89534E5330B984E8A141A078ACD671C2EDC274E1A446BA5B4" + "AC7F623E3DA401E28387F05966E0E2FAF9FCA39800CB5524DE3FEA5171E2376C368F77C7F4" + "E66FB14F5CA4328EE5D9F6AC7FDFD712CA4A00A8ECEEEEDA0BF2B06EEBAB9BDDCDCD90C54D" + "840ED421884C022F4BA7BE81ABF417C7D27D1B05247FAF49C20A1266C17E15E47FEC926477" + "5C221E61B3A66C440D7F247D839BDD2E4A0993723F927A150DB43D9D012578865306C1E05D" + "4E3D54937F194C221A8629A0825307977E0FF81401BC8403624CB54E5D266DD072D220A57F" + "0C3072F34BBA8EFD096C0CAB3D303C5B69E426D5711BB78A2892859C042204103447767600" + "E645E9275A5B0CD4D66868767CF504E9569C3831C1ED8F25A63519488F8F11FB825425C740" + "632C0C94FC1AE2DD7627BCF95194FFBFF885738E7E3BA287F3F5C7B498C101D61EE2D6011A" + "0D27BE635D433AAA5A5C04407F94961FDEC925822B45F6F676E5DEBD7B2FBFF9CD3765776F" + "B79BF38B58CECECEE499679F96DBB79FBE14FD7DAF652500542CFDEA7872D6B66EBFBC57F6" + "BEDD7E85272F316DFC0D0954967D06E236E55F2E7001A57DAE4BFC835CF5EAAA74C98049D8" + "CA616FC9D980379F7E2362BF86F3DBCADFA9D2F3CC618A413F90211F53C68EA2718442166F" + "B291869300892008404A1CD451538CE6B00799F3A12C00CA8C5F3D4C7A1A643E4048DC0C92" + "D5E7C1AF209EDE0767D1F42C074DFBD909A5E18C814B694706B379201E8BF356402F5D889E" + "3F8B0E600AA01BD7F8DC267A869DE12F4B619725A9E9693DDA3B1667DEB502481E15EBD4E4" + "379264FAF97C7D24B78A5A0EF66A810941D3BBC74440682C284AC6CC5F9CBDD0BE07791D66" + "8D8039EB4D349248B9ADA5D9FC289184157F375DBDC1BC249BB07C6E379B8D3C3E7AFCF237" + "EF7D53AEEE5FEDAEB988E5E8E84876AE6C4238E65AFAB212002A96E7BFCA140DF01A6F0200" + "4A4A14620E7E9CBA1740C60972E41C3FAA85B5D91F5292BDEE93885F32D891406662A9B9AC" + "5BDD493A0E1B2601B8123ABB344571FD85C98A3800B73AA1022E7DBD660260B085A0466708" + "04C54ADA0F9592D0B88DBBCF1BE0A68194CE500BA99B0B0D52BAA4757649EA8C17D151C319" + "04B21F007F601B7BFA2DCF2B7AD488051E41A4C22346648AF93782802F1B391C092F2732CC" + "B4353A8BE5851BCD1FA1ADF81C19696756E27EF2B2263782B9DF15E35FC636535E09F74F60" + "47CB58409E91B56F409A5D90175287616DA50803C6D9C2CF09F3DCFE2609DD921115CA6DE0" + "192CA383E2B807CD73432428981B347C096B90AB3A52E366307CF18517BFF6B1975EEA340B" + "17B14CEAFFB3337970F0504ECECE2E7C7FDF4F59090095A3C3C7D387939313B97EEDDA9787" + "C1136D84B73DE9B8F918D0A2A5DF8198A4F317396110EFBFFC37271C11A118B3A8FDC76D41" + "F5405ED6007F426D95E54D1FA15AAC83F0DF1D2E97D21959FD71B30969564D22630D726B8B" + "118DEC21AE9DCD76C1933F8D7980F405E2730E2C4BFFF5B244ADA1169268393AA3FA08E24A" + "26046D4E6680AAD109A2D2097CF8DE531F9706F22048CA7533C8C573F8A382010174D40D77" + "768DA98D0BDDA1295DB3F573F04C8F6CAA2102192744FD6F264334F7D212F14CAAF7852803" + "F793905E83806693238B8595E636141E0DA24514E64B14D743639BBDDDAE0F1A07EA2BFBB5" + "1436EE5B9B52A3036745BC5FBF8EA7FF0D8393661DBCF5F3F55FD7B35E9B7521CB38C667E4" + "97B19673CB4A00A8BC79E78DE9C393274FC685F385679E7926E5FC7715A1B2BAB948DA40D4" + "5F7029611F2B795F2BF64F221624D923E4CF8DED24123B58F4ECBE20722062146FF4F47D56" + "3FD8469F801C4E548D4484CFCC5C38769B494C101953F63FFB5B500511A54446A4040264E1" + "60BD2924FA4A18B7285D56BF4EB51287F36D764FC7539F37B397FB3259A8A48D83B63E6800" + "6052ED7B1E207CAF04C612EAE741F0DF6CAE823663FAB3D2DF9473A1CB549846074B90809B" + "5BE4AA1AA60D0162ED9EA05DE2C106024BD368B8786DA431F499233D9810E70C3F58900242" + "AC6AA6FFEE05A57659DE0FAF7600A98EEF9042D390D3822FAC814E95487D0CECB14C270A6A" + "4B140607C5E06BE2891D8A966315F9ADEDE580FF9973D39BB196F3CB4A00A85816AF297186" + "D6D7AB496EE9BD94F01AF3B660E822F0DA5706EB25677DDC48E0174578EC0D9028499A1223" + "085DC5421BA48BD87DFDDE218B1D37E7642109875DA0A1050821F264BBA52EFB466CA365E4" + "A8C95D18DC482454FCDE98D285DBE563A7002BA20A456469D043A819791014DA6CA784B934" + "BE4CAEF2E730976264AC060C2B4404BAEBF1B749C00EDEF13149F33311984A555098A86D83" + "441A949EE59196819EF868B002A61AC122B3925D2342E3C452ACAF677C44822C7E96B3BC9C" + "E849D2ECC0E782CE24108999F5E606D5E0EC0722451973B02AC9F10F4B065D2C510B408FC1" + "809023E36469D3E553438BFD26E61869F7FAC97F79AD8EE33A645A4DFE3E3CDE7C5AA7AF16" + "A820EED4AA77BBF57A418B8500AEE5EDCB4A00A8FC913F0A9FBF7101BD7C7C72BCDD6C361B" + "DF50E712DD7D4C8868C93C5A7C387B7F0BDD99C548763C8207BC6D4FD8C4344A52257AD433" + "4C2E2AF9F8ABA4C297FE27FF9D05250261BBAC9608D0D8AAE8665C5192A737B73D38021278" + "737C3B132AB17619412B2473A6115870E2C3A0A83F1B0468E8EFE91CF28848050FFF56574D" + "F1E890FE34B7C0A1B2F2DE1FD60EF91E04957A896D0030110983FEB8C20925F04DE719D194" + "C33667EB5F61A1D2CE1170E0B776F4DDA49A09C9B2E3A19030EE5E19943382350AB88156A5" + "A5CDD6A8290BF932EC3D1571091E5D2E7171C7D10AF562651371E7FD60BE7CCE23E2CEA7C2" + "149922885C33508CCC87D9504BF38376B3050FCB62323BC4FEC041B6C8977636BA954B52A6" + "2D78BBEC14B99658560240E5EC787618691BEB2BE5AA7CBD887C12FB473C05A86D7C83AB4D" + "8542F448522878413318D166B5642FB4AB9A448ADF59CA5A020A7E82CEA1C9512EEBB02800" + "315AC1B6DD25ADC1A282CD241A577B1A6E21B39A50A8A026351DA9F1A3C413AF5BF25A779B" + "7840366C960CB0D3E6384ACF4DF24228A12EF7AA9B376B833F5AD4548F2C6A53C6BC7ED453" + "DD9ADACDCF0A7DEF2E02A18921912DE7BE49B9D57CDE4B0359BFDFB52EE2122AD7AF8C982E" + "2DFBBB51C91CC31A1BF2E9109A073E1130AD5E5FE742442735032D21C93D8F2924788D5E79" + "06CAC9B567D410982330DE475634D13BA14261830CE2F1A5C520F1C98041A64F5644CBC258" + "88BCE014462652D6AAE42C88EE141A94AABFB3ED84918B5B40464B7C43D6D29795005079F4" + "E8C1F4610AC5D956B97A75FFAB7BBBBB9F3C3BDB02CC3825105ECBE0D424C8F95D9A3620B0" + "7F2206B34E95127A84D29E018949C0E8B1D9B017783B163E0B30EFF425B08D8585208DBFB6" + "FAACEFA40AA65DD4DA294661348E51F6A447CF1D49001CFE8CD41E3A2E38441974042B0A70" + "B1A2A4AA8E97A36FCC8116A3001C0FDB478732233E01C293DADEC64697329671A3037BE148" + "83D8E3DA9CE5B8FB95E449074D8DA4C89A086994C6525DF2769BBBF67D07C1B31EB7954F0E" + "88F06F081A2D7FB4E6C690A3686A64F2FF1019D2738352DC99702011F6411BFA464D88372C" + "24126AE03FD9F7416C3984B1CF9829441884D759FEC2CEDD40A40D6BC94A1C7A655291A6D1" + "C679ECF6B0F9DAB0B95C498066DFA715FEDFAEAC0480CAC34600C6727CFC446EDEBCF9C59B" + "B76E7EDFD9769B80959195BC7D7903C55B2B71D3E9929C2C2D52A553CE8A7BBF17DF9A0180" + "ED792C2163232D692BC70D0BCF6CBB066F27053B896D52BE5B875310296D70A1EFCCEF21DB" + "F05902A2210B367F3303E06B462B1C0C24D8B87361412C7777E12B1F25D33800C453CCBA48" + "404DA5369994DDB545A91D9A4567070D154DAA7F1A35ABBB8B0275899F9D07D909CEB53273" + "7F60F757F79A8F03426DD0EA935024304A06605E8391D425A2910842C111D0740FF866F533" + "35AC45E4286A6DF13F7D2CD164B60C38079BDBD5C8B7DAF1BD85C83685EC5967959B47A278" + "45BBC67A066A2DB17DAEAFF870E29D34BE93DFCFC856A6A090229ED9701A736D1908398F44" + "D016E817E668927E6D5EC8D2CE5E59CBDB97950068C43214000020004944415450B975F316" + "3E9C5ED91F5F9EDF199302454142FDC8CF69C3B3B3D55D5A998D71E2A24D28EED61436B6F0" + "8BBDBC66634ED2323ED946488E6949AA0B2C98584A0E9562E72C5C4A09D2834C4571FDD45A" + "DF808A83B76DF81C4DE6518CAD2D9CF6B46DB30EFC3C36345644B0883BE08A407AD2384B94" + "6F693CB5A951EC89C9765F6629AEF6931AC8D6FCAB5DC58E7BDE269F47C5B3D0964E52D36E" + "0D8A12E02B1146D23AB8A9A044B4569EFD1451A2025064DD4500A6328F13AAC4B2E9C71DE4" + "0263983F77934375F5253B05DA7DAE868FFDD5C5BAF3FA1982032BAB25EA04EA42527CD458" + "84EA393AA8D532D80F6C42634D5ACEB7454C2998E0C29A4924147FF979961EAEABB2D5FADB" + "DB250DD3052DA536134A9EF4B574652500549EF9C873F830BE88C330FCAB31A14494C9F8ED" + "8F4E6C00D78670853609C52655C2CB6C7719C0DA1706D3A60A74894AFC101EDA2C5CF53E37" + "A666DB3A3FACF4BF2DBF2C1CD6681B60037E7204631348218F6A5649B80069A1710D2E2976" + "DAAEED0420D663140C66DB5DBB44023C5071E0961E1006864992B87326E15E200E4A7F50D2" + "9DB9DDF69C148C6C5A0EAA776E22EB76B95E0DDDD2006C89DC9155463024AE3D325650A8AB" + "F930215FDAE4AC89E69430B69E6A18904C6B35D124F88228EA8F9902F3BCB3EA5CA9FDA41B" + "C96BC5E6C192ED34D6197C384DFA17A0334C14A0163009240D1B3BD092E209927861E95F3C" + "3C8FDFA1D6BE697F99F40E46DA4B78DFE3C4D2F8E09D72A38E4737A4F99ADB7E7F10F9EAE2" + "EB7D41CB24A06D5703C03B292B01A0727078880F633ACE9D9DCD6FECEFEF8D363402AD56B2" + "082011B85972B6DF961DE9788365B539FD12E2E293B77510512D8C4D53A6FAA58725F094D8" + "A700ECFC3D795EB38733364B3AB1CDDA39D88140E2B6E7ECAD2D905A8A879B116110652195" + "230D7A89486C5C48A205644202CD23E3D7E0AFD6EFD242E97449A1230CA0061CB91E965431" + "53C1A76051725E9A3FBB571DEC780CA7875623244B27F0713D0CA88E664835CDD7EAD2673F" + "912E3CAB4402D181BC91E0E56611E131321B3F171A5C076D411F14269CF810A79D5390A70C" + "55A0655B0C8D150963175E07D63688DB9D89F27B482AA4D166DF6FC04F5E2CED8E4275729E" + "82E83B10C8F2E24614BEFF2D1539EC7EBEA065ECF5E69C11594B5F86EE9BCB5CE0B535BF8A" + "755B5FA9B5BE02D2CD221BDBBAB32755916C708CF7F3BB1B8453DBC0A8B60EA8B80E023989" + "1B6A27F4842A087C4A92BEF9EF243D9B24A5019E6C435690A4501DD2D2FA66ECD0E15A0E93" + "B2942454FBCE4949EC09342CB9DDE9A2BCB777A04A04C1C2F8308FE10E2280A42908D34884" + "C31CE67055F7601B536DD79BC4DF3BF9D9D874CE97095B81F7A1CF1A5A19CEC4C77DD177C0" + "7952942AE3D1B876798DBE10EE9510A56F89EDCAB22EBB7F8266B6BCF8856E9F0C6FAD9F20" + "BA58AB0E986E268B036E99236DBC105590B8143F94A359965E38FE0D7F57D78228FBECA80B" + "0800F6128FDF9E0AEDCE4E1278BC9A6F47CDE3E8CF14959779F62FD3FFD7F2F665D5005019" + "4A7CE36AADDB5AF577A4948FCD2ACDE21BD3A481D600A005A0DC0368A1FD02C211F66182C3" + "2EE7803BC4D9668A7308B2B290939A500DF9B324BBE302CE01B8DDCB99A42C2340CA557A3B" + "5D346BED6462A2095C8AC764BB263FE61270D5BBA646B751CBA17FE2BB78216D028F6815DF" + "7F434821AAF1780F039B89E4982975618E7396BD29A77BB6BD4272454383F31F1C3DAB87D5" + "2947376881F41DB40441EB409EF7466E388D72D01650D814B7AACC69883DF3A5ABD335ADBD" + "38BCCD1F41C95E9ECD09F63DB1267BB77CEEADF9C5D7302D545E666AA172CA64CB9D6DF164" + "5A138EBCA4D973A66F0DF7F922CD9727F4111C2E55F0B7B826203BF771B29ED2CE24002F9E" + "D54C469AA72BAB3BB828DE0F5BF3FC2E700403BD96B309E40B974DCA2BF4DFB5BC75590900" + "95DFFCEDDF089F0F0E0EE4B37FECB3BFF699CF7CE6DF7DF4E811D47E31F77F442F27F6856C" + "914D7AD5E80C4772A73F3449757C6849A14D30D83F33A884938732175EE2C62E35595D9E21" + "95748FAD3E549F1CA382742831548FA00AB722316CF18D19B6F3000E44A00C1834111B3A83" + "9ED5CF954F36C470B81DBAD7B044E1DF5DF9FA6B3BE9D61BB3903CC8EB66099F87254BE1EA" + "D541ADCFC973C51C12D9A722101F1771557A27306F6392948B4492A0EE0C89313F37CFBA33" + "D54036786C6C55C0BC93D4E7AADCACB9ED2066693E490D444BA6452FA4BC1D1281DC409EDF" + "2BB3DB8FBF0E8340A217B5C37A4AD80398C99B00AFE70C4D11715241861723C03C675A9C80" + "3AC7F4798FCE832D92013E0AA5455714D9AA7E7E71895ED032F675578B6CCA65EAF57B2F2B" + "01A0726D6F3F7EB15FA554FDDC199D28E5DEC2299908B47AE6ECC31BA8D0CBEBF8CCFBAD08" + "6DAEFCBBD23EC69B30D4D6D1CE69DB939A04D04005070212D6FB4923A94122DD2E1669CFD2" + "6D0E942216872B34162CFDB814A3F8EC5EED1E6D1082CC01CC533F8A46100A5232110D624C" + "66A58F9DCCA5072C96B6E1F196E78CEB9C3B15E2DFC5001E662621C99FDA4B5EFCBDC73C4B" + "B4F9D934962D2A20A8E5719383BE2F517A4E22A039DBA04E29922B69C2624644D3E874B84F" + "912746A602C161B226C42338577F7A3F405438610E3B428A913973AECBD91062D82B6BE627" + "DAD1224206318249EF41F247C0DACE39B382F68B8894B68C75445C3D3D70230524BA3B914E" + "A986F11C1B0326E1BADD94CD17BBA9B8A065ECF976495BB89673CB4A00A87CFB777C47F83C" + "BEB055EB6F1E1E1E4E676A07C46E9BB1C50FAB98EDBA249577DC1134289715D71491CEBE3A" + "BFE88EB24AB9C97DF71EC26E8BAF4D7273C565DC9CC2E62AE1FEB8C1B4AD872497183198AD" + "FE2EBD15489951E2970805688A5108F7AF88F5165303B00A8577FA542296A9BB5E652D035D" + "A8A44AEDC68605B838D088FF0F36621BF90488390B1F8BB3EEE068A4A5A2ABBAA08588C4C7" + "0324647113742B3BE644694CB91EF1EC8BB5AA8397D020E0044327B7814BD06148C29EF60C" + "82AE5A4863EEA06DA405876F85DECC63EF297F35AD097E1F29A222E4E1A7B56A9F0B351463" + "568840BA23A3FB2128FA639A289C01314515D93D9E2F635A0F93C4EA2C1DA482CD09BC5248" + "ABA56899B701D796F2FB9B8DBC2297A4CCDB72790BCDDC5A7259090095870FEF85CFA3FD76" + "1886DFBD79F3E683A10CB7B7B225FB3C2912A166F54DA5940C4049B48A3B9984CC15B083B2" + "37BDD9C22DD48EAE0DC54C0E95C4B176294B45F4B8EC30CF5ED1268585930FC1499CF4188A" + "F3C62FD04290A4082863DBA74B6D85BBCF23DD1DDAE26685B99A2178E063840B6FAA06D436" + "53ED1A967C39ED2C9304E1DF086C16C67FFEA6011EC5AF45873ACE18C836F5147758C39531" + "DA51FCB2798874E94C19D41FA812CC3306F6DE26C264CC423B6936C4DB2B7AEB286A0E766C" + "5E01BDA1A39B0D5F358D4BE7D762BF0FF1CC034B6B8CE892401F3468E8C2BA551A6202618B" + "60E0C01490F252789049D417E894F05F5EF7E27E437C580FFB203890378D96F0E146D14FA2" + "9F570923E6B91AA045F9F5B36D6FFAB9D8A5CC446B89C1AFA52B2B01A0727074143ED7C903" + "B91C3CB7DDBE5EF6CA6D5739CF52E4D0D9FB9AA442E4C08AA5078E88831F49B55C5C7228EC" + "BC451B543E74053BA1018DA07E0E032B7C3D3559D387E0845828B90D3D5B4AB4C313ACD386" + "6E80D234089CE0A73D2FF444E348F2F8208A80C6105231EDEAF899367BCC0CC0B1C4F14BCF" + "B27F3B3F03348C6578FE6F6BD7A439EA7B52E8EAEED984FB06D0F849E3BF937F9E49C542E1" + "89EC5C9958828F7B93DA9B935F18C5A0D510A8A6031D6B2191497FD3DA677F576F9011A224" + "4DBB86A1787FC6C373E0A16FCCADFD4EC42D06E07B7DF3AC36A63291D1817AEF0BC0899703" + "70468C8C1F8581DF46BE49E836134302F7D9B7C11933C63D84B4D6C9F9D89211D9DE82B5C4" + "FE1BCA0445A1A95B50E49903EAAF6DCFCEDE22C4F1629549CBB2D99561D8BD14FDFD20CA4A" + "00A8DCBEF954F88CB0B452DE54D1F9A840C321780F1398B7177A1497786341F6C080D8FC20" + "97868D3A682FE3FAD9FED8FF2C2D709F14C54BFFF277E4C1FE64AD8398C45B02A181F68152" + "15DB58716AD5E02028AE24C083B29D9E6CBEEE796E528F4B981D5748E8E8B89B7508E4BBC1" + "9274220A689339EB05D2E68F0A7DA9A03E8950D9F5DE36A587666FFA44C5CE2DDE2C8EBFA7" + "4540DEE93CD1C5C025B030B7EDE3D95509D899540ACC05D115C2D742E41D91789A831B4BB2" + "DEE479CE6B5BE750CF737A5BF687992225E8C017BC8604EE1CAA2BEE65074F7A5EE703110B" + "E9D37B2B9DA828852932788A3F376403E630D6F8EE156A7C6D1945E75750692F88841CCF1B" + "DCB9389C51D688C8F84DDD6EBFD88498F396D2852A53C8E8B0D385FDAEE5FCB212002A63CE" + "FF5C4E4F4FE5E4E4F4D7AE5DBBFA27B75BB357BB04C2D8642F64E1BD93BD7BE51CD55409FF" + "CC7F07A9248886513D4F12E32207704C709C635C14DE3CBDD1F346142F081B925D6FA04D88" + "EA698967114509D4A539089201BAF3C667302AAC8A8692A4572FE7B1845F450C6E0B60C243" + "1B4B265A5DA8F5B4C9DAC97F2CEC2BF425D1DEDCD9EE692C6348DD0283082442DD3411B8C3" + "D2C20A35A4BE3329595613078244F5B8E39DC6EF4C3D609D3672C2848FC1577922D09AF655" + "F577262DD830AEF893B510441CE2E4A4B5CEEFAD109957EFCE2855D6C1D7B891D39CAC8B69" + "7B17CEDA5A884B13A48F8E86C3007F093BD6F75C20E31C41ADADE168EAE9E1F5B4C8F02F76" + "86CB1304584B5D7C77D6727E590900956D3DEBBE3BAB67F2F8F1E1FF73FBD6ADFF50D5EDEF" + "167B9EC1B8D0661F94F7A63580AD7DE1EDCE9A00167C39C6599601C59F1A1DC358E25AD076" + "4ADCD1AC83A5F936A86B31CC939A361A56494A72100407E053D68ACB969E80965A0291B0C4" + "F6A12F0C3C113C21D8DAE69925DB7EC8FCBE30A6CDB26DCE6574219CFD84316529AC8F0030" + "7581BD0217B4F5FDD9012C4227E037234AC5278FD577BD8D2F82369B52E9DE4E7BC45A8FEA" + "73112EC987E3D85C963838452C2D75D410446E11C7EAAD7C02D8074152AADD40283AAE9D62" + "FD9B36C48948152D4393A1ED8A617E670BBDCBC542ECC8A46204C2528087987D7B19B0E8DD" + "604144A1E2284F57213051EBA6681C5EA4C2CE6181D37DBFBED9D9BC2A97A48C63BD3D3DEB" + "88FA5ADEBAAC0480CAB3CF3CDB7D377BEF0EBF76B6757250C2C6C7EAC6F3E38172340036B0" + "9060D8D4F9EC894E9E4CA5502298E8E027B4611AFEC086BD28C951090E7F90B34008602B4D" + "4A5D777F7295E9BC112A7C07F2C68B8D31A27EEB00A960CD5ECBF9CD731A641A471732691E" + "DEAEA80F6F778A5F3883DE23150CC4A650B826E181C8303077A82EA1ED6AD232A4B865A2B2" + "24ED1B1855DBFCE90278E59B944E40E2AE844D50C751B3B4405A47B33F40D4F953FE79267A" + "02268269C43282CABECC97C03B1E74AB1BB39E53517406BD677E1BD191A6268F9A25BE2D25" + "1C0AF79760E673199A935231B94D51031C1A9049776D2759D2FB85DC02165581FA59279188" + "3B36218F290A3FA97CAE2EE47EB8C8854D8F6B79676525005416F6ECE975DA6EEB684BFBDA" + "66B3F9F8A82E553B87061288D2164280CE46CB2CB9E12756214AF3F66F9B44498D22093598" + "15826E802AB34D44B54FDCE255A1043705DE4C8DD0D84ED70B355E25C6A5B5A9C40C6EDE8D" + "28B903DC4BB8324ACE25F793C63D90A46E1223B8740CAEBBBC1B1CF7AE67B737B3B35B05DA" + "014ED78E00C8CB73167E254D804BCF4A805E1688C71C7286B626E2E47F6B4F7C0A3B30928E" + "C6B02DDA9D705DFFEE1839A4015F32DD5896BB6EA4D021AA23715956F72FBC4BC19192D752" + "FBDEDE612375859EE226AB14D9028C77FF9FE02563A06D6D826B46047D5E7A5A9BFD3FF916" + "C4681C3AB298DE8FB9EAE8DBD0E6F8572F9B383C11CCF51CE07755560240E5CEDD6F76DFC9" + "EC07505FFAE84B9FBF7DFBA98F9F9C9CB819C02E4878AFE22A75682BBB92162AA4D1D9990E" + "D8CF1BDBA294C89547D200A734AB874567D25828651B0C7571F39642EFD87200FB3FB52838" + "2709BC9899B8F8662E5E33F533F80BB6FB58F11F257F49FA6C9795FBBE74438FBB22C8C7EB" + "82BABF9C0788B990C4585D129FB534C943C148564DAD0A64AFD97DCD0C25CB216273F6C98A" + "FB220784BDC9EB20E00CE619CE9B401382B9EAF236F03C667D8D4BBB3C86E6215F6CFC693C" + "E1E8CAE27A5057155F0CD591DCABF038D3123256BAE6CBC0BC5A0AFE50BFDBD9ED2507012C" + "12D20987387C91603E20DA18356ACDF4A0966D90C9D312A1A413154126695ECA94B170F897" + "DD02BEE0A5D6ED5BBC836B592A2B01A0728E2FD4A80190E3E3E35F92227FD6BEC3EB8C33F3" + "59959F00DB3C8C958FEC668849EA7C100C889D7E19D5ED20E8628149D141E065146E3F426D" + "9B24A73820B4B74A59747C7212447E0BC2EA73234211D4ED52A50C7D254892051BAF7F92EE" + "057762125842BBA9C9CA35D660D2AFF52B271C6C0259AB3F828E8533CE6DB723023B4F0680" + "0E1335D5D47A0E77A36E874801EA8B4BE0ED37D20D67E741F55642EB60201AE098C194E6DC" + "CD080E345A52AE79FAAFB6CE625D2DECC39D76C4DE077109BC06DF05EE4F0D6D9C97B2AF08" + "93C27192246CF0960FA38443A6D01E28E862040EC6C7E0DA88C6C2AB52A784608D0498B360" + "92E6F98D99879B426EA3D81F7365A4F7BB70B042AADFFB2F5F2BAA9FEB2EBAC0256B4FD6F2" + "CECA4A00A8E4D3E6AC0C65471E1F3EF9F9D1C9C4CF0437A7A33227E39BBEA593ECF890A0F6" + "C2B3A390A83B30B184A82C89B25C6E1B90D5852F9592FE48AC004D618050B4CF6B68CFE370" + "266CAB82E747392682246F56C888885E483AA8455CF5CA8E94245DC7BB3318F47265D47EB8" + "C41A3CD585C18965617F0A837F571A808E2035500300A92D739F2849CDD60F8DFD830D3CB2" + "04EA1B9F0ED8AB2358BAA5A7F8DF649AB0C801489D5DDAE4A40FC11229296B211F4494E45A" + "5EEC04E64632541369C51091B6413DAA22122FB143A531B4968ED75ED929DC6D885931BB37" + "09027DF53301B074EC8D1A706BA133B58D7058FE8F4C4E65B0F0D996B0971CF326330B2D98" + "403878F092093BBD8151E3449F4D3351CB7CD0B088FCE2B63B85EA6297F35ED9B5BC755909" + "00956BD7F6BBEF44F092FDCBD3B3ED377676362FCD7E00A64EF44DD470A5D016896427D8D4" + "A3FA8FB7DF187B4D2ABE924EFA4BEAC88471DD9ECE9B29A46593F4A89F11F84A2003255618" + "6CED8537ABC0333CA3A1D22637DFC41B68DBF84DDB50EC5B0DDEE61C8211D5CD1178B2E738" + "F789C3C466E65643C8A2859F8180A8E72248829A6B0108805D75DE3B99B1E467E0E7731601" + "BB2B26C677D7C6B937E0470E0B9A2173DCCB1009C2400B82CD208833B08B1BC9F3B1F10660" + "A5F3FC383F429F3587118A6BE090213234B67A0E031A23ADB6F292DA2B01282FD9C25FF42D" + "6F843D39F4D2590696046818E8FEB66640D98B42B3643E0683750B127B6C1B9E448D557722" + "F0F797D751E8DCAC27194AF965D95CB23300C77570B938CF0752560240E5E4C971F79D95D3" + "D3D3B31B376EFC8BDB4F3DF5E74F4F4E49ADEE277705B24EA20EA4DCA461089B4DFC81E288" + "2998902457282A135694E439DF6F89EDAA9C3E8C36BB04F71E4D00221202B0BBBFD99FC054" + "9C9E1BC1231F0C1040048280DC36784EFD9FA568E9DBC0CF0D404A8E70F69963CD7D8BE7EB" + "29AE1880DF72006882694AB78B1C02ED48DF5262BB973CFB4DB51FE3FDAD290BE603CC4DB6" + "FD6756961302D1DA20B2364F0DCD4521A2C83A928269A48746E934EAA7729F2D14D4FC0BC8" + "B1AE35B718F071DB4C826712440DE8A465104B3A58CA5AC5E6008E7A91B63EB3331E3CEDFD" + "CA81E6ACD71895EE4F4C0778961FA20487627E914CAB366A1C065F2F5A9CC8A727D1E7B35F" + "EA9A74E1CBB0301A6B79BBB212002AC3CEA6FBCE4AA9DB3156F7578AC89F8F0A5081E454C4" + "BD80A56D3AA60598ED84836F84A6BA543E0620EAFF0A6CAFBC9BBAAD3C66772FFEEC2C7149" + "91B8CDC53684476BDAD0A26B429088C4B65E2306E0289415D011C5DB4FE469A9642D6E0011" + "D4A5A1CD180DCAEEA61119BDBB002C09BE1B41B2361378D62AA3BF06FA3484ED78DEE9BF70" + "C89A430667D42873BCB78666CC766F9004FA71C1012CF32EFC7F7A06F7D7BA9DCC45F4B3D2" + "984D8505281B23C37D34BB796C701F8A1F1824545F1E36CC21931190DA9A0EF4E13E90C4CF" + "CB9642140B6BB7EC493AC411538759989D9AD320087AD03018F07BB8250EFBA138FED0C7C1" + "D68F135F0B2914F275109A8DE0C0D771360DA6C9420E8069686D1D7F45B47C5E2E53299E0B" + "63A500EFAEAC04804A5D480464A5942A078F1EFCC2AD9B37A7B85DECB5C5E55D4049A75E6C" + "E955A1956355618FC5220EB47CA4AFABF085100C1747DBAE231C9ED9D7DF7EA110A2BE212C" + "71B8CAD72FA7F028729A92E21B97DBDD1B784CF1E0FD31B2240CBE7509E82378F579237DCB" + "3A42DF5DDB0207340ED11706A2F98F1A480D4BF374D681C67CF7B3437E75E0565D9CAF9236" + "7407700D736C75D8EF213F82D273A7CF35CE15F7AB4800578C69D0AA3061A4F0C239CD4F78" + "170263C3182B9125BB86099BF21B111C55F957A1FE41438617C81B6CEB39127277EE2BD626" + "5BC3F6AE220287421359CACF449DBF83D6C2926129A4FAC97BA1CE64324710959601D0DA61" + "EF8C0A399C36958BF767F6FB613D05CE0CD0F2F9BAF8125FDC32F676B3EC9FB996B7292B01" + "A0F2C61B77BBEFAC6CB75BD9D9D9F97F9F7FFE85AF5EB972E513A7676760F45329BEB17076" + "C0E8D4A3DD22CD8401D72633006CA7B26C7F977340CFA5226D8E54A67E74C98A4F11EB0C00" + "45C2E62FF061F06DCC139624AD88110693A212C7601C859F40D214383F208D83DFD4AE1D00" + "2A3E7E24099217065A43A607FB1B5D6CCF1F92F48921698F32C12C78D62F6320B52D49F551" + "D86DD275F59E72B33BE0771139D050062F1ABFE885DF870EF2F8703B67873603574C2B1D52" + "638E8A0AA0427FA0898944D4CF729881AB3475430121606A44400700A736DB9827DF1097B8" + "D55F5060BACA00D59A80303171F5C12B5EDF24E473A8C302EC70EAEB05626D4A342D9108D9" + "22D4A0F28BA6435B339E9B236BDAE4CB0BABEFE216956C595DCBBB282B01A0F2CC479EE9BE" + "E3727C7CAC8F0E0FFED9D56BD77E40260290360076E621EF5C4F063447192B6D28D8C05231" + "09A0D0E6E432943B689D2F32BBDC3397019F7C038DA18B24B82480275027D40E321549F89D" + "5A949207B124984D2674835D4AF847ED61808F68B030204176C4DCF487A4105832C0134908" + "23DBC68855FDD3C65D6B90D8712F3413A6AC4C9278A6715AE0A310894A1F356097E3709C40" + "187C1D687E2649B7AE54499AA5329B1782C35E3AF4C7A6184F3230B64434C86B108FA4B6B9" + "0AE404911BBAB0267D7960DACD3F229F8A37E5BEAA8864284247579748984012D4D76269E6" + "9F681EB230BC4684D3FBEDAF8D6BF78644B267D3C01C6A3294CDAC191CC9880CC1A421349E" + "DE57ABCF86C1FD346CFD95A2BF72A972E114E9D6E35ADE7959090015DD9E6F0298CAF64C4E" + "8E8F7F5E557F40D4C0CE254A696140143D34150B0B0268B64D2260175EDA64231E1636BDF6" + "5CDEE85D9DEEA895642EDA54DF4A7A89BF1991891BB18530FA130A452C582E027C974D15E6" + "E14C1BB36D984E4C04E319BD0453CC7F77B46F0DC4C1A52ADFF483C9C34092DACE0A992065" + "9739AF7DB05353DCB988A7CEB7E728F922587B7039343A82715249D7DB7F29A4B1F3CAB7F9" + "2ED23FAF2B34B7BECAE0F0587CE0E7B6B06DDFC2EE905CA824722001C89D5C44353E6B43D8" + "31333AF69923A5D29A4B2F0BF097C56B331F54928C0B4E09CE075769C851E184885621E6DE" + "0F048A5EFBE85329612D0F20F796E8C7341CA5A50456BCEF3C63EC806999ED5827A0CD6F88" + "C967FBF74C6AF9C5E579BFB8A5AC28F69ECB3A74549E7BF6B9EE3B2EE36638EC0CBF34A9FF" + "1380B857AEBFE80E2A055A6A387B9932DE0E07E18DAC14F2164E1B5D899B26DAB0A86276C3" + "BA12F02E43035B48BDF1316491FAC93CA1C53C8B7969739080991A9A84068D869D556052A8" + "B07620748CFEEEA5FBDC1BC262AF8F1C26BBFAEDCB4224867A8CB14D870205CCA0E8845232" + "90F9184471DEEB89F67B7A805D1EE6CC0941A888D9A0CD566A078D74B834FA6144308F9A8F" + "394DB59FF7A7DD35A85C09C884D6B681568A78086DC4B838B10DCA1A1EFFE40C5728D3A2FB" + "8A12D9D5F81CAC495BBF986F8A3E31D0A768111B9F6259FCDABB625A311303E63E783F4DD3" + "E02464AEBF98F9244B0F183227BB4CB2955F36D15F2DA2DF904B54C2814B6B79D765250054" + "FEEF5FFEE7DD775CC697EEE8E8E82BDFF77D7FEA573EFDA94F7FCFC1C101D90EE70B9B1A2E" + "80A8BDBC24F68A790539BCA667357D62699B851692764DDDD759A7BB5A4858D2F9917DCC52" + "6BB724A95882646F12BDB0045D5C2A04B0B03E99DB672441687327295A4872C23029693A78" + "E34D32BFBB909D2FFD7684A9FBCEC1DFECAB4A696545664F775633AB649B731A50A00D9109" + "FA3F1B3FA0DA6F9B3D7C144C12E76A430BE858583298E4A9989D0E8D54A698722127BD7603" + "7B54CFCF607D013B27F603AB24F963F4A7E43B7E0D1F5463C442EC99D459BC2EC47FF0CEA1" + "DF3E9BD6FBD909903C4BAB7BFF43AB151C25D5186CBBC56C0A834BFB639CF9740EFF10DF17" + "5801D89787E2082CBB5F218D0891E74244DFE7BF4507599402D70A9624E164CE41E4E7FA95" + "7E810B581C93A0B5BC9BB212002ABA7DFB4412A7C727F2F8E0F0E78661F81E568F47679EF3" + "5D523D67F8BC734F11059577BED280BF5536B8E39D7B1933108A03FD792F01EC83B4991689" + "1F32F88360F8261E54E79A8F460534B4CBFDE4B4C21B9724643212E10D6D7FF98973A125B6" + "A14FE3569BF46436E6F3068027405DA52BD249BC26B956062C69E162198D009EDC462620A6" + "CA553750ABCF9CA9F58DB880C0A8AF8710D616EAF6A1302212E78B3888F58384F07006418B" + "8098D5DFC31C9247DA875258E7AD04FE29F48FF92E7BEAAB457FF4673248EA93ABF0ADF103" + "BCDE6DDE4B3B8D91D7BC69C78C44D99A747230602D8A38C84FDF0DB3F7BD1DC295B2000452" + "9F730D70B1289278D640330DB497AF5242A0390740094204B40F14ED83C3C1106A2CEEA8D8" + "F69199AB6C7EF65C427A018B3647DDA2BCC8D7F26ECA4A00A8FCA14F7F7BF75D2E6312A0B3" + "33FD998383831F195A084F7712304BC9F897748EB60D553AF58F4D07D045DAF1A1ED3B2D24" + "052775333DDC12F7108EB444AA26FD518317741085FEC3B2ABABFF5DE5AC4D2B11CE7A4FC9" + "624C9A170B69E24D5FF9B9C138823E75E44184407CDE55119A472D76BF88A6C267A73478DB" + "C7C1F3DC09A40EE671E16994BEF90000200049444154F6910603CF615B3D017000639670D9" + "66C1EAE8A0960F823D08134BE66261FCCC0EE270D038B2694489BCF96F4167C18E67542DC6" + "CBD6928197ABA97C9E89481A99615F10620E74E540E486E6CBE69D6CE0E65B83F78266ADB4" + "D109BFABA7FA9D9C27C73F860163524202A18277122411F9212C15B88634C2C20706E158EE" + "39A9CFACF1E054D8769F2F16989430BC7D1E0BA1CF9B9DCDEF6F86CDAF9EA397B970651CBB" + "F18C96590818E49274FB032F2B01A072E5EA6EF75D2E7BFB3BE326F02BC7C7C7BF7E757FFF" + "B367DB6D7823CD5E3CE39CB6B3FD05AE77F1E5D5499219A60DC04F040BDEC5ADB8B3613B70" + "84251DAF2EFC91E418FA94D832139805325D246E9C5284C88DE25C83F962766E24F2434808" + "DB657A6BB5DD8F186D8D9784BC01B4F96F09880328047BB3F76F521870D61BF2522FDD5885" + "81153B0E7AFE1055FBE18EE98B1A3E1B49307F00F6D00FCFC03DDC57EDBF8343A1A63B637B" + "70763FF92A4467BD25D2E563C9CFF027599D3E516EE5A2C439612D50E21D76EA2BD41FE891" + "662D0CF7C71C2C4DDB5699CC5031AF7FDCA98393E34665914E1A6BBDC07FC5A828D2009156" + "6624FDB8DE720DA81DFE632F07AD3D76962946F23D3477F4FE8F0284FABBCF04DADA2296F4" + "A88D7151D9BFB22F6FBE79E71F7FE3D557656F6F4F2E43194F657DEEB9E7E4E32FBD2427F5" + "F452F4F95B5156024065F30ED448E3CB7B74FC643403FCECF51B373EABE610D8D08563E4B1" + "852430E7CD45C4D5A5C2026E77F80E81CD1296D377195639352F5FE37B1F4980494C0AEE8D" + "45927F833B39315E875CEAAD4E06815E4AA7E706A611461E0E6725850E420B63DB7BBA3D78" + "F62FF8418633F1B34980BE0AFA09AB90F209CC8EF9012209480D8109F4D1E565500EF3458D" + "E61C06D3D8DA01461A2E0EF70597045B8385FB2218431B67CB5068991D49EF80F616F61750" + "9262E1F0467DA0B5C6EB67185A3401C8464C72C364C41658382D99884164A902B919EF256B" + "8AA0861F401960B67235182E071920926B6AE8A96925B79AC30A11570862EC9143E2EF2938" + "D168E7F0774EED7EE5F62888EBE3C787FFE4E4F8C9C29B7EF1CA48C20E0E1ECAFEFEBE6C36" + "3B222B0178CF652500540E1E3FEEBE5B2A27C72772FFC1C3FFFD23CF3DFB83EEB94F1929E0" + "A55BE2FB6A45D317D9914E24485021D18784FDB3038B5CB52C6E09297EDFECE1E2B64811FA" + "9EBCDB957ECFC4C254E69691CF1CB52C5C72DEE0B9F1EC84C6EC814C2800DC8A26B174550D" + "BC0752392F8C891D658B67C67C4A04ED12EB38C7D9CDEDDBEDB3F2F1BCAEC205C09204ED0E" + "7E4AE31881B3AA8F03084C98F0061E95234BA8BF4C56082DCD7E0FF5BBA2AAE99A2AA659A0" + "239D2BCD0BAF521BBFCA6D721536A0B7A1A556F5B50CC63213189F3327243C832C146B204B" + "964FC2A6ABB5B391F141A813C592E648B4E9EB6C98575FD82198D7C9BA829973FF743E80C7" + "170EF4F64AC4B9444120F9D3B44514176DE1BEB34985B2776C3672F4F8E8AB577676FFCF6F" + "FBE84BE73AF95EA43212806BFBFB52CB2097ECD0C30FBCAC0480CA37DF78ADFB6EA99C9D9D" + "C98347F77EF9A31F7DE1B5EB376EBC787A7ACAA22F49F869C320B0E56279C5CD1EE89799FA" + "51A363D182BD5BE89BAE04C761A708D189B18F10F0EF69C32535BB37C77FF3A37DFDB01F00" + "3919C2E774AB51424442BFCA1A9308C0FE68B7EF47608C61636C0608B525C7E1220C2A5E9F" + "8D5655C51CD90F3144CE7FABEA217CACF6EFFE36A0E0587B9E517B140E868959CF68387B49" + "BB8D71E5FEDA9FC9BC629F0DFCBD7E058047C193A22DD4C74929B2C3A4592327201D360BE4" + "B0A0D562F62B54EBD139D3257C4020AE199D354D322EA1F7F35C548F0808ABC97272B81739" + "BCF8DB580CD06E8C497B9A56823CFF992BF7EF526B4E211B3FDD67FD98B8476B9FAFE75E70" + "E81D70E77F37B291ADD49FD34175B339FF2C938B54C67E6E1F6DA56C860503D05ADE4D5909" + "00951BB76E76DF2D95F165DC6ECF4E4ECFCEBE52CAF0A2871589A718A5585DA84C17368CF6" + "8954AF151B86B64D2D3A15A7059F367FDBF6494C6F366E4FDE82CDCB4289B80D413B408E8C" + "2241B2D766F385C5A0DDE3CE4BE4AEC8A8C55A05A57B68283CC4507A69D0889179FD5BFF49" + "0F6DB7573540F34C6D35FB4076A686244D376098FB9FD4FC0948610E30340C756BA84F5121" + "A7CA55F818988600A4026399FC00CCFF8E33EA21AF40CC2FE164C8F052F12C0376B045529B" + "5B6D614CDB0157CC194D5B65F9014CDA0F2481C01923136C360A85C2608E7FC1A443512506" + "DC26698B3B09064267D76B7A3611BAD98BDE7FB3F7B6609C6C0DB5F1C151C09C562BF7AC84" + "674CE940E05C188FF806A1C1F9198AF73F3BE680C74F0E88455E7DEDD59F79F8F0E1A5B0FF" + "8F63B4D9993328EEEFEC7523BE967757560240E5234F3FDF7D775E79F2E4584E4E4E7FA614" + "F96E8D8A514029ABB807DA84017041FDEBF67093448C3BB0739440FDDCEEA7F8F9D2249790" + "3B3D9C62665F723BDBDF10F31C7C39B6DF719853124B3A63DEC123F689C553E5263888770D" + "32C977C129301BB417F6005714781A1868AA2DBB628BB080735ABC9342D9582B6012BDD326" + "E4EEE708006BE6C251BE006BA8E45D9A56060C939DC94971B19F0680CA73E124C2E78C0887" + "D2A146CA94B104821334173C3A399BA256CCB52B5234E49DE47BADBF2EEA7ADBAD0550EEF2" + "65DC307192E1EF4A04F6C512D67FD61F09A4705F5774D68565E7ABCDCC300C4E38C668820D" + "691A12E180D73F8DB39BC95CC3A4DEB8A4CF88C3359290B3EDD92BD7F6AFFDF4A6ECCA6643" + "9A860B5A46E9FFF8C991DC7FF040AE5FBF75E1FBFBAD2E2B01A0B2B3FBCE556857872BF2E4" + "C9D14F1E1F1FFFF501894104406009440AE51DCF19C96C7377396B689B71732C0A2A794E60" + "62084DCE60E433C0D2F75B6D842CF99BA423266399E3979DAC66DA074846669AF0FDD8DD20" + "0C399A34C6EA5B93E0D98CA171878F922A017D92EA70CD4217392CAF98398223315C3110FC" + "CB02E99008E80E44D4B24EC54F800976461EFB006B03C008FE78C8823942683CE010496003" + "200903C463ADDDDF85B552894B8536D13CB80D7F2E5382A404FE3E4AFC398EA5B016A94AD0" + "28CDDCA9927600AC34CE41CB0511CD49159EF9D68F48562C12C16C4ECD43BF16CFCD315D38" + "6BE38AB837BFB5DDDE0968048AC5F9CFE33F143F7B638009A2D7C89830A0D5422369A4ECFD" + "5227E1203D6D9E77377BFFCB8BCF7FF474D80CDDBB71D1CA3856C3B0917BF7EFC8C3834752" + "A708ACB5BC9FB212002AF7EEDDEBBE3BAF8C2FDBE9D9E9EFDC7EEAF62F3CF5D4537FFAF8C9" + "B1ABBAED183929B46139D04607C1EA8058DCFBB94B2A82703B3E4A971BE72AD16435A7ADF8" + "1C5199249D09244D7DDBD4D426C9C6F046DA149532BFB1C63E98386C53F7CFF1377532107E" + "F196D5850D0EA4824C11AC2060A7AB789FCBC4C8B5A01EBED617C5E96D95005ADBE13F1A1A" + "64A6003789301C2A9E1FC13C92A0D85649F50B698FCCC9AF9F77FF3CAFA7A82128094C0BB7" + "4518FC0B324FB2341F3DE699C751D4808D8738E8F93A68C01B744A3EDF29B4817F7132A3C2" + "CC93E67C80096520121408CE7458103FD169BA745A3D26F2E2EFAF39484ED9BE07AA8BFE52" + "3AF9AF090B3611F0B360A25B6CC17355BC067402C2F1DEAF7FE5EB3F75727C7629A4FFD1E1" + "EFEAFE55B976F3EA1439B2E44FB59677575602406572E67B17E5E8F191DCBD73F7A79E79FA" + "993F1D9DBA045EEF63284FC8EF4D0668DBDF2C02006A4C426593486DDB9D7F2E90D2E39EBF" + "045C25FC6B720CE848F712713A540992557125013DB320D7396DCDDE5F6A60DC16491AF4CA" + "7C0B8663D67C4D8D1D8D04A83DA7901A7C7E98E756E0510DCE562469070251180C1DB85815" + "8F396787B640B8882868AEAF9E0FFAEAD2BC0849C5B14AEF1B3BA6A56177206E6A7ECA77AF" + "26B94A0A4F0C8541B0B4C39622182BAF8B60727035366701CC9A9D68E1F1F1EE0B9FB341F5" + "D310CCAAF87159569088AC834B4B0ECA0276142CB59DDF11C6D5432747309A347FACC66FEF" + "F6406B832334DCA4D6FE190A06C87C3C7C4DE6F79B48B6691BB6F5B78F8E8F7E617B762A83" + "5E7C07C0B1EF678F4F65777F87F231ACE5FD94950050B9B67FADFBEEADCAEE666FF405F827" + "2727273F3A3372CF2C57119E34BEDD83CCA77D16DA3439C21EB739894810191CEF44DC06CB" + "CE42BCBD65FD2B3D22782362FF3775A3ED86063E6C3F76202EE4CCE48013257E971587F41D" + "353385D941BAC2A64BE23D5DD5E383DBD4C33905943BA004E9770E210478F0813C90689384" + "DE49FA460CCAE2F5D05CD706F809DC4A0B916449593A10CE017E2E35B60F48174D13ECE718" + "100162C1D2C7D56117D2793F4D704A5372F0845740C4A9B998B36497D241038101F12A74D0" + "10DD604EA84ED9EC677B83A27DDC136BD950901F05EEE0D174131492F6E810D4F46A3E7F21" + "12A7F900140B1274523F6B1DCC91B70445C67CF4EF5C87696506F2B151731E16E9C83993A2" + "91806CB7DB9FFEF8A73E31D9C59709D3C52AA3F3DFF1E1B11C1E1C78F8EF5ADE5759090095" + "878FEE77DFBD5DB97BF79B2F3FF3CCD3FFF8631FFBD89F7B7C78E84E4900C6B6F134A72127" + "022EB9286D1C9ED0461600817612B237B244E5CE658B0A7FDC1B48074B748540913DF4D5EF" + "335867F28164257846B2DD26721381C040DFCC21FEBD932A4216F1F1E3BE843E938D3D1FFD" + "0B29117B2CD9F3D5C15018CC85C609D2B98A7BF2532300E6649221C20069BBF8337D50BD7E" + "046FB416A20D068A96A8097D6D8F347B32AA270A9148514F6CDC9384C903F7DF9D143DBCDE" + "EFC52FF35FA61D49CE8D21B35D93EC59B3002C66DB7838F4DFC800122104FB3A6600E6367E" + "3FC49D0EEC7DC498D3DB44B6FE79608B73676349C54D0D26BD7BD86C69C102A6A9D9886EC7" + "1345C9EF65289EF301FF61D2CB66C436625565B3BB2B3B7BBB3F79B63D1B350172D1CB380F" + "67A767F2644C76544AE6466B798F65250054DEAD09602C27A727F2C61B6FFCC3975E7AE9CF" + "C9501699384BD0F38E3890631D9905C4A56A465C6CF0A69524E0CBEF810136E76CEF5B944A" + "0B497219C9EA7222D0275A27294DC4B50AE4441762AF494B5DD21841259D8F61618974A117" + "BC0974521354C4D486A07F714D4A50A9B3A405C9B98D22F66D05F6B3A39F722DF69DCEEA62" + "13EFD0CE68BF2069D90997372339E3390F990FB2A935023AEBC4E9623C89C80D6620A8F597" + "7EAFF49D016499D3E7B3C623CC079188C05DF37BE23FC299D09206A1BDC44241F6BC9D3894" + "AA910237A9C53C17A6422F9241246AE632B9B4F7D423711AA968390C70C00F6903267DD938" + "40D5DA515DBB651A85A00173731AE67BF030DCA9C7A5C8DEDEAE3C7AF4F057BFF1EAABBFB6" + "B37339B6F0ED762B376EDE90DB379E7A4787B6ADE59D95950050F9C37FE4ED0F03CA657C51" + "B7DBB3FFF1F0F0F06FECEEECFC1B67754BF9EF19DCC5B7179232077358B24D183B8E81051B" + "22D5F69B10BE04792D88E724A0B2404E42646407CD6C0156617051A0AAE68ADD3991243F60" + "8D49B85DFC5B9066F035F0D5BCB1BDB29EE8A76F309609FB030846FE004FFC66CB05E8E066" + "1B0FA10C868ABE9AB4D8A2FFA67E9AF6A622AC4F9BF4EE836EE0597DA092E4CDED23B211BB" + "EBD790346FDFB1BADBEE37700EDAA6301E15D7829CE0E85AEF8313191FD411A8AB50B40511" + "8CAA4AF3E852B8F2B5BE8482198727AD44A617D60D738B82CC3D83217E17018015C4E18306" + "EE65C0180E20BEE8A9BF73BCD88DAC9169CE88D9F8EED6CA5101AEAD7387C130BB14E16344" + "A272E5D3B34755F8E993935F7A74E7BE5CD9DFEF6AB988E5F4E444B667671301E8F784B5BC" + "D7B212002A77EEBCD17DF77665DC5C0E0E0F647777F3539FFAC4277FF8F4F090E27D4B0226" + "45489A79DA4FC78392B86ED2238EB93599A2F0061A374690000EA19288952CE5F239E6ED1B" + "DFEB10AE570218048995B3FFB5E297D0569F90DE2563CB2E9754DFF068EF4B068C22740230" + "9D51E0F66192E902097100AF410D1EF3DA9B8499A55AEE16B2E31337B01F2BEE27713D8379" + "10E5DB9795C886CD294BFF793E49FD6C752DF92EB033DA8C9CD51789DA2AA0D1B594CE4620" + "4425EFBCEEC448CC533574C8C6B726274091DEFCA06887A4EF6D92352C6A8B7E2876D223DD" + "A8830F12B405648C371F16114F1BCC63856B6B69B67E6FB39D04C82458D1776A1F16058F69" + "730B6A646A202D009B1F8AF918F0815393C5619872909CD5EDFFFAB16FFBB80C97C0FB5FDB" + "790D677A3665615DCB0757560240E5E0D1A3EEBB77528E8F8FE5F5D75EFF471F7DF1A51F9E" + "1D72DC96CD581B00A994F079DE5848FDAE01896033B52C720E5A43275573BE812C08475610" + "D59CD8AA28EF3F4B4F7C8009367D647E73E9A613FA13A61722103321A290A865F504C882F9" + "BB55CA2B6023132205C8CF021B2A4898FDAB344F6D73A7A376F16C036068F663063E7752D3" + "20C1AAD035A82B4AB7B80BC4C17C14A24921D44960AA2294CEB6620CF15CD29C8FF667BFCF" + "33F6615CC86C505590DCC71EC45116517AAF780C876A664D0324FCA0FA9034DAAE118AC999" + "3C83A53D1FE44238ACD64C6004B8A691534FCB8BD6A7F56F923F4C2A01A4FD74CB3926DDB3" + "F719B11FA6EF075F274D809F62D64B99C3F5688DF3788100253F067E1586E918DCEDE74ECF" + "CEFEF954AF5E7C75F8389E4F8E8FA5EA566EDE1CB52A6BFCFF0755560240E5E68D77960A78" + "E9BEE3E3E3DF7AF4E8E1CF3CFDF4D3DF7FF4E4099C7F5C954F6AC72E418919941DB87C538A" + "0CC20549CF851E728DE7BBCFF196315531EB104A93FAFC3B6FBF14270353C6B3E2B1F306E5" + "01E8101E45DF363B45704A83D4AFB32924A85C9D54D84ECD5261A1F18494DAC80040B4781D" + "AC9A36C252A1D1F0619EEAD0D41B76A6E310410BEF235073B0F64A6DDE202BE35A56802848" + "9534A9B57679F73580A0B7B0067F86E83F114F3C44EBD03C566F5748DB45C9AC0112949E4A" + "635293368095399E27823A5E789DF8A01400A3FB9E808C581E7C96D8B917ED1DC23C5210AD" + "6BD1987CBB146FB12A0311DDA005B3A7E03D704D01DE9BD124388508CEA46C94F4C70463E3" + "D9F5433BB3405540F6FDDC0B027D8EC8C16CCDBE1E57F7F7E5C1D1D17F3B4AC29721F5EF08" + "FE27A7A772B63D95DDDDDD4415D7F27ECB4A00A8EC5C79EFC331AAA7EE3FBCFFDF3DFDF433" + "DF1F3D881432B9C4D73C4AE3AC56071696704928D81B9C60D826E4CE60FC889885ACCBC74E" + "6F56544752B64203230BA98A61EC00523C36BFAD4D14F76447952A885103215F3FDD1F9DF9" + "3C214B6532656009FCD000C6428054585D4F0F32A918F704709FC146F3A089D709A2A6CA1F" + "50AF0A39432AA8482033DC77685E58E0630D7CB58C7C714ECC37C1D5EB39CA823CF4515993" + "A0395D31BCE90B45664452E764AD0DB039ACB0292249B5215574D362F00146711066D5FE34" + "14AA7EF8169FA950CCF37F007D86677E0A038CEBC84D39768010CC0A940DD05F692658B476" + "E99D97A6D51B876BD4160CB63E89389BA600EF0F29FEA8A68970EF8ECE7F0707475FF9CA57" + "7EF2EC747B2992FF8CCE7F57AF5D959D4B72D0D1875D560240E5F7BFF4E5EEBB775AC685FA" + "E5AF7CEDBFBF7DF3D65F7BF623CF7EF6F1D151F40180084879C5C537F12CA89BDDD38E3A0D" + "47EC2E4999B031C63CE9B483D85EDDA9E44538D4C97745F61BA04BC3F903768F014894A4A3" + "B6832557957E73B73F547C03C743435FA3C75F55EFDF2C75D6700F484BC8BBDFE001260102" + "916E1EBCCDAEC177AFF8EC550FA8D7D46FD22274068E60BBAF685B949AA306840751B14E62" + "9B7C1EFB3309BAC1A74801E31A39C200E3C7044612D9CC923E93195710501D8A4C834EAF3C" + "B48F8D4B059E7F4650442CD50F65236A6338D73FE0901F4EB13BC4762B5E93B6C80B7D1EC7" + "60088E82F6CE44023CB7CB24FDD9BFD7C9019F841DD6B20CD12C62249DA283C6327AFCBF79" + "E7CD1FBF7BF7DEEB57AE5CE966F12296715F1DD5FFCF3FFBECA5E8EF875D560240E585679F" + "EBBE7BA76572063C3890FBF71FFCFDE79E7BFEBFCA9B84EF8DBE81DA9721931849E91CE32C" + "6A6A4593324BD8AC59EA08FB483E49ACB2C391EFA54DA8A1B6767EFF50872A24AB08CD1AB7" + "43983CD0DF4A36FEF3341B9C9085BE0B271CB64D117E106DD3B614BD8E191442466A7A4D79" + "055D6B2009EC7CCEA0029FC020910BF2B667F17F6A0E87E60532A05477F2E40760D3D831A9" + "605F0348D875F67EE7A3854D5D0E9F3F72A4844F04E57C30C9BEA9AE89AAC1898F786CB067" + "D8F74B9C71D24CD151D9CA0E929477DF9C0E2348A6037428CE7E16CE6358AAFD39B4B8BC62" + "5CADC4675ADAED6A9138EA273472182EB466445E9D20AB6CCAD0D6B977DC56E9B6F2C15FEC" + "6C593C3AA753732402440E88474747B2BBB3F313DFF527FEF8B9A6BD8B56767776E52B5FF9" + "9A8C66D55103B2960FB6AC0480CAB8D8DE4F197D01EEDEB9F38F1E3EFFFCDFDCBB72E5E9ED" + "745885DB1BA1518497727BD8A4DD2A9448A460930E805F92D465400E2FE2D8F8E070868D8C" + "9CF628B10F3CDE495A728242129D309B61BBB4D9AD79AF53FCCB9A086A7E57343965C1AE4D" + "BE110618F30649E6894C4000ECEC34A768A7B6410E9BB7399605F53F03B424893DAAE6F977" + "47456A170E05EAC7D5C2F178EE6072C8D1004AF519ACD4446CE8943AD0336B1FA45EEF8B62" + "AE0953E17027CB8750F15412F1B175608362F0CCC42A10C4E0FB42CE82F45D49C7171BA9B4" + "94BCDDCC375BBB3956FAB84878D7A2E988020026ED814013376B0D5CE3C5CE7E46AAB56533" + "1C7FC3A142296787C214C3AD4EE7FDDBBB60C7030FA3345CFFA7CDB0F3BB458773DF9F8B54" + "C6313C3D399DF2B3AC26806F4D59090095078F1E74DFBDDB72787878FFA58FBDF44F3FFEF1" + "4FFCC58383C3E0F13E1596E271A0884E1289CBD32459370FEFA98E8A9CA44185C87B01B6B2" + "E082DD3CC4DBDF1C5E186CB183DB4C39689C3F065343F1E71B318134A7DE1E216027B8EB4D" + "0E0B9A12DF184B93BE81B2941AB67A050D1D664FF9E844C0B66693D619583BB34A92C05DB5" + "50F137A4F2E2218C4B527C9CA9789DDBD96106A736E7F1A036805B558076D01A98EA590831" + "115FAE4D9F61D7B9C4AB21E90F49BE38E4C7DB04D2D0A23972E246AEA38AFB7CB03A5C2CDF" + "021F3CC5D459DBFBA10306C54F9D24BB7B0B9393E6583AB5AECEDEFA46564750294DC33139" + "E2B1632E85E84D367B03788BB6111FA77CCC7581DA7F7E5F4747C00D4BFF4D8B326BF106E4" + "9F68CBB199AE5CFBC1AFE8DCBE8D9C1C3FFE6F4E8E4F2E45E8DF5CCAD4D7B75218AEE5FD95" + "950050F9C833CF77DFBDDB72FDDAB19C1C6F7FFEECECEC2F4212B4CD8C364548F9337A40FD" + "3F98F3543B67BCA17E70EC6349DF34DC0221BB491366E3B64D8BBC986159A00DAC925ADB9B" + "1A55F831D14B3428E71CEED66B21D9ACA72A6EE28439812544E5CE92144CE44659CDDFB66A" + "564947B0D5308EA68E26713DDAD609A0313612BF9B1DE02AEE61CEB5443E0C98421D4C8BCC" + "44A16EE2F06B0AF5D7AE9F11370092B29A1AD98A7CBD00BC15CBC363D67D3E943A03D53BBC" + "F2F10B6926A4650614223471D67D499AE3601BD52AEEEFB23088855443E4B80F67BC794D97" + "748AA6025CFDBB18833F45B498D6AB11B951A350482391CFC3F077A7340D821303017F615F" + "9FF9FB09B8E11712A5FDDE2FC7A77D4CFC73727AF2B9870F1FFC1F201997A11491ABD7AEAF" + "07FF7C0BCB4A00A8DCBD7BB7FBEEDD96313CE7F0F0E01F3EFFC2B37F637F7FFFA3C727C762" + "A2454A74EB214BC5373893044A9310904EB544D0466C73E1BDE73CBD8097804BEA9BBD4994" + "B07FABC753032862F3DBF7F603DB3A35D89BFD61AD102F89E0EF52AC4952906195F980834E" + "9505C7486645C9C1CE9DD214B66106F8A8EAF74605F0AF0E5450E322B18E4BDFC1AECF84C2" + "3CD2D56DE2AE35A8684D2425314BDE6CBF6EE02E432406F04E732065534F55154DD360DA93" + "865D89CCC53109DA08CAEA27960780788BF3A9E85322E447328B78EEE4377D4FD103EC1E08" + "35BC8D316BB0480B80353079F0539E8AD925BF9946D89741DBD1001B6808A4A9A135ADB1D2" + "C698D762AFE9C37FDC0D87D765E5138CFB705D37FBE9E4FCF7F557BEF113AFBDFE4DD9DFBF" + "F8CE7F66827BEAD62DB9756BC7876D2D1F7859090095A79FB9D57DF76ECBF8223F7EFCF8C9" + "1B6FBEF19F7DEA939FFA51AD96EE57DC76CAF66C6429F30771367E0DD20FDB2A4D32B20DDF" + "A4ED3E9108DB4507AAC6240FE03687148A7467E347E7B298B780377D6F795F0208F88366C7" + "3373F4CB590FD51FE1D2A78BAE0E985CF26143CD07D1B42310DAC9AF9D55F8B9AEF048F2F0" + "174746CDFDD73857B69169B5F0438E248800C2E4A1E610BB76AF819C3FCBB504A1F5E4DD6F" + "64C454F98A6759929B3A8FCDE2F471F887425B042747BA26DC0ED295895A985033F2CC7F13" + "D169BE8D2DE14ED06B39716EF598B4A866BFE7043E6D043DB976036F33AD8DDA99A1B614CE" + "9E7DB3500E0C8BEF67FF05BCDFACD5620D8FDBE59C3098DAC5AE4D69B3EDD9A336E2F4F4E4" + "8BD7AF5DFF894F7DE21397421ADE1DA31DEEDD9B12AC5D1A6DC7BFA6B212002A878F0EBBEF" + "DE4B199DFF5EFEFD977FE2A9DB4FFDD51B376E7EDBC9C97108093469B79207FBBC990C3145" + "AED019E0EC042FAE0637105B942218FC0D7C42157C0543B39FB9DEC16A023D9700F9A9E797" + "100AD5ECCDAEBE8EA9E3B2177C21D050A87523D0D776108CB5A593C6C5D5F08E240EA430B3" + "10E89B5A9EB00A7056D904D1FC2C1CCC15A9DC0D855993609FB3BFC376DB0EEC05B892D04B" + "6DA28122F386CF036B42306E6D6D6DC76C6AE60F50D4330FAA3FD3CD1C99D0D58EB0B4C190" + "D4ACB47616D647BBCF33F911A1327CF4E00402FE949CA769A1408C4D0B60984CBE2C62592E" + "2DD746BBCFC1D5EA6D0484499591777220B4353614096182460C586BA72D6203393BA0614B" + "59095BDB475F847BF7EEFFDDB3B32A57F6F63A727711CB6618A6844767A727FD7A59CB075A" + "560240E5E17B4C059CCBF8A2DF7FF0E0F4956F7CE3C7BFEBBBBEF33F3D9E8EB09416EF3B6F" + "16DD0127ED385C537DB36ED6C2938440ABD086E2D598BAB4824C902F1FAE91763889A6CDC9" + "F7FB2CC2F9F70CD60650F01B8016E22D4A071C8E6CAC10C8C6034FE8637D9779E366757C52" + "D506153C03200D3BE9A8B1D968BA47DAF33D6D2BFB1090094012D9B07D5DE1B287EB4110AC" + "E3CD53BFA6A37CADC351C5BF80AE4105ED806E7676B1C39E002E2DF363F2608F842481B649" + "AB697EDC1C914867CF544C6903B2A314C921380E3B111E5F7D6E512FEE30083399A5F9695A" + "134BBCC3A17693F665B3F1303B1C356CCAA724D943833090DF4E7C9F3877C0D976DB9C0E55" + "B667B3FDDED6B9AF3E4F47E4AEB99E7D92FD0F54E5ABA7B5FE83D3EDA99C1D5FFC14B863DF" + "60E773E100002000494441549F9C1CCB932727ABEDFF43282B01A072FBF67B4B05BC54AE5D" + "DB9707F7EFFDFD87F71FFCD0EEEEEE53A36F40A1237519ACA70DB0ED7438EDCF5405D8C45A" + "099B6F76ADB74D5F20936B732C3468B3B8E91A0E7277C02B85DA478F61F08114E50222001A" + "25D9A1678FECA49218BDB09B131D6455CA36E87D7635BD2750822C4D2AE7960CA85A3639F6" + "00D39845CF9B16EA70A08DD2EF12282A69609834996620FC5D3875309DAA679A00F67FE039" + "69E316C636F090A84150009B5F5C013929478038CA80F074C4C2099239DAD9B30D76399F3D" + "6B8D6CE5321170B397C0D9D1482AF20058DF9828DB5A02283790C460CDCE7806C6859E07C5" + "92F9078884508B49926F1A032618588FA47AE3EC1BEEB4384CA03F6A55748A2ED89DDE2FF3" + "1F90998EDB04636E0BD8B9AD278F101AFFDDDDD9933B6FBEF95F3FB87B6FBB774962E0C7F1" + "BB7EEB96ECECEEC8D9D949F7FB5A3ED8B212002A37AEBF7F1F002E4F9E3CB9FBE0C1C31F7B" + "F1A32FFEB5319615928BF41B6D216054FFB36D6A85E2E34DA5ABF33900B8AEB89A73BE1B68" + "EBC7B2BAFA524CCD4D1EF53009D81E6CE1585A025E09A0D625A5A89AD4A8B66D366265502F" + "169A55C949CE03A60BA467661951BA379264646ABC666B5EF1A65A2D4C549C148198A887D0" + "4DEAFCDAC6B45823D80721DAFE554DED0FFAD2694010835F29D4CFFA50FD2861F30DB052C3" + "813FC62C66425371725FF4158019C3FA202E25B3206F6AE8BC5290837F0188D19690B8683E" + "9F9EACFB6D6D4A237702F24A9C259104A5EF0B5D559A3ADDB562485E451A106DC0AFB626D5" + "92E7508BB61EF2470C790668CAB688B300064B79AC786EB14442B4A6ED99657222D834DF0E" + "0F2BCC0E843C5F083F145E624492AAC866B3233B3BBBFF6CCCF93FE6C1BFE8651836F2F8F0" + "400E1F1DC8DED5EB17BEBF7F10CA4A00A85CBD7AB5FBEEFD9431B1D0E3A3A31FAD5AFFF230" + "0C57C98D7AFEB760DB8A7AFA560A6F188481C1539F33F495AC1170294DC481788E3C20305A" + "52DBE20442539386A079476889E0000427B04512A3A06A9E77B9DAF4E4F3A68B2DD51DEDA8" + "236C0F2FA4FE475295006A35DCEF3E0011384505E7D9BB2926025F5465A7B1F4CECF4F55B3" + "85BBD6821D19A1A5A90CCA14D6C8EB838A4245AC0EA0CC140DF239C7BC5480624DCE84EC2F" + "60C412E688EC3BE18AAB48064AD312B12ADDDACAFE007E3369497C0D66695F9BFDBCD68AB5" + "B26971F69EFF5F1B28D37A9AD4FB03C6DC007E06FFA1F92ECC2680A18BA8F171D3D13D6270" + "326C24A59AF9ADCE270779E85E01C11B55FE530E00442264F38CB5B9CD58CAD4696331EE45" + "0F1EDCFFA7F71F3EFC95ABD7AF5D78DB7F99CC276772F3A9DBD3581E9FAEC7FE7E18652500" + "54FEEEDFFB3BDD77EFA78C8BFAAB5FFDDA6B7FE1DFFF0B3FF2037FE92FFD1777EEDC21699B" + "12ECB8FC2226C79940CC3A5325A00805898218F939432027315992E8E21E640F77185B0028" + "52F9339842371C6A736036291BCD9442D50FF3B5E7259391B89906E73ACB9F8F843CA5EB6F" + "766084F4DFA9B0A34900B27DAA47592B80F8FEE6BC57533220A56BB39F842A0EF281431F1F" + "C4637C4BE38141F12858375D3859C0A410F8866F51262236022E0BFF69CEB9BF582644AA6A" + "D5606AF0AA4A480EE55F65A6EA1671EB9B81F8B6A5C2D6364EA6A470FF13E79EA5BA96CC3D" + "F82B346518EF6663F63043CFFF3F45EF0C1E6130B478C1A8A92BA87FBB3DC318889226808F" + "06B6316D7554B1281CCAC73D3E6B3A525CE4B5D75EFF2B6FDE79532E4BDEFFD3B353B971FD" + "86BCF0FC0B524ED7237F3F8CB212002AFFDEBFF367BAEFDE6F19F3773FFFEC33FFE5C3870F" + "FEA39DDDDD4F9E9E9C90ED8F55F651796CB9C76C47725B6945201369EF432CF65C01DB78FB" + "33C3BB0D8F4F8A6B52A0C764B3CAD6C3C742ECB5CC5211AE4CC2319F3D2FB45907006EE00D" + "E934A595EB803C83713BCF3F8C26E7A90F527FCC6D1F340CDC70E138FD9E3801E425A9C6E9" + "596CB250066ABBA4C6E7585B82F6874D1913805527865AA84D4C58DC866FAA7D50A3404804" + "A46C1EF27E9CD936CDE48FFD15A491BDCC07358CB5529C7D70EBA3F5ABBC38A66FCCA3BEB4" + "B4BF76E21F344B48E613CD0B768CAE3BDD9A6D89CF1568C48AEA189D644352215A67E1F46D" + "4BFD5B365807A32A1BC4D9C6D5228090D9CF6EB70C84BE5E6FDCB829BFFBAF7EF7273EFF85" + "CF7FF1E9A79F1EB38BCA452F63C8DFF5EBD7E5C5E75F98A2A8B210B1966F4D59090095CF7E" + "E79FE8BE7BBF65B3D9C8BD7BF7B65FFFFA2B7FEBD39FFAF48F9D1C1FC333892DB00CA43370" + "3707A95AA416DA28915380B2F4353B6949260055B2AB13B2517A21CA49D0D49DE63525D051" + "FAEED580AB64E06EFD806D3DE50FC84F37DB7A14BC39835B23032DEE1B206A3501BC0CD4DC" + "3410C147FD7A0E99A46BB01993F4CC44C3DB48E05F3DDC6F061C4F3B0C3B3AC69FB408AA1E" + "B5507DDEB5CD2FECFE682F1D0A93FB24313064CE0B505B9D05A05B28DF40698E66E61302AB" + "13B440A07661EE4A09410E0865AD3541368841246C34F34E7A2D0220382796A411A2512F96" + "81B936C97DBE6E08F3C8E627EA03C803F911E094C018C24A94CBEB83B42F8DBC9866A111E7" + "0A4ED1B208527FAD6FED28652850685D69CBC930DDBFD98C7944C6B9FCDB9FF937BFE352D8" + "FEA71C20752BAFBFFE4D79F2E458AE5EBDD65DB2966F4D59090095C78F1E77DF7D1065D041" + "EEDDBDFFE31FF9C8A3BF7CE5CADE1F3DDBCEEA2D4885E6C4860C7132AB41C5BC9239CB1939" + "5D2DA86BED6B48E50CFE0C98D2DF27761F36AB598559E88439DEBC8A44C9327A38538570A8" + "72293D680200FC9656D7F513AE56F7902CB6AD662063C95A54BA1CEB26917A157E48132470" + "AB036DB6F31A9A5DDA47A20135644D220DD1F6DF110BA16B7216399E077252732748EF8E9B" + "1B0878CD86521A48D3F43B81B0C37534100F03C4C26D8DE7D806497DBED60F9832606676E7" + "9A8C12E6CCB2E9A9527A6A1036F24524AD03D6811DE96BC4A0A071615D0E4C05482BE64497" + "DF0B99F46E6AB92470668446BACE070859222A9DCF2AD0327BFF4F3E2D18038A8861B71923" + "D8EDFB71AEAE5CD9973B77DEFC3B0FEEDDFFFD6BD7AE49DDF6DABB8B56C6711AB31DEEED5D" + "090777ADE55B5F560240E5D5D75EEDBEFBA0CAA3478FE4C6CDEBFFF3673EF399BF7AFFFE7D" + "DFBCC89E6DB2380A3629DB24A2F357514B314C21541A251A5C6BFA47729C12DBC2CDBC202E" + "0D4245B9ADAEFA0D3687947B9E4AF0656CEAE7C207C5B45B70926B2B73121FD824DCC6DD6C" + "BF70522B253CB75852A504DC95DA9720CB2099409F2561DF94959DD918FC494A9F1BEB31DD" + "1240D9FA66B6E1F9FE22124E1CACCD1E3E8ED3FC67C54970619C832942DD5B5DA377BC8FB3" + "A30DCE2C286E23E7104C1F536DF1255E0780DA08049144A2336E16B28E81C29219C17E6D3E" + "07856CEB513D6E2D2F813C95962C665B5DF355E87155388C74EE0B22557436AECDCBA4CE12" + "FBE0E7F147726C63C31ABAA87128F4E0620708351F03218D9693FDB906239D4CACE6B8773D" + "92227FEBF9175E98B48717BD8C63727272228707075227D5FFAAFBFF30CB4A00A89C9C3CE9" + "BEFBA08AEA565E7EF94B3FFAC94F7EF23FDEDBDBDB9FC30219AC6D5F75902F26CDA9ABFEDD" + "E63A23DF047E96D4853903A475D60C445B6BBE1EE0CBA5386098664248522B03930989C7C9" + "02B35A243AA2B08AB782D4F92695CDF66DEA6CD5B0194B0348489CE29EF1680B6B2D028031" + "F8B53164934629E4A4C8668D48BEDC8BDFC14F6DFE2A01A5F1110B3514C10142EE7761BF94" + "395D71D308558D24424C9DAF46C05A1E0903233CB104674A801A816C17E2C8EBA4112E5E98" + "A684F085855E77008DEFC22AE3EC87822C7C0A26E8D127514CB6E43D255CBF6D2A034EA815" + "92F80CB6DA4B1B7B5B834C76DB38E900895E9BFDDFC88A3902460D8469111A012A4CE214C4" + "D6D607B4097367287C751E1F6BF7A8EE7FFDB5D7FEF3FB0F1EBCBABFBFDFCDFD452B63FF46" + "22376A3D26C747B9D8FDFD8358560240E5C68DDBDD771F54B979F32979F0E0C12BBFF77B5F" + "FAC1EFFCE37FECEF9D9ECE492ECC1E6A5ECCE649ED2182E6C9EF2A6ADFAC9AD4A04DA2B0D3" + "52AB9EB33102A29D48F8F68C6B719D72981D4B470551809D731C83F0B8194E86DA01D2B59A" + "745B22684C854ECA631601B52BC5A0BB6777AC83FB290423D3B39BDB359B114C63504C2E27" + "A7AD0CFE26C5077F02023D31C98F40C2EAD7D44625153F341F2CD9272743BE4FD8DE1E1CC8" + "EDFE9A00DC257BBF6456FD6B29C9D4320F41303B602AECBF943B229107762CF452DDCC6292" + "396B83C6B4C4E292BC79FAE331B6249BC1DFD4E21B3A16D7C6C9925E995EC242F53A2D43BA" + "0FD9F7CC51B3CE24A2B684589E67239E0DD096730B359CC9EED6AE6FE1AFAE31C280756B76" + "F4F43F3C3CFCDCCB5FFEF25F1FE7E53248FFD2F25DBCF8E28BEDBCFF55FAFFB0CB4A00A8EC" + "5FF9D63ADC7CE499A7E48DD75FFDB1BB1F7FE93FB871FDE6771D1D3D6E9B46818848427603" + "603F1FC00E706175AAC776037DA77B94764E3893B5CDC7C9410B892A963F5FA522491D65AB" + "133095B6E1557F1ED4BA0592B4389C0298DCCFA179A5A30E8D751079C859FF5845CBAAF9F1" + "EFCA71E4ED62066A26158A3CFB86F4B3DA98CD0706224E5C7286404AEB5BAB83A89047BD8A" + "6FF6E468E01A8978760187F5B16982FD0A3CBC339B03DC119104741F2F12BF8D8C606C3822" + "81247DA77B441EC8D19489986FDEECE419350F8255A6F8DB2F88F9F64148A4F8013F95D631" + "01E8D4A4A104C264E45829C7FF7C063FB9F027A74021023C6BE24AC8D1CF3E3BF6FF9980CF" + "6D9B400C4EA99AD36460048249C7A4FF9D1DB9FFE4E43FF9C833CF4D79F02FBAF43F8EDB98" + "1DF5DEFD7BD0BCADE5C32F2B01A072FFC183EEBB0FB28C9BC7E80BF0DBBFF9DB3FF2DDFFF6" + "77FF6F93ED71DA94142420E73587B7BB0EB0F74F71C93284F0A4023BF9A82165B1C980BA81" + "7B2950474F5906874212D7807B424E7DF158EB5228FF8F990ED2266A85A557980DAA607347" + "E30C1BABD211A92C4DF291B0D2341E49262D1293FF98B41E6B83AD1812B92A2502B294FC19" + "2C6BDCC959BD6F59F7B089317A6AEC3FA539564BA1CB110DDC6792C0D1D379905CBBC19237" + "457B204A41381F00013B83336B7648536458AF4410005EA9CE1928D36955BC1470803FDB86" + "9AF681CE2020AF83C6333C535FD668D107B46906FC396490A575BA18FE126563A7060A2477" + "4BCD6D4EB0E1C021FABB2049D178ED664EFB5B52BCBF38D9A657A5D3988D3F8EEAFE575F7F" + "FD67BFFAB5AFFDF4F8F7D1C9915CF4328ED9ADEBB726E7BF8E25ADE5432B2B01A0F2D4D34F" + "77DF7DD0E5D6EDDB7278F4F81FDFBB7BF7676EDFBEFDFD878F1FCF59CFF82014110FC952DB" + "DC5B885713B8E6D0400E2E6EB7959646D64E1B2B9619CDA518F701A0330848BDCD6985E74A" + "5DE2732DA603B8861D8EEC15ECB10D9333353485025A1D9C1487D51B00A78C68E2CF76F53A" + "617090DA231833E0C243A293BC05C0A1E20E7BCA5A8EE23660F4B2FD004741EAE4A41168A0" + "03ED83F0737DFC828627680BDCE9136353BDFF85884E9833900BF181A208088C55037605CF" + "9CD715A78F76FF0E72D46CEBAE5038A8F9B6F0DC943C974440598B83654504495A463FB7B1" + "63353A89834921F90B2C499BF40E291D24A42D61903BE9FAF21EE3FD41981131E07336F587" + "320CB2D90B263DD1E994BF870F1FFD83BBF7EE4DB1F017BD8CE370F4E4681ADBAB7B9723C9" + "D11FD4B212002A47C7DF9A30402EE3E27FFCE4B17CE1377FFD87BFF77BFEE4F78F0C784A7C" + "61C0605A001C176CEA740F172CEDE440E14D0A8E450DF8213DD6A639263B7E311BA520FB9A" + "9F40D6D4C8745890811F0BB79DD318C881C2FECCA0A39D24AA0E44E65818B2D799D354810D" + "394ABC242137708A6A73DF60433B0C7C93894060068804C0FE05B67B2F64D63944293ECAB1" + "4E984CDA178B9F47B6BF582F24549C1E17490EDA4B24248F0B54FAA483C6EC581E023EEC06" + "44CB4D2E0CBED60955B7A947A248DA25711579319577719F86D2FC41C4325BB6E7CD24D8D6" + "AAAD9142EB5282B66092F241ADCC3C66B10B64CF37C0B525076D91F9DD98062A49EA96669A" + "88AD3901D6C97F623B39258CEE087BBBBB64DAF7F52C1E90496BD088D5FC794CF97BE7DEDD" + "FFEFF1D1C1FFF0A98F7F5BB75F5CD452F5B60CC38E8C89D1F6AEEC5D9A7EFF412B2B01A032" + "E6EEFF30CAAD9B37C783823EF7F5575EF9DB9FF8F8277EE8E0E0A0A9E0933D92DAE24056B0" + "39F3E1264A16568ECBB7A43FE668361FB0228004B3DDB31D3F488A5C8A6F845C4010D22F00" + "1B218936ABC853063EDE80213B294161003593EA7B10E4919BBDEE9988C4308528F193D42F" + "EE98C8200F2D84A6E705C99863FCE1FEDFFEF19CFCECACC7A4060D503A265A66FF0BF6EFE3" + "AE56FC50C27C3868D7743812D7A341DBA04A9593C4CCA484C7BD0F7723DF93104110E7DF54" + "E4A6015352B3F35A0C2680400EE99C0A5E41C54F820C4EB0031DC2D3DA5186C19B555A1480" + "B5D17257D03854F878CC6607CBC5C06CC13277BA56C9B518362FA3A3DF28007CF94B5FFAA1" + "D75F7F4DAE5FBFD199D12E62D99E9DC9FEB5ABF2F433CF2513CD5A3EECB212002E671FCEF9" + "D3E3A2DF1195DFF9ADDFFB2BB76FDEFE33376FDDF8B71E3F3EC26F015084A5EFD23204363B" + "E4D6549B2EC53AC8923C13AAA9A82B80FD5414C7110B808DAF7335F840DEE1B3E06859E808" + "C4E743F41D68821681804F5CEAAD2D36DD7D10CCAE6FC4A75244846DCE2EF5770443E63CF2" + "0ED6AD9D06E8B699D3B30CEC83BD9BC0006393C850B4AD378FFA96FD6F56F58BDB9A176CC1" + "4CEAFC04C75877D47CF8B354CC498E1A2BAEFEF6F9E4E16120972ECF8469578A5D8B443AEC" + "DDE68494EB9DE75282BBBFB6F64CA0D9FC5F8C189ADB4A2155BF2B262CDFFEAC299BB4055A" + "DBE99581CA7A9A616DC74D0B9191C92C614363E4611E6BCB1360668BC2FD6F1A1336918DE0" + "5DDA897F205E785F6C5CDDF136BC664D03B17F755FBEF4A52FFDCD575E79EDFF1A93FE9C9E" + "5EEC24383A85FD8DE3B6139D31D7F2AFADAC0480CADD0777BBEFBE5565DC3C1E3E7C285FF8" + "8D2FFEE09FFADEEFFDC542523B83A89050C840399522423BDA0CCC9036E229E628FF7F7B5F" + "026459765497F7FDAAFABFB6EEE9E9694D0BC90824C10C20096C85C42609894560908D3084" + "0399C50E108B30410022641C8158044480418EC06121DB028523B09130C82C811CD8488025" + "248D66ED999EEE9EEA6D7AEFE9AEBDAAABEA57FDFFEF75DCF76E669EBCAF06648566A6BBFA" + "A662D4DDBFFE7FEFBEFB7EBD3C79F264A688C55117602F523ECF75E6E4B23735947880D3E3" + "7B34D80BE64FFE89A9BB07E3FEF0F21913A9EAC3DE360B531A58DF0D8374783F2517AB0FF2" + "DC2172D42BDD03F15AE2FF46A665A0E299C08EBD31CFA365833A659B5A909641F62A902DE1" + "E3C27EF810CC39E406846C263FF1F74845704DD50547E4BC63FA77CC81E3AAF8FB4069EF02" + "4F1C8472545E9F3A6F271DDD581A583B4BA7ED8E83DC6B278041F612016874B6D1F927B050" + "25902803AE645E052560E7818871720EE7603A5F0D22F4FBC0E9014DA9A571C6700FB815B7" + "C7E643927EB2BF1B9CDCA97F7BA0219000E310278FF6687363F3F4030F3CF88BFDED6DEA6E" + "DCC8377FCF59FD1DF444CF3FFCBC7A6A62B1E7DE0A00003B74E7332F02443B78C77EEA6F6E" + "7DFCEA95ABBF73E8EEBBDFBAB9B1A9B4A199B1AF942211D726DBA6244C8C7AE24A2D109BF1" + "9319F2999241CD3A034A18261DFCB4CC4D233FDBFC8795FD794318062B4809373F843CB10C" + "FE494EC934E2E1F5B2802CB4F685CFA50F628DCC853E1647EA1278512A9F903A378E140580" + "DE9EDB44EE411C51707A169F95689A123FF8A873593742CA1C3FE48F75A01236CB476D45FA" + "0C34B531ED8479AF08D11B5E07C0A7A07BC0AF11384BA4D5112088DA53BF4DB0874E588046" + "BB5A09403300C61414E8EC828E99ACE77462A57CCF9AA85B9914A866F069FF986149C0B682" + "FB41022434A54575BD8D5D1433632C565531AE05D4DCBF0BB31E5CDB135F5F595E7AFB8B5F" + "FC22DFBD4D847093BD2ECD9D3E530F37DA7F605FEBE7C59E7D2B00006CB8B3D37AED99B4BA" + "0D66BF4F972E5EFCDDBB0F1F7E2B76D523118905F3600969C63B3FC8982EC5284E1FD5201E" + "CC54CE6CFC2C55C3875F9EB26D53949A4FB7FDFEE567AD839074AF0BB81ECC979BE3668003" + "287BA4B0F3CF28E0702A10ACCB2782B029EC7843E6B082800276A404CEC63A66A6671CB41E" + "56111C680A1C44DBB87F019C2C03976CDF4C5A24A8F4CD6105007E5F20AAAF8F8F4386F8D8" + "2233E02A0892325401248132A7063A0E6F418A96016A44CFC76FC6F83A6024E00BE71C4C70" + "4C552B2E487F0406234D531E82630719C78BE9985C0B8222C2A6A706DF1347DCE4CF0BCD56" + "69C320F89CFE3EA9E640D364DCBE96D31838DF40BB5BCAEF860F34BB6F96AE5FBBF681E3C7" + "4EFCD9ECCC0C8D76F6FEE8DB58F3DFB9F38E5AE7B0BEB6DEA61D8B3D27560000D8F4813B5A" + "AF3DD33675C73EDADCECDFB7BEB1F1DE43070FBEEDDAB56BD4191B03C7AE1160E041C09C57" + "247842A1716321422196F5F4010710B5684C3C96A60C20FED7879A7A4275C0EC384CB4AE0E" + "4823D03C6F4EA051502C12602680A2089BD7D6D364023DD299FFFA96E63D1EB4017CAC10F4" + "7C1CF94B7FFB1C7008DE80681F86EBE4BD0BD841B193553682DBD326A621E820A8007DE5D5" + "F13751BE94021A6919AF0FC143BA0607691F283BD5FD87C98A245E93B24D827361DF7F276B" + "E3BD73A95705EBE3A42B5FD550F5DE2B43C1552CECF863DC1D5C48F9F94A58A22030837500" + "55FB7B26DF6FF8CA03D3825DFC506FC3804E7634ED194E6A8CDA85D8B3BED10090AE267DC8" + "07DBE5126E4FFDF7898971EA6FF737CE9E3DFBF678DC9D9D01DD0E16B7238E468F02C022FC" + "BB79AC0000B00FFCFE075AAF3DD3167F19363637E8437FF4873FF1F69FFAE937BCE4A52FBD" + "777E7E5EBA9769F91E3F6E311F9F62130D47348FC9C22616CCF9A6232F3E901A07E854A825" + "BD03F4143985290FFE005165501AD43CF8C482298383975B0F6E82923282884EA96C7E5BD0" + "9F1B10A33BC5E57E9CD30F4CD5A35314B19F8D204346ADABF3508A5FCE08AC8487F485EDEC" + "A7023BE3A885B1106901E7546C631E6C93CF6912533649667F1C03181CD0835F9B9C692732" + "FBAD98D02B5BC034850726C0DCBFCA02024C7AF3F02B479247AFCCF74CD5F98EC7EE8E5830" + "C88257F4A59A3E329C02FFAE544D3AC00505B95A7BDF8C1FE04BAC9CB940B85E98C9408D80" + "0D7FF738F520EBC90195ECA3AE75726A9A4E9D3AF9B61B1B37AECEEE9F6D7FFFF7984580B6" + "33E893DF19D1F8D88411CD167BEEAD0000B07BBEF89ED66BCF86C587C3FCFCFCF0F4D9B36F" + "B9E7DE7B1F890C408C325A222388FC396212E7C1EC4032AE8252591637084A4A68F014D2CC" + "45A222398AE43BAD7308ADA82B640F46799DCC015B8C80C6B51879E6542E47E3FC2175989A" + "1A697411CD358F803D808835CD93E787904FC8082379612484F9D0D7752D4C9B0798584850" + "D2A7C37E1893702A47F700BAEBA53E07DA6CA0D9EF118F8D4637EF77DB1F0303E4331ADD22" + "CDCF7B1D76B9DFC11C8FFF6E35225A36176BE11D8C7434D578D241D23523A5134B22C7D3AF" + "98301DF67A5418C8208F9718E0678E34EAB6AC9993AA8600423CF1EFF07BC42C99E72E9A49" + "9BC02C0051109050A734EA5E06068DC877C5EC13964F8640FBF6EDA39327E7DE73FFA71FF8" + "BDD9580ABCF5ECA61C9F0B8BDFE199D969EA4D4C28EB58ECA6B10200C01616175BAF3D5B16" + "9DFE073EF007475657567EFE2D6FF9EE775DBF763D0FA35B69338C561D7468E3AE6CF5E7D3" + "2C01E2E754A45E71C89044F4F20E28FBD6EE7BB200A16A9313C1E8DB1213E9EF1E400446C0" + "482D73F80BAF2857ADD74BBEB51414007A70DE41DACE2A40C24D7C3A619C3A4DFB5E5E66ED" + "F01DAC472E5317DA302BC010704546C0F526B603F638489B66152AE6FDE499D99097014CA8" + "A0330EA4818A01AF6BE17E0470400596B230007CD0A0A94925352D7C779BDB2E550A81FDA3" + "ED47400233F50622258F9D23514F2080573EA374BCD96716E5C1D8DDFC5A8DAE061C7D60FD" + "81302DEAA8EA16C37539AB97F902F14DDC2F80C1251176E60CE6DAE2E0A2106874EDFAFC6F" + "4C4D4FD525807B3FFA77B4BDDDAFC7FD460050ECE6B30200C0AAEAB943A7F1A176F8F0F3E8" + "C973E77E796969F94DD33333AF8E7303AA0AF84B74F6011FB47294569ED3C19859C228518E" + "C77F0DE054B3B5816776E0248DC300A76B1D33D2F368DAB98F23C240B65D3C87A7F6DF1011" + "0732ED670352E2D93A910908FCB0CE4AF94C7E5BD68E91BFA63DB8DD32396DE68340C23868" + "C43FBBD0C3BA3DA9BC9273FB59632159BF1CD6ABF02C1D2B820F6FFA2804732EB9340717DE" + "4AC5645D0065105F5A6B4A7370EF7D0142692D75091D96AB825E45EAECB3B2431371CB9E05" + "6118F43E402A80074A41C31D12D16B3A2630143C21A1E2EF2BC2121116EA7538B8EF75CAA2" + "E352EF82743CAFEC8F013632EC9FA9014753B3D374ECD8D15FBD7CF9E2F9FDFBF7D3C80F69" + "2F1B3F83F6DF71A09EF6381CED7DA1E3AD68050080FDA357BEB2F5DAB3695158B4B8B4441F" + "FBDB8FFFD037BEE1EB1F8D9D0987A3A17D582733CD72789849702687E960BA9F65DE382D00" + "39F0D45AD59B76C3ED08C5F31C76A0DF5D46B50A3821702270FE1C40785190B37383281C1E" + "B426674FECF0700A6030E7E0D4C0D346FD92CAF64A8F7BBB36BE0EE380A4F73D7C0E236887" + "A585FC0776FD6B40C86ECD7EF8E73EB5DD155095446F81D5FF1C0D9BB23B664094A5D13611" + "81D0E5E15F02AC9102E7C475BAA131E84CA81A06A5D7EBFDF1A9214F682266AEDB67AADFA5" + "EE79DC9D127BF5C77BDD0CD60101AB3198E7EFB57D81685EF8FBE714858590F42FECD0D161" + "8BD604F3F9DC4A1B01684CC9A4CA9B5CC4B60BABE552EF81783DD3D353B1F4EDC10B172FFE" + "C2D4E464F3BBB7C799F0F82CBBB1BE4EA391A7D999D9F69E15BB29AC0000B0A5C567AF11D0" + "D3597C905D3877F1B11373277FE6CB5FF1F2DF5C5A5E4ACD4EDA8EA25628439B5BD5047099" + "133F10B9A44A1FD2411E52B6818FD0BDD2CD2CB4223927D15473D230DAA5DB5CE6F409A7EE" + "C1FBB0EBA0CB1D69CE4460AB5E0791BF6785BD5C85E6EAC1D953061E4883D996F357FF1804" + "4484049B8C5680D8C9B4EF0F33341402CC1F687EA4EA7C2B6CE4FD16F71B5C6B5F444F8040" + "2AADBF95B3CFF7D4EEA8617BF8AC5893EF82145C28B23360254B843B6C8DDB80371CE7CBDF" + "516C67ECF8E7B2C741CFED950971150FF7692A03BC0BE6D40C425D62F29C2E699761852101" + "E61CE6346045012DB303B9F36774A120824F80CD97EA3901CED191441FA7A2000020004944" + "4154471EF9013FF43433BBAFDDE7628F59BC8FBD6E8FC63AE3B5F23F403AAAD8CD65050080" + "DDFDBCBB5AAF3DDB161F1677DD75809E3C7BEADDFBF7CDBCE6F0E1CF7BF3E6E686E64BAD14" + "DA3CBCF4A19FE552C5E93BCDC28293738493ECD4192A606067AD0364481E793E319ED63160" + "A95BE0F385B6789023DDDC69EBB19CFD3CFF6CA4E15F90CFB21ADF96DAA92E40697A8554C9" + "E578A08533B0601D34493F046101E03B12E0FF545790031C8216B99A6FB79A04B9C1AA6DE0" + "AB03D601EF9F361E529A9AF7CE9424B6EEA18288C6795B67A9DD1E816C30CA7906523EA5AC" + "F43B8AE239FCEA4AAA87D986B4A79DAA82EB57C11EA70CE43B049D2EE37B2BDD787991071E" + "91CFAE95D76DF415007A788F13E860108303B3F07BCAAF4A7A82D759399A9E99A1B9B9B91F" + "5C5858381A27FD0D76B6692F5BDCA3C16010679D5067AC127057ECE6B40200C00EEC9F6DBD" + "F65C58FC25DA9AEAD1E2C2FCDB0F1F7EFE9BC6C6C6C68683A1A12BE5699A3DDCD929118BB2" + "9C031A35D8671E0148B00959FE2950A5409BC3C394E97B89BED383DDE3033A0413E9F339A4" + "2C8FDD25D3DA21E85AB33EEA3E13D135A7F500365C332E398C4CFF767B6E89635580476D07" + "CCCE47D66A225F88B8B98D6D42337C7F84B100278D3A01CA06CBC8B90170514A1DB0A29E3F" + "8A60C80338E2EB21CA53381EF64DA74E3A70B6B22C27375786F388A814341EF02D4A7B80A5" + "7C9C9E72CA404127C5104059CF804AF63E408D62D3AF0FF7A7452733AD9FB149B857EA9839" + "3D601B16B1559DAA29398C0FC74E87467E240D87F05AE5BBD14A95A5E98821D09D771CA027" + "9E78E2371E7AE8A1F7C7BC7FCC83EFCEC4EC0DE37B13271CC6BDD9DEE360672F580100604F" + "3E79A9F5DA736531E2585E3E7B766373EB475EF39AD7FCEEEAEA9AA5A6914E172A323919CA" + "1EC246AD8FA6EA6E33535D92E318FE2A4E6026977B0C608B557EAB02952000C13A0DC01CD8" + "1B1FF2CA42459B5C7F162503289128CCFC2C6F34C49FF7B2883C3511200AD4063FBBB113EA" + "A4CDD0A374FC808E990119DE04BC4E2991D2E14E01D70FAF35D1B032072CFA035CA4FBC1BD" + "FBF1B6B333DCB513404A192524604085C975EB07B0DB9EF4DC0F4DEB5EDE5B6D07DD7CC825" + "B53C8B2925F287D01F9DAE2C5DE6EEE7013F5014503ECBEC3D0259028C035BD2DC8B91EEF1" + "C837FBC3CA7F66219AB1C54E7B096469B2F8B958F2B7BAB27ADFFDF77FFA1DFDADBE0E3EDA" + "E31615FF879FFF7C9A999E69B14EC56E3E2B0000AC3735DD7AEDB9B43BEF1AA78D1B5BEF5F" + "5C58FABA7DFB66BF7FEDC67AD38C2499A16FE101687D770308A4A3792B1FA779D796E02A28" + "DD9CDE6ACE2DE26F991780F42A3B9FE46CA1EA40140441F504D8B2B8112F7A795D9703B9F8" + "108C435766C06B0918BE9748236860381048C86E04001F4948D95C53680D466AE5D7B93F00" + "36043269086025329122B22686F64FD1ADF7D95AF3E3647795835D4B56E359B2BD4D9FAC3B" + "E427275B0B0F535E1D8187DE66273752A9F1E6AE8DA05A80F016E884A03A6560C155D0F7A5" + "71BB02CA9CED5EC4E7AAB852807521A9919474EA638D047FBF1820407742CE5A34958595EC" + "6D5319A48C0AEB019AAD482C027C1FE33F262626E2BD1A7CF2139FFCBEB89643771DBA2D9C" + "7FD5E9D0C6C68DDBE25AF78A150000367360A6F5DA7369F1E1B7B9B545F73D74FF0FBCE1B5" + "AFFBCAD999D97BD61904400EB7B144253B1BB5710A0073BE367A559C2E0FB8F899543E8091" + "BA1003386C48A267981048A40D7672DD40C08325ED0134A761C7D7382FAB41B0D17C1045B7" + "44CB1CA5628D3A83208E0E3360B45B549FB3068D830BEA4CC0A104E360C974E893DEFE48FB" + "07E617ACF36F6E213BDDAC235FAA1AC8E9776FC4647A5CFE77FB31ACAC8C4B144ECD54B0F3" + "162008331D76A5B9ED7748098206B0357DFBB9173ED7F273D7BED40190F70AA27A05802463" + "A931A541CC5A84C6890B38605D877702964CCA09CB103195C520D529FBE57D7A7FAAF367B2" + "C139440BD85650BB4130091647FB1E3972E4472F5FB972FA8E3BF6D3D0EFFD123807331DB8" + "ACB8D8CD6F050080FDC5FFFA8BD66B37832D2D2D8DAE5FBDFE2FDEF6B61F7928526CDB3B3B" + "A2F097BC2CD3A0A02A670A18A35E4704747D90875680285C0C68F1F437F29ECBB0829ED350" + "03361DA0EA79A8DBE695A023CD1C26095881E30450FB030D2C39F2C05701256C19FDAD9300" + "A9BDC60CA4E8753B0127641C9682A65A3DCFF4B168DB1213901C9682066FF6941417D40D7C" + "0CB8C3B5C0FD0876CBDBC70297EEA40F04C0C524DA4B9B22E374F95DC20DC818DF00801001" + "6466CE46FED6B9933412926B1280A0B2162E49B5A023310BA0C817EE4AF637004E83350A1A" + "D02EC615AE5DBE0EF09B2060D701402501D5F8BD21523C103BFCADADADDD7FE5CAE5F71F7E" + "FEDD341E1BE0ECF188B84E970C47B4DDDFDEF3D7BAD7AC0000B08BE79E6CBD7633589CA4F5" + "A13FFA1F0F4F4D76DFF4FDFFF25FFDF9FCB56B34180D0D3DEE533DBE8DD6A1F69C035023CE" + "E387B0A65483E4F4AD363070339894AFAF6446813A28A15CBD3A678F2587840F4DACD3F6C6" + "D9336360F3C7DE44F1F5117CBBAE9F9CD7C97DE93C6D870EE58469111E8E6D1D2F479D41DF" + "8B9DF578CE7972DCA8BB10711C44C8C82E8400F783CB3953BAC4A6263871ADA023C03E610A" + "A0ED929D0017F1B7AD74012F2675BA031684F3DE3CA5CFA579D3EAD835A5E0B283D6837C2A" + "68AA270C113A73A4E23375BD73D977509DB0DEA26058913A7DB1BBE0251D538FC50C534C36" + "35CD8112A87671DD9D2C6DD5ACB97276CFF93B3FF281A6A7A6A202FEECE3478F7DCB646FB2" + "16C3ED753A3C24E43E18ECD4257FC56E2D2B0000ECCB5FF68AD66B3783C587F28D1B37E8C8" + "43473EFC452FF9D4BF7EE5AB5FF99E85857959193A147D603AD325AF79438A8E1814C88359" + "E70AA833E49F3B9931E0E57C4DA4AAB385B207A5E902080E1A3C013B73AEF34665BC89C6BD" + "97E8144BEE4246F50BAD0F4A7C3907800179AF9CCB0BF2D107BDAEDDB000D2A2D7CE86575D" + "853A30EF3DDC8C264AE5B1BEC664F21FF7A983B548A0C922BF4634974BE302003F3CABA23E" + "1D596C28750445F53F2B1D9C84400B340CCE71139FE6FECB5C20CB1BA4AF1482AC749A5D58" + "8380743FDC838656CECB17D589239D2F15058E8CD032E44A7F73005D738C6099EDA8F7C137" + "3D1A84FEAF941D91940E7CEF62DE7F301C0C3FF1F14F7CEBD2E2E27264026EACDF685DEB5E" + "B3F86C9A999985E64AEDFB5BECE6B50200C0EEF9927B5BAFDD2C163B6BADAEAED2B1E3477F" + "FBF0E73DEFCBEFBAEBD00FAFADAD65ED8B4160C68D7CC4EBDB4A80FAA149D0BD8D3F0FD63C" + "98432AE50ACAD1A607B597B7230069DED74C7CC363422E3677AE59DA8107EC1871A3E4ECA9" + "155585CC793338C054482EF243F68242C89C0CB2142E018B2609DFF85A70FEEC90A14BA1AE" + "4FF7D57355403ADF6EC39430DD215B26DB82258BE967C9F11A8D6696F2E09484BE9F243584" + "33EE658F0110B41FE5E9BABDEA4D3415951CA9F7B5A29F1781797AE9A30F6BCBAF9358FD1F" + "48C11C83CC1AC47801AC21EDBB0BA07D085A05C014BE8570B8FF7ACD807793AF57AEA27E0D" + "CAFB5804C8A78BBF833333D3F4C489B97F7EE5F2A5B9A9E949DADCDA68EDDE5EB37A601945" + "0030B32BB02B76F35B010060DDC96EEBB59BC90EDD7D28E617E9E8D1A3EFF8DAAF7DCD7774" + "BBDD43DBDBDB5A872E4E3690E9106B22DF3C1A6C0C9BF9A0A1C31505B7D4E8F3D121D2F52E" + "1B0D1CC0D968AB61BB323D1847EEFA80F5961DC81CB97D8DA3E71C20D89C7B0E22BCB77D0A" + "F8E71CD9AA6E404B0E253DD1EC8C3A07643C52DA04FBF3F32C7FA49151A7818A0ABE1E5D17" + "5C97C562B09726FE17D57B53DB0E2A4E0F8D7F8014A85BF77A2D2BE43BA5A98DD41447F2F6" + "802A9D4B69222C4BF5BA86A0C39C449C4A81070D883E45C01C88506B85BF1CCB7E67CCA619" + "50AAE0C6658806D718524AAB6E51AC4204F9CE93A4B2B84D36C177CFD3FEFD07E8ECD927DF" + "79EEC2A53FFEC2977EF12EC069EF591C5EB6B97183569616E1BB57EC56B30200C076B66FFE" + "F19CB1C5E6C2E2C2EAA71FF8F45BBEFEEBDEF091386E336A04720BD62B8830CAD4E666B952" + "71384E3D03966D2110F038A68ED1466846DB62B91D767063AA3A481960E3228389F81B6BC4" + "F45E7403A4B50A10B99165183CB5227923420C0A90889BCB046F9C203B64CDDD676986A06D" + "901500053DB66133020D4348E43DD4AF2368C9589090D205F84845B53F3A65BDEC201A0B4F" + "5AC3CFBBC11A0B6AA641032054D53DB303722E03C260F63E8FE115B5BE5770C03DFDE3CF2B" + "0080522EE8E4C0A291A08A1040F0AA438BD968F7B5E0D90194A5166CDA0B762CE4DFE3E6F5" + "CA593D021FA305821263C2C78FE73E74E8109D3A75FAF71F7EE0E15F3978F02085BAD90FED" + "69ABF7DF3B69F15CECD6B50200C03ADD9B9B01A8CD111D3878889657D73E7AEC89277EECCB" + "EEBDF7B75756571B10E0E069C9742DE7873938CDC6EDA153170E20790B17487BB627A7C2F9" + "5FA195217AA5947F35317FD0A97F968A2775B046BC6823683935D7AF87147FCB835D1D2F04" + "B2BB88ED6094705A174C0C067684C459A3100EC58E215B6F9E57579681018C0390C0E7B757" + "87DB83CECF67DEC499CFAA808E07F4F0D278C6808081BC348E296E00207205C6D13A0993D9" + "E90668CCA48E37A4F1BC7C2C74EAEDD6B9411C2A540298EF155CE22E397C66ACB48F405A03" + "320BE96C8E142431F381637D796DF578DFFADA52939F0A00564A03482A278AFEA6A7696969" + "F181C78E3CF23D55D5E83876F6FC8CFFE6CE55BDA632A4D53BA4D82D650500806DDDD86CBD" + "76335AADC8F6440F3DF8E07BB7B7B626EFBDF7DE77D7BDE053BD71DBD12330603F9789AB60" + "504B004795BC497AB802456E88FFF4518F6A7E7374530EA6D17BFA6C683B6CFEA9270B5876" + "13DCE57FCA2C7D383E41BFF9907998562E5E0714C87950F8D576FE9A2A7121E8A02471FCDE" + "E4A26DCA424E23743EB31E3E009462B065AA0C205277162C98BF65BD18246584A02B900CD1" + "E11412D2FE913AA840F9DFCAF942F42E6C93D375E3F70D9D337FDF1C5E93FCD4093B833F41" + "A959907CD1EE20B2B68A0146F3B9081A86C341F37B942C6A6C90E297FD6100E8038D0004F6" + "BADDF8FAE5871F7CF89B63D43F3B3B435BFD8DF6BEEC316B52406332F4A9D8AD6D0500806D" + "ACADB75EBB59ADA63F07C30802FEFD810307665EF4F99FFF4B8B4BCB8D72391798412C1D12" + "D7EB53446CDEDB72A8FCB0D50AE9E035498FB4691E0407EE5AC7AC002BF3D381C15DC8585C" + "C42CBB8AC5D267B9B0501908702CD817201997F885AC4C3293CB65437734B246C042E2A8D2" + "6B3E3B46AB7E9D4CF7C000A38749F56C164CC0BF9DD152002E31F78B170C151EC1C9B1B883" + "5E90609B9BDFC0E43F017FEA6125324E237DA38EA08E9C5DA591BD1F49048F7D12B465EF2E" + "A3A5F98B013743181DECAE27DB66F793EF81A615D2F5F15F84B1489BEB03A6F5EB92BD0898" + "AB668250EDFC8531832F315713F0A02C0631B1B67FA2D7A507EF7FF8BB96965797A7A6F7D1" + "309EB69AD865A57BC9E2BE0C8BE06F0F59010060D3FB6E8E61409FA94DD14C9D877CF4B1A3" + "EF1A0E879FFFC217BCE007373636343AE767AA3C16392255E7005E99D97523A40A781CF1FD" + "1A851A1A20A3B6B9A18A0F6D36A20104B6CC0EA7D8E1FA3477CE3D7103D0EBECACBC384E3D" + "9E320138FBCF84DAC18210318F2D7CADE04D15FBC144B7E67A845ED6E83484343931092BD8" + "B7050066E8F04C07C700D704FFB6E573640083EC630205AED27BD59C337D2B5C0555130E5C" + "33F7D2C77152CD3B1A30E040ECE9B21443F367B54B4DBE4D99C8C2F11B27CD80F433D29347" + "BF83928A2075E0697CB2D4AE22D30500656CAC79F435CD8A2C2B830DFF580F4249CC193FD7" + "ED76E9E4DCC9EFBCFAD4D5FBBAB742DAB058B1A7B10200C0EEBCF360EBB59BDE9CAB2B03CE" + "5FB8F8D697BCE4A5CFF7217CEBFAFA7A436942C42BF176EEB8C419A6A8882A8DACC1020CBB" + "31CF739CCB12B435AFD2BB7A2CEDD087550ADCA90E856E5896965E63A53E38486125386561" + "16C6F5FDA4EF85E97DE4A8E5B41D94F2E12C80265876A6E18FE6D5E17552BFD62C13AE18FA" + "D9A31EA1B59F229AE3BB84BE5C3D1376232404014E9D237EAC16E5713AA1E296B7DAFF011B" + "FBF03746F6A925B4F3920E483F30DF15D44C982B932F09A632109C05396F0836DDA1838112" + "A085A15704A902462AB655B5A6413067ED00BCE85A9CB23299D623828598F73F76ECF84FCE" + "9D9CFB9FB1D63F2F6DDC8B26BF1FA188FEF69A150000F6F0C30FB45EBB152CFE82C616C197" + "2E5DF8DE377FFB9B4FEDDFB7EFE0DAFA3AB42CD59892808EAF635CE84BE35264CE16C0F172" + "0997D2FE3A7C28E43EAA76984DB48BAA2EED1A87D13150F7E0D8194788C3E5B901C4AC00BC" + "3FCB7D7384C978C1438ED96A11AC051EE203039578EF9A696EAC2180C989BE199623BED4B4" + "62D63CBEBC227301AC0A034B155D9E59E0FD11702557AAA91725AE559029258EF83D5070E5" + "9C89B1EB63AB434BBC090F78A870FF08EFB8CDDF43C4CFAD9D5B9F1167EED9D366C25124E2" + "2D1090EF0B672A10483881039A4249E75399803A75EE61600146AA26410621FD3C8EF4BD78" + "F1E22F3FF9E4D9DFEAF57AF54FFD6DD0E71F28C1627BCC0A00003B72E4A1D66BB782C58753" + "EC07D0EF6F2FDF75F0AE6F7BE31BDF785F77628276760624C2791141890C5D224C99C48768" + "9F1FC44CEF92F2AFCDB31CF3B6EAB0C5599165D49516E6923FCE37F01B8CB20BC486408587" + "009DEE827998B71C7A70AA2DE0D79C330241973C6DE004844C3302A15D085653D05AA396BE" + "D9A83B0D8961CA9DFBD4CBBEA0F3071D43688BDA746FACD377BBF473B07FE705ABA3E79EFE" + "92510187CB4DA538B78ED7CCF75FBE27124193B02421387B1DB86CDA658D0EEE936B5AF306" + "C7771CC1A5BDB7FA55C1681E7F8EE90D4D53C46E7FF554438A298071F9FE88F83168CA43BF" + "57BE9E72373B33434F3C71E23F3E7AE4D19F8F5DFF9AE3B5CB6FF79231F0E94DF61AC1641B" + "3317BBC5AD0000B0D8D2F256B5988B3C7870829E7AEADAA78F1D3FF6852F79F14B3E353E41" + "872333C026E97AA1ABD59512BB63163E710455691995387C138536227743E20235ACF9697D" + "4233FB2014AC72DEE978A968CB852CF5A00C81662E2CD59D0303F3B94C60C06D7D4390598A" + "CD3ABD320E0C58543F01D17A72C252A900DB23E0C9C9F07D3D07BF8F2C905206217FD2A691" + "C4D89A961FD0927B77B2AFCDE29BBDA8AA6C2094E3897D98F7E756BAB016266AB22819057D" + "8E852108FEE4CBA6647BDD9B80B49F0447E55251C057C94023908C5ED6E85CD7E5B8698122" + "3B652D041D38A87668AEAB51FAE3B5F2F8DF0C34F162234B53399A9A9CA493A74EFDDB4F7E" + "F293BF36D5EBD5C7B19318F7A685B441C1FCF616DB4B560000D8603068BD76AB585C7BFC7D" + "9D9A9A8E02AD73ABABAB5F79E8AE438F4469437FBBDF8C364585FA2E2D63317A473AD713F8" + "7CCC67A77F6B691EBF83C451F2C35E8F1DE44DD2F29792525B06D5000360A2C520CE885589" + "EC245A42AF04473C72E9820B20F72FE043214CED74B87780ACBF59635E97AF5909BDAE0003" + "9094F287FDE38B36F78053268E882CA8D125B043CCEF07BFB151BF39A72439EF230BE81AC1" + "5FA0D8400AF3E2DCA9504F2A8ABEC69132D3C06F012D83F91EF199CD4026B0741DB56E8275" + "28750741693329FB837D0790C6D7F368A65FEE7DD61088EF41FCFE3760C8254095B116000A" + "F4A23C4D4DCED0B56BD73E78EAD4A95F9B9CEC51AF3B99EEC7EDE1129D73C5F9EF612B0000" + "EC15AFB83987017D26C691920F03BA70F13C9D3D77E6C2CCF4CC577FE5ABBFEA91E9A9E9A9" + "581DF074CF2C76FE2173D01CAE6919943EEC85F2174A581FC81AA76B34EC1217CCB95AAE02" + "1089000EC0E1CFC0FA1A67EDA4CC918FEDB9477F76DE11D3047A958D13F30076D295A09680" + "1D003A21290F34DE4CC1888AE56017B05490AF11D311212878208E82B51980CB4100FA29A9" + "3CD0CE8B752682821EA2AED5AF54449976887B22188D03E83E1CA9EF17DADF5EB600102F0C" + "848A27F97301B417A65535B300CEA9FB0E5A964ACC04C097D50A1E217ACF50A79969255F08" + "6E291CEAF2BFE6B05AC38EAC56CE70757BBD3884EB2FCF9F3FF796C8B0F5FB63BBB033C58A" + "DDBA560000D8F30E1F6EBD76AB183F24FB5B5BB4B4BC48C3D188AE5CB972D20FC3AB5EFBBA" + "D73ED8EDF526FB5B7DC8192BE5DBA2C825D40AA0B676E2E489C87C4E0D404456FE05BA326D" + "1F9B9C885405903EB40339B32E7532CDA147A24CD77480320F785EFBF77CCDF5317D303F77" + "90BAE0E8DDB4BC85689F480592FAB26D1004AECCCC1560B19F5192439A833F23037176E914" + "E852573C333487237D8954B1C700680042D37BBF1E7F1B5C72C44DB4CCA726664292421E2B" + "01342580330815BAF0843EE415347BCFF7D323E210E087CD8FF2689B531B782F1AB6A9B2EF" + "8F4C47ACF3771DD96F2DE5B447D4AD6EF62AE6BDFBDBDB1F9D9F9F7F631C0DDCF40A28566C" + "6F59010060FD3D32CF7A62A24B51E234D59BA28585C5E38F3F76F4B52FFF8A573CE8FD04ED" + "F0F0A08034781ED967F5EC9446F906EB4C8C652905DAC5A1B1C29ACC83BBF993F3E8824D38" + "5A358D72883D7CFA993A1CA5ED6109924ED0081C7FC6117B08ED73E5EF7190B33740C76001" + "C8FBA77F2B0E6207E9CD5EF37C79A5F1390AD5697F5A76C82EB4926E7CBC03288D14D236B1" + "0C8DB3C4060F4AF90B0DEE9AF6C1E495B141F6C2A8E4E57A487B1BE0BA316FC1F47DFDEF4A" + "76AA014DEA78F9EAADBE41BF2B467F508F504EE71441E72E256A5545758B9FA6BC257DFF20" + "EDE390B14A69A854EAB7B0B0F057F30B0BDF18C57FB5F3DFE52B5FACD8AD6EBBFCD614DB13" + "961EC4FBF6CDD2E2E2E2438F3EF2E8778D753A343E3E0E0E473D1193B43E957E0940604E38" + "09B6542B60236BDB108FD5F38D7360271BBC46C73E95DC858C5E779C5BA8DFE3EBF768873E" + "88D4A9399E4F6578E297D2B5790138483F28751DE473DC754E204AB33EAEED57F902119E07" + "008BAAF82DE8D1BF06B96EEB4C756F10492040402F2C7F754106B134F78A242DC02C83D0F1" + "95D6BA0B3B40C13A5AA7C0803502AC9D70694A1E51768944464428DF17F494463390314EFC" + "33455672DF59631210B2C00C027E6F30A053D9091D6814E4FB213DFC11A0C15AE2F721EE69" + "FCFD585D5DFDD3A79E7AEA1BA24E62BC6E1854BC7FB1BD690500DC063635354967CE9CF9D0" + "A54B975F5C75AAC5C810708B5B093225421FD94859F2F3DEE4B5C551329D9DF2ACECB01B6B" + "E8612F35F4411FC6E0FCF9BF16289045F0B99A63F37FBC26EC59AF0E068E2F550CCD3A9A73" + "DBF2AF86E60F027ECC06489A20082BE0A5D531382ED2CD4427561FC2C3BE21906921B16C6C" + "2DFE45027D97F6519D3497D3B95CB12DEF211135321893C1395CC691EE6F05D136F74510EF" + "CFED1F3CC7E0694C2F65B58578EF92533640874B0D05F024E70DC23307517AFCFC88D7026C" + "871C4AF654AFB1163A06222B51CD4D9D7F14F99D3B77EE9DA74F9F7973FC1D895DFF769131" + "162BB667AC00803D6EFC708DA54C233F7AF2C2858B2FDBDEEE5F8E346730D12C44B7791D3C" + "387D716EA26E6FA27F1F02F4A15787AD0EBE9D3B17C7EFBD3A1A30FECC28F0CF9B8143DEAB" + "C30DA9B6DD7BAF7E0AA3695EAFCFD6E00030903A7E75CECC2264690CF98CB20DE2E87D30D7" + "E647C1CC44B0EBB2D7EAE0D85C86A8CC427A8363068104BCC97C7C10B5A19A9FDFD3D4FF3B" + "D15E48EA41931702287865D52E7DFC47DE4B6740112CA6E89E097515F7095204C76D8742B1" + "DB6726A282697E0C1C79553A7DD027D123942490DE2F1EDED3B0170E8E41ADF4175F73FC7D" + "3877FEFC6F9D3B77EE57E2A09F4EA73C1A8BED7D2BDFF2DBC29A876F6C603218EC3C75ECC4" + "F12F5B5B5F7F6272B2296982946E46D5EA439EBDBFE4808589D5085129D68C424F16029908" + "5F82DA7C04AF44E25E73C1CE320E2139FD510D0846BB8AFD98B978FAB445B04E96AF300475" + "5AE2EC31579F7A1DC87FE0DB5D52B4F375396E7B6CCF6D3685C4E7F32220F4E7BC3ADCA4A0" + "4AFA781255B72BF340A2E26F4AEEEA9EF7A42575E23E01A071631FD465F0FDCAF7CA015381" + "258A446458148DD21D8C71D64B4761293B79CFE922736B5CB656DC42AE9C6846FA86D4BFA2" + "4993044D2B19C166736D1170CCCCCEC6DEFEEF79FCE8E33F393D3D5337FE2956EC76B00200" + "6E1B6B1E9E93BD49DAEEF7571F7BECB197EF6CEF7C329637718E581EB252F617A4A48B840D" + "0F49711E6C842CB477A2C7E143E2D4A5F31FD6C2AB236087EDD37F48954BBE36A3CE91F296" + "CFF9A0DA030033ACFF12B6C2A30FE71FEE4ED32B9BC154B9031C0491B487BD80F5E25E1081" + "834FA913E94047A9ED2E0B1B1D4933261C4AC3F723A4497752AA99D5C1FB00AA7E8111207D" + "AB000C24C19CD0FFA3A0AA7A5E5A5541131F3E930369633B774F109D93ACC099AA02D61D20" + "70AC2A6781A16170B49A205DA9DC279781D840DC0DB0595C0468313DD0E98CD582D9959595" + "5FB974F9D28F8FFC88C6C7C75A4C54B1627BD50A00B8CD2C3E88BBCD2CF3E1C54B97BE766B" + "6BEB9138D444F2FCF250D5813968216894A8511E32017904CE795EA8F30EDC2D10DC273E74" + "B5D24C3405D87087190B06061E41060387A0A23D92E307D100345ECA8AD27CD012B9C04E29" + "8FD8813497A566298D56949BE5C41BEFA600C850E6A4347FE554D447E2F8710CAE299B4842" + "B8E6BA38BFCF024789929DD6F6FBA0E7E2FD0CE084D1013B10116A4A04A64E9AEB5631A5EE" + "6F5AA1343A62B0E8ED90260621C4402B98BDC94B31E39E8C864DFAA7CEF923B2C22F1C83CB" + "F87D1A79EA757B3436D6A163C78EFDD895CB57DE393333D3B4072ECEBFD86D640500DCA6D6" + "9DE8D6D1DCD1A38F7DF5D5AB57FFCF742A77B2B4BDAAC6590780FC34966969BA179D2D08EF" + "20CA97A318E760D30EC204F81A058833E052AE80FF26A5859599F7266A37D43EAF5B9AF5AB" + "DB10AA3B3941679CB95E3BAF5BF2E898E776995347C53A23061E5BEFD481EA9EB705837837" + "D8E9AAF0CE020E97F930EC97277A8E90A50D82DD0702E607AFD3E2341C3D0CBA91B41F9594" + "FF395B1A89FB29D13E541AC07A75DF59BF108C5FAFFB3374183834550C389D11410D7F1FE2" + "773F50D8397EFCD83F3E75EAE47BA7A6A780252956ECF6B1D207E036B5F890EEF56277B3FE" + "F6DCDCDC376F6F6FBFF5052F78C1FBA2F2796B6BAB51C8CB133845534E4BDF24E79B5803E9" + "87CF664AB40037C0F01A9EE5DEB48D7566225E7322ACFD0E2AB293D23C74485ABAA66C4650" + "87E202401682B1B7249F1161A1D104C0CF0D08424023996E627D822D304FC6EC028F4984B5" + "07C8A547EA5DA278A4BC53C73F76FA0128DEC6A12500001A5C49444154764989C04C05A973" + "B70446FD4253EEE753174270B8D200802B05F87C4E6F62EBDE06B8B724171A64A64050C53F" + "365AE235E23580F05040964853F8337A3111B4F248663CAE057D8CB902CD4C4FC7EE7E8F1C" + "3B7EFC95C3E120CC4CCFA4A64925F22F76FB590100B7B1458739D1EDD278678CAE5EBDFA3B" + "FD7EFFCC0B5FF8C28F4E4E4EBAADFE56DE695D5C802316BA350D82A48C2C593B6D000E02A2" + "6E870FE99C6A0F10518A23C0E81DF3DDC01E64112DFE9CA34F9C0FA08D6D64B1F033124A9E" + "7FB4FB38E1F6885CEB90795E82EA06640F9C36CFE15409972736F4BD46B72185F74A16E8A0" + "1E75EA2C7AD3D63FE27489530E0AE44CCBDDEC1E7069A7D10C64CE3F8400797868310C204D" + "7713C0831CD2C96B5CFE878243D53E28609073425749BE8FFC7EA9EC48DF9598CEB863763F" + "CD2FCE3F78E2F889D7BBAA0A71A46F7F736B17A456ACD8ED6185F7BADDADAE87AF6876DF2C" + "3DF5D4537FFDF8D1C7BF20105D687A055815BFD2BCFC3C77BB384CF015E2980388FAB4FC8A" + "1FDA52FF9F030750A163B99AB6EE557119EA1242D0E8579693225F19E266AA0DDAB97BA3F0" + "978B06E7D4BA5E9827C85E502A17B20E774613C0423BFE0852E920A24B658E066805A0DA21" + "15C07D0AF06688F68182EC9B4D0164004C00830562CC7470DAA25E5F256801B8104100766F" + "E5A4A03B95BD625D824DD5E4DF2F652500388ABEC102B478CD51D81723FD4B972FFDF79327" + "E75EB5B3B3B311CB62773B47B162B7931500504C1EB83122DADEDEBE70E6F4E997F6FB5B8F" + "4F4D4D59553944F20491AC74981327C20F71C811C3201E93170F764EBE3A9A90446FE8C89B" + "F7AAB27CD4741B1C8D0440A0904E95DF01169C0110A6AB51B8D868CA413300A90103120844" + "879605A8D7C86314C1D13606B4B7EC6D0660F8456048908E0F12ED3258C0813C4EA87C9EE5" + "60901C747ECC511BF6D1CF1187AE2D08FB4384657FE16900839ED700012E23857B63DE5EB1" + "76007B0090D127B4BE9F70A8280A8CA5AE31AD75E6EC999F397EFCD8F74696247ECFF3A98E" + "C58ADD8E560040B1DAF8711823FFD16834B878E1C22B9697963FCC1DD150F9AD0F71762CFA" + "333E58D08470FABB46B18EB2288DEBC3D987D411EC2839F1609881C08D7FA47B1D9C9D27FD" + "A5E890270532B0F026128523068D4053182AB0C367E569CA68041137EAF194D110BA1E6978" + "8E8BBD65116449B510CE35EA7BC735FC24CE0FFBF7B3DFF3018089C7B5E8001F3693236727" + "6D26EF653322C0919388EC52E41FFF97B106F2738CCA83B2010146FC4AD5006E00E96751CB" + "C0A90D06195E9A11B9D6F983748AF435ABD5DFEE6F1F3F7EFCF5972E5D7A776CF6135BFB86" + "E2FC8B15ABAD00806262EC1AC6C6C768ACEA84E3274EBCE9DC934FFE3051E847AD009B4C91" + "67AD5BFE3C85522F7EC0D70FF58A8CD04F9C10CCCD97FF3196F00D6DAF75FB81A0581FC001" + "381F6EDB2BED83B5FD2F6A0AD2BB814296C3C22C81B69EA036EE958FD16ECE6EE080046116" + "788BA4D8D238C5AAB275EF04C7CF81530D08704A9F808244E9BB907A08B80C440500114102" + "7D6DDBDC76AAF89A50F664DF4739C072FC2DF00A84A4F530A301480BA0F307E0A26B72C29E" + "54F01939574A2575624A6B76965657D63EF5C8238F1C5E989FFFBFFBF7EF57AD40B162C56A" + "2B00A058DB02D551FFC4C4781C24F4BEF3E7CF1DBC71E3C6C762D580940AA647BAD5D2DBFC" + "35A18349113A250161F352302D80793E8044ECD03F5E810137F9C998812C6A1D7975C279C4" + "6EB4093E18674F5C1FEFD969C135E5ED84C91ECB6E8586E94AD12B10C08A036455A42D321F" + "1D0193C7757BD9724F59D7C5C01A08A73D0F4C2A84A97B655B2477BF4BFB5FDD5F2B84E405" + "3871F4E0B8859920018B81CB3983F6F8178602C483D98C3E59136B0131D72FE74A2C4DEC71" + "115357737373BFF4C0830F7C8DF77E657A66DAA6568A152B565B0100C576B590A8D73817DD" + "07BF79E5D2E5AFBB7EEDFACF474155FC8F1D9733111594C3494AB8250B4FCE009DB70AE8F8" + "B8989767315E2010B3F9F6D3DC44DD2DA70CDE11B408CC52F0B93D0C9C71E0AC1DE9F020B9" + "44AE82741AC52B34E2723715FEC94E243A3F104CF58336B9DCCB5E36856449789034331FC7" + "FF423A210762523140027C988FD07A7D0534CAECA036204BF5A47485AE139917ABF60FBC66" + "1401C2C85FCCC878CACA4121D71F1C833C5D07DFC398EF1F8E861BC78F1F7FDDE9D3A77F31" + "82D898EFE7A98FC58A15B356CA008BFD9D161FC4B143DA583546972F5DFEE585A5A5935FFA" + "A55FF2C19812585F5FAB41020BD1306E7332818F327117ABDB91E2269D6F2F8E5B8BCB584F" + "604565D884C69E38070D4275B7E6C205A5C741F316D8D1E44EC324FC0387A37A7A1F140870" + "AA02E7F3C361787E7D0D38B81742D0F5CB9556EA5CB944DF393C621041A00110FC77C8C7B7" + "E9EF4C88684095D39600D99E110A31BD82377C7303802A9B5E712A5E1431639666C85B193B" + "AA9A79FE0CAC80FD71A1015271846F77AA4B0BF3F3FFFBECD9B36F1E8E86FD999959AA3A55" + "8BC92856AC985A61008AFDFD169A4835D2AB8B0B8B7FF0C413739FBFB8B870AC7EC8C687BC" + "8707344781CA0580A08B95E36DE5B63874F927170D9228D91B5D5DE37C476934B0E8DF9052" + "17AA9F34C72FE7200B223C8A03830AE88D33B41502ED8645CD4859FE6D42919F4BBDF331FA" + "97A63640617B65E66BC0C0C37BB84AA1F98875D8040C896554F4758F4CC7D3589EC260E005" + "A7E1C50BDD91A76828355E422E1FF5000C106AE0E7ECF93CB4020EB00827BA0EDD43011A09" + "0844862A5EFB9933677EF4FCB90BDF321C0EFB3105D066808A152B965B0100C53E334BCFF6" + "A8A45E5B5BBBF8E0830FBD6C7E7EFE5D1114C41C2B29592B8773E2B0B8C77E0531670E0238" + "2F1ED45143D919F7C16720C00ED4382F1C9243417BCA73D4AD2BD31C75C60A30E8E0B1B470" + "61E91499204E2257CD7B4B8959E05488D51988EE00AF9FE9701143A6D7BD3219942A1FB8EC" + "D10CD881CA00145922858E7A3BC41F92634F07E25E4D7C9C2AD5F9F3FC861C0085C4F5DBDC" + "BDC30D3715222C5A94AC06AC5B3A1F061C42647B03C4FD8BF4FEFE3BF6C7BEFEEB274E9C78" + "E585F317FE73B7D7A3F18989E2F88B15FB0CAD008062FF5F161FAE715E7AAC14585858F885" + "134F9C78D9952B57FE2A0E5799181F87FA6A671EFACD4B0C11B4DDAC31539A17E04F2C1DF4" + "022824324F4E2878D299F6E068F958465C27CEDEBA7F17D009A76361B73C6030741D2AF623" + "6AA6D899B1CA94D1E39C263082BF54DE98DE1BB271CAC840409F21D95EC144F6242945A33D" + "023C3023E6D8929A51911D1F870157ABF451EE4CC64C6821887D573C56DE7910A6FAD57F4D" + "D309894B211525E9ECFE0838030D1E7EE8A177DF77DF7D8747DE3F1CFBF99B76C1C58A15FB" + "7BAD008062FFDF161FB1B1D42AB201CB4B4BC78E3E7EF41B2E5FBDF27D316A9E9AEC31716F" + "CABB889DADE47051B0860E04A971EE658FE575565D4E1C95FB51D21CA8430D189183C2DF30" + "0E011805737E3C97E6FA1B3120AE81528E3AEB699FC39B7C2F82F6BD470CE252C25CA364DB" + "450FA7F2C9A442604334E59231248184BA97720C282F7481D5FC7AB79AB7F0B43E6D32D012" + "EEED36A5CF5B512346FCE4F435826645CDB6739F834ACA39A38D7C93EB8FA2BED5D595BF3C" + "72E4C8DD0F3EF0E0CFF4B7FA9BD353D32D2C59AC58B1BFDF0A0028F6595B7CC047DA35E65C" + "171717FFDBE3C78F1D5C5959F9C8E4E4148D8F4F68F49EA266EE1B842567AC18202108F2BA" + "02FD539D0449CDBABC2F2845AE638DD584720736D9B1F3CEB4041CF50616F0014008B82848" + "13B4F3E89A10D1AE7F0C2C8238F8A6542F7DA6D24FAAD6404E2580C09375F64614288047AB" + "2202000189B7418D6F7A2F48CF04040E21DB4DFEA863B99FD5591009F3601A11E537356D2B" + "A65B74BC0377766C8E3D3B331B5F8D0AFF373FF1C4DC1B0783C1F2BEFDFB69A23B61BE07C5" + "8A15FBCCAD0080629FBD410E39A600FAFDFED2B9F3E7BEE9FCF98BFF64381C2CC5B22C5562" + "63499DB3822F624764FE29EA7C3507797D76BC19550E1A014D0DB03620FE7B249F41919A56" + "0FA47E042A21D4285C18056D5F2B79788E6A655D0070BC878E785ADD40E0DC9B0F653DF65D" + "65DC2EAEC34C4934658CA8A5A016CBC00C82321879032328FF0BD93130979FDF2950FB338B" + "2FB58F4616802D7DD35B22DD5FB95412A9EB8836313E4653935374EDFAB58F1F3B7EFC45F3" + "F3D7FF34EA4EBA130A308B152BF6D9590100C53E27169FC51313137544B6B4BCF4E767CF9E" + "3D78FDFAF57F37313E51D7670B2D0E896B530F806A3062078E79767E5DA7DAC11F42E5B3F0" + "10A9FE00F47D608620551948AF7C983027416F569A26B4BD7A535927330F021C8C4A1ED60C" + "984332EF928BF7962E8736BDAA94CF1AF148D7C5E45C2BA7F43AA6F2255AF7D27320AF0DB0" + "AD1592277704331DC844F488CEEA73569C7E70646E5B4502DA3C6A2BD2DE8AF831DDC378EF" + "C6C7C6EBD1BD3B83C1CA99B367BEFDE8E38FBD6EBBBFB5182B4F5AE0B158B1629F95150050" + "EC7367C989C63C6D7C48CFCF2FFC9B93274F7DC5DADAEAA5E9E9991A1C54ACF06775372AD4" + "310F0F797027912A0002831C50108769077E519D31A72384E6268DBCD1218A83092415088A" + "205438C76B474D4008B6F61F697BA97220482B40542F0DFD530540032A9A7242672FBC791B" + "B7246696DEA9339575729EDD44F8BA56D9C9AC520037C1A609DADF18B33667B19C6334139A" + "0E8B7C3F8479616C141A75FFCCCC0C0DFD884E9F3DFBEB67CE9C39B8B4BCF467B5C8942754" + "162B56EC73620500147B46ACAA3A3433334DCBCB4B8F9E3871FC1F5CB870FE4D37D637FE36" + "9669C516C3EAFF4100085D034D9C99227B744C98D366D5BAA1A9812190940350FE0126EA35" + "2C4190B23B43E54B239F5485E0D36C01A2D42E181CAB38FF3673D18807530D3D281FC451A3" + "78909D360A14B9A5B1B7FDFA6B7001ED8479063E5F7D0026643702BF32DEBE614B8C18D074" + "1774BADF5A68A0CC8950F778E11CAD27A05235DA00E9731074A67FD4920C8723BA7CE5F2AF" + "3D76F4B14327E7E67EB6D3A97C4C01943EFEC58A7DEEAD74022CF60C59E38C22FDDFDFDEA2" + "F985850F2F2FAD7C7876FFCC6B9F77D7A1F74DF4BAF70C07231A0E07CDE92157ACCEDA52E4" + "EAD4A1CD1076B8C3C896348F8C6C41483DFE0D83C05A37FE9C0CFA61DA1C1B14B9247B8B3E" + "D901ABA03D0BA4B430901E188049A867F857352FC0FC05EB129A614072AAA46B0CBA0650D2" + "85EC252E56307D0A901868F10799EE02BA393A1136646C8ACB3FE59A8982A9FC11A74012FF" + "9DA5FF0453FE1C7730747535891F795A5A5CFAC3D3A74FFDF86034BC1E01C2D4F434559D0E" + "0D07C3F24B5AACD833600500147B46AD71769D1A08C40636376EDCF8F8607BE7DEC9C9C91F" + "3A78F0AEFF127503716EFB70B8FB439E5BF506108619DF6D4AEE6C84E8A06F3CA90B92FF8F" + "AFC4F2328C6A099C25A70884E297E392796F93264F036F28F7B400449052E772B9E4D8B9A5" + "BEF7DA114FCBE01C5622C299AD436F97F12993D28A9DA14DB296246A5EDE9E8C5332F6A5FC" + "FC9A02701694106B0212ABE21A56653275F17BEAA9AB7F73E1E2A51FED4E74E7760683BACE" + "3F3201B5E36F2DBC58B1629F2B2B298062CF8E25C712458131AA5B5A5A7ADFC2C2FCE4FADA" + "FAAF0E87C341AF3759E77FA5AE3DCFE3B3E34DD1A4D0D0FCFFC218A0B7538F289C4155D5B4" + "37E7E32B337F20BD97F3D23032D7393D5B7CEB086AD439B1EEF9DF228643E24027DA350D71" + "829E8C87F038F5E24E52036D472FEB04CD012AF969974F20800A29E581024149BE1874A39A" + "0047615710C4A91B66411C9E039A16F1BF63FA2452FA5127B2B8B474DFF5EB4FFDC39373A7" + "DEB0B0303F1741624C0F152B56ECD9B102008A3DAB169293898240E75C7F756DEDE72E5CBC" + "74C7C2C2FCBBB6B7B7B77A9393D4E98C9906388E63D9ACAE5C47EEAAD383C01B3AE319A2DC" + "8CAD75D94FF8EDAAA783BCB60B123AA3425F72FFE0903D34CFD12A02F8CF43082D0B75A67C" + "512269583EF61DA0CC29E3BE180DA400104A4E3ADB47780A70473E497E08EACA5BFDEABE19" + "9601BAF72644236BECF6BA343D35137B469C9E9B9BFB8EB9279EF8EAADFEF691C9A9C99A21" + "D283142B56ECD9B002008A3D27C675F271B470E5DCE6B56BF3BF70FEFCB9E9E5C5C59F0B21" + "84C9DE64DDF98D67CD635B588D52837547E8244DAE1A29E9C631A3D3729DCAE00874D8A699" + "0DFBE90A3A1472531ED3B79E843697939075D8DC05AF211652EE1CEBF0B041202CD68ECA45" + "200242488CF68D1631989C7DF3924E217402729CD02D6DE641CF450EBB15AA3893AF2BEE73" + "A7D3A9C57DDD6EECE0B7FAD0A95373DFF4E8A347BEE8EA5357FF24B20012F117C75FACD8B3" + "6E450350EC39B5DA415515F57A5DDAD9D90E2BCB2BBF3A3F7FFD3F757B93DF7AE0C081EFEA" + "75BBFFB43BD9A3C160407E900483905377CAA2AB9A9EF2DE01FC67B09A01A6FF475E1DB84F" + "CD748CB08F856B5AB3EE838212337E57D2E05AEA27023ED35340D7240A7788D2A5E411C90B" + "C7F593C1B00F4ED57F2D3F6AB228BC5E84461569640F690EB37726D47732CA592F41FB13D4" + "1A86CAD593236337C08D8D1B0B972E5FFA0F3BDB833FDFDEE93FB2BEB656FF2C8E98367AC1" + "62C58A3DEB560040B19BC04252C057F578D7F5F9F5C5D5B5F5DF0B21FCDEF8D8F80BC7C6C6" + "7E7A7A66FA8726A7266746434F83C18E46DD211068D7ADD25C1C6D1679938D54CD88BCE48C" + "35411070AC516ADB6B1579562CD8BCD688F9F29D4DBA00A76BB12D7AB1BCD1890601DBEC52" + "12D029B8900FFE9DB7D191321A3958D177F0B1ACD36791A386F904AC81AEBB33D6A15E6FA2" + "1674AEAEAECE2D2F2FBF7B6575E5BFAEAFAD0F620DFF810377D0706A8AB6FBFD12F2172B76" + "13580100C56E220BA9194C875CD5AB5300C3C1F0D2F5F9859FEEF6C67FF6D0C1BB7E6AA2D7" + "FD89A9C9E9CF8BEF8DAC40AC20108FE5342C55F15D501A5C66E480F3317EC849346B7AFFCB" + "413942B7E5870C22A45AA05D0A608F959D93249901D50E590AA0358207F41172F06C041F5E" + "33D45368531E63FC591638C051042CC911EAAA85D41EA99E0E19859DFDAD7E585C5BF8D0B5" + "6BD77F737979E9D363E3E3750A2036F671CEAEBB58B162CFBD150050ECA6B6E858627AC039" + "DA595A5EFAF58D8DCD5F9F9D9D7AFD810377BDADDBEDFDB36EB73B1641C020F613C03A76F5" + "5BCDDF4DE4EB9419D82D1205311FC9F112DB8091308FB04DF4BE6737EDA079105940E032AA" + "DEE54A86BCEE9E3B043A1536EE1A3D3BFBBA43C72FA57E20226C65F7B3EA01EED9ECB86111" + "C938E7F8DECED838F526C6EB687F7DE3C6F5CDCDCDF75EBF36FF9B7E30BCB13DDCA92B3AA6" + "262769381A358D7E427EBE62C58A3DD7560040B19BDEA2F3E9745C3D6B6073738B565756FE" + "A6DFDFF99B89F1EED4ECBED9EFEEF526DFD1EBF5EE898D68624399E168D8A2FC4DAF7F981E" + "673A049252EE3037AF290B04559D7607B4A5789A1AE09F6553EA1CF4EC87D6C0D22A57B310" + "F0190DCCB1CDB101337C7D29E447D642357DDA3F3F4090DF821286EA573161DDB9CF550D3B" + "E31C6D6D6D6D2E2E2EFEC96030F89DF9F985BF667D42CCEF53C7D160B8D33E76B162C56E2A" + "2B00A0D8AD63A1A9DBEF74BB753F81D168B4B9BCB2F2FEF1F1CDF7BBE05F363D33F3FDBD5E" + "EF3BC7C7C75F1C1D568C4263841AFF033FDAA8F85164472A08E45AB69636003C735B6340C6" + "F9BA4C8CC73E95A7F0E9CF02542C9011F7590B1890CB414D6D3E7C4C9305580D81B5FE0A2C" + "9CB0010070123019EB8CD5A02B46F0A3E190463E3672EA7F646575E53D0B0BF31FDEDEDC1E" + "DC79F0CE7A9FC7C62B1AEEC01C8362C58ADDF4560040B15BD6E2A8E198638E7D03D6D7571F" + "EF6FF7DF3131D17DC7E4E4E4DD3B3B3B2F9F9C9CFC9E5EAFF7EDBD5EEF40A350F7341AC55E" + "FA2388E239479076C10C1C60F91F97BC2968C0EE79F5BB833A51AE3090CA84449F6B4F7EB6" + "DCD96B431DD41948B65F861785F4CFBC220089FDDDEA08C18C14814B19E37E56754BDFF8F7" + "9DED9D303F3FFF89ADADCD3F1E0E861F1F793F173CADF577B6EBBD8CB5FBE313E329FD42BB" + "F109C58A15BB89AD008062B7AE81BF895168A7D338B0C160706D7373F3DAD2D2F247A6A6EA" + "EE725F35313EF16DD333D36FEC74C65E353E3EEE2270A84B0BE36C801136ECC966E21B4C80" + "A90214E5E554BF2AF4E5A3DEE6E049DDBC31AB1900E2213064C10404D9230881919A152561" + "A48DFFF952820CE5897B17FF1EF7637B7B7BD57BFF899595953F5D5E5EF9E0DADAEA5AD460" + "70A7C658CFDF758186C31DA0548A152B762B5A0100C5F698354E2D5610C452BC4E55D1DAFA" + "FA7DC1FBFB6EDCB8F1CEAAD399EDF57ADF363E3EFEA65EAFF79AB1B1B117459A3BFAC74871" + "470D8144D222FE87B6BCDC16172B0152DAC050F8DC4EF869DA1AF327A4B63E041212810180" + "E3FC81B360417A0D28DB5071A99ED34E7E69F1DA96971A8014EBF4836F260B0E0683ADF5F5" + "F54FF6B7B6FE7A736BEBA35B5BFDFB6766A7FDEACA4A2DE0EBD6CEBF47E3112454A56F58B1" + "627BC90A0028B6274D7C72DD6D70BC51AE773A340A7EFDC68D1B1FDCDADCFCE0CCBE59EA54" + "9D178D8F8D7DCDF8C4F8AB7BDDC9D75755F5159DF1311AEB74EACF47FD401DBD87103F9B28" + "FDACDC4E28F8CC51FF1DE1B11E416706483F00F0F43CAEB7AAB47F01971B8E929831FE7D00" + "2245EE41502587DDB0236375FF84ADADC153C3C1E0A1C17078FFD656FF5383C1CE27FA5B5B" + "9B31FA8FCAFE665EC3781DE9D3F67643EF172B566C4F5A0100C56E0F0B1C2957D419EFD068" + "A29B68EF9DF31B1B37CE3BE73E30333D1B7505776EEFECBCAC5355AFEA76BBF774BBDD7BC7" + "C6C65E5E55D51D5114C77D0A46A366F44F64187892619D0CE01EFD3E48E31D342EE9ABC701" + "73D39F1895431320AA8FD3808D58E2583BFB9D91F40888D505F1F5AAEAD44E3E8E54668D43" + "FCF9A0E99838F47E74696767702C84707A63E3C6A73B639DFBC2289C1F0E073ECE5C88EB8E" + "E0607C62A2064771613E40D95FB162C5F6B4150050ECF6B4948FEF549D3473A0E2F2BAA5CD" + "CDCD8FF537B73E169DE3BE7DFB687BB033DEE9545F303D35FDC5CEB9C3C3E1E805BD5EEF0B" + "C7C7C65E38F4C39989898938BD68D27BDFABAA6A22920EB1C96E3A1E92FCD1498F82F7431F" + "FC8E739D8D884186A3E164F061CA39371E9ADF49E7EB11C92357552E24F162188D4623E7DC" + "2084B053B96AC3D3CED668381C753A63CB5B5B9B17ABAA736E341A5D1C8D46A707839D33C3" + "D168398ED48D60647D6395EEB8E34EEA757B34513525953CA1B02E8B6CD51F162B56AC58B1" + "62C58A152B56AC58B162C58A152B56AC58B162C58A152B56AC58B162C58A152B56ECA63722" + "FA7FA2AD0F1FF68D68610000000049454E44AE426082"; // Converts |log_message| to a raw dictionary value used as a JSON argument to // JavaScript functions. @@ -42,8 +3778,9 @@ QuickPairHandler::QuickPairHandler() : fast_pair_notification_controller_( - std::make_unique<ash::quick_pair::FastPairNotificationController>()) { -} + std::make_unique<ash::quick_pair::FastPairNotificationController>()), + image_decoder_(std::make_unique<ash::quick_pair::FastPairImageDecoder>( + std::unique_ptr<image_fetcher::ImageFetcher>())) {} QuickPairHandler::~QuickPairHandler() = default; @@ -64,6 +3801,10 @@ "notifyFastPairPairing", base::BindRepeating(&QuickPairHandler::NotifyFastPairPairing, base::Unretained(this))); + web_ui()->RegisterDeprecatedMessageCallback( + "notifyFastPairAssociateAccount", + base::BindRepeating(&QuickPairHandler::NotifyFastPairAssociateAccountKey, + base::Unretained(this))); } void QuickPairHandler::OnJavascriptAllowed() { @@ -95,16 +3836,61 @@ } void QuickPairHandler::NotifyFastPairError(const base::ListValue* args) { + std::vector<uint8_t> bytes; + base::HexStringToBytes(kImageBytes, &bytes); + image_decoder_->DecodeImage( + std::move(bytes), + base::BindOnce(&QuickPairHandler::OnImageDecodedFastPairError, + weak_ptr_factory_.GetWeakPtr())); +} + +void QuickPairHandler::OnImageDecodedFastPairError(gfx::Image image) { fast_pair_notification_controller_->ShowErrorNotification( - kTestDeviceName, gfx::Image(), base::DoNothing(), base::DoNothing()); + kTestDeviceName, image, base::DoNothing(), base::DoNothing()); } void QuickPairHandler::NotifyFastPairDiscovery(const base::ListValue* args) { + std::vector<uint8_t> bytes; + base::HexStringToBytes(kImageBytes, &bytes); + image_decoder_->DecodeImage( + std::move(bytes), + base::BindOnce(&QuickPairHandler::OnImageDecodedFastPairDiscovery, + weak_ptr_factory_.GetWeakPtr())); +} + +void QuickPairHandler::OnImageDecodedFastPairDiscovery(gfx::Image image) { fast_pair_notification_controller_->ShowDiscoveryNotification( - kTestDeviceName, gfx::Image(), base::DoNothing(), base::DoNothing()); + kTestDeviceName, image, base::DoNothing(), base::DoNothing()); } void QuickPairHandler::NotifyFastPairPairing(const base::ListValue* args) { + std::vector<uint8_t> bytes; + base::HexStringToBytes(kImageBytes, &bytes); + image_decoder_->DecodeImage( + std::move(bytes), + base::BindOnce(&QuickPairHandler::OnImageDecodedFastPairPairing, + weak_ptr_factory_.GetWeakPtr())); +} + +void QuickPairHandler::OnImageDecodedFastPairPairing(gfx::Image image) { fast_pair_notification_controller_->ShowPairingNotification( - kTestDeviceName, gfx::Image(), base::DoNothing(), base::DoNothing()); + kTestDeviceName, image, base::DoNothing(), base::DoNothing()); +} + +void QuickPairHandler::NotifyFastPairAssociateAccountKey( + const base::ListValue* args) { + std::vector<uint8_t> bytes; + base::HexStringToBytes(kImageBytes, &bytes); + image_decoder_->DecodeImage( + std::move(bytes), + base::BindOnce( + &QuickPairHandler::OnImageDecodedFastPairAssociateAccountKey, + weak_ptr_factory_.GetWeakPtr())); +} + +void QuickPairHandler::OnImageDecodedFastPairAssociateAccountKey( + gfx::Image image) { + fast_pair_notification_controller_->ShowAssociateAccount( + kTestDeviceName, kTestEmail, image, base::DoNothing(), base::DoNothing(), + base::DoNothing()); }
diff --git a/chrome/browser/ui/webui/nearby_internals/quick_pair/quick_pair_handler.h b/chrome/browser/ui/webui/nearby_internals/quick_pair/quick_pair_handler.h index 74440c4..49b23c8 100644 --- a/chrome/browser/ui/webui/nearby_internals/quick_pair/quick_pair_handler.h +++ b/chrome/browser/ui/webui/nearby_internals/quick_pair/quick_pair_handler.h
@@ -8,14 +8,17 @@ #include <memory> #include "ash/quick_pair/common/log_buffer.h" #include "ash/quick_pair/common/logging.h" +#include "base/callback.h" #include "base/memory/weak_ptr.h" #include "base/scoped_observation.h" #include "base/values.h" #include "content/public/browser/web_ui_message_handler.h" +#include "ui/gfx/image/image.h" namespace ash { namespace quick_pair { class FastPairNotificationController; +class FastPairImageDecoder; } // namespace quick_pair } // namespace ash @@ -50,9 +53,16 @@ void NotifyFastPairError(const base::ListValue* args); void NotifyFastPairDiscovery(const base::ListValue* args); void NotifyFastPairPairing(const base::ListValue* args); + void NotifyFastPairAssociateAccountKey(const base::ListValue* args); + + void OnImageDecodedFastPairError(gfx::Image image); + void OnImageDecodedFastPairDiscovery(gfx::Image image); + void OnImageDecodedFastPairPairing(gfx::Image image); + void OnImageDecodedFastPairAssociateAccountKey(gfx::Image image); std::unique_ptr<ash::quick_pair::FastPairNotificationController> fast_pair_notification_controller_; + std::unique_ptr<ash::quick_pair::FastPairImageDecoder> image_decoder_; base::ScopedObservation<ash::quick_pair::LogBuffer, ash::quick_pair::LogBuffer::Observer>
diff --git a/chrome/browser/ui/webui/signin/profile_picker_handler.cc b/chrome/browser/ui/webui/signin/profile_picker_handler.cc index 0917ab4..3e21ece9 100644 --- a/chrome/browser/ui/webui/signin/profile_picker_handler.cc +++ b/chrome/browser/ui/webui/signin/profile_picker_handler.cc
@@ -13,10 +13,8 @@ #include "base/metrics/histogram_functions.h" #include "base/notreached.h" #include "base/strings/utf_string_conversions.h" -#include "base/trace_event/trace_event.h" #include "base/values.h" #include "chrome/browser/browser_process.h" -#include "chrome/browser/metrics/first_web_contents_profiler_base.h" #include "chrome/browser/new_tab_page/chrome_colors/chrome_colors_service.h" #include "chrome/browser/new_tab_page/chrome_colors/generated_colors_info.h" #include "chrome/browser/profiles/profile.h" @@ -244,99 +242,6 @@ } #endif // BUILDFLAG(IS_CHROMEOS_LACROS) -void RecordProfilingFinishReason( - metrics::StartupProfilingFinishReason finish_reason) { - base::UmaHistogramEnumeration( - "ProfilePicker.FirstProfileTime.FirstWebContentsFinishReason", - finish_reason); -} - -class FirstWebContentsProfilerForProfilePicker - : public metrics::FirstWebContentsProfilerBase { - public: - explicit FirstWebContentsProfilerForProfilePicker( - content::WebContents* web_contents, - base::TimeTicks pick_time); - - FirstWebContentsProfilerForProfilePicker( - const FirstWebContentsProfilerForProfilePicker&) = delete; - FirstWebContentsProfilerForProfilePicker& operator=( - const FirstWebContentsProfilerForProfilePicker&) = delete; - - protected: - // FirstWebContentsProfilerBase: - void RecordFinishReason( - metrics::StartupProfilingFinishReason finish_reason) override; - void RecordNavigationFinished(base::TimeTicks navigation_start) override; - void RecordFirstNonEmptyPaint() override; - bool WasStartupInterrupted() override; - - private: - ~FirstWebContentsProfilerForProfilePicker() override; - - const base::TimeTicks pick_time_; -}; - -FirstWebContentsProfilerForProfilePicker:: - FirstWebContentsProfilerForProfilePicker(content::WebContents* web_contents, - base::TimeTicks pick_time) - : FirstWebContentsProfilerBase(web_contents), pick_time_(pick_time) { - DCHECK(!pick_time_.is_null()); -} - -FirstWebContentsProfilerForProfilePicker:: - ~FirstWebContentsProfilerForProfilePicker() = default; - -void FirstWebContentsProfilerForProfilePicker::RecordFinishReason( - metrics::StartupProfilingFinishReason finish_reason) { - RecordProfilingFinishReason(finish_reason); -} - -void FirstWebContentsProfilerForProfilePicker::RecordNavigationFinished( - base::TimeTicks navigation_start) { - // Nothing to record here for Profile Picker startups. -} - -void FirstWebContentsProfilerForProfilePicker::RecordFirstNonEmptyPaint() { - const char histogram_name[] = - "ProfilePicker.FirstProfileTime.FirstWebContentsNonEmptyPaint"; - base::TimeTicks paint_time = base::TimeTicks::Now(); - base::UmaHistogramLongTimes100(histogram_name, paint_time - pick_time_); - TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0("startup", histogram_name, - this, pick_time_); - TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0("startup", histogram_name, - this, paint_time); -} - -bool FirstWebContentsProfilerForProfilePicker::WasStartupInterrupted() { - // We're assuming that no interruptions block opening an existing profile - // from the profile picker. We would detect this by observing really high - // latency on the tracked metric, and can start tracking interruptions if we - // find that such cases occur. - return false; -} - -void BeginFirstWebContentsProfiling(Browser* browser, - base::TimeTicks pick_time) { - content::WebContents* visible_contents = - metrics::FirstWebContentsProfilerBase::GetVisibleContents(browser); - if (!visible_contents) { - RecordProfilingFinishReason(metrics::StartupProfilingFinishReason:: - kAbandonNoInitiallyVisibleContent); - return; - } - - if (visible_contents->CompletedFirstVisuallyNonEmptyPaint()) { - RecordProfilingFinishReason( - metrics::StartupProfilingFinishReason::kAbandonAlreadyPaintedContent); - return; - } - - // FirstWebContentsProfilerForProfilePicker owns itself and is also bound to - // |visible_contents|'s lifetime by observing WebContentsDestroyed(). - new FirstWebContentsProfilerForProfilePicker(visible_contents, pick_time); -} - } // namespace ProfilePickerHandler::ProfilePickerHandler() = default; @@ -536,12 +441,6 @@ } #endif // BUILDFLAG(IS_CHROMEOS_LACROS) - if (!creation_time_on_startup_.is_null() && - // Avoid overriding the picked time if already recorded. This can happen - // for example if multiple profiles are picked: https://crbug.com/1277466. - profile_picked_time_on_startup_.is_null()) { - profile_picked_time_on_startup_ = base::TimeTicks::Now(); - } profiles::SwitchToProfile( *profile_path, /*always_create=*/false, base::BindRepeating(&ProfilePickerHandler::OnSwitchToProfileComplete, @@ -938,13 +837,6 @@ DCHECK(browser); DCHECK(browser->window()); - // Measure startup time to display first web contents if the profile picker - // was displayed on startup and if the initiating action is instrumented. For - // example we don't record pick time for profile creations. - if (!profile_picked_time_on_startup_.is_null()) { - BeginFirstWebContentsProfiling(browser, profile_picked_time_on_startup_); - } - // Only show the profile switch IPH when the user clicked the card, and there // are multiple profiles. std::vector<ProfileAttributesEntry*> entries =
diff --git a/chrome/browser/ui/webui/signin/profile_picker_handler.h b/chrome/browser/ui/webui/signin/profile_picker_handler.h index ab92d9b..026c3b3 100644 --- a/chrome/browser/ui/webui/signin/profile_picker_handler.h +++ b/chrome/browser/ui/webui/signin/profile_picker_handler.h
@@ -182,11 +182,6 @@ // Creation time of the handler, to measure performance on startup. Only set // when the picker is shown on startup. base::TimeTicks creation_time_on_startup_; - - // Time when the user picked a profile to open, to measure browser startup - // performance. Only set when the picker is shown on startup. - base::TimeTicks profile_picked_time_on_startup_; - bool main_view_initialized_ = false; #if BUILDFLAG(IS_CHROMEOS_LACROS)
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt index 9b5c4ff..52d20e49 100644 --- a/chrome/build/win64.pgo.txt +++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@ -chrome-win64-main-1638964457-fc2eeb7c869576bb8931072d2327e83226cbb9a1.profdata +chrome-win64-main-1638975444-336691a69f954ff560514f94e1b6d15d096843b5.profdata
diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json index 73af270..d126cc96 100644 --- a/chrome/common/extensions/api/_permission_features.json +++ b/chrome/common/extensions/api/_permission_features.json
@@ -970,7 +970,8 @@ // Extension id hash for API test. "74801F84D31D23D08BEA48AB82C6A6120EDB4D45", "6A4D7AEC6414492BD25219C7A479265BF1177D42", // http://crbug.com/1206836 - "F80B163C896D50A685E0B683BEEEA94217F253D6" // http://crbug.com/1206836 + "F80B163C896D50A685E0B683BEEEA94217F253D6", // http://crbug.com/1206836 + "454738352012A22901C55B348E5E530BDE1AF2B0" // http://crbug.com/1259253 ] } }
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index 1c1b3ec..a8f2ca7 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn
@@ -4553,6 +4553,7 @@ "../browser/federated_learning/floc_remote_permission_service_unittest.cc", "../browser/file_select_helper_unittest.cc", "../browser/file_system_access/chrome_file_system_access_permission_context_unittest.cc", + "../browser/first_party_sets/first_party_sets_util_unittest.cc", "../browser/font_pref_change_notifier_unittest.cc", "../browser/geolocation/geolocation_permission_context_delegate_unittest.cc", "../browser/google/google_search_domain_mixing_metrics_emitter_factory_unittest.cc", @@ -6466,6 +6467,7 @@ sources += [ "../browser/chromeos/policy/dlp/data_transfer_dlp_controller_unittest.cc", "../browser/chromeos/policy/dlp/dlp_clipboard_notifier_unittest.cc", + "../browser/chromeos/policy/dlp/dlp_confidential_contents_unittest.cc", "../browser/chromeos/policy/dlp/dlp_content_tab_helper_unittest.cc", "../browser/chromeos/policy/dlp/dlp_drag_drop_notifier_unittest.cc", "../browser/chromeos/policy/dlp/dlp_reporting_manager_test_helper.cc",
diff --git a/chrome/test/base/browser_with_test_window_test.cc b/chrome/test/base/browser_with_test_window_test.cc index b2ae093c..4555a0d2 100644 --- a/chrome/test/base/browser_with_test_window_test.cc +++ b/chrome/test/base/browser_with_test_window_test.cc
@@ -116,9 +116,6 @@ constrained_window::SetConstrainedWindowViewsClient(nullptr); #endif - profile_manager_->DeleteAllTestingProfiles(); - profile_ = nullptr; - // Depends on LocalState owned by |profile_manager_|. if (SystemNetworkContextManager::GetInstance()) { SystemNetworkContextManager::DeleteInstance(); @@ -128,7 +125,10 @@ manager_.reset(); #endif + // Calling DeleteAllTestingProfiles() first can cause issues in some tests, if + // they're still holding a ScopedProfileKeepAlive. profile_manager_.reset(); + profile_ = nullptr; #if BUILDFLAG(IS_CHROMEOS_LACROS) tablet_state_.reset();
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_app_test.js b/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_app_test.js index 69ec7b00..ca888e5b 100644 --- a/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_app_test.js +++ b/chrome/test/data/webui/chromeos/shimless_rma/shimless_rma_app_test.js
@@ -89,6 +89,26 @@ return flushTasks(); } + /** + * Utility function to click back button + * @return {Promise} + */ + function clickBack() { + const backButton = component.shadowRoot.querySelector('#back'); + backButton.click(); + return flushTasks(); + } + + /** + * Utility function to click cancel button + * @return {Promise} + */ + function clickCancel() { + const cancelButton = component.shadowRoot.querySelector('#cancel'); + cancelButton.click(); + return flushTasks(); + } + test('ShimlessRMALoaded', async () => { await initializeShimlessRMAApp(fakeStates, fakeChromeVersion[0]); assertNavButtons(); @@ -249,26 +269,120 @@ suppressedErrorMessage(component)); }); - test('BusyStateButtonSpinners', async () => { - await initializeShimlessRMAApp(fakeStates, fakeChromeVersion[0]); + test('NextButtonSpinner', async () => { + await initializeShimlessRMAApp( + [{ + state: State.kSelectComponents, + canCancel: true, + canGoBack: true, + error: RmadErrorCode.kOk + }], + fakeChromeVersion[0]); const initialPage = - component.shadowRoot.querySelector('onboarding-landing-page'); + component.shadowRoot.querySelector('onboarding-select-components-page'); assertTrue(!!initialPage); - const resolver = new PromiseResolver(); - initialPage.onNextButtonClick = () => resolver.promise; - const nextButtonSpinner = component.shadowRoot.querySelector('#nextButtonSpinner'); + const backButtonSpinner = + component.shadowRoot.querySelector('#backButtonSpinner'); + const cancelButtonSpinner = + component.shadowRoot.querySelector('#cancelButtonSpinner'); + + // Next spinner + const nextResolver = new PromiseResolver(); + initialPage.onNextButtonClick = () => nextResolver.promise; assertTrue(nextButtonSpinner.hidden); + assertTrue(backButtonSpinner.hidden); + assertTrue(cancelButtonSpinner.hidden); await clickNext(); assertFalse(nextButtonSpinner.hidden); + assertTrue(backButtonSpinner.hidden); + assertTrue(cancelButtonSpinner.hidden); - resolver.resolve({state: State.kUpdateOs, error: RmadErrorCode.kOk}); + nextResolver.resolve({state: State.kUpdateOs, error: RmadErrorCode.kOk}); await flushTasks(); assertTrue(nextButtonSpinner.hidden); + assertTrue(backButtonSpinner.hidden); + assertTrue(cancelButtonSpinner.hidden); + }); + + test('BackButtonSpinner', async () => { + await initializeShimlessRMAApp( + [{ + state: State.kSelectComponents, + canCancel: true, + canGoBack: true, + error: RmadErrorCode.kOk + }], + fakeChromeVersion[0]); + + const initialPage = + component.shadowRoot.querySelector('onboarding-select-components-page'); + assertTrue(!!initialPage); + + const nextButtonSpinner = + component.shadowRoot.querySelector('#nextButtonSpinner'); + const backButtonSpinner = + component.shadowRoot.querySelector('#backButtonSpinner'); + const cancelButtonSpinner = + component.shadowRoot.querySelector('#cancelButtonSpinner'); + + // Back spinner + const backResolver = new PromiseResolver(); + service.transitionPreviousState = () => { + return backResolver.promise; + }; + await clickBack(); + assertTrue(nextButtonSpinner.hidden); + assertFalse(backButtonSpinner.hidden); + assertTrue(cancelButtonSpinner.hidden); + + backResolver.resolve({state: State.kUpdateOs, error: RmadErrorCode.kOk}); + await flushTasks(); + assertTrue(nextButtonSpinner.hidden); + assertTrue(backButtonSpinner.hidden); + assertTrue(cancelButtonSpinner.hidden); + }); + + test('CancelButtonSpinner', async () => { + await initializeShimlessRMAApp( + [{ + state: State.kSelectComponents, + canCancel: true, + canGoBack: true, + error: RmadErrorCode.kOk + }], + fakeChromeVersion[0]); + + const initialPage = + component.shadowRoot.querySelector('onboarding-select-components-page'); + assertTrue(!!initialPage); + + const nextButtonSpinner = + component.shadowRoot.querySelector('#nextButtonSpinner'); + const backButtonSpinner = + component.shadowRoot.querySelector('#backButtonSpinner'); + const cancelButtonSpinner = + component.shadowRoot.querySelector('#cancelButtonSpinner'); + + // Cancel spinner + const cancelResolver = new PromiseResolver(); + service.abortRma = () => { + return cancelResolver.promise; + }; + await clickCancel(); + assertTrue(nextButtonSpinner.hidden); + assertTrue(backButtonSpinner.hidden); + assertFalse(cancelButtonSpinner.hidden); + + cancelResolver.resolve({state: State.kUpdateOs, error: RmadErrorCode.kOk}); + await flushTasks(); + assertTrue(nextButtonSpinner.hidden); + assertTrue(backButtonSpinner.hidden); + assertTrue(cancelButtonSpinner.hidden); }); }
diff --git a/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_pairing_device_item_test.js b/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_pairing_device_item_test.js index efb55f0f0..9d94042 100644 --- a/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_pairing_device_item_test.js +++ b/chrome/test/data/webui/cr_components/chromeos/bluetooth/bluetooth_pairing_device_item_test.js
@@ -86,9 +86,9 @@ }); test('Pairing message is shown', async function() { + const deviceName = 'BeatsX'; const device = createDefaultBluetoothDevice( - /*id=*/ '12//345&6789', - /*publicName=*/ 'BeatsX', + /*id=*/ '12//345&6789', deviceName, /*connectionState=*/ chromeos.bluetoothConfig.mojom.DeviceConnectionState.kConnected, /*nickname=*/ 'device1', @@ -98,11 +98,28 @@ bluetoothPairingDeviceItem.device = device.deviceProperties; await flushAsync(); + const itemIndex = 1; + const listSize = 10; + bluetoothPairingDeviceItem.itemIndex = itemIndex; + bluetoothPairingDeviceItem.listSize = listSize; + const getSecondaryLabel = () => bluetoothPairingDeviceItem.shadowRoot.querySelector('#secondaryLabel'); + const getItemSecondaryA11yLabel = () => + bluetoothPairingDeviceItem.shadowRoot.querySelector('.text-row') + .ariaLabel; + const getItemA11yLabel = () => + bluetoothPairingDeviceItem.shadowRoot.querySelector('#container') + .ariaLabel; assertTrue(!!getSecondaryLabel()); assertEquals('', getSecondaryLabel().textContent.trim()); + assertEquals( + getItemA11yLabel(), + bluetoothPairingDeviceItem.i18n( + 'bluetoothPairingDeviceItemA11YLabelMouse', itemIndex + 1, listSize, + deviceName)); + assertEquals(getItemSecondaryA11yLabel(), ''); bluetoothPairingDeviceItem.deviceItemState = DeviceItemState.PAIRING; await flushAsync(); @@ -110,6 +127,10 @@ assertEquals( bluetoothPairingDeviceItem.i18n('bluetoothPairing'), getSecondaryLabel().textContent.trim()); + assertEquals( + getItemSecondaryA11yLabel(), + bluetoothPairingDeviceItem.i18n( + 'bluetoothPairingDeviceItemSecondaryPairingA11YLabel', deviceName)); bluetoothPairingDeviceItem.deviceItemState = DeviceItemState.FAILED; await flushAsync(); @@ -117,6 +138,10 @@ assertEquals( bluetoothPairingDeviceItem.i18n('bluetoothPairingFailed'), getSecondaryLabel().textContent.trim()); + assertEquals( + getItemSecondaryA11yLabel(), + bluetoothPairingDeviceItem.i18n( + 'bluetoothPairingDeviceItemSecondaryErrorA11YLabel', deviceName)); bluetoothPairingDeviceItem.deviceItemState = DeviceItemState.DEFAULT; await flushAsync();
diff --git a/chrome/test/data/webui/settings/import_data_dialog_test.js b/chrome/test/data/webui/settings/import_data_dialog_test.js index 9202783f..c64b926 100644 --- a/chrome/test/data/webui/settings/import_data_dialog_test.js +++ b/chrome/test/data/webui/settings/import_data_dialog_test.js
@@ -126,7 +126,6 @@ function simulateBrowserProfileChange(index) { dialog.$.browserSelect.selectedIndex = index; dialog.$.browserSelect.dispatchEvent(new CustomEvent('change')); - return new Promise(resolve => setTimeout(resolve, 0)); } test('Initialization', function() { @@ -150,7 +149,7 @@ }); }); - test('ImportButton', async function() { + test('ImportButton', function() { assertFalse(dialog.$.import.disabled); // Flip all prefs to false. @@ -160,7 +159,7 @@ assertTrue(dialog.$.import.disabled); // Change browser selection to "Import from Bookmarks HTML file". - await simulateBrowserProfileChange(2); + simulateBrowserProfileChange(2); assertTrue(dialog.$.import.disabled); // Ensure everything except |import_dialog_bookmarks| is ignored. @@ -194,10 +193,10 @@ webUIListenerCallback('import-data-status-changed', status); } - test('ImportFromBookmarksFile', async function() { - await simulateBrowserProfileChange(2); + test('ImportFromBookmarksFile', function() { + simulateBrowserProfileChange(2); dialog.$.import.click(); - await browserProxy.whenCalled('importFromBookmarksFile').then(function() { + browserProxy.whenCalled('importFromBookmarksFile').then(function() { simulateImportStatusChange(ImportDataStatus.IN_PROGRESS); assertInProgressButtons(); @@ -210,16 +209,16 @@ }); }); - test('ImportFromBrowserProfile', async function() { + test('ImportFromBrowserProfile', function() { ensureSettingsCheckboxCheckedStatus('import_dialog_bookmarks', false); ensureSettingsCheckboxCheckedStatus('import_dialog_search_engine', true); const expectedIndex = 0; - await simulateBrowserProfileChange(expectedIndex); + simulateBrowserProfileChange(expectedIndex); dialog.$.import.click(); const importCalled = browserProxy.whenCalled('importData'); - await importCalled.then(([actualIndex, types]) => { + importCalled.then(([actualIndex, types]) => { assertEquals(expectedIndex, actualIndex); assertFalse(types['import_dialog_bookmarks']); assertTrue(types['import_dialog_search_engine']); @@ -236,18 +235,18 @@ }); }); - test('ImportFromBrowserProfileWithUnsupportedOption', async function() { + test('ImportFromBrowserProfileWithUnsupportedOption', function() { // Flip all prefs to true. Object.keys(prefs).forEach(function(prefName) { ensureSettingsCheckboxCheckedStatus(prefName, true); }); const expectedIndex = 1; - await simulateBrowserProfileChange(expectedIndex); + simulateBrowserProfileChange(expectedIndex); dialog.$.import.click(); const importCalled = browserProxy.whenCalled('importData'); - await importCalled.then(([actualIndex, types]) => { + importCalled.then(([actualIndex, types]) => { assertEquals(expectedIndex, actualIndex); Object.keys(prefs).forEach(function(prefName) {
diff --git a/chrome/test/data/webui/settings/privacy_review_page_test.ts b/chrome/test/data/webui/settings/privacy_review_page_test.ts index f0e398e..f0acb32e 100644 --- a/chrome/test/data/webui/settings/privacy_review_page_test.ts +++ b/chrome/test/data/webui/settings/privacy_review_page_test.ts
@@ -70,6 +70,10 @@ teardown(function() { page.remove(); + // Reset route to default. The route is updated as we navigate through the + // cards, but the browser instance is shared among the tests, so otherwise + // the next test will be initialized to the same card as the previous test. + Router.getInstance().navigateTo(routes.BASIC); }); /**
diff --git a/chrome/test/media_router/media_router_integration_browsertest.cc b/chrome/test/media_router/media_router_integration_browsertest.cc index 14402eec..0ef4b5d1 100644 --- a/chrome/test/media_router/media_router_integration_browsertest.cc +++ b/chrome/test/media_router/media_router_integration_browsertest.cc
@@ -37,6 +37,7 @@ #include "content/public/test/test_utils.h" #include "media/base/test_data_util.h" #include "net/base/filename_util.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest-param-test.h" #include "testing/gtest/include/gtest/gtest.h" @@ -45,6 +46,9 @@ namespace media_router { namespace { + +using ::testing::Optional; + // Command line argument to specify receiver, const char kReceiver[] = "receiver"; // The path relative to <chromium src>/out/<build config> for media router @@ -192,12 +196,11 @@ ASSERT_TRUE(value->GetAsDictionary(&dict_value)); // Extract the fields. - bool passed = false; - ASSERT_TRUE(dict_value->GetBoolean("passed", &passed)); std::string error_message; ASSERT_TRUE(dict_value->GetString("errorMessage", &error_message)); - ASSERT_TRUE(passed) << error_message; + ASSERT_THAT(dict_value->FindBoolKey("passed"), Optional(true)) + << error_message; } void MediaRouterIntegrationBrowserTest::StartSessionAndAssertNotFoundError() {
diff --git a/chromeos/components/quick_answers/public/cpp/quick_answers_state.cc b/chromeos/components/quick_answers/public/cpp/quick_answers_state.cc index 3d098d64..f9d55e7 100644 --- a/chromeos/components/quick_answers/public/cpp/quick_answers_state.cc +++ b/chromeos/components/quick_answers/public/cpp/quick_answers_state.cc
@@ -199,6 +199,11 @@ use_text_annotator_for_testing_; } +bool QuickAnswersState::IsSettingsEnforced() { + return pref_change_registrar_->prefs()->IsManagedPreference( + ash::quick_answers::prefs::kQuickAnswersEnabled); +} + void QuickAnswersState::InitializeObserver( QuickAnswersStateObserver* observer) { if (prefs_initialized_)
diff --git a/chromeos/components/quick_answers/public/cpp/quick_answers_state.h b/chromeos/components/quick_answers/public/cpp/quick_answers_state.h index dd9ad37..1499d6a 100644 --- a/chromeos/components/quick_answers/public/cpp/quick_answers_state.h +++ b/chromeos/components/quick_answers/public/cpp/quick_answers_state.h
@@ -61,6 +61,8 @@ bool ShouldUseQuickAnswersTextAnnotator(); + bool IsSettingsEnforced(); + bool settings_enabled() const { return settings_enabled_; } quick_answers::prefs::ConsentStatus consent_status() const { return consent_status_;
diff --git a/chromeos/profiles/orderfile.newest.txt b/chromeos/profiles/orderfile.newest.txt index 54e50b0..50b21a31 100644 --- a/chromeos/profiles/orderfile.newest.txt +++ b/chromeos/profiles/orderfile.newest.txt
@@ -1 +1 @@ -chromeos-chrome-orderfile-field-97-4685.4-1636370490-benchmark-97.0.4666.0-r1.orderfile.xz +chromeos-chrome-orderfile-field-98-4692.27-1638182350-benchmark-98.0.4729.0-r2.orderfile.xz
diff --git a/chromeos/strings/chromeos_strings_vi.xtb b/chromeos/strings/chromeos_strings_vi.xtb index 66fc5a3..bcf3c8b6 100644 --- a/chromeos/strings/chromeos_strings_vi.xtb +++ b/chromeos/strings/chromeos_strings_vi.xtb
@@ -17,6 +17,7 @@ <translation id="1175951029573070619">Trung bình (<ph name="SIGNAL_STRENGTH" />)</translation> <translation id="1181037720776840403">Xóa</translation> <translation id="1195447618553298278">Lỗi chưa biết.</translation> +<translation id="1196959502276349371">Phiên bản <ph name="VERSION" /></translation> <translation id="1201402288615127009">Tiếp theo</translation> <translation id="1204296502688602597">Độ trễ DNS</translation> <translation id="123124571410524056">Nghi là có cổng</translation>
diff --git a/components/account_manager_core/pref_names.cc b/components/account_manager_core/pref_names.cc index 2c65a3b..d483a0a 100644 --- a/components/account_manager_core/pref_names.cc +++ b/components/account_manager_core/pref_names.cc
@@ -13,5 +13,13 @@ const char kSecondaryGoogleAccountSigninAllowed[] = "account_manager.secondary_google_account_signin_allowed"; +// A boolean pref to store the list of accounts and their availability in ARC in +// the format: {string gaia_id, bool is_available_in_arc}. +const char kAccountAppsAvailability[] = + "account_manager.account_apps_availability"; + +// Keys for `kAccountAppsAvailability`. +const char kIsAvailableInArcKey[] = "is_available_in_arc"; + } // namespace prefs } // namespace account_manager
diff --git a/components/account_manager_core/pref_names.h b/components/account_manager_core/pref_names.h index e5ebc1c..afaccf5 100644 --- a/components/account_manager_core/pref_names.h +++ b/components/account_manager_core/pref_names.h
@@ -13,6 +13,12 @@ COMPONENT_EXPORT(ACCOUNT_MANAGER_CORE) extern const char kSecondaryGoogleAccountSigninAllowed[]; +COMPONENT_EXPORT(ACCOUNT_MANAGER_CORE) +extern const char kAccountAppsAvailability[]; + +COMPONENT_EXPORT(ACCOUNT_MANAGER_CORE) +extern const char kIsAvailableInArcKey[]; + } // namespace prefs } // namespace account_manager
diff --git a/components/autofill/core/browser/autofill_regex_constants.cc b/components/autofill/core/browser/autofill_regex_constants.cc index 71a186d..8dc59151 100644 --- a/components/autofill/core/browser/autofill_regex_constants.cc +++ b/components/autofill/core/browser/autofill_regex_constants.cc
@@ -328,11 +328,11 @@ // name_field.cc ///////////////////////////////////////////////////////////////////////////// const char16_t kNameIgnoredRe[] = - u"user.?name|user.?id|nickname|maiden name|title|prefix|suffix" + u"user.?name|user.?id|nickname|maiden name|title|prefix|suffix|mail" u"|vollständiger.?name" // de-DE u"|用户名" // zh-CN u"|(?:사용자.?)?아이디|사용자.?ID"; // ko-KR -const char16_t kNameRe[] = +const char16_t kFullNameRe[] = u"^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name" u"|name.*first.*last|firstandlastname|contact.?(name|person)" u"|nombre.*y.*apellidos" // es @@ -345,7 +345,7 @@ u"|(\\b|_|\\*)ad[ı]? soyad[ı]?(\\b|_|\\*)" // tr u"|성명" // ko-KR u"|nama.?(lengkap|penerima|kamu)"; // id -const char16_t kNameSpecificRe[] = +const char16_t kNameGenericRe[] = u"^name" u"|^nom" // fr-FR u"|^nome"; // pt-BR, pt-PT
diff --git a/components/autofill/core/browser/autofill_regex_constants.h b/components/autofill/core/browser/autofill_regex_constants.h index c606c849..639a768 100644 --- a/components/autofill/core/browser/autofill_regex_constants.h +++ b/components/autofill/core/browser/autofill_regex_constants.h
@@ -45,8 +45,8 @@ extern const char16_t kDayRe[]; extern const char16_t kEmailRe[]; extern const char16_t kNameIgnoredRe[]; -extern const char16_t kNameRe[]; -extern const char16_t kNameSpecificRe[]; +extern const char16_t kFullNameRe[]; +extern const char16_t kNameGenericRe[]; extern const char16_t kFirstNameRe[]; extern const char16_t kMiddleInitialRe[]; extern const char16_t kMiddleNameRe[];
diff --git a/components/autofill/core/browser/form_parsing/autofill_scanner.cc b/components/autofill/core/browser/form_parsing/autofill_scanner.cc index 50c9b8a4..ecc9cf9 100644 --- a/components/autofill/core/browser/form_parsing/autofill_scanner.cc +++ b/components/autofill/core/browser/form_parsing/autofill_scanner.cc
@@ -22,7 +22,7 @@ Init(non_owning_); } -AutofillScanner::~AutofillScanner() {} +AutofillScanner::~AutofillScanner() = default; void AutofillScanner::Advance() { DCHECK(!IsEnd());
diff --git a/components/autofill/core/browser/form_parsing/form_field.h b/components/autofill/core/browser/form_parsing/form_field.h index 3d12de9a..6d9cd75 100644 --- a/components/autofill/core/browser/form_parsing/form_field.h +++ b/components/autofill/core/browser/form_parsing/form_field.h
@@ -80,7 +80,7 @@ static const float kBaseSearchParserScore; // Only derived classes may instantiate. - FormField() {} + FormField() = default; // Attempts to parse a form field with the given pattern. Returns true on // success and fills |match| with a pointer to the field.
diff --git a/components/autofill/core/browser/form_parsing/name_field.cc b/components/autofill/core/browser/form_parsing/name_field.cc index 6d3f5e27..80a1673 100644 --- a/components/autofill/core/browser/form_parsing/name_field.cc +++ b/components/autofill/core/browser/form_parsing/name_field.cc
@@ -68,14 +68,31 @@ // A form field that can parse a first and last name field. class FirstLastNameField : public NameField { public: - static std::unique_ptr<FirstLastNameField> ParseSpecificName( + // Tries to match a series of name fields that follows the pattern "Name, + // Surname". + static std::unique_ptr<FirstLastNameField> ParseNameSurnameLabelSequence( AutofillScanner* scanner, const LanguageCode& page_language, LogManager* log_manager); - static std::unique_ptr<FirstLastNameField> ParseComponentNames( + + // Tries to match a series of fields with a shared label: The first field + // needs to have a unspecific name label followed by up to two fields without + // a label. + static std::unique_ptr<FirstLastNameField> ParseSharedNameLabelSequence( AutofillScanner* scanner, const LanguageCode& page_language, LogManager* log_manager); + + // Tries to match a series of fields with patterns that are specific to the + // individual components of a name. Note that the order of the components does + // not matter. + static std::unique_ptr<FirstLastNameField> ParseSpecificComponentSequence( + AutofillScanner* scanner, + const LanguageCode& page_language, + LogManager* log_manager); + + // Probes the matching strategies defined above. Returns the result of the + // first successful match. Returns a nullptr if no matches can be found. static std::unique_ptr<FirstLastNameField> Parse( AutofillScanner* scanner, const LanguageCode& page_language, @@ -153,8 +170,8 @@ const std::vector<MatchingPattern>& name_patterns = PatternProvider::GetInstance().GetMatchPatterns("FULL_NAME", page_language); - if (ParseField(scanner, kNameRe, name_patterns, &field, - {log_manager, "kNameRe"})) + if (ParseField(scanner, kFullNameRe, name_patterns, &field, + {log_manager, "kFullNameRe"})) return std::make_unique<FullNameField>(field); return nullptr; @@ -182,7 +199,7 @@ FirstTwoLastNamesField::ParseComponentNames(AutofillScanner* scanner, const LanguageCode& page_language, LogManager* log_manager) { - std::unique_ptr<FirstTwoLastNamesField> v(new FirstTwoLastNamesField); + auto v = base::WrapUnique(new FirstTwoLastNamesField()); scanner->SaveCursor(); const std::vector<MatchingPattern>& honorific_prefix_patterns = @@ -289,22 +306,73 @@ AddClassification(middle_name_, type, kBaseNameParserScore, field_candidates); } -std::unique_ptr<FirstLastNameField> FirstLastNameField::ParseSpecificName( +std::unique_ptr<FirstLastNameField> +FirstLastNameField::ParseNameSurnameLabelSequence( + AutofillScanner* scanner, + const LanguageCode& page_language, + LogManager* log_manager) { + // Some pages have a generic name label that corresponds to a first name + // followed by a last name label. + // Example: Name [ ] Last Name [ ] + auto v = base::WrapUnique(new FirstLastNameField()); + + const std::vector<MatchingPattern>& name_specific_patterns = + PatternProvider::GetInstance().GetMatchPatterns("NAME_GENERIC", + page_language); + + const std::vector<MatchingPattern>& last_name_patterns = + PatternProvider::GetInstance().GetMatchPatterns("LAST_NAME", + page_language); + // Check that the field should not be ignored. + + const std::vector<MatchingPattern>& name_ignored_patterns = + PatternProvider::GetInstance().GetMatchPatterns("NAME_IGNORED", + page_language); + const std::vector<MatchingPattern>& address_name_ignored_patterns = + PatternProvider::GetInstance().GetMatchPatterns("ADDRESS_NAME_IGNORED", + page_language); + scanner->SaveCursor(); + + bool should_ignore = + ParseField(scanner, kNameIgnoredRe, name_ignored_patterns, nullptr, + {log_manager, "kNameIgnoredRe"}) || + ParseField(scanner, kAddressNameIgnoredRe, address_name_ignored_patterns, + nullptr, {log_manager, "kAddressNameIgnoredRe"}); + scanner->Rewind(); + + scanner->SaveCursor(); + + if (should_ignore) + return nullptr; + + if (ParseField(scanner, kNameGenericRe, name_specific_patterns, + &v->first_name_, {log_manager, "kNameGenericRe"}) && + ParseField(scanner, kLastNameRe, last_name_patterns, &v->last_name_, + {log_manager, "kLastNameRe"})) { + return v; + } + + scanner->Rewind(); + return nullptr; +} + +std::unique_ptr<FirstLastNameField> +FirstLastNameField::ParseSharedNameLabelSequence( AutofillScanner* scanner, const LanguageCode& page_language, LogManager* log_manager) { // Some pages (e.g. Overstock_comBilling.html, SmithsonianCheckout.html) // have the label "Name" followed by two or three text fields. - std::unique_ptr<FirstLastNameField> v(new FirstLastNameField); + auto v = base::WrapUnique(new FirstLastNameField()); scanner->SaveCursor(); AutofillField* next = nullptr; const std::vector<MatchingPattern>& name_specific_patterns = - PatternProvider::GetInstance().GetMatchPatterns("NAME_SPECIFIC", + PatternProvider::GetInstance().GetMatchPatterns("NAME_GENERIC", page_language); - if (ParseField(scanner, kNameSpecificRe, name_specific_patterns, - &v->first_name_, {log_manager, "kNameSpecificRe"}) && + if (ParseField(scanner, kNameGenericRe, name_specific_patterns, + &v->first_name_, {log_manager, "kNameGenericRe"}) && ParseEmptyLabel(scanner, &next)) { if (ParseEmptyLabel(scanner, &v->last_name_)) { // There are three name fields; assume that the middle one is a @@ -323,11 +391,12 @@ } // static -std::unique_ptr<FirstLastNameField> FirstLastNameField::ParseComponentNames( +std::unique_ptr<FirstLastNameField> +FirstLastNameField::ParseSpecificComponentSequence( AutofillScanner* scanner, const LanguageCode& page_language, LogManager* log_manager) { - std::unique_ptr<FirstLastNameField> v(new FirstLastNameField); + auto v = base::WrapUnique(new FirstLastNameField()); scanner->SaveCursor(); // A fair number of pages use the names "fname" and "lname" for naming @@ -444,9 +513,15 @@ const LanguageCode& page_language, LogManager* log_manager) { std::unique_ptr<FirstLastNameField> field = - ParseSpecificName(scanner, page_language, log_manager); - if (!field) - field = ParseComponentNames(scanner, page_language, log_manager); + ParseSharedNameLabelSequence(scanner, page_language, log_manager); + + if (!field && base::FeatureList::IsEnabled( + features::kAutofillEnableNameSurenameParsing)) { + field = ParseNameSurnameLabelSequence(scanner, page_language, log_manager); + } + if (!field) { + field = ParseSpecificComponentSequence(scanner, page_language, log_manager); + } return field; }
diff --git a/components/autofill/core/browser/form_parsing/name_field_unittest.cc b/components/autofill/core/browser/form_parsing/name_field_unittest.cc index c886303f..495d00d9 100644 --- a/components/autofill/core/browser/form_parsing/name_field_unittest.cc +++ b/components/autofill/core/browser/form_parsing/name_field_unittest.cc
@@ -69,6 +69,28 @@ ClassifyAndVerify(ParseResult::PARSED); } +TEST_F(NameFieldTest, NameSurname) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitAndEnableFeature( + features::kAutofillEnableNameSurenameParsing); + + AddTextFormFieldData("name", "name", NAME_FIRST); + AddTextFormFieldData("surename", "surname", NAME_LAST); + + ClassifyAndVerify(ParseResult::PARSED); +} + +TEST_F(NameFieldTest, NameSurname_DE) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitAndEnableFeature( + features::kAutofillEnableNameSurenameParsing); + + AddTextFormFieldData("name", "name", NAME_FIRST); + AddTextFormFieldData("nachname", "nachname", NAME_LAST); + + ClassifyAndVerify(ParseResult::PARSED); +} + TEST_F(NameFieldTest, FirstLast2) { AddTextFormFieldData("first_name", "Name", NAME_FIRST); AddTextFormFieldData("last_name", "Name", NAME_LAST);
diff --git a/components/autofill/core/browser/pattern_provider/resources/regex_patterns.json b/components/autofill/core/browser/pattern_provider/resources/regex_patterns.json index 7d50e813..7664323 100644 --- a/components/autofill/core/browser/pattern_provider/resources/regex_patterns.json +++ b/components/autofill/core/browser/pattern_provider/resources/regex_patterns.json
@@ -2183,7 +2183,7 @@ "en": [ { "pattern_identifier": "en_name_ignored_preserving", - "positive_pattern": "user.?name|user.?id|nickname|maiden name|title|prefix|suffix", + "positive_pattern": "user.?name|user.?id|nickname|maiden name|title|prefix|suffix|mail", "positive_score": 0.9, "negative_pattern": null, "match_field_attributes": 3, @@ -2333,7 +2333,7 @@ } ] }, - "NAME_SPECIFIC": { + "NAME_GENERIC": { "en": [ { "pattern_identifier": "en_name_specific_preserving",
diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc index 52855e32..437762ca 100644 --- a/components/autofill/core/common/autofill_features.cc +++ b/components/autofill/core/common/autofill_features.cc
@@ -164,6 +164,12 @@ "AutofillEnablePasswordInfoBarAccountIndicationFooter", base::FEATURE_ENABLED_BY_DEFAULT}; +// Enables the parsing of a sequence of fields that follows the pattern of Name, +// Surname. +// TODO(crbug.com/1277480): Remove once launched. +const base::Feature kAutofillEnableNameSurenameParsing{ + "AutofillEnableNameSurenameParsing", base::FEATURE_DISABLED_BY_DEFAULT}; + // When enabled, the address profile deduplication logic runs after the browser // startup, once per chrome version. const base::Feature kAutofillEnableProfileDeduplication{
diff --git a/components/autofill/core/common/autofill_features.h b/components/autofill/core/common/autofill_features.h index 35a90752..595d0d3 100644 --- a/components/autofill/core/common/autofill_features.h +++ b/components/autofill/core/common/autofill_features.h
@@ -68,6 +68,8 @@ COMPONENT_EXPORT(AUTOFILL) extern const base::Feature kAutofillEnableLabelPrecedenceForTurkishAddresses; COMPONENT_EXPORT(AUTOFILL) +extern const base::Feature kAutofillEnableNameSurenameParsing; +COMPONENT_EXPORT(AUTOFILL) extern const base::Feature kAutofillEnableProfileDeduplication; COMPONENT_EXPORT(AUTOFILL) extern const base::Feature kAutofillEnableSupportForParsingWithSharedLabels;
diff --git a/components/autofill_assistant/browser/actions/collect_user_data_action.cc b/components/autofill_assistant/browser/actions/collect_user_data_action.cc index bc2c43ca..d476bad 100644 --- a/components/autofill_assistant/browser/actions/collect_user_data_action.cc +++ b/components/autofill_assistant/browser/actions/collect_user_data_action.cc
@@ -1513,6 +1513,9 @@ credit_card->set_record_type(autofill::CreditCard::MASKED_SERVER_CARD); AddProtoDataToAutofillDataModel(payment_data.card_values(), proto_data.locale(), credit_card.get()); + if (!payment_data.network().empty()) { + credit_card->SetNetworkForMaskedCard(payment_data.network()); + } // Note: If the incoming card did not set a network GetPaymentRequestData // will fall back to "generic". if (!collect_user_data_options_->supported_basic_card_networks.empty() &&
diff --git a/components/autofill_assistant/browser/actions/collect_user_data_action_unittest.cc b/components/autofill_assistant/browser/actions/collect_user_data_action_unittest.cc index 71841a98..3df465c2 100644 --- a/components/autofill_assistant/browser/actions/collect_user_data_action_unittest.cc +++ b/components/autofill_assistant/browser/actions/collect_user_data_action_unittest.cc
@@ -114,7 +114,6 @@ const std::string& name, google::protobuf::Map<int32_t, AutofillEntryProto>* values) { (*values)[51] = MakeAutofillEntry(name); - (*values)[58] = MakeAutofillEntry("Visa"); (*values)[53] = MakeAutofillEntry("8"); (*values)[55] = MakeAutofillEntry("2050"); } @@ -2665,7 +2664,10 @@ Pair(field_formatter::Key(55), "2050"), Pair(field_formatter::Key(54), "50"), Pair(field_formatter::Key(56), "08/50"), - Pair(field_formatter::Key(57), "08/2050")})); + Pair(field_formatter::Key(57), "08/2050"), + Pair(field_formatter::Key(58), "Visa"), + Pair(field_formatter::Key(-2), "visa"), + Pair(field_formatter::Key(-5), "Visa")})); auto address_mappings = field_formatter::CreateAutofillMappings( *user_data_.available_payment_instruments_[0]->billing_address, "en-US"); @@ -2694,6 +2696,7 @@ ->add_available_payment_instruments(); AddCompleteCardEntriesToMap("John Doe", payment_instrument->mutable_card_values()); + payment_instrument->set_network("visaCC"); AddCompleteAddressEntriesToMap("John Doe", payment_instrument->mutable_address_values());
diff --git a/components/autofill_assistant/browser/service.proto b/components/autofill_assistant/browser/service.proto index a429851..3e4963d 100644 --- a/components/autofill_assistant/browser/service.proto +++ b/components/autofill_assistant/browser/service.proto
@@ -2405,6 +2405,8 @@ message PaymentInstrumentProto { // The values for the card, where the key is one of autofill::ServerFieldType. map<int32, AutofillEntryProto> card_values = 2; + // The network of the card. + optional string network = 5; // The values for the billing address, where the key is one of // autofill::ServerFieldType. map<int32, AutofillEntryProto> address_values = 3;
diff --git a/components/browser_ui/styles/android/BUILD.gn b/components/browser_ui/styles/android/BUILD.gn index 943dd76..d204d8c4 100644 --- a/components/browser_ui/styles/android/BUILD.gn +++ b/components/browser_ui/styles/android/BUILD.gn
@@ -23,7 +23,6 @@ android_resources("java_resources") { sources = [ - "java/res/color/checkbox_tint.xml", "java/res/color/default_icon_color_accent1_tint_list.xml", "java/res/color/default_icon_color_light_tint_list.xml", "java/res/color/default_icon_color_secondary_light_tint_list.xml", @@ -34,6 +33,7 @@ "java/res/color/default_text_color_list.xml", "java/res/color/default_text_color_secondary_list.xml", "java/res/color/progress_bar_bg_color.xml", + "java/res/color/selection_control_button_tint.xml", "java/res/color/switch_thumb_tint.xml", "java/res/color/switch_track_tint.xml", "java/res/color/text_highlight_color.xml",
diff --git a/components/browser_ui/styles/android/java/res/color/checkbox_tint.xml b/components/browser_ui/styles/android/java/res/color/selection_control_button_tint.xml similarity index 86% rename from components/browser_ui/styles/android/java/res/color/checkbox_tint.xml rename to components/browser_ui/styles/android/java/res/color/selection_control_button_tint.xml index 868caa3..d9757f7 100644 --- a/components/browser_ui/styles/android/java/res/color/checkbox_tint.xml +++ b/components/browser_ui/styles/android/java/res/color/selection_control_button_tint.xml
@@ -3,9 +3,7 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<selector xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - tools:ignore="UnusedResources" > +<selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:alpha="@dimen/default_disabled_alpha" android:color="@macro/default_control_color_normal" android:state_enabled="false" /> <item android:state_checked="true" android:color="@macro/default_control_color_active" />
diff --git a/components/browser_ui/styles/android/java/res/values/styles.xml b/components/browser_ui/styles/android/java/res/values/styles.xml index 22eee6b..f82081b 100644 --- a/components/browser_ui/styles/android/java/res/values/styles.xml +++ b/components/browser_ui/styles/android/java/res/values/styles.xml
@@ -9,7 +9,9 @@ themes.xml. --> <style name="Theme.BrowserUI" parent="Base.Theme.BrowserUI"> <!-- Control styles --> - <item name="switchStyle">@style/SwitchMaterialStyle</item> + <item name="checkboxStyle">@style/Widget.BrowserUI.CheckBox</item> + <item name="radioButtonStyle">@style/Widget.BrowserUI.RadioButton</item> + <item name="switchStyle">@style/Widget.BrowserUI.Switch</item> <!-- Window Properties --> <item name="android:windowBackground">@macro/default_bg_color</item> @@ -36,12 +38,7 @@ <!-- This is for keeping the current TextInputLayout style. TODO(crbug.com/1206024): Remove or update once the design for the app is updated. --> - <item name="textInputStyle">@style/TextInputStyle</item> - <!-- Overrides to keep the current styling for the widgets after migrating to the - MaterialComponents theme. - TODO(crbug.com/1165077): Remove these when the widgets are being migrated. --> - <item name="radioButtonStyle">@style/Widget.AppCompat.CompoundButton.RadioButton</item> - <item name="checkboxStyle">@style/Widget.AppCompat.CompoundButton.CheckBox</item> + <item name="textInputStyle">@style/Widget.BrowserUI.TextInputLayout</item> </style> <style name="Theme.BrowserUI.DayNight" /> @@ -70,7 +67,24 @@ <item name="android:windowContentOverlay">@null</item> </style> - <style name="TextInputStyle" parent="Widget.Design.TextInputLayout"> + <!-- Control styles --> + <style name="Widget.BrowserUI.CheckBox" parent="Widget.Material3.CompoundButton.CheckBox"> + <item name="buttonTint">@color/selection_control_button_tint</item> + <item name="android:minWidth">0dp</item> + <item name="android:minHeight">0dp</item> + </style> + <style name="Widget.BrowserUI.RadioButton" parent="Widget.Material3.CompoundButton.RadioButton"> + <item name="buttonTint">@color/selection_control_button_tint</item> + <item name="android:minWidth">0dp</item> + <item name="android:minHeight">0dp</item> + </style> + <style name="Widget.BrowserUI.Switch" parent="Widget.MaterialComponents.CompoundButton.Switch"> + <item name="thumbTint">@color/switch_thumb_tint</item> + <item name="trackTint">@color/switch_track_tint</item> + <item name="trackTintMode">src_in</item> + </style> + <!-- TextInputLayout style --> + <style name="Widget.BrowserUI.TextInputLayout" parent="Widget.Design.TextInputLayout"> <item name="errorTextAppearance">@style/TextAppearance.ErrorCaption</item> </style> @@ -161,13 +175,6 @@ <item name="android:popupElevation" tools:targetApi="21">0dp</item> </style> - <!-- Switch styling --> - <style name="SwitchMaterialStyle" parent="Widget.MaterialComponents.CompoundButton.Switch"> - <item name="thumbTint">@color/switch_thumb_tint</item> - <item name="trackTint">@color/switch_track_tint</item> - <item name="trackTintMode">src_in</item> - </style> - <!-- Switch styling for dark theme --> <!-- TODO(sinansahin): See if we can pass the baseline dark theme to the incognito NTP instead. --> @@ -177,7 +184,7 @@ <item name="colorSwitchThumbNormalNonDynamic">@color/baseline_neutral_100</item> <item name="colorSwitchTrackNormalNonDynamic">@color/baseline_neutral_variant_400</item> <item name="colorControlHighlight">@color/default_control_color_highlight_dark</item> - <item name="switchStyle">@style/SwitchMaterialStyle</item> + <item name="switchStyle">@style/Widget.BrowserUI.Switch</item> </style> <!-- Styling for an app menu item row. -->
diff --git a/components/cast_certificate/BUILD.gn b/components/cast_certificate/BUILD.gn index 2e67a689..675bfec 100644 --- a/components/cast_certificate/BUILD.gn +++ b/components/cast_certificate/BUILD.gn
@@ -2,10 +2,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -declare_args() { - # Allow use of custom Cast root certificate for authentication. - cast_allow_developer_certificate = false -} +import("//third_party/openscreen/src/build/config/cast.gni") config("certificate_config") { defines = []
diff --git a/components/certificate_transparency/data/log_list.json b/components/certificate_transparency/data/log_list.json index bb5394b..05f2d09a 100644 --- a/components/certificate_transparency/data/log_list.json +++ b/components/certificate_transparency/data/log_list.json
@@ -1,6 +1,6 @@ { - "version": "4.51", - "log_list_timestamp": "2021-12-07T01:34:19Z", + "version": "4.52", + "log_list_timestamp": "2021-12-08T01:34:10Z", "operators": [ { "name": "Google",
diff --git a/components/language/android/BUILD.gn b/components/language/android/BUILD.gn index d3a6191b..58ca0d3b 100644 --- a/components/language/android/BUILD.gn +++ b/components/language/android/BUILD.gn
@@ -28,9 +28,11 @@ sources = [ "java/src/org/chromium/components/language/AndroidLanguageMetricsBridge.java", "java/src/org/chromium/components/language/GeoLanguageProviderBridge.java", + "java/src/org/chromium/components/language/LanguageProfileController.java", ] deps = [ + ":ulp_delegate_java", "//base:base_java", "//third_party/androidx:androidx_annotation_annotation_java", ]
diff --git a/components/language/android/java/src/org/chromium/components/language/LanguageProfileController.java b/components/language/android/java/src/org/chromium/components/language/LanguageProfileController.java new file mode 100644 index 0000000..919b1074 --- /dev/null +++ b/components/language/android/java/src/org/chromium/components/language/LanguageProfileController.java
@@ -0,0 +1,54 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.components.language; + +import org.chromium.base.Log; +import org.chromium.base.ThreadUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeoutException; + +/** + * Controller to manage getting language preferences from device. + */ +public final class LanguageProfileController { + private static final String TAG = "ULP"; + private static final int TIMEOUT_IN_SECONDS = 60; + + private LanguageProfileDelegate mDelegate; + + /** + * @param delegate LanguageProfileDelegate to use. + */ + public LanguageProfileController(LanguageProfileDelegate delegate) { + mDelegate = delegate; + } + + /** + * Get the preferred languages for user. The list is empty if an error occurs. + * This method is blocking and must be called on a background thread. + * @param accountName Account to get profile or null if the default profile should be returned. + * @return A list of language tags ordered by preference for |accountName| + */ + public List<String> getLanguagePreferences(String accountName) { + ThreadUtils.assertOnBackgroundThread(); + if (!mDelegate.isULPSupported()) { + // (TODO:https://crbug.com/1258261) Add initiation histogram. + Log.d(TAG, "ULP not available"); + return new ArrayList<String>(); + } + try { + return mDelegate.getLanguagePreferences(accountName, TIMEOUT_IN_SECONDS); + } catch (TimeoutException e) { + // (TODO:https://crbug.com/1258261) Add initiation histogram. + Log.d(TAG, "ULP getLanguagePreferences timed out"); + } catch (Exception e) { + // (TODO:https://crbug.com/1258261) Add initiation histogram. + Log.d(TAG, "ULP getLanguagePreferences threw exception:", e); + } + return new ArrayList<String>(); + } +}
diff --git a/components/language/android/java/src/org/chromium/components/language/LanguageProfileDelegate.java b/components/language/android/java/src/org/chromium/components/language/LanguageProfileDelegate.java index 7a93b43..0961a30 100644 --- a/components/language/android/java/src/org/chromium/components/language/LanguageProfileDelegate.java +++ b/components/language/android/java/src/org/chromium/components/language/LanguageProfileDelegate.java
@@ -5,19 +5,23 @@ package org.chromium.components.language; import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; /** * Interface to get language profile data for device. */ public interface LanguageProfileDelegate { /** - * @return True if ULP is currently available. + * @return True if ULP is currently supported. */ - public boolean isULPAvailable(); + public boolean isULPSupported(); /** * @param accountName Account to get profile or null if the default profile should be returned. + * @param timeoutInSeconds Seconds to wait before timing out on call to device. * @return A list of language tags ordered by preference for |accountName| */ - public List<String> getLanguagePreferences(String accountName); + public List<String> getLanguagePreferences(String accountName, int timeoutInSeconds) + throws ExecutionException, InterruptedException, TimeoutException; }
diff --git a/components/language/android/java/src/org/chromium/components/language/LanguageProfileDelegateImpl.java b/components/language/android/java/src/org/chromium/components/language/LanguageProfileDelegateImpl.java index d695c8e..732a11d 100644 --- a/components/language/android/java/src/org/chromium/components/language/LanguageProfileDelegateImpl.java +++ b/components/language/android/java/src/org/chromium/components/language/LanguageProfileDelegateImpl.java
@@ -12,20 +12,21 @@ */ public class LanguageProfileDelegateImpl implements LanguageProfileDelegate { /** - * @return True if ULP is currently available. + * @return True if ULP is currently supported. */ @Override - public boolean isULPAvailable() { - // ULP is not available in the default implementation. + public boolean isULPSupported() { + // ULP is not supported in the default implementation. return false; } /** * @param accountName Account to get profile or null if the default profile should be returned. + * @param timeoutInSeconds Seconds to wait before timing out on call to device. * @return A list of language tags ordered by preference for |accountName| */ @Override - public List<String> getLanguagePreferences(String accountName) { + public List<String> getLanguagePreferences(String accountName, int timeoutInSeconds) { // The default implementation always returns an empty list. return new ArrayList<String>(); }
diff --git a/components/omnibox/browser/BUILD.gn b/components/omnibox/browser/BUILD.gn index 28518a0b..93d4703 100644 --- a/components/omnibox/browser/BUILD.gn +++ b/components/omnibox/browser/BUILD.gn
@@ -183,6 +183,8 @@ "omnibox_triggered_feature_service.h", "omnibox_view.cc", "omnibox_view.h", + "omnibox_watcher.cc", + "omnibox_watcher.h", "on_device_head_model.cc", "on_device_head_model.h", "on_device_head_provider.cc",
diff --git a/components/omnibox/browser/omnibox_watcher.cc b/components/omnibox/browser/omnibox_watcher.cc new file mode 100644 index 0000000..24deb39 --- /dev/null +++ b/components/omnibox/browser/omnibox_watcher.cc
@@ -0,0 +1,71 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "omnibox_watcher.h" + +#if !defined(OS_IOS) +#include "base/memory/singleton.h" +#include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "components/keyed_service/content/browser_context_keyed_service_factory.h" +#endif // !defined(OS_IOS) + +namespace { + +#if !defined(OS_IOS) +class OmniboxWatcherFactory : public BrowserContextKeyedServiceFactory { + public: + static OmniboxWatcher* GetForBrowserContext( + content::BrowserContext* context) { + return static_cast<OmniboxWatcher*>( + GetInstance()->GetServiceForBrowserContext(context, true)); + } + + static OmniboxWatcherFactory* GetInstance() { + return base::Singleton<OmniboxWatcherFactory>::get(); + } + + OmniboxWatcherFactory() + : BrowserContextKeyedServiceFactory( + "OmniboxWatcher", + BrowserContextDependencyManager::GetInstance()) {} + + private: + // BrowserContextKeyedServiceFactory overrides + KeyedService* BuildServiceInstanceFor( + content::BrowserContext* context) const override { + return new OmniboxWatcher(); + } + + content::BrowserContext* GetBrowserContextToUse( + content::BrowserContext* context) const override { + return context; + } +}; +#endif // !defined(OS_IOS) + +} // namespace + +OmniboxWatcher::OmniboxWatcher() = default; +OmniboxWatcher::~OmniboxWatcher() = default; + +#if !defined(OS_IOS) +// static +OmniboxWatcher* OmniboxWatcher::GetForBrowserContext( + content::BrowserContext* browser_context) { + return OmniboxWatcherFactory::GetForBrowserContext(browser_context); +} +#endif // !defined(OS_IOS) + +void OmniboxWatcher::AddObserver(Observer* observer) { + observers_.AddObserver(observer); +} + +void OmniboxWatcher::RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); +} + +void OmniboxWatcher::NotifyInputEntered() { + for (auto& observer : observers_) + observer.OnOmniboxInputEntered(); +}
diff --git a/components/omnibox/browser/omnibox_watcher.h b/components/omnibox/browser/omnibox_watcher.h new file mode 100644 index 0000000..f9999d8 --- /dev/null +++ b/components/omnibox/browser/omnibox_watcher.h
@@ -0,0 +1,47 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_OMNIBOX_BROWSER_OMNIBOX_WATCHER_H_ +#define COMPONENTS_OMNIBOX_BROWSER_OMNIBOX_WATCHER_H_ + +#include "base/observer_list.h" +#include "base/observer_list_types.h" +#include "build/build_config.h" +#include "components/keyed_service/core/keyed_service.h" + +#if !defined(OS_IOS) +#include "content/public/browser/browser_context.h" +#endif // !defined(OS_IOS) + +// This KeyedService is meant to observe the omnibox and provide notifications +// to observers on suggestion changes and provided input. +class OmniboxWatcher : public KeyedService { + public: + class Observer : public base::CheckedObserver { + public: + // Notifies listeners that omnibox input has been entered. + virtual void OnOmniboxInputEntered() {} + }; + +#if !defined(OS_IOS) + static OmniboxWatcher* GetForBrowserContext( + content::BrowserContext* browser_context); +#endif // !defined(OS_IOS) + + OmniboxWatcher(); + ~OmniboxWatcher() override; + OmniboxWatcher(const OmniboxWatcher&) = delete; + OmniboxWatcher& operator=(const OmniboxWatcher&) = delete; + + void NotifyInputEntered(); + + // Add/remove observer. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + private: + base::ObserverList<Observer> observers_; +}; + +#endif // COMPONENTS_OMNIBOX_BROWSER_OMNIBOX_WATCHER_H_
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc index daa2718..38bf159 100644 --- a/components/omnibox/common/omnibox_features.cc +++ b/components/omnibox/common/omnibox_features.cc
@@ -123,12 +123,13 @@ const base::Feature kOmniboxLocalZeroSuggestAgeThreshold{ "OmniboxLocalZeroSuggestAgeThreshold", base::FEATURE_DISABLED_BY_DEFAULT}; -// Used to force enable/disable trending zero-prefix suggestions on the NTP -// (Omnibox and NTP realbox). This feature triggers a server-side behavior only -// and has no direct impact on the client behavior. +// Used to enable/disable remote zero-prefix suggestions on the NTP +// (Omnibox and NTP realbox). Enabling this feature permits the code to issue +// suggestions request to the server on the new tab page for users who decided +// not to sign in. const base::Feature kOmniboxTrendingZeroPrefixSuggestionsOnNTP{ "OmniboxTrendingZeroPrefixSuggestionsOnNTP", - enabled_by_default_desktop_only}; + enabled_by_default_desktop_android}; // Enables on-focus suggestions on the Open Web, that are contextual to the // current URL. Will only work if user is signed-in and syncing, or is
diff --git a/components/optimization_guide/content/browser/page_content_annotations_model_manager.cc b/components/optimization_guide/content/browser/page_content_annotations_model_manager.cc index 269fcc25..d3264ed 100644 --- a/components/optimization_guide/content/browser/page_content_annotations_model_manager.cc +++ b/components/optimization_guide/content/browser/page_content_annotations_model_manager.cc
@@ -475,6 +475,28 @@ out_content_annotations->categories = final_categories; } +void PageContentAnnotationsModelManager::NotifyWhenModelAvailable( + AnnotationType type, + base::OnceCallback<void(bool)> callback) { + if (type == AnnotationType::kPageTopics && + on_demand_page_topics_model_executor_) { + on_demand_page_topics_model_executor_->AddOnModelUpdatedCallback( + base::BindOnce(std::move(callback), true)); + return; + } + + if (type == AnnotationType::kContentVisibility && + page_visibility_model_executor_) { + page_visibility_model_executor_->AddOnModelUpdatedCallback( + base::BindOnce(std::move(callback), true)); + return; + } + + // TODO(crbug/1249632): Add support for page entities. + + std::move(callback).Run(false); +} + absl::optional<ModelInfo> PageContentAnnotationsModelManager::GetModelInfoForType( AnnotationType type) const {
diff --git a/components/optimization_guide/content/browser/page_content_annotations_model_manager.h b/components/optimization_guide/content/browser/page_content_annotations_model_manager.h index 4c4b8d6..f2e515f 100644 --- a/components/optimization_guide/content/browser/page_content_annotations_model_manager.h +++ b/components/optimization_guide/content/browser/page_content_annotations_model_manager.h
@@ -56,6 +56,13 @@ const std::vector<std::string>& inputs, AnnotationType annotation_type) override; + // Runs |callback| with true when the model that powers |BatchAnnotate| for + // the given annotation type is ready to execute. If the model is ready now, + // the callback is run immediately. If the model will never become ready, due + // to feature flags for example, the callback run with false. + void NotifyWhenModelAvailable(AnnotationType type, + base::OnceCallback<void(bool)> callback); + // Returns the model info associated with the given AnnotationType, if it is // available and loaded. // TODO(crbug/1249632): Add support for more than just page topics.
diff --git a/components/optimization_guide/content/browser/page_content_annotations_model_manager_unittest.cc b/components/optimization_guide/content/browser/page_content_annotations_model_manager_unittest.cc index e1600d9..1373b938 100644 --- a/components/optimization_guide/content/browser/page_content_annotations_model_manager_unittest.cc +++ b/components/optimization_guide/content/browser/page_content_annotations_model_manager_unittest.cc
@@ -834,6 +834,85 @@ model_manager()->GetModelInfoForType(AnnotationType::kContentVisibility)); } +TEST_F(PageContentAnnotationsModelManagerTest, + NotifyWhenModelAvailable_NotAvailable) { + absl::optional<bool> topics_callback_success; + absl::optional<bool> visibility_callback_success; + + model_manager()->NotifyWhenModelAvailable( + AnnotationType::kPageTopics, + base::BindOnce([](absl::optional<bool>* out_success, + bool success) { *out_success = success; }, + &topics_callback_success)); + model_manager()->NotifyWhenModelAvailable( + AnnotationType::kContentVisibility, + base::BindOnce([](absl::optional<bool>* out_success, + bool success) { *out_success = success; }, + &visibility_callback_success)); + + ASSERT_TRUE(topics_callback_success); + ASSERT_TRUE(visibility_callback_success); + EXPECT_FALSE(*topics_callback_success); + EXPECT_FALSE(*visibility_callback_success); +} + +TEST_F(PageContentAnnotationsModelManagerTest, + NotifyWhenModelAvailable_TopicsOnly) { + SetupPageTopicsV2ModelExecutor(); + + absl::optional<bool> topics_callback_success; + absl::optional<bool> visibility_callback_success; + + model_manager()->NotifyWhenModelAvailable( + AnnotationType::kPageTopics, + base::BindOnce([](absl::optional<bool>* out_success, + bool success) { *out_success = success; }, + &topics_callback_success)); + model_manager()->NotifyWhenModelAvailable( + AnnotationType::kContentVisibility, + base::BindOnce([](absl::optional<bool>* out_success, + bool success) { *out_success = success; }, + &visibility_callback_success)); + + ASSERT_TRUE(topics_callback_success); + ASSERT_TRUE(visibility_callback_success); + EXPECT_TRUE(*topics_callback_success); + EXPECT_FALSE(*visibility_callback_success); +} + +TEST_F(PageContentAnnotationsModelManagerTest, + NotifyWhenModelAvailable_VisibilityOnly) { + proto::Any any_metadata; + any_metadata.set_type_url( + "type.googleapis.com/com.foo.PageTopicsModelMetadata"); + proto::PageTopicsModelMetadata page_topics_model_metadata; + page_topics_model_metadata.set_version(123); + page_topics_model_metadata.mutable_output_postprocessing_params() + ->mutable_visibility_params() + ->set_category_name("DO NOT EVALUATE"); + page_topics_model_metadata.SerializeToString(any_metadata.mutable_value()); + SendPageVisibilityModelToExecutor(any_metadata); + + absl::optional<bool> topics_callback_success; + absl::optional<bool> visibility_callback_success; + + model_manager()->NotifyWhenModelAvailable( + AnnotationType::kPageTopics, + base::BindOnce([](absl::optional<bool>* out_success, + bool success) { *out_success = success; }, + &topics_callback_success)); + model_manager()->NotifyWhenModelAvailable( + AnnotationType::kContentVisibility, + base::BindOnce([](absl::optional<bool>* out_success, + bool success) { *out_success = success; }, + &visibility_callback_success)); + + ASSERT_TRUE(topics_callback_success); + ASSERT_TRUE(visibility_callback_success); + EXPECT_FALSE(*topics_callback_success); + EXPECT_TRUE(*visibility_callback_success); +} + class PageContentAnnotationsModelManagerEntitiesOnlyTest : public PageContentAnnotationsModelManagerTest { public:
diff --git a/components/optimization_guide/content/browser/page_content_annotations_service.cc b/components/optimization_guide/content/browser/page_content_annotations_service.cc index 167b127..e3e0e1b 100644 --- a/components/optimization_guide/content/browser/page_content_annotations_service.cc +++ b/components/optimization_guide/content/browser/page_content_annotations_service.cc
@@ -134,6 +134,17 @@ #endif } +void PageContentAnnotationsService::NotifyWhenModelAvailable( + AnnotationType type, + base::OnceCallback<void(bool)> callback) { +#if BUILDFLAG(BUILD_WITH_TFLITE_LIB) + DCHECK(model_manager_); + model_manager_->NotifyWhenModelAvailable(type, std::move(callback)); +#else + std::move(callback).Run(false); +#endif +} + void PageContentAnnotationsService::ExtractRelatedSearches( const HistoryVisit& visit, content::WebContents* web_contents) {
diff --git a/components/optimization_guide/content/browser/page_content_annotations_service.h b/components/optimization_guide/content/browser/page_content_annotations_service.h index dc01fc9..f88c4342 100644 --- a/components/optimization_guide/content/browser/page_content_annotations_service.h +++ b/components/optimization_guide/content/browser/page_content_annotations_service.h
@@ -84,6 +84,13 @@ // available. absl::optional<ModelInfo> GetModelInfoForType(AnnotationType type) const; + // Runs |callback| with true when the model that powers |BatchAnnotate| for + // the given annotation type is ready to execute. If the model is ready now, + // the callback is run immediately. If the model file will never be available, + // the callback is run with false. + void NotifyWhenModelAvailable(AnnotationType type, + base::OnceCallback<void(bool)> callback); + // EntityMetadataProvider: void GetMetadataForEntityId( const std::string& entity_id,
diff --git a/components/optimization_guide/core/hints_manager.cc b/components/optimization_guide/core/hints_manager.cc index ae5e3358..394d4c18 100644 --- a/components/optimization_guide/core/hints_manager.cc +++ b/components/optimization_guide/core/hints_manager.cc
@@ -257,6 +257,33 @@ return config; } +// Logs information that will be requested from the remote Optimization Guide +// service. +void MaybeLogGetHintRequestInfo( + proto::RequestContext request_context, + const base::flat_set<proto::OptimizationType>& + registered_optimization_types, + const std::vector<GURL>& urls_to_fetch, + const std::vector<std::string>& hosts_to_fetch) { + if (!switches::IsDebugLogsEnabled()) + return; + + DVLOG(0) << "OptimizationGuide: Starting fetch for request context " + << proto::RequestContext_Name(request_context); + DVLOG(0) << "OptimizationGuide: Registered Optimization Types: "; + for (const auto& optimization_type : registered_optimization_types) { + DVLOG(0) << "OptimizationGuide: Optimization Type: " + << proto::OptimizationType_Name(optimization_type); + } + DVLOG(0) << "OptimizationGuide: URLs and Hosts: "; + for (const auto& url : urls_to_fetch) { + DVLOG(0) << "OptimizationGuide: URL: " << url; + } + for (const auto& host : hosts_to_fetch) { + DVLOG(0) << "OptimizationGuide: Host: " << host; + } +} + } // namespace HintsManager::HintsManager( @@ -652,20 +679,9 @@ // Add hosts of active tabs to list of hosts to fetch for. Since we are mainly // fetching for updated information on tabs, add those to the front of the // list. - if (switches::IsDebugLogsEnabled()) { - DVLOG(0) << "OptimizationGuide: ActiveTabsFetching starting fetch for: "; - DVLOG(0) << "OptimizationGuide: Registered Optimization Types: "; - for (const auto& optimization_type : registered_optimization_types_) { - DVLOG(0) << "OptimizationGuide: Optimization Type: " - << proto::OptimizationType_Name(optimization_type); - } - DVLOG(0) << "OptimizationGuide: URLs and Hosts: "; - } base::flat_set<std::string> top_hosts_set = base::flat_set<std::string>(top_hosts.begin(), top_hosts.end()); for (const auto& url : active_tab_urls_to_refresh) { - if (switches::IsDebugLogsEnabled()) - DVLOG(0) << "OptimizationGuide: URL: " << url; if (!url.has_host() || top_hosts_set.find(url.host()) == top_hosts_set.end()) { continue; @@ -673,10 +689,11 @@ if (!hint_cache_->HasHint(url.host())) { top_hosts_set.insert(url.host()); top_hosts.insert(top_hosts.begin(), url.host()); - if (switches::IsDebugLogsEnabled()) - DVLOG(0) << "OptimizationGuide: Host: " << url.host(); } } + MaybeLogGetHintRequestInfo(proto::CONTEXT_BATCH_UPDATE_ACTIVE_TABS, + registered_optimization_types_, + active_tab_urls_to_refresh, top_hosts); batch_update_hints_fetcher_->FetchOptimizationGuideServiceHints( top_hosts, active_tab_urls_to_refresh, registered_optimization_types_, @@ -932,6 +949,138 @@ blocklist_optimization_filters_.end(); } +base::flat_map<proto::OptimizationType, OptimizationGuideDecisionWithMetadata> +HintsManager::GetDecisionsWithCachedInformationForURLAndOptimizationTypes( + const GURL& url, + const base::flat_set<proto::OptimizationType>& optimization_types) { + base::flat_map<proto::OptimizationType, OptimizationGuideDecisionWithMetadata> + decisions; + + for (const auto optimization_type : optimization_types) { + OptimizationMetadata metadata; + OptimizationTypeDecision type_decision = + CanApplyOptimization(url, optimization_type, &metadata); + OptimizationGuideDecision decision = + GetOptimizationGuideDecisionFromOptimizationTypeDecision(type_decision); + decisions[optimization_type] = {decision, metadata}; + } + + return decisions; +} + +void HintsManager::CanApplyOptimizationOnDemand( + const std::vector<GURL>& urls, + const base::flat_set<proto::OptimizationType>& optimization_types, + proto::RequestContext request_context, + OnDemandOptimizationGuideDecisionRepeatingCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + // TODO(crbug/1275612): Check whether we have consent to fetch. + + InsertionOrderedSet<GURL> urls_to_fetch; + InsertionOrderedSet<std::string> hosts_to_fetch; + for (const auto& url : urls) { + base::flat_map<proto::OptimizationType, + OptimizationGuideDecisionWithMetadata> + decisions = GetDecisionsWithCachedInformationForURLAndOptimizationTypes( + url, optimization_types); + + bool has_something_to_fetch_for = false; + if (!hint_cache_->HasURLKeyedEntryForURL(url)) { + urls_to_fetch.insert(url); + has_something_to_fetch_for = true; + } + // We check for the hint being loaded mostly for code simplicity. If we just + // check for the presence in the cache and the hint wasn't loaded, we need + // to run the load callback and then invoke the callbacks. However, as the + // fetch will just ignore the host if cached, this is ok since the callback + // from the fetch will initiate the loading of the hint and the resulting + // callback to be run. + if (!hint_cache_->HasHint(url.host()) || + (hint_cache_->GetHostKeyedHintIfLoaded(url.host()) == nullptr)) { + hosts_to_fetch.insert(url.host()); + has_something_to_fetch_for = true; + } + if (!has_something_to_fetch_for) { + callback.Run(url, decisions); + } + } + + if (urls_to_fetch.empty() && hosts_to_fetch.empty()) { + // Nothing to fetch for. + return; + } + + MaybeLogGetHintRequestInfo(request_context, registered_optimization_types_, + urls_to_fetch.vector(), hosts_to_fetch.vector()); + + // Fetch the data for the entries we don't have all information for. + if (!batch_update_hints_fetcher_) { + DCHECK(hints_fetcher_factory_); + batch_update_hints_fetcher_ = hints_fetcher_factory_->BuildInstance(); + } + batch_update_hints_fetcher_->FetchOptimizationGuideServiceHints( + hosts_to_fetch.vector(), urls_to_fetch.vector(), + registered_optimization_types_, request_context, application_locale_, + base::BindOnce(&HintsManager::OnOnDemandHintsFetched, + weak_ptr_factory_.GetWeakPtr(), hosts_to_fetch.set(), + urls_to_fetch.set(), optimization_types, callback)); +} + +void HintsManager::OnOnDemandHintsFetched( + const base::flat_set<std::string>& hosts_requested, + const base::flat_set<GURL>& urls_requested, + const base::flat_set<proto::OptimizationType>& optimization_types, + OnDemandOptimizationGuideDecisionRepeatingCallback callback, + absl::optional<std::unique_ptr<proto::GetHintsResponse>> + get_hints_response) { + if (!get_hints_response.has_value() || !get_hints_response.value()) { + OnReadyToInvokeOnDemandHintsCallbackForURLs(urls_requested, + optimization_types, callback); + return; + } + + // TODO(crbug/1278015: Figure out if the update time duration is the right + // one. + hint_cache_->UpdateFetchedHints( + std::move(*get_hints_response), + clock_->Now() + features::GetActiveTabsFetchRefreshDuration(), + hosts_requested, urls_requested, + base::BindOnce(&HintsManager::OnReadyToInvokeOnDemandHintsCallbackForURLs, + weak_ptr_factory_.GetWeakPtr(), urls_requested, + optimization_types, callback)); + + if (switches::IsDebugLogsEnabled()) + DVLOG(0) << "OptimizationGuide: OnOnDemandHintsFetched complete"; +} + +void HintsManager::OnReadyToInvokeOnDemandHintsCallbackForURLs( + const base::flat_set<GURL>& urls, + const base::flat_set<proto::OptimizationType>& optimization_types, + OnDemandOptimizationGuideDecisionRepeatingCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + for (const auto& url : urls) { + // Load the hint for host if we have a host-keyed hint before invoking the + // callbacks so we have all the necessary information to make the decision. + LoadHintForHost( + url.host(), + base::BindOnce(&HintsManager::InvokeOnDemandHintsCallbackForURL, + weak_ptr_factory_.GetWeakPtr(), url, optimization_types, + callback)); + } +} + +void HintsManager::InvokeOnDemandHintsCallbackForURL( + const GURL& url, + const base::flat_set<proto::OptimizationType>& optimization_types, + OnDemandOptimizationGuideDecisionRepeatingCallback callback) { + base::flat_map<proto::OptimizationType, OptimizationGuideDecisionWithMetadata> + decisions = GetDecisionsWithCachedInformationForURLAndOptimizationTypes( + url, optimization_types); + callback.Run(url, decisions); +} + void HintsManager::CanApplyOptimizationAsync( const GURL& navigation_url, proto::OptimizationType optimization_type, @@ -1243,6 +1392,8 @@ "OptimizationGuide.HintsManager.ConcurrentPageNavigationFetches", page_navigation_hints_fetchers_.size()); + MaybeLogGetHintRequestInfo(proto::CONTEXT_PAGE_NAVIGATION, + registered_optimization_types_, urls, hosts); bool fetch_attempted = it->second->FetchOptimizationGuideServiceHints( hosts, urls, registered_optimization_types_, proto::CONTEXT_PAGE_NAVIGATION, application_locale_, @@ -1257,24 +1408,6 @@ if (!hosts.empty() && !urls.empty()) { race_navigation_recorder.set_race_attempt_status( RaceNavigationFetchAttemptStatus::kRaceNavigationFetchHostAndURL); - DVLOG(0) << "OptimizationGuide: Fetch hints for Navigation: "; - DVLOG(0) << "OptimizationGuide: Registered Optimization Types: "; - for (const auto& optimization_type : registered_optimization_types_) { - DVLOG(0) << "OptimizationGuide: Optimization Type: " - << proto::OptimizationType_Name(optimization_type); - } - if (!hosts.empty()) { - DVLOG(0) << "OptimizationGuide: Fetching for hosts: "; - for (const auto& host : hosts) { - DVLOG(0) << "OptimizationGuide: Host: " << host; - } - } - if (!urls.empty()) { - DVLOG(0) << "OptimizationGuide: Fetching for URLs: "; - for (const auto& optimization_guide_url : urls) { - DVLOG(0) << "OptimizationGuide: URL: " << optimization_guide_url; - } - } } } else { race_navigation_recorder.set_race_attempt_status(
diff --git a/components/optimization_guide/core/hints_manager.h b/components/optimization_guide/core/hints_manager.h index b6b6c300..549652e 100644 --- a/components/optimization_guide/core/hints_manager.h +++ b/components/optimization_guide/core/hints_manager.h
@@ -25,7 +25,6 @@ #include "components/optimization_guide/core/optimization_hints_component_observer.h" #include "components/optimization_guide/core/push_notification_manager.h" #include "components/optimization_guide/proto/hints.pb.h" -#include "components/optimization_guide/proto/models.pb.h" #include "third_party/abseil-cpp/absl/types/optional.h" class OptimizationGuideNavigationData; @@ -43,7 +42,6 @@ class OptimizationFilter; class OptimizationMetadata; class OptimizationGuideStore; -enum class OptimizationTargetDecision; enum class OptimizationTypeDecision; class StoreUpdateData; class TabUrlProvider; @@ -118,6 +116,17 @@ proto::OptimizationType optimization_type, OptimizationGuideDecisionCallback callback); + // Invokes |callback| with the decision for all types contained in + // |optimization_types| for each URL contained in |urls|, when sufficient + // information has been collected to make decisions. If information is not + // available for all URLs, the remote Optimization Guide Service will be + // contacted to fetch information for those URLs using |request_context|. + void CanApplyOptimizationOnDemand( + const std::vector<GURL>& urls, + const base::flat_set<proto::OptimizationType>& optimization_types, + proto::RequestContext request_context, + OnDemandOptimizationGuideDecisionRepeatingCallback callback); + // Clears all fetched hints from |hint_cache_|. void ClearFetchedHints(); @@ -240,6 +249,38 @@ absl::optional<std::unique_ptr<proto::GetHintsResponse>> get_hints_response); + // Called when the on demand hints have been fetched from the remote + // Optimization Guide Service and are ready for parsing. This is used when + // fetching hints on demand. + void OnOnDemandHintsFetched( + const base::flat_set<std::string>& hosts_fetched, + const base::flat_set<GURL>& urls_fetched, + const base::flat_set<proto::OptimizationType>& optimization_types, + OnDemandOptimizationGuideDecisionRepeatingCallback callback, + absl::optional<std::unique_ptr<proto::GetHintsResponse>> + get_hints_response); + + // Called when information is ready such that we can invoke the on-demand + // hints callback. + void OnReadyToInvokeOnDemandHintsCallbackForURLs( + const base::flat_set<GURL>& urls_fetched, + const base::flat_set<proto::OptimizationType>& optimization_types, + OnDemandOptimizationGuideDecisionRepeatingCallback callback); + + // Returns decisions for |url| and |optimization_types| based on what's cached + // locally. + base::flat_map<proto::OptimizationType, OptimizationGuideDecisionWithMetadata> + GetDecisionsWithCachedInformationForURLAndOptimizationTypes( + const GURL& url, + const base::flat_set<proto::OptimizationType>& optimization_types); + + // Invokes |callback| for |url| and |optimization_types| based on what is + // cached on device. + void InvokeOnDemandHintsCallbackForURL( + const GURL& url, + const base::flat_set<proto::OptimizationType>& optimization_types, + OnDemandOptimizationGuideDecisionRepeatingCallback callback); + // Called when the hints for a navigation have been fetched from the remote // Optimization Guide Service and are ready for parsing. This is used when // fetching hints in real-time. |navigation_url| is the URL associated with
diff --git a/components/optimization_guide/core/hints_manager_unittest.cc b/components/optimization_guide/core/hints_manager_unittest.cc index d4f124c..c405660 100644 --- a/components/optimization_guide/core/hints_manager_unittest.cc +++ b/components/optimization_guide/core/hints_manager_unittest.cc
@@ -3139,6 +3139,122 @@ RaceNavigationFetchAttemptStatus::kRaceNavigationFetchHost, 1); } +TEST_F(HintsManagerFetchingTest, + CanApplyOptimizationOnDemandDecisionMetadataComesFromFetch) { + base::HistogramTester histogram_tester; + + hints_manager()->RegisterOptimizationTypes({proto::COMPRESS_PUBLIC_IMAGES}); + InitializeWithDefaultConfig("1.0.0.0"); + + // Set to online so fetch is activated. + SetConnectionOnline(); + + hints_manager()->SetHintsFetcherFactoryForTesting( + BuildTestHintsFetcherFactory( + {HintsFetcherEndState::kFetchSuccessWithURLHints})); + std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>(); + hints_manager()->CanApplyOptimizationOnDemand( + {url_with_url_keyed_hint()}, {proto::COMPRESS_PUBLIC_IMAGES}, + proto::RequestContext::CONTEXT_BOOKMARKS, + base::BindRepeating( + [](base::RunLoop* run_loop, const GURL& url, + const base::flat_map<proto::OptimizationType, + OptimizationGuideDecisionWithMetadata>& + decisions) { + ASSERT_EQ(decisions.size(), 1u); + auto it = decisions.find(proto::COMPRESS_PUBLIC_IMAGES); + ASSERT_TRUE(it != decisions.end()); + EXPECT_EQ(OptimizationGuideDecision::kTrue, it->second.decision); + EXPECT_TRUE( + it->second.metadata.public_image_metadata().has_value()); + + run_loop->Quit(); + }, + run_loop.get())); + run_loop->Run(); +} + +TEST_F( + HintsManagerFetchingTest, + CanApplyOptimizationOnDemandDecisionMultipleTypesBothHostAndURLKeyedMixedFetch) { + base::HistogramTester histogram_tester; + + hints_manager()->RegisterOptimizationTypes( + {proto::NOSCRIPT, proto::COMPRESS_PUBLIC_IMAGES}); + InitializeWithDefaultConfig("1.0.0.0"); + + // Set to online so fetch is activated. + SetConnectionOnline(); + + hints_manager()->SetHintsFetcherFactoryForTesting( + BuildTestHintsFetcherFactory( + {HintsFetcherEndState::kFetchSuccessWithURLHints})); + std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>(); + hints_manager()->CanApplyOptimizationOnDemand( + {url_with_url_keyed_hint()}, + {proto::NOSCRIPT, proto::COMPRESS_PUBLIC_IMAGES}, + proto::RequestContext::CONTEXT_BOOKMARKS, + base::BindRepeating( + [](base::RunLoop* run_loop, const GURL& url, + const base::flat_map<proto::OptimizationType, + OptimizationGuideDecisionWithMetadata>& + decisions) { + ASSERT_EQ(decisions.size(), 2u); + auto it = decisions.find(proto::COMPRESS_PUBLIC_IMAGES); + ASSERT_TRUE(it != decisions.end()); + EXPECT_EQ(OptimizationGuideDecision::kTrue, it->second.decision); + EXPECT_TRUE( + it->second.metadata.public_image_metadata().has_value()); + + it = decisions.find(proto::NOSCRIPT); + ASSERT_TRUE(it != decisions.end()); + EXPECT_EQ(OptimizationGuideDecision::kTrue, it->second.decision); + + run_loop->Quit(); + }, + run_loop.get())); + run_loop->Run(); +} + +TEST_F(HintsManagerFetchingTest, + CanApplyOptimizationOnDemandDecisionFailedFetchDoesNotStrandCallback) { + base::HistogramTester histogram_tester; + + hints_manager()->RegisterOptimizationTypes( + {proto::NOSCRIPT, proto::COMPRESS_PUBLIC_IMAGES}); + InitializeWithDefaultConfig("1.0.0.0"); + + // Set to online so fetch is activated. + SetConnectionOnline(); + + hints_manager()->SetHintsFetcherFactoryForTesting( + BuildTestHintsFetcherFactory({HintsFetcherEndState::kFetchFailed})); + std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>(); + hints_manager()->CanApplyOptimizationOnDemand( + {url_with_url_keyed_hint()}, + {proto::NOSCRIPT, proto::COMPRESS_PUBLIC_IMAGES}, + proto::RequestContext::CONTEXT_BOOKMARKS, + base::BindRepeating( + [](base::RunLoop* run_loop, const GURL& url, + const base::flat_map<proto::OptimizationType, + OptimizationGuideDecisionWithMetadata>& + decisions) { + ASSERT_EQ(decisions.size(), 2u); + auto it = decisions.find(proto::COMPRESS_PUBLIC_IMAGES); + ASSERT_TRUE(it != decisions.end()); + EXPECT_EQ(OptimizationGuideDecision::kFalse, it->second.decision); + + // Should still populate with what's on device. + it = decisions.find(proto::NOSCRIPT); + ASSERT_TRUE(it != decisions.end()); + EXPECT_EQ(OptimizationGuideDecision::kTrue, it->second.decision); + + run_loop->Quit(); + }, + run_loop.get())); + run_loop->Run(); +} + class HintsManagerFetchingNoBatchUpdateTest : public HintsManagerTest { public: HintsManagerFetchingNoBatchUpdateTest() {
diff --git a/components/optimization_guide/core/model_handler.h b/components/optimization_guide/core/model_handler.h index 878ca9ce..010734b 100644 --- a/components/optimization_guide/core/model_handler.h +++ b/components/optimization_guide/core/model_handler.h
@@ -7,6 +7,7 @@ #include "base/bind.h" #include "base/callback_forward.h" +#include "base/callback_list.h" #include "base/logging.h" #include "base/memory/raw_ptr.h" #include "base/metrics/histogram.h" @@ -123,6 +124,11 @@ &ModelExecutor<OutputType, InputTypes...>::UpdateModelFile, background_executor_->GetBackgroundWeakPtr(), model_info.GetModelFilePath())); + + // Run any observing callbacks after the model file is posted to the + // background thread so that any model execution requests are posted to the + // background thread after the model update. + on_model_updated_callbacks_.Notify(); } // Returns whether a model is available to be executed. Virtual for testing. @@ -131,6 +137,18 @@ return model_available_; } + // Runs |callback| now if |ModelAvailable()| or the next time |OnModelUpdated| + // is called. + void AddOnModelUpdatedCallback(base::OnceClosure callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (ModelAvailable()) { + std::move(callback).Run(); + return; + } + // callbacks are not bound locally are are safe to be destroyed at any time. + on_model_updated_callbacks_.AddUnsafe(std::move(callback)); + } + absl::optional<ModelInfo> GetModelInfo() const { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return model_info_; @@ -193,6 +211,11 @@ // Set in |OnModelUpdated|. absl::optional<ModelInfo> model_info_ GUARDED_BY_CONTEXT(sequence_checker_); + // Populated with callbacks if |AddOnModelUpdatedCallback| is called before a + // model file is available, then is notified when |OnModelUpdated| is called. + base::OnceClosureList on_model_updated_callbacks_ + GUARDED_BY_CONTEXT(sequence_checker_); + // Set in |OnModelUpdated|. bool model_available_ GUARDED_BY_CONTEXT(sequence_checker_) = false;
diff --git a/components/optimization_guide/core/model_handler_unittest.cc b/components/optimization_guide/core/model_handler_unittest.cc index 6def3dc..181cb44 100644 --- a/components/optimization_guide/core/model_handler_unittest.cc +++ b/components/optimization_guide/core/model_handler_unittest.cc
@@ -198,5 +198,60 @@ 1); } +TEST_F(ModelHandlerTest, AddOnModelUpdatedCallback_RunsImmediately) { + CreateModelHandler(); + + proto::Any any_metadata; + any_metadata.set_type_url("type.googleapis.com/com.foo.Duration"); + proto::Duration model_metadata; + model_metadata.set_seconds(123); + model_metadata.SerializeToString(any_metadata.mutable_value()); + PushModelFileToModelExecutor( + proto::OptimizationTarget::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD, + any_metadata); + EXPECT_TRUE(model_handler()->ModelAvailable()); + + bool callback_run = false; + model_handler()->AddOnModelUpdatedCallback( + base::BindOnce([](bool* flag) { *flag = true; }, &callback_run)); + + EXPECT_TRUE(callback_run); +} + +TEST_F(ModelHandlerTest, AddOnModelUpdatedCallback_RunsOnUpdate) { + CreateModelHandler(); + + bool callback_run = false; + model_handler()->AddOnModelUpdatedCallback( + base::BindOnce([](bool* flag) { *flag = true; }, &callback_run)); + EXPECT_FALSE(callback_run); + + proto::Any any_metadata; + any_metadata.set_type_url("type.googleapis.com/com.foo.Duration"); + proto::Duration model_metadata; + model_metadata.set_seconds(123); + model_metadata.SerializeToString(any_metadata.mutable_value()); + PushModelFileToModelExecutor( + proto::OptimizationTarget::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD, + any_metadata); + EXPECT_TRUE(model_handler()->ModelAvailable()); + + EXPECT_TRUE(callback_run); +} + +TEST_F(ModelHandlerTest, AddOnModelUpdatedCallback_NeverRun) { + CreateModelHandler(); + + bool callback_run = false; + model_handler()->AddOnModelUpdatedCallback( + base::BindOnce([](bool* flag) { *flag = true; }, &callback_run)); + EXPECT_FALSE(callback_run); + + // Resets model_handler + CreateModelHandler(); + + EXPECT_FALSE(callback_run); +} + } // namespace } // namespace optimization_guide
diff --git a/components/password_manager/core/browser/login_database.cc b/components/password_manager/core/browser/login_database.cc index d685c92..65e8a3d 100644 --- a/components/password_manager/core/browser/login_database.cc +++ b/components/password_manager/core/browser/login_database.cc
@@ -1319,43 +1319,18 @@ // PSL matching only applies to HTML forms. if (should_PSL_matching_apply) { - const GURL signon_realm(form.signon_realm); - std::string registered_domain = GetRegistryControlledDomain(signon_realm); - DCHECK(!registered_domain.empty()); - // We are extending the original SQL query with one that includes more - // possible matches based on public suffix domain matching. Using a regexp - // here is just an optimization to not have to parse all the stored entries - // in the |logins| table. The result (scheme, domain and port) is verified - // further down using GURL. See the functions SchemeMatches, - // RegistryControlledDomainMatches and PortMatches. - // We need to escape . in the domain. Since the domain has already been - // sanitized using GURL, we do not need to escape any other characters. - base::ReplaceChars(registered_domain, ".", "\\.", ®istered_domain); - std::string scheme = signon_realm.scheme(); - // We need to escape . in the scheme. Since the scheme has already been - // sanitized using GURL, we do not need to escape any other characters. - // The scheme soap.beep is an example with '.'. - base::ReplaceChars(scheme, ".", "\\.", &scheme); - const std::string port = signon_realm.port(); - // For a signon realm such as http://foo.bar/, this regexp will match - // domains on the form http://foo.bar/, http://www.foo.bar/, - // http://www.mobile.foo.bar/. It will not match http://notfoo.bar/. - // The scheme and port has to be the same as the observed form. - std::string regexp = "^(" + scheme + ":\\/\\/)([\\w-]+\\.)*" + - registered_domain + "(:" + port + ")?\\/$"; - s.BindString(placeholder++, regexp); + s.BindString(placeholder++, GetRegexForPSLMatching(form.signon_realm)); if (should_federated_apply) { // This regex matches any subdomain of |registered_domain|, in particular // it matches the empty subdomain. Hence exact domain matches are also // retrieved. s.BindString(placeholder++, - "^federation://([\\w-]+\\.)*" + registered_domain + "/.+$"); + GetRegexForPSLFederatedMatching(form.signon_realm)); } } else if (should_federated_apply) { - std::string expression = - base::StringPrintf("federation://%s/%%", form.url.host().c_str()); - s.BindString(placeholder++, expression); + s.BindString(placeholder++, + GetExpressionForFederatedMatching(form.url) + "%"); } PrimaryKeyToFormMap key_to_form_map;
diff --git a/components/password_manager/core/browser/psl_matching_helper.cc b/components/password_manager/core/browser/psl_matching_helper.cc index 41a1f451..105421d 100644 --- a/components/password_manager/core/browser/psl_matching_helper.cc +++ b/components/password_manager/core/browser/psl_matching_helper.cc
@@ -8,6 +8,7 @@ #include <ostream> #include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" #include "components/password_manager/core/browser/password_form.h" #include "net/base/registry_controlled_domains/registry_controlled_domain.h" #include "url/gurl.h" @@ -121,4 +122,40 @@ net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); } +std::string GetRegexForPSLMatching(const std::string& signon_realm) { + const GURL signon_realm_url(signon_realm); + std::string registered_domain = GetRegistryControlledDomain(signon_realm_url); + DCHECK(!registered_domain.empty()); + // We are extending the original SQL query with one that includes more + // possible matches based on public suffix domain matching. Using a regexp + // here is just an optimization to not have to parse all the stored entries + // in the |logins| table. The result (scheme, domain and port) is verified + // further down using GURL. See the functions SchemeMatches, + // RegistryControlledDomainMatches and PortMatches. + // We need to escape . in the domain. Since the domain has already been + // sanitized using GURL, we do not need to escape any other characters. + base::ReplaceChars(registered_domain, ".", "\\.", ®istered_domain); + std::string scheme = signon_realm_url.scheme(); + // We need to escape . in the scheme. Since the scheme has already been + // sanitized using GURL, we do not need to escape any other characters. + // The scheme soap.beep is an example with '.'. + base::ReplaceChars(scheme, ".", "\\.", &scheme); + const std::string port = signon_realm_url.port(); + // For a signon realm such as http://foo.bar/, this regexp will match + // domains on the form http://foo.bar/, http://www.foo.bar/, + // http://www.mobile.foo.bar/. It will not match http://notfoo.bar/. + // The scheme and port has to be the same as the observed form. + return "^(" + scheme + ":\\/\\/)([\\w-]+\\.)*" + registered_domain + + "(:" + port + ")?\\/$"; +} + +std::string GetRegexForPSLFederatedMatching(const std::string& signon_realm) { + return "^federation://([\\w-]+\\.)*" + + GetRegistryControlledDomain(GURL(signon_realm)) + "/.+$"; +} + +std::string GetExpressionForFederatedMatching(const GURL& url) { + return base::StringPrintf("federation://%s/", url.host().c_str()); +} + } // namespace password_manager
diff --git a/components/password_manager/core/browser/psl_matching_helper.h b/components/password_manager/core/browser/psl_matching_helper.h index 188d9a0..3058702 100644 --- a/components/password_manager/core/browser/psl_matching_helper.h +++ b/components/password_manager/core/browser/psl_matching_helper.h
@@ -53,6 +53,24 @@ // registry-controlled domain part. std::string GetRegistryControlledDomain(const GURL& signon_realm); +// Returns the regular expression to match |signon_realm| when Public Suffix +// Domain matching is enabled. Used to retrieve logins from LoginsDatabase with +// 'WHERE signon_realm REGEX x' query and to verify logins retrieved from the +// downstream PasswordStoreBackend implementation with C++ Regex matcher. +std::string GetRegexForPSLMatching(const std::string& signon_realm); + +// Returns the regular expression to match |form| when Public Suffix Domain & +// federated matching is enabled. Used to retrieve logins from LoginsDatabase +// with 'WHERE signon_realm REGEX x' query and to verify logins retrieved from +// the downstream PasswordStoreBackend implementation with C++ Regex matcher. +std::string GetRegexForPSLFederatedMatching(const std::string& signon_realm); + +// Returns the expression to match |url| when federated matching is enabled. +// Used to retrieve logins from LoginsDatabase with 'WHERE signon_realm LIKE x' +// query and to verify logins retrieved from the downstream PasswordStoreBackend +// implementation with C++ Regex matcher. +std::string GetExpressionForFederatedMatching(const GURL& url); + } // namespace password_manager #endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_PSL_MATCHING_HELPER_H_
diff --git a/components/payments/content/BUILD.gn b/components/payments/content/BUILD.gn index a8fa36b..6483e7c 100644 --- a/components/payments/content/BUILD.gn +++ b/components/payments/content/BUILD.gn
@@ -32,8 +32,6 @@ "payment_credential.h", "payment_credential_factory.cc", "payment_credential_factory.h", - "payment_credential_manager.cc", - "payment_credential_manager.h", "payment_details_converter.cc", "payment_details_converter.h", "payment_event_response_util.cc",
diff --git a/components/payments/content/payment_credential.cc b/components/payments/content/payment_credential.cc index c3d4afe..9d127236 100644 --- a/components/payments/content/payment_credential.cc +++ b/components/payments/content/payment_credential.cc
@@ -28,16 +28,11 @@ } PaymentCredential::PaymentCredential( - content::WebContents* web_contents, - content::GlobalRenderFrameHostId initiator_frame_routing_id, - scoped_refptr<PaymentManifestWebDataService> web_data_service, - mojo::PendingReceiver<mojom::PaymentCredential> receiver) - : WebContentsObserver(web_contents), - initiator_frame_routing_id_(initiator_frame_routing_id), - web_data_service_(web_data_service) { - DCHECK(web_contents); - receiver_.Bind(std::move(receiver)); -} + content::RenderFrameHost& render_frame_host, + mojo::PendingReceiver<mojom::PaymentCredential> receiver, + scoped_refptr<PaymentManifestWebDataService> web_data_service) + : DocumentService(&render_frame_host, std::move(receiver)), + web_data_service_(web_data_service) {} PaymentCredential::~PaymentCredential() { Reset(); @@ -85,39 +80,9 @@ : mojom::PaymentCredentialStorageStatus::FAILED_TO_STORE_CREDENTIAL); } -void PaymentCredential::DidStartNavigation( - content::NavigationHandle* navigation_handle) { - // Reset the service before the page navigates away. - // TODO(1251691): Using DidStartNavigation to infer the document's lifetime - // like this is incorrect. Consider making this class a DocumentService - // instead. - if (!navigation_handle->IsSameDocument() && - (navigation_handle->IsInPrimaryMainFrame() || - navigation_handle->GetPreviousRenderFrameHostId() == - initiator_frame_routing_id_)) { - Reset(); - } -} - -void PaymentCredential::RenderFrameDeleted( - content::RenderFrameHost* render_frame_host) { - // Reset the service before the render frame is deleted. - if (render_frame_host == web_contents()->GetMainFrame() || - render_frame_host == - content::RenderFrameHost::FromID(initiator_frame_routing_id_)) { - Reset(); - } -} - bool PaymentCredential::IsCurrentStateValid() const { - content::RenderFrameHost* render_frame_host = - content::RenderFrameHost::FromID(initiator_frame_routing_id_); - - if (!IsFrameAllowedToUseSecurePaymentConfirmation(render_frame_host) || - !web_contents() || - web_contents() != - content::WebContents::FromRenderFrameHost(render_frame_host) || - !web_data_service_ || !receiver_.is_bound()) { + if (!IsFrameAllowedToUseSecurePaymentConfirmation(render_frame_host()) || + !web_data_service_) { return false; } @@ -141,12 +106,9 @@ void PaymentCredential::Reset() { // Callbacks must either be run or disconnected before being destroyed, so // run them if they are still connected. - if (receiver_.is_bound()) { - if (storage_callback_) { - std::move(storage_callback_) - .Run(mojom::PaymentCredentialStorageStatus:: - FAILED_TO_STORE_CREDENTIAL); - } + if (storage_callback_) { + std::move(storage_callback_) + .Run(mojom::PaymentCredentialStorageStatus::FAILED_TO_STORE_CREDENTIAL); } if (web_data_service_ && data_service_request_handle_) {
diff --git a/components/payments/content/payment_credential.h b/components/payments/content/payment_credential.h index a79b2e83..def9be0 100644 --- a/components/payments/content/payment_credential.h +++ b/components/payments/content/payment_credential.h
@@ -15,16 +15,12 @@ #include "components/payments/core/secure_payment_confirmation_metrics.h" #include "components/webdata/common/web_data_service_base.h" #include "components/webdata/common/web_data_service_consumer.h" +#include "content/public/browser/document_service.h" #include "content/public/browser/global_routing_id.h" -#include "content/public/browser/web_contents_observer.h" #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/receiver.h" #include "third_party/blink/public/mojom/payments/payment_credential.mojom.h" -namespace content { -class WebContents; -} // namespace content - namespace payments { class PaymentManifestWebDataService; @@ -33,18 +29,17 @@ // PaymentCredential instruments and their associated WebAuthn credential IDs. // These can be retrieved later to authenticate during a PaymentRequest // that uses Secure Payment Confirmation. -class PaymentCredential : public mojom::PaymentCredential, - public WebDataServiceConsumer, - public content::WebContentsObserver { +class PaymentCredential + : public content::DocumentService<mojom::PaymentCredential>, + public WebDataServiceConsumer { public: static bool IsFrameAllowedToUseSecurePaymentConfirmation( content::RenderFrameHost* rfh); PaymentCredential( - content::WebContents* web_contents, - content::GlobalRenderFrameHostId initiator_frame_routing_id, - scoped_refptr<PaymentManifestWebDataService> web_data_service, - mojo::PendingReceiver<mojom::PaymentCredential> receiver); + content::RenderFrameHost& render_frame_host, + mojo::PendingReceiver<mojom::PaymentCredential> receiver, + scoped_refptr<PaymentManifestWebDataService> web_data_service); ~PaymentCredential() override; PaymentCredential(const PaymentCredential&) = delete; @@ -80,22 +75,15 @@ WebDataServiceBase::Handle h, std::unique_ptr<WDTypedResult> result) override; - // content::WebContentsObserver: - void DidStartNavigation( - content::NavigationHandle* navigation_handle) override; - void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override; - bool IsCurrentStateValid() const; void RecordFirstSystemPromptResult( SecurePaymentConfirmationEnrollSystemPromptResult result); void Reset(); State state_ = State::kIdle; - const content::GlobalRenderFrameHostId initiator_frame_routing_id_; scoped_refptr<PaymentManifestWebDataService> web_data_service_; absl::optional<WebDataServiceBase::Handle> data_service_request_handle_; StorePaymentCredentialCallback storage_callback_; - mojo::Receiver<mojom::PaymentCredential> receiver_{this}; bool is_system_prompt_result_recorded_ = false; base::WeakPtrFactory<PaymentCredential> weak_ptr_factory_{this};
diff --git a/components/payments/content/payment_credential_factory.cc b/components/payments/content/payment_credential_factory.cc index a63dbcb..a0b20df 100644 --- a/components/payments/content/payment_credential_factory.cc +++ b/components/payments/content/payment_credential_factory.cc
@@ -6,7 +6,6 @@ #include "components/keyed_service/core/service_access_type.h" #include "components/payments/content/payment_credential.h" -#include "components/payments/content/payment_credential_manager.h" #include "components/payments/content/payment_manifest_web_data_service.h" #include "components/webdata_services/web_data_service_wrapper_factory.h" #include "content/public/browser/render_frame_host.h" @@ -24,19 +23,20 @@ content::WebContents* web_contents = content::WebContents::FromRenderFrameHost(render_frame_host); - if (!web_contents) - return; - content::GlobalRenderFrameHostId initiator_frame_routing_id = - render_frame_host->GetGlobalId(); - PaymentCredentialManager::GetOrCreateForWebContents(web_contents) - ->CreatePaymentCredential( - initiator_frame_routing_id, - webdata_services::WebDataServiceWrapperFactory:: - GetPaymentManifestWebDataServiceForBrowserContext( - web_contents->GetBrowserContext(), - ServiceAccessType::EXPLICIT_ACCESS), - std::move(receiver)); + // If the frame is allowed to use secure payment confirmation, the render + // frame host should be non-null and valid. Consequently, the web contents + // should also be non-null. + CHECK(web_contents); + CHECK(render_frame_host); + + // The object is bound to the lifetime of |render_frame_host| and the mojo + // connection. See DocumentService for details. + new PaymentCredential(*render_frame_host, std::move(receiver), + webdata_services::WebDataServiceWrapperFactory:: + GetPaymentManifestWebDataServiceForBrowserContext( + web_contents->GetBrowserContext(), + ServiceAccessType::EXPLICIT_ACCESS)); } } // namespace payments
diff --git a/components/payments/content/payment_credential_manager.cc b/components/payments/content/payment_credential_manager.cc deleted file mode 100644 index 6f5f68b..0000000 --- a/components/payments/content/payment_credential_manager.cc +++ /dev/null
@@ -1,47 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "components/payments/content/payment_credential_manager.h" - -#include <utility> - -#include "base/check.h" -#include "components/payments/content/payment_manifest_web_data_service.h" -#include "content/public/browser/navigation_handle.h" -#include "content/public/browser/web_contents.h" - -namespace payments { - -PaymentCredentialManager::~PaymentCredentialManager() = default; - -PaymentCredentialManager* PaymentCredentialManager::GetOrCreateForWebContents( - content::WebContents* web_contents) { - DCHECK(web_contents); - // CreateForWebContents does nothing if the manager instance already exists. - PaymentCredentialManager::CreateForWebContents(web_contents); - return PaymentCredentialManager::FromWebContents(web_contents); -} - -void PaymentCredentialManager::DidStartNavigation( - content::NavigationHandle* navigation_handle) { - payment_credential_ = nullptr; -} - -void PaymentCredentialManager::CreatePaymentCredential( - content::GlobalRenderFrameHostId initiator_frame_routing_id, - scoped_refptr<PaymentManifestWebDataService> web_data_sevice, - mojo::PendingReceiver<payments::mojom::PaymentCredential> receiver) { - payment_credential_ = std::make_unique<PaymentCredential>( - web_contents(), initiator_frame_routing_id, web_data_sevice, - std::move(receiver)); -} - -PaymentCredentialManager::PaymentCredentialManager( - content::WebContents* web_contents) - : content::WebContentsObserver(web_contents), - content::WebContentsUserData<PaymentCredentialManager>(*web_contents) {} - -WEB_CONTENTS_USER_DATA_KEY_IMPL(PaymentCredentialManager); - -} // namespace payments
diff --git a/components/payments/content/payment_credential_manager.h b/components/payments/content/payment_credential_manager.h deleted file mode 100644 index f2b949a..0000000 --- a/components/payments/content/payment_credential_manager.h +++ /dev/null
@@ -1,60 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef COMPONENTS_PAYMENTS_CONTENT_PAYMENT_CREDENTIAL_MANAGER_H_ -#define COMPONENTS_PAYMENTS_CONTENT_PAYMENT_CREDENTIAL_MANAGER_H_ - -#include <memory> - -#include "base/memory/scoped_refptr.h" -#include "components/payments/content/payment_credential.h" -#include "content/public/browser/global_routing_id.h" -#include "content/public/browser/web_contents_observer.h" -#include "content/public/browser/web_contents_user_data.h" -#include "mojo/public/cpp/bindings/pending_receiver.h" -#include "third_party/blink/public/mojom/payments/payment_credential.mojom.h" - -namespace content { -class NavigationHandle; -class WebContents; -} // namespace content - -namespace payments { - -class PaymentManifestWebDataService; - -class PaymentCredentialManager - : public content::WebContentsObserver, - public content::WebContentsUserData<PaymentCredentialManager> { - public: - ~PaymentCredentialManager() override; - PaymentCredentialManager(const PaymentCredentialManager&) = delete; - PaymentCredentialManager& operator=(const PaymentCredentialManager&) = delete; - - static PaymentCredentialManager* GetOrCreateForWebContents( - content::WebContents* web_contents); - - // Creates the mojo IPC endpoint that will receive requests from the renderer - // to store payment credential in user's profile. - void CreatePaymentCredential( - content::GlobalRenderFrameHostId initiator_frame_routing_id, - scoped_refptr<PaymentManifestWebDataService> web_data_sevice, - mojo::PendingReceiver<payments::mojom::PaymentCredential> receiver); - - // WebContentsObserver: - void DidStartNavigation( - content::NavigationHandle* navigation_handle) override; - - private: - explicit PaymentCredentialManager(content::WebContents* web_contents); - friend class content::WebContentsUserData<PaymentCredentialManager>; - - std::unique_ptr<PaymentCredential> payment_credential_; - - WEB_CONTENTS_USER_DATA_KEY_DECL(); -}; - -} // namespace payments - -#endif // COMPONENTS_PAYMENTS_CONTENT_PAYMENT_CREDENTIAL_MANAGER_H_
diff --git a/components/policy/resources/policy_templates_vi.xtb b/components/policy/resources/policy_templates_vi.xtb index 230e6af..d3344be 100644 --- a/components/policy/resources/policy_templates_vi.xtb +++ b/components/policy/resources/policy_templates_vi.xtb
@@ -1489,6 +1489,10 @@ <translation id="2716623398185506073">Nếu bạn đặt chính sách này thành Bật, thiết bị đã đăng ký sẽ báo cáo số liệu thống kê phần cứng và giá trị nhận dạng liên quan đến nguồn. Nếu bạn không đặt hoặc đặt chính sách này thành Tắt, thiết bị đã đăng ký sẽ không báo cáo số liệu thống kê nguồn.</translation> +<translation id="2717628606602248727">Chính sách này được bật theo mặc định. Chính sách này kiểm soát các thiết bị đã đăng ký để báo cáo thông tin bộ nhớ. + + Nếu bạn tắt chính sách này thì các thiết bị đã đăng ký sẽ không báo cáo bất kỳ thông tin bộ nhớ nào. + Trường hợp ngoại lệ: Thông tin bộ nhớ còn trống do <ph name="REPORT_DEVICE_HARDWARE_STATUS" /> kiểm soát trong M95 trở xuống.</translation> <translation id="2721185634942265347">Tắt tính năng đề xuất tìm kiếm</translation> <translation id="2721582713721006926">Tắt tính năng tra định nghĩa của Thông tin nhanh</translation> <translation id="2723692978495226412">Việc đặt chính sách này thành Bật sẽ tạo ra các đề xuất cho những ứng dụng mà trước đó người dùng đã cài đặt trên các thiết bị khác. Các đề xuất này sẽ xuất hiện trong trình chạy sau các đề xuất ứng dụng trên thiết bị nếu người dùng chưa nhập nội dung tìm kiếm nào. @@ -3991,6 +3995,9 @@ Nếu bạn đặt chính sách này thành false hoặc không đặt, thì thông tin sẽ không được báo cáo. Nếu bạn đặt chính sách này thành true, thì thông tin về VPD của thiết bị sẽ được báo cáo. Dữ liệu sản phẩm quan trọng (VPD) là một tập hợp dữ liệu cấu hình và thông tin (chẳng hạn như bộ phận và số sê-ri) liên quan đến thiết bị đó.</translation> +<translation id="576158229686912964">Chính sách này không còn dùng trong M96 nữa. Thay vào đó, vui lòng dùng <ph name="REPORT_DEVICE_NETWORK_CONFIGURATION" /> và <ph name="REPORT_DEVICE_NETWORK_STATUS" />. + + Nếu bạn bật hoặc không đặt chính sách này, thì những thiết bị đã đăng ký sẽ báo cáo danh sách các giao diện mạng kèm thông tin về loại giao diện và địa chỉ phần cứng. Nếu bạn tắt chính sách này thì các thiết bị đã đăng ký sẽ không báo cáo giao diện mạng.</translation> <translation id="5762969307102447459">Tắt tính năng Tự động điền đối với địa chỉ</translation> <translation id="5765780083710877561">Mô tả:</translation> <translation id="5766438888216077649">Không đặt <ph name="WINDOW_OPENER_PROPERTY" /> cho các đường liên kết nhắm đến <ph name="BLANK_PAGE_NAME" /></translation> @@ -5392,6 +5399,10 @@ Nếu bạn không đặt chính sách này, thì giá trị mặc định chung của chính sách "DefaultKeygenSetting" (nếu bạn đã đặt chính sách này) hoặc trong cấu hình cá nhân của người dùng sẽ được sử dụng cho tất cả các trang web. Để biết thông tin chi tiết về các mẫu URL hợp lệ, vui lòng truy cập vào https://cloud.google.com/docs/chrome-enterprise/policies/url-patterns. Chúng tôi không chấp nhận giá trị <ph name="WILDCARD_VALUE" /> đối với chính sách này.</translation> +<translation id="7394023172636522064">Nếu bạn bật chính sách này thì hệ thống sẽ báo cáo trạng thái bảo mật TPM của thiết bị. + + Nếu bạn tắt hoặc không đặt chính sách này, thì các thiết bị đã đăng ký sẽ không ghi lại hoặc báo cáo trạng thái bảo mật TPM. + Trường hợp ngoại lệ: Thông tin TPM do <ph name="REPORT_DEVICE_HARDWARE_STATUS" /> kiểm soát trong M95 trở xuống.</translation> <translation id="739556497251174388">Cho phép sử dụng lại thông tin đăng nhập <ph name="PRODUCT_OS_NAME" /> để xác thực mạng</translation> <translation id="7400971609879083218">Nếu bạn đặt chính sách này thành Bật, hình ảnh của bên thứ ba trên một trang sẽ được phép hiển thị lời nhắc xác thực. @@ -5469,6 +5480,9 @@ Nếu bạn không đặt chính sách này, thì chế độ cài đặt mặc định sẽ là <ph name="LACROS_AVAILABILITY_LACROS_DISALLOWED_VALUE" /> đối với người dùng do doanh nghiệp quản lý và là <ph name="LACROS_AVAILABILITY_USER_CHOICE_VALUE" /> đối với người dùng không được quản lý. Trong tương lai, bạn có thể dùng giá trị <ph name="LACROS_AVAILABILITY_LACROS_ONLY_VALUE" /> để đặt <ph name="LACROS_NAME" /> làm trình duyệt duy nhất sử dụng được trên <ph name="PRODUCT_OS_NAME" />.</translation> +<translation id="7509761893401042250">Chính sách này không còn dùng nữa kể từ M96. Thay vào đó, vui lòng dùng <ph name="REPORT_DEVICE_CPU_INFO" />, <ph name="REPORT_DEVICE_MEMORY_INFO" />, <ph name="REPORT_DEVICE_STORAGE_STATUS" />, <ph name="REPORT_DEVICE_SECURITY_STATUS" /> và <ph name="REPORT_DEVICE_AUDIO_STATUS" />. + + Nếu bạn bật hoặc không đặt chính sách này, thì các thiết bị đã đăng ký sẽ báo cáo số liệu thống kê phần cứng, chẳng hạn như mức sử dụng CPU/RAM. Nếu bạn tắt chính sách này thì thiết bị đã đăng ký sẽ không báo cáo số liệu thống kê phần cứng.</translation> <translation id="7519218194072744342">URL của trình xử lý giao thức.</translation> <translation id="7519251620064708155">Cho phép tạo khóa trên các trang web này</translation> <translation id="7529144158022474049">Yếu tố phân tán tự động cập nhật</translation> @@ -5476,6 +5490,10 @@ Khi bạn đặt thành Bật hoặc không đặt chính sách này, một cảnh báo sẽ hiển thị để nhắc người dùng thoát khỏi chế độ toàn màn hình trước khi nhập mật khẩu. Khi bạn đặt chính sách này thành Tắt, không có cảnh báo nào sẽ hiển thị.</translation> <translation id="7534199150025803530">Chính sách này không ảnh hưởng đến ứng dụng Google Drive trên Android. Nếu muốn ngăn việc sử dụng Google Drive trên kết nối di động thì bạn phải không cho phép cài đặt ứng dụng Google Drive trên Android.</translation> +<translation id="7538583957913002726">Chính sách này được bật theo mặc định. Chính sách này kiểm soát các thiết bị đã đăng ký để báo cáo tên kiểu máy, cấu trúc và tốc độ xung nhịp tối đa của CPU (cũng như nhiệt độ và quá trình sử dụng CPU trong M96 trở lên). + + Nếu bạn tắt chính sách này thì các thiết bị đã đăng ký sẽ không báo cáo bất kỳ thông tin CPU nào. + Trường hợp ngoại lệ: Báo cáo nhiệt độ và quá trình sử dụng CPU do <ph name="REPORT_DEVICE_HARDWARE_STATUS" /> kiểm soát trong M95 trở xuống.</translation> <translation id="7540622499178214923">Cho phép bộ điều khiển đo từ xa và chẩn đoán wilco</translation> <translation id="7540826630642174841">Nếu đặt chính sách này, các chính sách thiết bị được chỉ định sẽ bị bỏ qua (sử dụng tùy chọn cài đặt mặc định của các chính sách này) trong khoảng thời gian đã chỉ định. Các chính sách thiết bị được <ph name="PRODUCT_NAME" /> áp dụng lại khi khoảng thời gian chính sách bắt đầu hoặc kết thúc. Người dùng sẽ nhận được thông báo và bị buộc đăng xuất khi khoảng thời gian này thay đổi và các tùy chọn chính sách thiết bị thay đổi (ví dụ: khi người dùng đăng nhập bằng tài khoản không được cho phép).</translation> <translation id="7540945123920084379">Việc đặt chính sách này sẽ kiểm soát chế độ mở khóa nhanh nào có thể mở khóa màn hình khóa. @@ -5647,6 +5665,10 @@ Lưu ý: Để xem các ví dụ cụ thể, hãy truy cập vào trang The Chromium Projects (https://www.chromium.org/developers/design-documents/network-settings#TOC-Command-line-options-for-proxy-sett).</translation> <translation id="7717938661004793600">Định cấu hình các tính năng hỗ trợ tiếp cận của <ph name="PRODUCT_OS_NAME" />.</translation> +<translation id="7718127720248642697">Chính sách này được bật theo mặc định. Chính sách này kiểm soát các thiết bị đã đăng ký để báo cáo số liệu thống kê phần cứng và giá trị nhận dạng cho các thiết bị lưu trữ. + + Nếu bạn tắt chính sách này thì các thiết bị đã đăng ký sẽ không báo cáo số liệu thống kê dung lượng lưu trữ. + Trường hợp ngoại lệ: Số liệu thống kê kích thước ổ đĩa và dung lượng ổ đĩa trống do <ph name="REPORT_DEVICE_HARDWARE_STATUS" /> kiểm soát trong M95 trở xuống.</translation> <translation id="7721944091689270995">Mã người dùng <ph name="PLUGIN_VM_NAME" /></translation> <translation id="7724161903134898864">Nếu bạn đặt chính sách này thành 1, thì các trang web có thể hiển thị cửa sổ bật lên. Nếu bạn đặt chính sách này thành 2, thì các trang web không thể hiển thị cửa sổ bật lên. @@ -6799,6 +6821,10 @@ Để biết thông tin chi tiết về mẫu URL hợp lệ, vui lòng truy cập vào https://cloud.google.com/docs/chrome-enterprise/policies/url-patterns.</translation> <translation id="9068629430243705879">cổng 6566 (có thể được bỏ chặn cho đến ngày 15/10/2021)</translation> +<translation id="9069588907259547232">Nếu bạn bật hoặc không đặt chính sách này, thì các thiết bị đã đăng ký sẽ báo cáo âm lượng của thiết bị. + + Nếu bạn tắt chính sách này thì các thiết bị đã đăng ký sẽ không ghi lại hoặc báo cáo trạng thái âm thanh. + Trường hợp ngoại lệ: Thông tin mức âm lượng của hệ thống do <ph name="REPORT_DEVICE_HARDWARE_STATUS" /> kiểm soát trong M95 trở xuống.</translation> <translation id="9073405975862312795">Nếu bạn không đặt hoặc đặt chính sách này thành Bật, thì thiết bị đã đăng ký sẽ báo cáo khoảng thời gian khi người dùng sử dụng thiết bị. Nếu bạn đặt chính sách này thành Tắt, thiết bị đã đăng ký sẽ không ghi hoặc báo cáo thời gian hoạt động.</translation>
diff --git a/components/reporting/proto/synced/metric_data.proto b/components/reporting/proto/synced/metric_data.proto index d615546..64b9c98 100644 --- a/components/reporting/proto/synced/metric_data.proto +++ b/components/reporting/proto/synced/metric_data.proto
@@ -97,6 +97,37 @@ optional HttpsLatencyRoutineData https_latency_data = 2; } +// Security level of the thunderbolt bus. +enum ThunderboltSecurityLevel { + UNSPECIFIED_THUNDERBOLT_SECURITY_LEVEL = 0; + // All devices are automatically connected by the firmware. No user approval + // is needed. + THUNDERBOLT_SECURITY_NONE_LEVEL = 1; + // User is asked whether the device is allowed to be connected. + THUNDERBOLT_SECURITY_USER_LEVEL = 2; + // User is asked whether the device is allowed to be connected. In addition + // the device is sent a challenge that should match the expected one based on + // a random key written to the key sysfs attribute + THUNDERBOLT_SECURITY_SECURE_LEVEL = 3; + // The firmware automatically creates tunnels for thunderbolt. + THUNDERBOLT_SECURITY_DP_ONLY_LEVEL = 4; + // The firmware automatically creates tunnels for the USB controller and + // Display Port in a dock. All PCIe links downstream of the dock are removed. + THUNDERBOLT_SECURITY_USB_ONLY_LEVEL = 5; + // PCIE tunneling is disabled. + THUNDERBOLT_SECURITY_NO_PCIE_LEVEL = 6; +}; + +message ThunderboltInfo { + optional ThunderboltSecurityLevel security_level = 1; +} + +// currently we are only reporting bus device info for thunderbolt, though we +// will certainly add more in the future. +message BusDeviceInfo { + optional ThunderboltInfo thunderbolt_info = 1; +} + // Information about keylocker. This is supported on Intel CPUs. message KeylockerInfo { // If keylocker is supported on the devices CPUs. @@ -115,6 +146,8 @@ message InfoData { // CPU info for the device. optional CpuInfo cpu_info = 1; + // Bus Device info for the device. + optional BusDeviceInfo bus_device_info = 2; } // Audio telemetry data recorded intermittently @@ -174,4 +207,4 @@ // Event data collected. optional EventData event_data = 4; -} +} \ No newline at end of file
diff --git a/components/safe_browsing/content/browser/download/download_stats.cc b/components/safe_browsing/content/browser/download/download_stats.cc index cc9fd0a..9fec9e41 100644 --- a/components/safe_browsing/content/browser/download/download_stats.cc +++ b/components/safe_browsing/content/browser/download/download_stats.cc
@@ -6,6 +6,8 @@ #include "base/files/file_path.h" #include "base/metrics/histogram_functions.h" +#include "base/metrics/user_metrics.h" +#include "base/metrics/user_metrics_action.h" #include "base/time/time.h" #include "components/download/public/common/download_danger_type.h" #include "components/safe_browsing/content/common/file_type_policies.h" @@ -89,6 +91,8 @@ RecordDangerousWarningIsHttps(danger_type, is_https, kShownMetricSuffix); RecordDangerousWarningHasUserGesture(danger_type, has_user_gesture, kShownMetricSuffix); + base::RecordAction( + base::UserMetricsAction("SafeBrowsing.Download.WarningShown")); } void RecordDangerousDownloadWarningBypassed( @@ -100,6 +104,8 @@ RecordDangerousWarningIsHttps(danger_type, is_https, kBypassedMetricSuffix); RecordDangerousWarningHasUserGesture(danger_type, has_user_gesture, kBypassedMetricSuffix); + base::RecordAction( + base::UserMetricsAction("SafeBrowsing.Download.WarningBypassed")); } void RecordDownloadOpened(download::DownloadDangerType danger_type,
diff --git a/components/safe_browsing/content/browser/download/download_stats_unittest.cc b/components/safe_browsing/content/browser/download/download_stats_unittest.cc index 73cedbb6..c76d8eb 100644 --- a/components/safe_browsing/content/browser/download/download_stats_unittest.cc +++ b/components/safe_browsing/content/browser/download/download_stats_unittest.cc
@@ -6,6 +6,7 @@ #include "base/files/file_path.h" #include "base/test/metrics/histogram_tester.h" +#include "base/test/metrics/user_action_tester.h" #include "components/download/public/common/download_stats.h" #include "components/safe_browsing/content/common/file_type_policies.h" #include "testing/gtest/include/gtest/gtest.h" @@ -23,6 +24,7 @@ TEST(SafeBrowsingDownloadStatsTest, RecordDangerousDownloadWarningShown) { base::HistogramTester histogram_tester; + base::UserActionTester user_action_tester; RecordDangerousDownloadWarningShown( download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT, @@ -37,6 +39,8 @@ histogram_tester.ExpectUniqueSample( "SBClientDownload.Warning.DownloadHasUserGesture.Malicious.Shown", /*sample=*/1, /*expected_bucket_count=*/1); + EXPECT_EQ(1, user_action_tester.GetActionCount( + "SafeBrowsing.Download.WarningShown")); RecordDangerousDownloadWarningShown( download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT, @@ -54,10 +58,13 @@ "SBClientDownload.Warning.DownloadHasUserGesture.Uncommon.Shown", /*sample=*/0, /*expected_count=*/1); + EXPECT_EQ(2, user_action_tester.GetActionCount( + "SafeBrowsing.Download.WarningShown")); } TEST(SafeBrowsingDownloadStatsTest, RecordDangerousDownloadWarningBypassed) { base::HistogramTester histogram_tester; + base::UserActionTester user_action_tester; RecordDangerousDownloadWarningBypassed( download::DownloadDangerType::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE, @@ -73,6 +80,8 @@ "SBClientDownload.Warning.DownloadHasUserGesture.DangerousFileType." "Bypassed", /*sample=*/0, /*expected_bucket_count=*/1); + EXPECT_EQ(1, user_action_tester.GetActionCount( + "SafeBrowsing.Download.WarningBypassed")); } TEST(SafeBrowsingDownloadStatsTest, RecordDownloadOpened) {
diff --git a/components/strings/components_strings_vi.xtb b/components/strings/components_strings_vi.xtb index 3c12db5b..e72eb6ad 100644 --- a/components/strings/components_strings_vi.xtb +++ b/components/strings/components_strings_vi.xtb
@@ -650,6 +650,7 @@ <translation id="3087734570205094154">Bên dưới</translation> <translation id="3095940652251934233">Tuyên bố</translation> <translation id="3096100844101284527">Thêm địa chỉ nhận hàng</translation> +<translation id="3098513225387949945">Chính sách bị bỏ qua vì danh sách vô hiệu có chứa một mẫu bằng '*', tương đương với việc vô hiệu hoá chính sách.</translation> <translation id="3105172416063519923">ID phần tử:</translation> <translation id="3107591622054137333"><ph name="BEGIN_LINK" />Kiểm tra cấu hình DNS bảo mật<ph name="END_LINK" /></translation> <translation id="3108943290502734357">Khay giữa</translation> @@ -1564,6 +1565,7 @@ <translation id="6008122969617370890">Thứ tự từ N đến 1</translation> <translation id="6008256403891681546">JCB</translation> <translation id="6014801569448771146">Kiểm tra mật khẩu của bạn</translation> +<translation id="6014851866995737824">Chính sách bị bỏ qua vì thiếu danh sách "bật" hoặc "tắt".</translation> <translation id="6015796118275082299">Năm</translation> <translation id="6017514345406065928">Xanh lục</translation> <translation id="6017850046339264347">Những kẻ tấn công trên <ph name="BEGIN_BOLD" /><ph name="SITE" /><ph name="END_BOLD" /> có thể cài đặt ứng dụng lừa đảo giả vờ là nội dung khác hoặc thu thập dữ liệu có thể dùng để theo dõi bạn. <ph name="BEGIN_LEARN_MORE_LINK" />Tìm hiểu thêm<ph name="END_LEARN_MORE_LINK" /></translation>
diff --git a/components/test/data/autofill/heuristics/input/180_namesurname.html b/components/test/data/autofill/heuristics/input/180_namesurname.html new file mode 100644 index 0000000..ef30674 --- /dev/null +++ b/components/test/data/autofill/heuristics/input/180_namesurname.html
@@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<body> +<form id="name-surname"> + <label for="name">Name: </label> + <input type="text" id="name" name="name"> <br> + <label for="surname">Surname: </label> + <input type="text" id="surname" name="surname"> <br> + <label for="city">City:</label> + <input type="text" id="city" name="city"><br> + <label for="zip">ZIP:</label> + <input type="text" id="zip" name="zip"><br> + <label for="state">State:</label> +</form> +</body> +</html>
diff --git a/components/test/data/autofill/heuristics/output/066_register_rediff.com.out b/components/test/data/autofill/heuristics/output/066_register_rediff.com.out index de6431d..0509ecfa 100644 --- a/components/test/data/autofill/heuristics/output/066_register_rediff.com.out +++ b/components/test/data/autofill/heuristics/output/066_register_rediff.com.out
@@ -1,4 +1,4 @@ -NAME_FULL | name | Create a Rediffmail account | | name_1-default +UNKNOWN_TYPE | name | Create a Rediffmail account | | name_1-default NAME_FULL | login | Full Name:Enter your first name & last nameEg. Sameer Bhagwat | | name_1-default UNKNOWN_TYPE | passwd | Choose a Rediffmail ID:@rediffmail.com | | name_1-default UNKNOWN_TYPE | confirm_passwd | Password:6-12 characters allowed | | name_1-default
diff --git a/components/test/data/autofill/heuristics/output/180_namesurname.out b/components/test/data/autofill/heuristics/output/180_namesurname.out new file mode 100644 index 0000000..2de6e309 --- /dev/null +++ b/components/test/data/autofill/heuristics/output/180_namesurname.out
@@ -0,0 +1,4 @@ +NAME_FIRST | name | Name: | | name_1-default +NAME_LAST | surname | Surname: | | name_1-default +ADDRESS_HOME_CITY | city | City: | | name_1-default +ADDRESS_HOME_ZIP | zip | ZIP: | | name_1-default
diff --git a/components/translate/content/browser/translate_model_service.cc b/components/translate/content/browser/translate_model_service.cc index 35166e7..2f28efa 100644 --- a/components/translate/content/browser/translate_model_service.cc +++ b/components/translate/content/browser/translate_model_service.cc
@@ -105,7 +105,7 @@ background_task_runner_->PostTaskAndReplyWithResult( FROM_HERE, base::BindOnce(&LoadModelFile, model_info.GetModelFilePath()), base::BindOnce(&TranslateModelService::OnModelFileLoaded, - base::Unretained(this))); + weak_ptr_factory_.GetWeakPtr())); } void TranslateModelService::OnModelFileLoaded(base::File model_file) {
diff --git a/components/translate/content/browser/translate_model_service.h b/components/translate/content/browser/translate_model_service.h index a3bdec1..9769f27 100644 --- a/components/translate/content/browser/translate_model_service.h +++ b/components/translate/content/browser/translate_model_service.h
@@ -12,6 +12,7 @@ #include "base/files/file.h" #include "base/files/file_path.h" #include "base/memory/raw_ptr.h" +#include "base/memory/weak_ptr.h" #include "components/keyed_service/core/keyed_service.h" #include "components/optimization_guide/core/optimization_target_model_observer.h" #include "third_party/abseil-cpp/absl/types/optional.h" @@ -81,6 +82,8 @@ std::vector<NotifyModelAvailableCallback> pending_model_requests_; scoped_refptr<base::SequencedTaskRunner> background_task_runner_; + + base::WeakPtrFactory<TranslateModelService> weak_ptr_factory_{this}; }; } // namespace translate
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc index da885b9a..6dd39da8 100644 --- a/content/browser/accessibility/browser_accessibility.cc +++ b/content/browser/accessibility/browser_accessibility.cc
@@ -405,36 +405,6 @@ return GetData().relative_bounds.bounds; } -gfx::Rect BrowserAccessibility::GetClippedScreenBoundsRect( - ui::AXOffscreenResult* offscreen_result) const { - return GetBoundsRect(ui::AXCoordinateSystem::kScreenDIPs, - ui::AXClippingBehavior::kClipped, offscreen_result); -} - -gfx::Rect BrowserAccessibility::GetUnclippedScreenBoundsRect( - ui::AXOffscreenResult* offscreen_result) const { - return GetBoundsRect(ui::AXCoordinateSystem::kScreenDIPs, - ui::AXClippingBehavior::kUnclipped, offscreen_result); -} - -gfx::Rect BrowserAccessibility::GetClippedRootFrameBoundsRect( - ui::AXOffscreenResult* offscreen_result) const { - return GetBoundsRect(ui::AXCoordinateSystem::kRootFrame, - ui::AXClippingBehavior::kClipped, offscreen_result); -} - -gfx::Rect BrowserAccessibility::GetUnclippedRootFrameBoundsRect( - ui::AXOffscreenResult* offscreen_result) const { - return GetBoundsRect(ui::AXCoordinateSystem::kRootFrame, - ui::AXClippingBehavior::kUnclipped, offscreen_result); -} - -gfx::Rect BrowserAccessibility::GetClippedFrameBoundsRect( - ui::AXOffscreenResult* offscreen_result) const { - return GetBoundsRect(ui::AXCoordinateSystem::kFrame, - ui::AXClippingBehavior::kUnclipped, offscreen_result); -} - gfx::Rect BrowserAccessibility::GetUnclippedRootFrameHypertextRangeBoundsRect( const int start_offset, const int end_offset,
diff --git a/content/browser/accessibility/browser_accessibility.h b/content/browser/accessibility/browser_accessibility.h index 529eb09..64dd6f0b 100644 --- a/content/browser/accessibility/browser_accessibility.h +++ b/content/browser/accessibility/browser_accessibility.h
@@ -253,18 +253,6 @@ // for (const auto& child : AllChildren()) {}. AllChildrenRange AllChildren() const { return AllChildrenRange(this); } - // Derivative utils for AXPlatformNodeDelegate::GetBoundsRect - gfx::Rect GetClippedScreenBoundsRect( - ui::AXOffscreenResult* offscreen_result = nullptr) const override; - gfx::Rect GetUnclippedScreenBoundsRect( - ui::AXOffscreenResult* offscreen_result = nullptr) const; - gfx::Rect GetClippedRootFrameBoundsRect( - ui::AXOffscreenResult* offscreen_result = nullptr) const; - gfx::Rect GetUnclippedRootFrameBoundsRect( - ui::AXOffscreenResult* offscreen_result = nullptr) const; - gfx::Rect GetClippedFrameBoundsRect( - ui::AXOffscreenResult* offscreen_result = nullptr) const; - // Derivative utils for AXPlatformNodeDelegate::GetHypertextRangeBoundsRect gfx::Rect GetUnclippedRootFrameHypertextRangeBoundsRect( const int start_offset,
diff --git a/content/browser/accessibility/browser_accessibility_cocoa.mm b/content/browser/accessibility/browser_accessibility_cocoa.mm index ac8f89a..c60528e 100644 --- a/content/browser/accessibility/browser_accessibility_cocoa.mm +++ b/content/browser/accessibility/browser_accessibility_cocoa.mm
@@ -70,8 +70,6 @@ NSString* const NSAccessibilityFocusableAncestorAttribute = @"AXFocusableAncestor"; NSString* const NSAccessibilityGrabbedAttribute = @"AXGrabbed"; -NSString* const NSAccessibilityHasPopupAttribute = @"AXHasPopup"; -NSString* const NSAccessibilityPopupValueAttribute = @"AXPopupValue"; NSString* const NSAccessibilityHighestEditableAncestorAttribute = @"AXHighestEditableAncestor"; NSString* const NSAccessibilityIsMultiSelectableAttribute = @@ -792,8 +790,6 @@ {NSAccessibilityFocusedAttribute, @"focused"}, {NSAccessibilityGrabbedAttribute, @"grabbed"}, {NSAccessibilityHeaderAttribute, @"header"}, - {NSAccessibilityHasPopupAttribute, @"hasPopup"}, - {NSAccessibilityPopupValueAttribute, @"popupValue"}, {NSAccessibilityHelpAttribute, @"help"}, {NSAccessibilityHighestEditableAncestorAttribute, @"highestEditableAncestor"}, @@ -1210,34 +1206,6 @@ return @NO; } -- (NSNumber*)hasPopup { - if (![self instanceActive]) - return nil; - return @(_owner->HasIntAttribute(ax::mojom::IntAttribute::kHasPopup)); -} - -- (NSString*)popupValue { - if (![self instanceActive]) - return nil; - int hasPopup = _owner->GetIntAttribute(ax::mojom::IntAttribute::kHasPopup); - switch (static_cast<ax::mojom::HasPopup>(hasPopup)) { - case ax::mojom::HasPopup::kFalse: - return @"false"; - case ax::mojom::HasPopup::kTrue: - return @"true"; - case ax::mojom::HasPopup::kMenu: - return @"menu"; - case ax::mojom::HasPopup::kListbox: - return @"listbox"; - case ax::mojom::HasPopup::kTree: - return @"tree"; - case ax::mojom::HasPopup::kGrid: - return @"grid"; - case ax::mojom::HasPopup::kDialog: - return @"dialog"; - } -} - - (id)header { if (![self instanceActive]) return nil; @@ -3222,12 +3190,6 @@ if (_owner->GetHtmlAttribute("aria-grabbed", &grabbed)) [ret addObject:NSAccessibilityGrabbedAttribute]; - if (_owner->HasIntAttribute(ax::mojom::IntAttribute::kHasPopup)) { - [ret addObjectsFromArray:@[ - NSAccessibilityHasPopupAttribute, NSAccessibilityPopupValueAttribute - ]]; - } - if (_owner->HasBoolAttribute(ax::mojom::BoolAttribute::kSelected)) [ret addObject:NSAccessibilitySelectedAttribute];
diff --git a/content/browser/accessibility/dump_accessibility_scripts_browsertest.cc b/content/browser/accessibility/dump_accessibility_scripts_browsertest.cc index 5a5fb752..fdf54e9d 100644 --- a/content/browser/accessibility/dump_accessibility_scripts_browsertest.cc +++ b/content/browser/accessibility/dump_accessibility_scripts_browsertest.cc
@@ -205,6 +205,10 @@ RunTypedTest<kMacAttributes>("ax-dom-identifier.html"); } +IN_PROC_BROWSER_TEST_P(DumpAccessibilityScriptTest, AXHasPopup) { + RunTypedTest<kMacAttributes>("ax-has-popup.html"); +} + IN_PROC_BROWSER_TEST_P(DumpAccessibilityScriptTest, AXInvalid) { RunTypedTest<kMacAttributes>("ax-invalid.html"); } @@ -253,6 +257,10 @@ RunTypedTest<kMacAttributes>("ax-math-under.html"); } +IN_PROC_BROWSER_TEST_P(DumpAccessibilityScriptTest, AXPopupValue) { + RunTypedTest<kMacAttributes>("ax-popup-value.html"); +} + IN_PROC_BROWSER_TEST_P(DumpAccessibilityScriptTest, AXPressButton) { RunTypedTest<kMacAction>("ax-press-button.html"); }
diff --git a/content/browser/accessibility/web_contents_accessibility_android.cc b/content/browser/accessibility/web_contents_accessibility_android.cc index 518eb8a..e0adbb3 100644 --- a/content/browser/accessibility/web_contents_accessibility_android.cc +++ b/content/browser/accessibility/web_contents_accessibility_android.cc
@@ -1033,7 +1033,7 @@ BrowserAccessibilityAndroid* node = GetAXFromUniqueID(unique_id); if (node) { node->manager()->ScrollToMakeVisible( - *node, gfx::Rect(node->GetClippedFrameBoundsRect().size())); + *node, gfx::Rect(node->GetUnclippedFrameBoundsRect().size())); } }
diff --git a/content/browser/attribution_reporting/attribution_test_utils.cc b/content/browser/attribution_reporting/attribution_test_utils.cc index 9ddd95d..2597e5af 100644 --- a/content/browser/attribution_reporting/attribution_test_utils.cc +++ b/content/browser/attribution_reporting/attribution_test_utils.cc
@@ -44,6 +44,15 @@ MockAttributionReportingContentBrowserClient:: ~MockAttributionReportingContentBrowserClient() = default; +MockAttributionHost::MockAttributionHost(WebContents* web_contents) + : AttributionHost(web_contents) { + SetReceiverImplForTesting(this); +} + +MockAttributionHost::~MockAttributionHost() { + SetReceiverImplForTesting(nullptr); +} + base::GUID DefaultExternalReportID() { return base::GUID::ParseLowercase("21abd97f-73e8-4b88-9389-a9fee6abda5e"); }
diff --git a/content/browser/attribution_reporting/attribution_test_utils.h b/content/browser/attribution_reporting/attribution_test_utils.h index 505d3fc..4711d53 100644 --- a/content/browser/attribution_reporting/attribution_test_utils.h +++ b/content/browser/attribution_reporting/attribution_test_utils.h
@@ -17,6 +17,7 @@ #include "base/observer_list.h" #include "base/task/sequenced_task_runner.h" #include "base/time/time.h" +#include "content/browser/attribution_reporting/attribution_host.h" #include "content/browser/attribution_reporting/attribution_manager.h" #include "content/browser/attribution_reporting/attribution_manager_impl.h" #include "content/browser/attribution_reporting/attribution_policy.h" @@ -53,6 +54,23 @@ (override)); }; +class MockAttributionHost : public AttributionHost { + public: + explicit MockAttributionHost(WebContents* contents); + + ~MockAttributionHost() override; + + MOCK_METHOD(void, + RegisterImpression, + (const blink::Impression& impression), + (override)); + + MOCK_METHOD(void, + RegisterConversion, + (blink::mojom::ConversionPtr conversion), + (override)); +}; + base::GUID DefaultExternalReportID(); class ConfigurableStorageDelegate : public AttributionStorage::Delegate {
diff --git a/content/browser/attribution_reporting/source_declaration_browsertest.cc b/content/browser/attribution_reporting/source_declaration_browsertest.cc index d6f5b94..58338449 100644 --- a/content/browser/attribution_reporting/source_declaration_browsertest.cc +++ b/content/browser/attribution_reporting/source_declaration_browsertest.cc
@@ -2,16 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include <stdint.h> #include <memory> -#include "base/bind.h" #include "base/run_loop.h" #include "base/test/metrics/histogram_tester.h" -#include "base/time/time.h" #include "build/build_config.h" -#include "content/browser/attribution_reporting/attribution_host.h" #include "content/browser/attribution_reporting/attribution_manager_impl.h" +#include "content/browser/attribution_reporting/attribution_test_utils.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/public/browser/navigation_handle.h" #include "content/public/common/content_switches.h" @@ -25,6 +22,7 @@ #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/default_handlers.h" #include "net/test/embedded_test_server/embedded_test_server.h" +#include "testing/gmock/include/gmock/gmock.h" #include "third_party/blink/public/common/input/web_mouse_event.h" #include "ui/gfx/geometry/point.h" #include "url/gurl.h" @@ -33,6 +31,8 @@ namespace { +using ::testing::Field; + // WebContentsObserver that waits until a source is available on a // navigation handle for a finished navigation. class SourceObserver : public TestNavigationObserver { @@ -84,53 +84,6 @@ base::RunLoop impression_loop_; }; -// A mock attribution host which waits until an impression registration -// mojo message is received. Tracks the last seen impression data. -class TestAttributionHost : public AttributionHost { - public: - explicit TestAttributionHost(WebContents* contents) - : AttributionHost(contents) { - SetReceiverImplForTesting(this); - } - - ~TestAttributionHost() override { SetReceiverImplForTesting(nullptr); } - - void RegisterImpression(const blink::Impression& impression) override { - last_impression_data_ = impression.impression_data; - num_impressions_++; - - // Don't quit the run loop if we have not seen the expected number of - // impressions. - if (num_impressions_ < expected_num_impressions_) - return; - impression_waiter_.Quit(); - } - - // Returns the last impression data after |expected_num_impressions| have been - // observed. - uint64_t WaitForNumImpressions(size_t expected_num_impressions) { - if (expected_num_impressions == num_impressions_) - return last_impression_data_; - expected_num_impressions_ = expected_num_impressions; - impression_waiter_.Run(); - return last_impression_data_; - } - - size_t num_impressions() { return num_impressions_; } - - void ResetImpressionWaitData() { - last_impression_data_ = 0; - num_impressions_ = 0; - expected_num_impressions_ = 0; - } - - private: - uint64_t last_impression_data_ = 0; - size_t num_impressions_ = 0; - size_t expected_num_impressions_ = 0; - base::RunLoop impression_waiter_; -}; - class AttributionSourceDisabledBrowserTest : public ContentBrowserTest { public: AttributionSourceDisabledBrowserTest() { @@ -714,7 +667,11 @@ shell(), https_server()->GetURL("b.test", "/page_with_impression_creator.html"))); - TestAttributionHost host(web_contents()); + base::RunLoop loop; + MockAttributionHost host(web_contents()); + EXPECT_CALL(host, RegisterImpression( + Field(&blink::Impression::impression_data, 200UL))) + .WillOnce([&]() { loop.Quit(); }); EXPECT_TRUE(ExecJs(web_contents(), R"( createImpressionTag({id: 'link', @@ -722,11 +679,7 @@ data: '200', destination: 'https://a.com', registerAttributionSource: true});)")); - - EXPECT_EQ(200UL, host.WaitForNumImpressions(1)); - EXPECT_EQ(1u, host.num_impressions()); - - host.ResetImpressionWaitData(); + loop.Run(); EXPECT_TRUE(ExecJs(web_contents(), R"( document.getElementById("link").removeAttribute("registerattributionsource");)")); @@ -736,7 +689,6 @@ // navigation message, it would be observed before the NavigateToURL() call // finishes. EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); - EXPECT_EQ(0u, host.num_impressions()); } IN_PROC_BROWSER_TEST_F( @@ -746,7 +698,14 @@ shell(), https_server()->GetURL("b.test", "/page_with_impression_creator.html"))); - TestAttributionHost host(web_contents()); + base::RunLoop loop1, loop2; + MockAttributionHost host(web_contents()); + EXPECT_CALL(host, RegisterImpression( + Field(&blink::Impression::impression_data, 200UL))) + .WillOnce([&]() { loop1.Quit(); }); + EXPECT_CALL(host, RegisterImpression( + Field(&blink::Impression::impression_data, 300UL))) + .WillOnce([&]() { loop2.Quit(); }); EXPECT_TRUE(ExecJs(web_contents(), R"( createImpressionTag({id: 'link', @@ -754,11 +713,7 @@ data: '200', destination: 'https://a.com', registerAttributionSource: true});)")); - - EXPECT_EQ(200UL, host.WaitForNumImpressions(1)); - EXPECT_EQ(1u, host.num_impressions()); - - host.ResetImpressionWaitData(); + loop1.Run(); EXPECT_TRUE(ExecJs(web_contents(), R"( let link = document.getElementById("link"); @@ -766,8 +721,7 @@ link.setAttribute("attributionsourceeventid", "300"); link.setAttribute("registerattributionsource", "");)")); - EXPECT_EQ(300UL, host.WaitForNumImpressions(1)); - EXPECT_EQ(1u, host.num_impressions()); + loop2.Run(); } IN_PROC_BROWSER_TEST_F( @@ -780,7 +734,8 @@ https_server()->GetURL("c.test", "/page_with_impression_creator.html"); NavigateIframeToURL(web_contents(), "test_iframe", subframe_url); - TestAttributionHost host(web_contents()); + MockAttributionHost host(web_contents()); + EXPECT_CALL(host, RegisterImpression).Times(0); RenderFrameHost* subframe = ChildFrameAt(web_contents()->GetMainFrame(), 0); EXPECT_TRUE(ExecJs(subframe, R"( @@ -791,7 +746,6 @@ registerAttributionSource: true});)")); EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); - EXPECT_EQ(0u, host.num_impressions()); } IN_PROC_BROWSER_TEST_F(AttributionSourceDeclarationBrowserTest, @@ -915,16 +869,18 @@ shell(), https_server()->GetURL("b.test", "/page_with_impression_creator.html"))); - TestAttributionHost host(web_contents()); + base::RunLoop loop; + MockAttributionHost host(web_contents()); + EXPECT_CALL(host, RegisterImpression( + Field(&blink::Impression::impression_data, 200UL))) + .WillOnce([&]() { loop.Quit(); }); EXPECT_TRUE(ExecJs(web_contents(), R"( window.attributionReporting.registerAttributionSource({ attributionSourceEventId: "200", attributionDestination: "https://a.com", });)")); - - EXPECT_EQ(200UL, host.WaitForNumImpressions(1)); - EXPECT_EQ(1u, host.num_impressions()); + loop.Run(); } IN_PROC_BROWSER_TEST_F( @@ -934,7 +890,8 @@ shell(), https_server()->GetURL("b.test", "/page_with_impression_creator.html"))); - TestAttributionHost host(web_contents()); + MockAttributionHost host(web_contents()); + EXPECT_CALL(host, RegisterImpression).Times(0); EXPECT_FALSE(ExecJs(web_contents(), R"( window.attributionReporting.registerAttributionSource({ @@ -942,7 +899,6 @@ });)")); EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); - EXPECT_EQ(0u, host.num_impressions()); } IN_PROC_BROWSER_TEST_F( @@ -952,7 +908,8 @@ shell(), https_server()->GetURL("b.test", "/page_with_impression_creator.html"))); - TestAttributionHost host(web_contents()); + MockAttributionHost host(web_contents()); + EXPECT_CALL(host, RegisterImpression).Times(0); EXPECT_FALSE(ExecJs(web_contents(), R"( window.attributionReporting.registerAttributionSource({ @@ -960,7 +917,6 @@ });)")); EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); - EXPECT_EQ(0u, host.num_impressions()); } IN_PROC_BROWSER_TEST_F( @@ -970,7 +926,8 @@ shell(), https_server()->GetURL("b.test", "/page_with_impression_creator.html"))); - TestAttributionHost host(web_contents()); + MockAttributionHost host(web_contents()); + EXPECT_CALL(host, RegisterImpression).Times(0); EXPECT_FALSE(ExecJs(web_contents(), R"( window.attributionReporting.registerAttributionSource({ @@ -979,7 +936,6 @@ });)")); EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); - EXPECT_EQ(0u, host.num_impressions()); } IN_PROC_BROWSER_TEST_F(AttributionSourceDeclarationBrowserTest,
diff --git a/content/browser/attribution_reporting/trigger_registration_browsertest.cc b/content/browser/attribution_reporting/trigger_registration_browsertest.cc index 9eddf1e..e98d3221 100644 --- a/content/browser/attribution_reporting/trigger_registration_browsertest.cc +++ b/content/browser/attribution_reporting/trigger_registration_browsertest.cc
@@ -2,12 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include <stdint.h> #include <memory> #include "base/bind.h" -#include "content/browser/attribution_reporting/attribution_host.h" #include "content/browser/attribution_reporting/attribution_manager_impl.h" +#include "content/browser/attribution_reporting/attribution_test_utils.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/public/common/content_switches.h" #include "content/public/test/browser_test.h" @@ -19,6 +18,7 @@ #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/default_handlers.h" #include "net/test/embedded_test_server/embedded_test_server.h" +#include "testing/gmock/include/gmock/gmock.h" #include "third_party/blink/public/mojom/conversions/conversions.mojom.h" #include "url/gurl.h" @@ -26,57 +26,16 @@ namespace { +using ::testing::AllOf; +using ::testing::Field; +using ::testing::Pointee; + // Well known path for registering conversions. const std::string kWellKnownUrl = ".well-known/attribution-reporting/trigger-attribution"; } // namespace -// A mock attribution host which waits until a conversion registration -// mojo message is received. Tracks the last seen conversion data. -class TestAttributionHost : public AttributionHost { - public: - explicit TestAttributionHost(WebContents* contents) - : AttributionHost(contents) { - SetReceiverImplForTesting(this); - } - - ~TestAttributionHost() override { SetReceiverImplForTesting(nullptr); } - - void RegisterConversion(blink::mojom::ConversionPtr conversion) override { - last_conversion_ = std::move(conversion); - num_conversions_++; - - // Don't quit the run loop if we have not seen the expected number of - // conversions. - if (num_conversions_ < expected_num_conversions_) - return; - conversion_waiter_.Quit(); - } - - // Returns the last conversion data after |expected_num_conversions| have been - // observed. - uint64_t WaitForNumConversions(size_t expected_num_conversions) { - if (expected_num_conversions == num_conversions_) - return last_conversion_->conversion_data; - expected_num_conversions_ = expected_num_conversions; - conversion_waiter_.Run(); - return last_conversion_->conversion_data; - } - - size_t num_conversions() { return num_conversions_; } - - const blink::mojom::ConversionPtr& last_conversion() const { - return last_conversion_; - } - - private: - blink::mojom::ConversionPtr last_conversion_ = nullptr; - size_t num_conversions_ = 0; - size_t expected_num_conversions_ = 0; - base::RunLoop conversion_waiter_; -}; - class AttributionTriggerDisabledBrowserTest : public ContentBrowserTest { public: AttributionTriggerDisabledBrowserTest() { @@ -114,12 +73,12 @@ EXPECT_TRUE(NavigateToURL( shell(), embedded_test_server()->GetURL("/page_with_conversion_redirect.html"))); - TestAttributionHost host(web_contents()); + MockAttributionHost host(web_contents()); + EXPECT_CALL(host, RegisterConversion).Times(0); EXPECT_TRUE(ExecJs(web_contents(), "registerConversion({data: 123})")); EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); - EXPECT_EQ(0u, host.num_conversions()); } class AttributionTriggerRegistrationBrowserTest @@ -153,12 +112,19 @@ EXPECT_TRUE(NavigateToURL( shell(), embedded_test_server()->GetURL("/page_with_conversion_redirect.html"))); - TestAttributionHost host(web_contents()); + + base::RunLoop loop; + MockAttributionHost host(web_contents()); + EXPECT_CALL( + host, + RegisterConversion(Pointee(AllOf( + Field(&blink::mojom::Conversion::conversion_data, 123UL), + Field(&blink::mojom::Conversion::event_source_trigger_data, 0UL), + Field(&blink::mojom::Conversion::priority, 0))))) + .WillOnce([&]() { loop.Quit(); }); EXPECT_TRUE(ExecJs(web_contents(), "registerConversion({data: 123})")); - EXPECT_EQ(123UL, host.WaitForNumConversions(1)); - EXPECT_EQ(0UL, host.last_conversion()->event_source_trigger_data); - EXPECT_EQ(0, host.last_conversion()->priority); + loop.Run(); } IN_PROC_BROWSER_TEST_F(AttributionTriggerRegistrationBrowserTest, @@ -166,13 +132,20 @@ EXPECT_TRUE(NavigateToURL( shell(), embedded_test_server()->GetURL("/page_with_conversion_redirect.html"))); - TestAttributionHost host(web_contents()); + + base::RunLoop loop; + MockAttributionHost host(web_contents()); + EXPECT_CALL( + host, + RegisterConversion(Pointee(AllOf( + Field(&blink::mojom::Conversion::conversion_data, 123UL), + Field(&blink::mojom::Conversion::event_source_trigger_data, 456UL))))) + .WillOnce([&]() { loop.Quit(); }); EXPECT_TRUE( ExecJs(web_contents(), "registerConversion({data: 123, eventSourceTriggerData: 456})")); - EXPECT_EQ(123UL, host.WaitForNumConversions(1)); - EXPECT_EQ(456UL, host.last_conversion()->event_source_trigger_data); + loop.Run(); } IN_PROC_BROWSER_TEST_F(AttributionTriggerRegistrationBrowserTest, @@ -180,12 +153,18 @@ EXPECT_TRUE(NavigateToURL( shell(), embedded_test_server()->GetURL("/page_with_conversion_redirect.html"))); - TestAttributionHost host(web_contents()); + + base::RunLoop loop; + MockAttributionHost host(web_contents()); + EXPECT_CALL( + host, RegisterConversion(Pointee( + AllOf(Field(&blink::mojom::Conversion::conversion_data, 123UL), + Field(&blink::mojom::Conversion::priority, 456))))) + .WillOnce([&]() { loop.Quit(); }); EXPECT_TRUE( ExecJs(web_contents(), "registerConversion({data: 123, priority: 456})")); - EXPECT_EQ(123UL, host.WaitForNumConversions(1)); - EXPECT_EQ(456, host.last_conversion()->priority); + loop.Run(); } IN_PROC_BROWSER_TEST_F(AttributionTriggerRegistrationBrowserTest, @@ -193,7 +172,8 @@ EXPECT_TRUE(NavigateToURL( shell(), embedded_test_server()->GetURL( "/page_with_conversion_measurement_disabled.html"))); - TestAttributionHost host(web_contents()); + MockAttributionHost host(web_contents()); + EXPECT_CALL(host, RegisterConversion).Times(0); GURL redirect_url = embedded_test_server()->GetURL( "/server-redirect?" + kWellKnownUrl + "trigger-data=200"); @@ -203,7 +183,6 @@ load_observer.WaitForResourceCompletion(redirect_url); EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); - EXPECT_EQ(0u, host.num_conversions()); } IN_PROC_BROWSER_TEST_F(AttributionTriggerRegistrationBrowserTest, @@ -211,7 +190,8 @@ EXPECT_TRUE(NavigateToURL( shell(), embedded_test_server()->GetURL("/page_with_conversion_redirect.html"))); - TestAttributionHost host(web_contents()); + MockAttributionHost host(web_contents()); + EXPECT_CALL(host, RegisterConversion).Times(0); GURL registration_url = embedded_test_server()->GetURL("/" + kWellKnownUrl + "?trigger-data=200"); @@ -228,7 +208,6 @@ // navigation message, it would be observed before the NavigateToURL() call // finishes. EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); - EXPECT_EQ(0u, host.num_conversions()); } IN_PROC_BROWSER_TEST_F( @@ -237,7 +216,8 @@ EXPECT_TRUE(NavigateToURL( shell(), https_server()->GetURL("c.test", "/page_with_conversion_redirect.html"))); - TestAttributionHost host(web_contents()); + MockAttributionHost host(web_contents()); + EXPECT_CALL(host, RegisterConversion).Times(0); // Create a url that does the following redirect chain b.test -> // a.test/.well-known/...; this conversion registration should not be allowed, @@ -259,7 +239,6 @@ // navigation message, it would be observed before the NavigateToURL() call // finishes. EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); - EXPECT_EQ(0u, host.num_conversions()); } IN_PROC_BROWSER_TEST_F(AttributionTriggerRegistrationBrowserTest, @@ -267,7 +246,15 @@ EXPECT_TRUE(NavigateToURL( shell(), https_server()->GetURL("c.test", "/page_with_conversion_redirect.html"))); - TestAttributionHost host(web_contents()); + + base::RunLoop loop; + MockAttributionHost host(web_contents()); + EXPECT_CALL( + host, + RegisterConversion(Pointee(AllOf( + Field(&blink::mojom::Conversion::conversion_data, 200UL), + Field(&blink::mojom::Conversion::event_source_trigger_data, 0UL))))) + .WillOnce([&]() { loop.Quit(); }); // Create a url that does the following redirect chain b.test -> a.test -> // a.test/.well-known/...; this conversion registration should be allowed. @@ -280,19 +267,19 @@ EXPECT_TRUE(ExecJs(web_contents(), JsReplace("createTrackingPixel($1);", registration_url))); - EXPECT_EQ(200UL, host.WaitForNumConversions(1)); - EXPECT_EQ(0UL, host.last_conversion()->event_source_trigger_data); + loop.Run(); } IN_PROC_BROWSER_TEST_F(AttributionTriggerRegistrationBrowserTest, ConversionRegistrationInPreload_NotReceived) { - TestAttributionHost host(web_contents()); + MockAttributionHost host(web_contents()); + EXPECT_CALL(host, RegisterConversion).Times(0); + EXPECT_TRUE( NavigateToURL(shell(), embedded_test_server()->GetURL( "/page_with_preload_conversion_ping.html"))); EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); - EXPECT_EQ(0u, host.num_conversions()); } IN_PROC_BROWSER_TEST_F(AttributionTriggerRegistrationBrowserTest, @@ -300,14 +287,20 @@ EXPECT_TRUE(NavigateToURL( shell(), embedded_test_server()->GetURL("/page_with_conversion_redirect.html"))); - TestAttributionHost host(web_contents()); + + base::RunLoop loop; + MockAttributionHost host(web_contents()); + // Conversion data and event source trigger data should be defaulted to 0. + EXPECT_CALL( + host, + RegisterConversion(Pointee(AllOf( + Field(&blink::mojom::Conversion::conversion_data, 0UL), + Field(&blink::mojom::Conversion::event_source_trigger_data, 0UL))))) + .WillOnce([&]() { loop.Quit(); }); EXPECT_TRUE(ExecJs(web_contents(), "createTrackingPixel(\"server-redirect?" + kWellKnownUrl + "\");")); - - // Conversion data and event source trigger data should be defaulted to 0. - EXPECT_EQ(0UL, host.WaitForNumConversions(1)); - EXPECT_EQ(0UL, host.last_conversion()->event_source_trigger_data); + loop.Run(); } IN_PROC_BROWSER_TEST_F(AttributionTriggerRegistrationBrowserTest, @@ -315,18 +308,24 @@ EXPECT_TRUE(NavigateToURL( shell(), embedded_test_server()->GetURL("/page_with_subframe_conversion.html"))); - TestAttributionHost host(web_contents()); + + base::RunLoop loop; + MockAttributionHost host(web_contents()); + EXPECT_CALL( + host, + RegisterConversion(Pointee(AllOf( + Field(&blink::mojom::Conversion::conversion_data, 200u), + Field(&blink::mojom::Conversion::event_source_trigger_data, 0u))))) + .WillOnce([&]() { loop.Quit(); }); GURL redirect_url = embedded_test_server()->GetURL( "/server-redirect?" + kWellKnownUrl + "?trigger-data=200"); ResourceLoadObserver load_observer(shell()); EXPECT_TRUE(ExecJs(ChildFrameAt(web_contents()->GetMainFrame(), 0), JsReplace("createTrackingPixel($1);", redirect_url))); - EXPECT_EQ(200u, host.WaitForNumConversions(1)); - EXPECT_EQ(0u, host.last_conversion()->event_source_trigger_data); + loop.Run(); EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); - EXPECT_EQ(1u, host.num_conversions()); } IN_PROC_BROWSER_TEST_F( @@ -339,7 +338,8 @@ https_server()->GetURL("b.test", "/page_with_conversion_redirect.html"); NavigateIframeToURL(web_contents(), "test_iframe", subframe_url); - TestAttributionHost host(web_contents()); + MockAttributionHost host(web_contents()); + EXPECT_CALL(host, RegisterConversion).Times(0); GURL redirect_url = https_server()->GetURL( "b.test", "/server-redirect?" + kWellKnownUrl + "?trigger-data=200"); @@ -350,7 +350,6 @@ load_observer.WaitForResourceCompletion(redirect_url); EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); - EXPECT_EQ(0u, host.num_conversions()); } IN_PROC_BROWSER_TEST_F( @@ -367,7 +366,14 @@ https_server()->GetURL("b.test", "/page_with_conversion_redirect.html"); NavigateIframeToURL(web_contents(), "test_iframe", subframe_url); - TestAttributionHost host(web_contents()); + base::RunLoop loop; + MockAttributionHost host(web_contents()); + EXPECT_CALL( + host, + RegisterConversion(Pointee(AllOf( + Field(&blink::mojom::Conversion::conversion_data, 200u), + Field(&blink::mojom::Conversion::event_source_trigger_data, 0u))))) + .WillOnce([&]() { loop.Quit(); }); GURL redirect_url = https_server()->GetURL( "b.test", "/server-redirect?" + kWellKnownUrl + "?trigger-data=200"); @@ -375,11 +381,9 @@ ResourceLoadObserver load_observer(shell()); EXPECT_TRUE(ExecJs(ChildFrameAt(web_contents()->GetMainFrame(), 0), JsReplace("createTrackingPixel($1);", redirect_url))); - EXPECT_EQ(200u, host.WaitForNumConversions(1)); - EXPECT_EQ(0u, host.last_conversion()->event_source_trigger_data); + loop.Run(); EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); - EXPECT_EQ(1u, host.num_conversions()); } IN_PROC_BROWSER_TEST_F( @@ -410,7 +414,16 @@ .expected_conversion = false}}; for (const auto& test_case : kTestCases) { - TestAttributionHost host(web_contents()); + base::RunLoop loop; + MockAttributionHost host(web_contents()); + if (test_case.expected_conversion) { + EXPECT_CALL( + host, RegisterConversion(Pointee( + Field(&blink::mojom::Conversion::conversion_data, 200UL)))) + .WillOnce([&]() { loop.Quit(); }); + } else { + EXPECT_CALL(host, RegisterConversion).Times(0); + } // Secure hosts must be served from the https server. net::EmbeddedTestServer* page_server = (test_case.page_host == kSecureHost) @@ -432,12 +445,11 @@ redirect_url))); if (test_case.expected_conversion) - EXPECT_EQ(200UL, host.WaitForNumConversions(1)); + loop.Run(); // Navigate the page. By the time the navigation finishes, we will have // received any conversion mojo messages. EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); - EXPECT_EQ(test_case.expected_conversion, host.num_conversions()); } } @@ -472,7 +484,8 @@ innermost_iframe_url))); EXPECT_TRUE(WaitForLoadStop(web_contents())); - TestAttributionHost host(web_contents()); + MockAttributionHost host(web_contents()); + EXPECT_CALL(host, RegisterConversion).Times(0); GURL redirect_url = embedded_test_server()->GetURL( "/server-redirect?" + kWellKnownUrl + "?trigger-data=200"); @@ -483,7 +496,6 @@ load_observer.WaitForResourceCompletion(redirect_url); EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); - EXPECT_EQ(0u, host.num_conversions()); } } // namespace content
diff --git a/content/browser/back_forward_cache_features_browsertest.cc b/content/browser/back_forward_cache_features_browsertest.cc index 16d6641..e656b97 100644 --- a/content/browser/back_forward_cache_features_browsertest.cc +++ b/content/browser/back_forward_cache_features_browsertest.cc
@@ -686,123 +686,6 @@ {}, {}, {}, {}, FROM_HERE); } -// Tests the case when fetch started in a dedicated worker, but the response -// never ends after the page is frozen. This should result in an eviction due to -// timeout. -IN_PROC_BROWSER_TEST_F(BackForwardCacheWithDedicatedWorkerBrowserTest, - ImageStillLoading_ResponseStartedWhileFrozen_Timeout) { - CreateHttpsServer(); - - net::test_server::ControllableHttpResponse image_response(https_server(), - "/image.png"); - ASSERT_TRUE(https_server()->Start()); - - GURL url_a(https_server()->GetURL("a.test", "/title1.html")); - GURL url_b(https_server()->GetURL("b.test", "/title1.html")); - - // Navigate to A. - EXPECT_TRUE(NavigateToURL(shell(), url_a)); - RenderFrameHostImplWrapper rfh_a(current_frame_host()); - - // Call fetch in a dedicated worker before navigating away. - std::string worker_script = - JsReplace(R"( - fetch($1); - )", - https_server()->GetURL("a.test", "/image.png")); - EXPECT_TRUE(ExecJs(rfh_a.get(), JsReplace(R"( - const blob = new Blob([$1]); - const blobURL = URL.createObjectURL(blob); - const worker = new Worker(blobURL); - )", - worker_script))); - - // Wait for the image request, but don't send anything yet. - image_response.WaitForRequest(); - - // Navigate away. - EXPECT_TRUE(NavigateToURL(shell(), url_b)); - // The page was still loading when we navigated away, but it's still eligible - // for back-forward cache. - EXPECT_TRUE(rfh_a->IsInBackForwardCache()); - - RenderFrameDeletedObserver delete_observer(rfh_a.get()); - // Start sending the image response while in the back-forward cache, but never - // finish the request. Eventually the page will get deleted due to network - // request timeout. - image_response.Send(net::HTTP_OK, "image/png"); - delete_observer.WaitUntilDeleted(); - - // 3) Go back to the first page. We should not restore the page from the - // back-forward cache. - ASSERT_TRUE(HistoryGoBack(web_contents())); - ExpectNotRestored( - {BackForwardCacheMetrics::NotRestoredReason::kNetworkRequestTimeout}, {}, - {}, {}, {}, FROM_HERE); -} - -// Tests the case when fetch started in a nested dedicated worker, but the -// response never ends after the page is frozen. This should result in an -// eviction due to timeout. -IN_PROC_BROWSER_TEST_F( - BackForwardCacheWithDedicatedWorkerBrowserTest, - ImageStillLoading_ResponseStartedWhileFrozen_Timeout_Nested) { - CreateHttpsServer(); - - net::test_server::ControllableHttpResponse image_response(https_server(), - "/image.png"); - ASSERT_TRUE(https_server()->Start()); - - GURL url_a(https_server()->GetURL("a.test", "/title1.html")); - GURL url_b(https_server()->GetURL("b.test", "/title1.html")); - - // Navigate to A. - EXPECT_TRUE(NavigateToURL(shell(), url_a)); - RenderFrameHostImplWrapper rfh_a(current_frame_host()); - - // Call fetch in a dedicated worker before navigating away. - std::string child_worker_script = - JsReplace(R"( - fetch($1); - )", - https_server()->GetURL("a.test", "/image.png")); - std::string parent_worker_script = JsReplace(R"( - const blob = new Blob([$1]); - const blobURL = URL.createObjectURL(blob); - const worker = new Worker(blobURL); - )", - child_worker_script); - EXPECT_TRUE(ExecJs(rfh_a.get(), JsReplace(R"( - const blob = new Blob([$1]); - const blobURL = URL.createObjectURL(blob); - const worker = new Worker(blobURL); - )", - parent_worker_script))); - - // Wait for the image request, but don't send anything yet. - image_response.WaitForRequest(); - - // Navigate away. - EXPECT_TRUE(NavigateToURL(shell(), url_b)); - // The page was still loading when we navigated away, but it's still eligible - // for back-forward cache. - EXPECT_TRUE(rfh_a->IsInBackForwardCache()); - - RenderFrameDeletedObserver delete_observer(rfh_a.get()); - // Start sending the image response while in the back-forward cache, but never - // finish the request. Eventually the page will get deleted due to network - // request timeout. - image_response.Send(net::HTTP_OK, "image/png"); - delete_observer.WaitUntilDeleted(); - - // 3) Go back to the first page. We should not restore the page from the - // back-forward cache. - ASSERT_TRUE(HistoryGoBack(web_contents())); - ExpectNotRestored( - {BackForwardCacheMetrics::NotRestoredReason::kNetworkRequestTimeout}, {}, - {}, {}, {}, FROM_HERE); -} - // TODO(https://crbug.com/154571): Shared workers are not available on Android. #if defined(OS_ANDROID) #define MAYBE_PageWithSharedWorkerNotCached \
diff --git a/content/browser/back_forward_cache_network_request_browsertest.cc b/content/browser/back_forward_cache_network_request_browsertest.cc index 8f56d445..a43e949 100644 --- a/content/browser/back_forward_cache_network_request_browsertest.cc +++ b/content/browser/back_forward_cache_network_request_browsertest.cc
@@ -739,6 +739,7 @@ // finish the request. Eventually the page will get deleted due to network // request timeout. image_response.Send(net::HTTP_OK, "image/png"); + std::string body(kMaxBufferedBytesPerRequest + 1, '*'); delete_observer.WaitUntilDeleted(); // 3) Go back to the first page. We should not restore the page from the
diff --git a/content/browser/interest_group/interest_group_browsertest.cc b/content/browser/interest_group/interest_group_browsertest.cc index 2571e514..44c69f1c 100644 --- a/content/browser/interest_group/interest_group_browsertest.cc +++ b/content/browser/interest_group/interest_group_browsertest.cc
@@ -631,6 +631,15 @@ execution_target)); } + std::string WarningPermissionsPolicy(std::string feature, std::string api) { + return base::StringPrintf( + "In the future, feature %s will not be enabled by default by " + "Permissions Policy (thus calling %s will be rejected with " + "NotAllowedError) in cross-origin iframes or same-origin iframes nested" + " in cross-origin iframes", + feature.c_str(), api.c_str()); + } + protected: std::unique_ptr<net::EmbeddedTestServer> https_server_; base::test::ScopedFeatureList feature_list_; @@ -4060,7 +4069,9 @@ "/cross_site_iframe_factory.html?a.test(" "a.test," "b.test(" - "c.test{allow-join-ad-interest-group;run-ad-auction}" + "c.test{allow-join-ad-interest-group;run-ad-auction}," + "a.test{allow-join-ad-interest-group;run-ad-auction}," + "a.test{allow-join-ad-interest-group;run-ad-auction}" ")" ")"); // clang-format on @@ -4071,6 +4082,10 @@ RenderFrameHost* cross_origin_iframe = ChildFrameAt(main_frame, 1); RenderFrameHost* inner_cross_origin_iframe = ChildFrameAt(cross_origin_iframe, 0); + RenderFrameHost* same_origin_iframe_in_cross_origin_iframe = + ChildFrameAt(cross_origin_iframe, 1); + RenderFrameHost* same_origin_iframe_in_cross_origin_iframe2 = + ChildFrameAt(cross_origin_iframe, 2); // The server JSON updates all fields that can be updated. constexpr char kDailyUpdateUrlPath[] = @@ -4086,14 +4101,21 @@ GURL url; url::Origin origin; std::string host; - RenderFrameHost* execution_targets[] = {main_frame, same_origin_iframe, - cross_origin_iframe, - inner_cross_origin_iframe}; + RenderFrameHost* execution_targets[] = { + main_frame, + same_origin_iframe, + cross_origin_iframe, + inner_cross_origin_iframe, + same_origin_iframe_in_cross_origin_iframe, + same_origin_iframe_in_cross_origin_iframe2}; for (auto* execution_target : execution_targets) { url = execution_target->GetLastCommittedURL(); origin = url::Origin::Create(url); host = url.host(); + WebContentsConsoleObserver console_observer(shell()->web_contents()); + console_observer.SetPattern(WarningPermissionsPolicy("*", "*")); + EXPECT_TRUE(JoinInterestGroupAndWaitInJs( blink::InterestGroup( /*expiry=*/base::Time(), @@ -4129,7 +4151,44 @@ execution_target)); EXPECT_EQ("done", UpdateInterestGroupsInJS(execution_target)); + // The second UpdateInterestGroupsInJS will not add a warning message, since + // the same message has already been added and redundant messages will be + // discarded. + EXPECT_EQ("done", UpdateInterestGroupsInJS(execution_target)); EXPECT_TRUE(LeaveInterestGroupInJS(origin, "cars", execution_target)); + + // It seems discard_duplicates of AddConsoleMessage works differently on + // Android and other platforms. On Android, a message will be discarded if + // it's not unique across all frames in a page. On other platforms, a + // message will be discarded if it's not unique per origin (e.g., iframe + // a.test and iframe b.test can have the same message, while the same + // message from another a.test will be discarded). + +#if defined(OS_ANDROID) + RenderFrameHost* execution_targets_with_message[] = {cross_origin_iframe}; +#else + RenderFrameHost* execution_targets_with_message[] = { + cross_origin_iframe, inner_cross_origin_iframe, + same_origin_iframe_in_cross_origin_iframe}; +#endif // defined(OS_ANDROID) + + if (std::find(std::begin(execution_targets_with_message), + std::end(execution_targets_with_message), execution_target) != + std::end(execution_targets_with_message)) { + EXPECT_EQ(WarningPermissionsPolicy("join-ad-interest-group", + "joinAdInterestGroup"), + console_observer.GetMessageAt(0)); + EXPECT_EQ(WarningPermissionsPolicy("run-ad-auction", "runAdAuction"), + console_observer.GetMessageAt(1)); + EXPECT_EQ(WarningPermissionsPolicy("join-ad-interest-group", + "updateAdInterestGroups"), + console_observer.GetMessageAt(2)); + EXPECT_EQ(WarningPermissionsPolicy("join-ad-interest-group", + "leaveAdInterestGroup"), + console_observer.GetMessageAt(3)); + } else { + EXPECT_TRUE(console_observer.messages().empty()); + } } } @@ -4225,6 +4284,9 @@ url = execution_target->GetLastCommittedURL(); origin = url::Origin::Create(url); host = url.host(); + WebContentsConsoleObserver console_observer(shell()->web_contents()); + console_observer.SetPattern(WarningPermissionsPolicy("*", "*")); + EXPECT_TRUE(JoinInterestGroupAndWaitInJs( blink::InterestGroup( /*expiry=*/base::Time(), @@ -4261,6 +4323,7 @@ EXPECT_EQ("done", UpdateInterestGroupsInJS(execution_target)); EXPECT_TRUE(LeaveInterestGroupInJS(origin, "cars", execution_target)); + EXPECT_TRUE(console_observer.messages().empty()); } }
diff --git a/content/common/partition_alloc_support.cc b/content/common/partition_alloc_support.cc index baef650..608a862 100644 --- a/content/common/partition_alloc_support.cc +++ b/content/common/partition_alloc_support.cc
@@ -214,38 +214,56 @@ CHECK(base::FeatureList::GetInstance()); bool enable_brp = false; + bool split_main_partition = false; + ALLOW_UNUSED_LOCAL(split_main_partition); + bool use_dedicated_aligned_partition = false; + ALLOW_UNUSED_LOCAL(use_dedicated_aligned_partition); #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) #if BUILDFLAG(USE_BACKUP_REF_PTR) + bool process_affected_by_brp_flag = false; if (base::FeatureList::IsEnabled( base::features::kPartitionAllocBackupRefPtr)) { // No specified process type means this is the Browser process. - enable_brp = process_type.empty(); + process_affected_by_brp_flag = process_type.empty(); if (base::features::kBackupRefPtrEnabledProcessesParam.Get() == base::features::BackupRefPtrEnabledProcesses::kBrowserAndRenderer) { - enable_brp |= process_type == switches::kRendererProcess; + process_affected_by_brp_flag |= + process_type == switches::kRendererProcess; } if (base::features::kBackupRefPtrEnabledProcessesParam.Get() == base::features::BackupRefPtrEnabledProcesses::kAllProcesses) { - enable_brp = true; + process_affected_by_brp_flag = true; + } + } + + if (process_affected_by_brp_flag) { + switch (base::features::kBackupRefPtrModeParam.Get()) { + case base::features::BackupRefPtrMode::kEnabled: + enable_brp = true; + split_main_partition = true; +#if !BUILDFLAG(PUT_REF_COUNT_IN_PREVIOUS_SLOT) + // AlignedAlloc relies on natural alignment offered by the allocator + // (see the comment inside PartitionRoot::AlignedAllocFlags). Any extras + // in front of the allocation will mess up that alignment. Such extras + // are used when BackupRefPtr is on, in which case, we need a separate + // partition, dedicated to handle only aligned allocations, where those + // extras are disabled. However, if the "previous slot" variant is used, + // no dedicated partition is needed, as the extras won't interfere with + // the alignment requirements. + use_dedicated_aligned_partition = true; +#endif + break; + case base::features::BackupRefPtrMode::kDisabledButSplitPartitions2Way: + split_main_partition = true; + break; + case base::features::BackupRefPtrMode::kDisabledButSplitPartitions3Way: + split_main_partition = true; + use_dedicated_aligned_partition = true; + break; } } #endif // BUILDFLAG(USE_BACKUP_REF_PTR) - bool force_split_partitions = false; - if (base::FeatureList::IsEnabled( - base::features::kPartitionAllocSimulateBRPPartitionSplit)) { - // No specified process type means this is the Browser process. - force_split_partitions = process_type.empty(); - if (base::features::kSimulateBRPPartitionSplitProcessesParam.Get() == - base::features::BackupRefPtrEnabledProcesses::kBrowserAndRenderer) { - force_split_partitions |= process_type == switches::kRendererProcess; - } - if (base::features::kSimulateBRPPartitionSplitProcessesParam.Get() == - base::features::BackupRefPtrEnabledProcesses::kAllProcesses) { - force_split_partitions = true; - } - } - base::allocator::ReconfigurePartitionAllocLazyCommit( base::FeatureList::IsEnabled(base::features::kPartitionAllocLazyCommit)); #endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) @@ -291,9 +309,11 @@ scan_enabled && process_type == switches::kRendererProcess; base::allocator::ConfigurePartitions( base::allocator::EnableBrp(enable_brp), + base::allocator::SplitMainPartition(split_main_partition), + base::allocator::UseDedicatedAlignedPartition( + use_dedicated_aligned_partition), base::allocator::ThreadCacheOnNonQuarantinablePartition( - enable_thread_cache_on_v8_partition), - base::allocator::ForceSplitPartitions(force_split_partitions)); + enable_thread_cache_on_v8_partition)); #endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) }
diff --git a/content/public/browser/document_service.h b/content/public/browser/document_service.h index e3446a5..fb8c6378 100644 --- a/content/public/browser/document_service.h +++ b/content/public/browser/document_service.h
@@ -87,6 +87,7 @@ } mojo::Receiver<Interface>* receiver() { return &receiver_; } + const mojo::Receiver<Interface>* receiver() const { return &receiver_; } // Returns the RenderFrameHost tracked by this object. Guaranteed to never be // null.
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc index b26ef8a..c350cc4 100644 --- a/content/public/common/content_features.cc +++ b/content/public/common/content_features.cc
@@ -952,6 +952,10 @@ #endif // (defined(OS_LINUX) || defined(OS_CHROMEOS)) && // defined(ARCH_CPU_X86_64) +// Enable WebAssembly dynamic tiering (only tier up hot functions). +const base::Feature kWebAssemblyDynamicTiering{ + "WebAssemblyDynamicTiering", base::FEATURE_DISABLED_BY_DEFAULT}; + // Enable WebAssembly lazy compilation (JIT on first call). const base::Feature kWebAssemblyLazyCompilation{ "WebAssemblyLazyCompilation", base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h index a010875f..f582e14 100644 --- a/content/public/common/content_features.h +++ b/content/public/common/content_features.h
@@ -246,6 +246,7 @@ CONTENT_EXPORT extern const base::Feature kWebAssemblyCodeProtectionPku; #endif // (defined(OS_LINUX) || defined(OS_CHROMEOS)) && // defined(ARCH_CPU_X86_64) +CONTENT_EXPORT extern const base::Feature kWebAssemblyDynamicTiering; CONTENT_EXPORT extern const base::Feature kWebAssemblyLazyCompilation; CONTENT_EXPORT extern const base::Feature kWebAssemblySimd; CONTENT_EXPORT extern const base::Feature kWebAssemblyTiering;
diff --git a/content/renderer/render_process_impl.cc b/content/renderer/render_process_impl.cc index 67b88d00..5bc0040 100644 --- a/content/renderer/render_process_impl.cc +++ b/content/renderer/render_process_impl.cc
@@ -237,6 +237,9 @@ SetV8FlagIfFeature(features::kWebAssemblyTiering, "--wasm-tier-up"); SetV8FlagIfNotFeature(features::kWebAssemblyTiering, "--no-wasm-tier-up"); + SetV8FlagIfFeature(features::kWebAssemblyDynamicTiering, + "--wasm-dynamic-tiering"); + #if (defined(OS_LINUX) || defined(OS_CHROMEOS)) && defined(ARCH_CPU_X86_64) if (base::FeatureList::IsEnabled(features::kWebAssemblyTrapHandler)) { if (command_line->HasSwitch(switches::kEnableCrashpad) ||
diff --git a/content/services/auction_worklet/BUILD.gn b/content/services/auction_worklet/BUILD.gn index 054880234..4cd7d76 100644 --- a/content/services/auction_worklet/BUILD.gn +++ b/content/services/auction_worklet/BUILD.gn
@@ -57,10 +57,8 @@ "report_bindings.h", "seller_worklet.cc", "seller_worklet.h", - "trusted_bidding_signals.cc", - "trusted_bidding_signals.h", - "trusted_scoring_signals.cc", - "trusted_scoring_signals.h", + "trusted_signals.cc", + "trusted_signals.h", "worklet_loader.cc", "worklet_loader.h", ] @@ -98,8 +96,7 @@ "bidder_worklet_unittest.cc", "debug_command_queue_unittest.cc", "seller_worklet_unittest.cc", - "trusted_bidding_signals_unittest.cc", - "trusted_scoring_signals_unittest.cc", + "trusted_signals_unittest.cc", "worklet_devtools_debug_test_util.cc", "worklet_devtools_debug_test_util.h", "worklet_loader_unittest.cc",
diff --git a/content/services/auction_worklet/bidder_worklet.cc b/content/services/auction_worklet/bidder_worklet.cc index 26597fb..3f8f21a4 100644 --- a/content/services/auction_worklet/bidder_worklet.cc +++ b/content/services/auction_worklet/bidder_worklet.cc
@@ -21,7 +21,7 @@ #include "content/services/auction_worklet/auction_v8_helper.h" #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h" #include "content/services/auction_worklet/report_bindings.h" -#include "content/services/auction_worklet/trusted_bidding_signals.h" +#include "content/services/auction_worklet/trusted_signals.h" #include "content/services/auction_worklet/worklet_loader.h" #include "gin/converter.h" #include "gin/dictionary.h" @@ -223,7 +223,7 @@ trusted_bidding_signals_keys_.has_value() && !trusted_bidding_signals_keys_->empty()) { generate_bid_task->trusted_bidding_signals = - std::make_unique<TrustedBiddingSignals>( + TrustedSignals::LoadBiddingSignals( url_loader_factory_.get(), *trusted_bidding_signals_keys_, top_window_origin.host(), *trusted_bidding_signals_url_, v8_helper_, base::BindOnce(&BidderWorklet::OnTrustedBiddingSignalsDownloaded, @@ -386,8 +386,7 @@ const url::Origin& browser_signal_top_window_origin, const url::Origin& browser_signal_seller_origin, base::Time auction_start_time, - std::unique_ptr<TrustedBiddingSignals::Result> - trusted_bidding_signals_result, + std::unique_ptr<TrustedSignals::Result> trusted_bidding_signals_result, GenerateBidCallbackInternal callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); @@ -452,7 +451,7 @@ if (!trusted_bidding_signals_result) { trusted_signals = v8::Null(isolate); } else { - trusted_signals = trusted_bidding_signals_result->GetSignals( + trusted_signals = trusted_bidding_signals_result->GetBiddingSignals( v8_helper_.get(), context, *interest_group.trusted_bidding_signals_keys); } @@ -718,12 +717,12 @@ void BidderWorklet::OnTrustedBiddingSignalsDownloaded( GenerateBidTaskList::iterator task, - std::unique_ptr<TrustedBiddingSignals::Result> result, + std::unique_ptr<TrustedSignals::Result> result, absl::optional<std::string> error_msg) { DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_); task->trusted_bidding_signals_error_msg = std::move(error_msg); - task->trusted_Bidding_signals_result = std::move(result); + task->trusted_bidding_signals_result = std::move(result); task->trusted_bidding_signals.reset(); GenerateBidIfReady(task); @@ -741,7 +740,7 @@ base::Unretained(v8_state_.get()), task->auction_signals_json, task->per_buyer_signals_json, task->top_window_origin, task->seller_origin, task->auction_start_time, - std::move(task->trusted_Bidding_signals_result), + std::move(task->trusted_bidding_signals_result), base::BindOnce(&BidderWorklet::DeliverBidCallbackOnUserThread, weak_ptr_factory_.GetWeakPtr(), task))); }
diff --git a/content/services/auction_worklet/bidder_worklet.h b/content/services/auction_worklet/bidder_worklet.h index 8ff5d16e..f481ec2 100644 --- a/content/services/auction_worklet/bidder_worklet.h +++ b/content/services/auction_worklet/bidder_worklet.h
@@ -18,7 +18,7 @@ #include "content/services/auction_worklet/auction_v8_helper.h" #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h" #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h" -#include "content/services/auction_worklet/trusted_bidding_signals.h" +#include "content/services/auction_worklet/trusted_signals.h" #include "content/services/auction_worklet/worklet_loader.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/remote.h" @@ -100,10 +100,9 @@ base::Time auction_start_time; // Set while loading is in progress. - std::unique_ptr<TrustedBiddingSignals> trusted_bidding_signals; + std::unique_ptr<TrustedSignals> trusted_bidding_signals; // Results of loading trusted bidding signals. - std::unique_ptr<TrustedBiddingSignals::Result> - trusted_Bidding_signals_result; + std::unique_ptr<TrustedSignals::Result> trusted_bidding_signals_result; // Error message returned by attempt to load `trusted_bidding_signals_`. // Errors loading it are not fatal, so such errors are cached here and only // reported on bid completion. @@ -160,14 +159,14 @@ double browser_signal_bid, ReportWinCallbackInternal callback); - void GenerateBid(const absl::optional<std::string>& auction_signals_json, - const absl::optional<std::string>& per_buyer_signals_json, - const url::Origin& browser_signal_top_window_origin, - const url::Origin& browser_signal_seller_origin, - base::Time auction_start_time, - std::unique_ptr<TrustedBiddingSignals::Result> - trusted_bidding_signals_result, - GenerateBidCallbackInternal callback); + void GenerateBid( + const absl::optional<std::string>& auction_signals_json, + const absl::optional<std::string>& per_buyer_signals_json, + const url::Origin& browser_signal_top_window_origin, + const url::Origin& browser_signal_seller_origin, + base::Time auction_start_time, + std::unique_ptr<TrustedSignals::Result> trusted_bidding_signals_result, + GenerateBidCallbackInternal callback); void ConnectDevToolsAgent( mojo::PendingReceiver<blink::mojom::DevToolsAgent> agent); @@ -214,11 +213,11 @@ void OnTrustedBiddingSignalsDownloaded( GenerateBidTaskList::iterator task, - std::unique_ptr<TrustedBiddingSignals::Result> result, + std::unique_ptr<TrustedSignals::Result> result, absl::optional<std::string> error_msg); // Checks if the script has been loaded successfully, and the - // TrustedBiddingSignals load has finished (successfully or not). If so, calls + // TrustedSignals load has finished (successfully or not). If so, calls // generateBid(), and invokes `load_script_and_generate_bid_callback_` with // the resulting bid, if any. May only be called once BidderWorklet has // successfully loaded.
diff --git a/content/services/auction_worklet/seller_worklet.cc b/content/services/auction_worklet/seller_worklet.cc index 1c90799..a6b661d 100644 --- a/content/services/auction_worklet/seller_worklet.cc +++ b/content/services/auction_worklet/seller_worklet.cc
@@ -19,7 +19,7 @@ #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h" #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h" #include "content/services/auction_worklet/report_bindings.h" -#include "content/services/auction_worklet/trusted_scoring_signals.h" +#include "content/services/auction_worklet/trusted_signals.h" #include "content/services/auction_worklet/worklet_loader.h" #include "gin/converter.h" #include "gin/dictionary.h" @@ -180,25 +180,23 @@ score_ad_task->browser_signal_interest_group_owner = browser_signal_interest_group_owner; score_ad_task->browser_signal_render_url = browser_signal_render_url; - score_ad_task->browser_signal_ad_components = browser_signal_ad_components; + for (const GURL& url : browser_signal_ad_components) { + score_ad_task->browser_signal_ad_components.emplace_back(url.spec()); + } score_ad_task->browser_signal_bidding_duration_msecs = browser_signal_bidding_duration_msecs; score_ad_task->callback = std::move(callback); - std::set<GURL> ad_component_render_urls(browser_signal_ad_components.begin(), - browser_signal_ad_components.end()); - if (score_ad_task->auction_config->trusted_scoring_signals_url) { - score_ad_task->trusted_scoring_signals = - std::make_unique<TrustedScoringSignals>( - url_loader_factory_.get(), - /*render_urls=*/std::set<GURL>{browser_signal_render_url}, - std::move(ad_component_render_urls), - browser_signal_top_window_origin.host(), - *score_ad_task->auction_config->trusted_scoring_signals_url, - v8_helper_, - base::BindOnce(&SellerWorklet::OnTrustedScoringSignalsDownloaded, - base::Unretained(this), score_ad_task)); + score_ad_task->trusted_scoring_signals = TrustedSignals::LoadScoringSignals( + url_loader_factory_.get(), + /*render_urls=*/ + std::vector<std::string>{browser_signal_render_url.spec()}, + score_ad_task->browser_signal_ad_components, + browser_signal_top_window_origin.host(), + *score_ad_task->auction_config->trusted_scoring_signals_url, v8_helper_, + base::BindOnce(&SellerWorklet::OnTrustedScoringSignalsDownloaded, + base::Unretained(this), score_ad_task)); return; } @@ -262,11 +260,11 @@ const std::string& ad_metadata_json, double bid, blink::mojom::AuctionAdConfigPtr auction_config, - std::unique_ptr<TrustedScoringSignals::Result> trusted_scoring_signals, + std::unique_ptr<TrustedSignals::Result> trusted_scoring_signals, const url::Origin& browser_signal_top_window_origin, const url::Origin& browser_signal_interest_group_owner, const GURL& browser_signal_render_url, - const std::vector<GURL>& browser_signal_ad_components, + const std::vector<std::string>& browser_signal_ad_components, uint32_t browser_signal_bidding_duration_msecs, ScoreAdCallbackInternal callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_); @@ -295,15 +293,9 @@ v8::Local<v8::Value> trusted_scoring_signals_value; if (trusted_scoring_signals) { - // TODO(mmenke): It's doubtless more efficient to just make GetSignals() - // take a vector of ad components instead of a set, though this API will - // likely change when we implement caching, anyways. - std::set<GURL> ad_component_render_urls( - browser_signal_ad_components.begin(), - browser_signal_ad_components.end()); - trusted_scoring_signals_value = trusted_scoring_signals->GetSignals( + trusted_scoring_signals_value = trusted_scoring_signals->GetScoringSignals( v8_helper_.get(), context, browser_signal_render_url, - ad_component_render_urls); + browser_signal_ad_components); } else { trusted_scoring_signals_value = v8::Null(isolate); } @@ -325,11 +317,8 @@ return; } if (!browser_signal_ad_components.empty()) { - std::vector<std::string> ad_component_url_strings; - for (const GURL& url : browser_signal_ad_components) { - ad_component_url_strings.push_back(url.spec()); - } - if (!browser_signals_dict.Set("adComponents", ad_component_url_strings)) { + if (!browser_signals_dict.Set("adComponents", + browser_signal_ad_components)) { PostScoreAdCallbackToUserThread(std::move(callback), /*score=*/0, /*errors=*/std::vector<std::string>()); return; @@ -541,7 +530,7 @@ void SellerWorklet::OnTrustedScoringSignalsDownloaded( ScoreAdTaskList::iterator task, - std::unique_ptr<TrustedScoringSignals::Result> result, + std::unique_ptr<TrustedSignals::Result> result, absl::optional<std::string> error_msg) { DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
diff --git a/content/services/auction_worklet/seller_worklet.h b/content/services/auction_worklet/seller_worklet.h index 2db775a9..e5d698c 100644 --- a/content/services/auction_worklet/seller_worklet.h +++ b/content/services/auction_worklet/seller_worklet.h
@@ -18,7 +18,7 @@ #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom-forward.h" #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h" #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h" -#include "content/services/auction_worklet/trusted_scoring_signals.h" +#include "content/services/auction_worklet/trusted_signals.h" #include "content/services/auction_worklet/worklet_loader.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/remote.h" @@ -93,12 +93,15 @@ url::Origin browser_signal_top_window_origin; url::Origin browser_signal_interest_group_owner; GURL browser_signal_render_url; - std::vector<GURL> browser_signal_ad_components; + // While these are URLs, it's more concenient to store these as strings + // rather than GURLs, both for creating a v8 array from, and for sharing + // ScoringSignals code with BidderWorklets. + std::vector<std::string> browser_signal_ad_components; uint32_t browser_signal_bidding_duration_msecs; ScoreAdCallback callback; - std::unique_ptr<TrustedScoringSignals> trusted_scoring_signals; + std::unique_ptr<TrustedSignals> trusted_scoring_signals; // Error message from downloading trusted scoring signals, if any. Prepended // to errors passed to the ScoreAdCallback. @@ -129,11 +132,11 @@ const std::string& ad_metadata_json, double bid, blink::mojom::AuctionAdConfigPtr auction_config, - std::unique_ptr<TrustedScoringSignals::Result> trusted_scoring_signals, + std::unique_ptr<TrustedSignals::Result> trusted_scoring_signals, const url::Origin& browser_signal_top_window_origin, const url::Origin& browser_signal_interest_group_owner, const GURL& browser_signal_render_url, - const std::vector<GURL>& browser_signal_ad_components, + const std::vector<std::string>& browser_signal_ad_components, uint32_t browser_signal_bidding_duration_msecs, ScoreAdCallbackInternal callback); @@ -193,7 +196,7 @@ // V8 thread. void OnTrustedScoringSignalsDownloaded( ScoreAdTaskList::iterator task, - std::unique_ptr<TrustedScoringSignals::Result> result, + std::unique_ptr<TrustedSignals::Result> result, absl::optional<std::string> error_msg); void DeliverScoreAdCallbackOnUserThread(ScoreAdTaskList::iterator task,
diff --git a/content/services/auction_worklet/trusted_bidding_signals.cc b/content/services/auction_worklet/trusted_bidding_signals.cc deleted file mode 100644 index ce23b6b..0000000 --- a/content/services/auction_worklet/trusted_bidding_signals.cc +++ /dev/null
@@ -1,189 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "content/services/auction_worklet/trusted_bidding_signals.h" - -#include <memory> -#include <string> -#include <vector> - -#include "base/bind.h" -#include "base/callback.h" -#include "base/logging.h" -#include "base/strings/strcat.h" -#include "content/services/auction_worklet/auction_downloader.h" -#include "content/services/auction_worklet/auction_v8_helper.h" -#include "gin/converter.h" -#include "net/base/escape.h" -#include "services/network/public/mojom/url_loader_factory.mojom-forward.h" -#include "url/gurl.h" -#include "v8/include/v8-context.h" -#include "v8/include/v8-json.h" -#include "v8/include/v8-object.h" -#include "v8/include/v8-primitive.h" - -namespace auction_worklet { - -TrustedBiddingSignals::Result::Result( - std::map<std::string, std::string> json_data) - : json_data_(std::move(json_data)) {} - -TrustedBiddingSignals::Result::~Result() = default; - -v8::Local<v8::Object> TrustedBiddingSignals::Result::GetSignals( - AuctionV8Helper* v8_helper, - v8::Local<v8::Context> context, - const std::vector<std::string>& trusted_bidding_signals_keys) const { - v8::Local<v8::Object> v8_object = v8::Object::New(v8_helper->isolate()); - for (const auto& key : trusted_bidding_signals_keys) { - auto data = json_data_.find(key); - // InsertJsonValue() shouldn't be able to fail, but the first check might. - if (data == json_data_.end() || - !v8_helper->InsertJsonValue(context, key, data->second, v8_object)) { - bool result = v8_helper->InsertValue(key, v8::Null(v8_helper->isolate()), - v8_object); - DCHECK(result); - } - } - return v8_object; -} - -TrustedBiddingSignals::TrustedBiddingSignals( - network::mojom::URLLoaderFactory* url_loader_factory, - std::vector<std::string> trusted_bidding_signals_keys, - const std::string& hostname, - const GURL& trusted_bidding_signals_url, - scoped_refptr<AuctionV8Helper> v8_helper, - LoadSignalsCallback load_signals_callback) - : trusted_bidding_signals_url_(trusted_bidding_signals_url), - v8_helper_(std::move(v8_helper)), - load_signals_callback_(std::move(load_signals_callback)) { - DCHECK(v8_helper_); - DCHECK(!trusted_bidding_signals_keys.empty()); - DCHECK(load_signals_callback_); - - std::string query_params = - "hostname=" + net::EscapeQueryParamValue(hostname, true); - - query_params += "&keys="; - bool first_key = true; - for (const auto& key : trusted_bidding_signals_keys) { - if (first_key) { - first_key = false; - } else { - query_params.append(","); - } - query_params.append(net::EscapeQueryParamValue(key, true)); - } - - GURL::Replacements replacements; - replacements.SetQueryStr(query_params); - GURL final_url = trusted_bidding_signals_url.ReplaceComponents(replacements); - - auction_downloader_ = std::make_unique<AuctionDownloader>( - url_loader_factory, final_url, AuctionDownloader::MimeType::kJson, - base::BindOnce(&TrustedBiddingSignals::OnDownloadComplete, - base::Unretained(this), - std::move(trusted_bidding_signals_keys))); -} - -TrustedBiddingSignals::~TrustedBiddingSignals() = default; - -void TrustedBiddingSignals::OnDownloadComplete( - std::vector<std::string> trusted_bidding_signals_keys, - std::unique_ptr<std::string> body, - absl::optional<std::string> error_msg) { - auction_downloader_.reset(); - - v8_helper_->v8_runner()->PostTask( - FROM_HERE, - base::BindOnce(&TrustedBiddingSignals::HandleDownloadResultOnV8Thread, - v8_helper_, trusted_bidding_signals_url_, - std::move(trusted_bidding_signals_keys), std::move(body), - std::move(error_msg), - base::SequencedTaskRunnerHandle::Get(), - weak_ptr_factory.GetWeakPtr())); -} - -// static -void TrustedBiddingSignals::HandleDownloadResultOnV8Thread( - scoped_refptr<AuctionV8Helper> v8_helper, - const GURL& trusted_bidding_signals_url, - std::vector<std::string> trusted_bidding_signals_keys, - std::unique_ptr<std::string> body, - absl::optional<std::string> error_msg, - scoped_refptr<base::SequencedTaskRunner> user_thread_task_runner, - base::WeakPtr<TrustedBiddingSignals> weak_instance) { - if (!body) { - PostCallbackToUserThread(std::move(user_thread_task_runner), weak_instance, - nullptr, std::move(error_msg)); - return; - } - - DCHECK(!error_msg.has_value()); - - AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper.get()); - v8::Context::Scope context_scope(v8_helper->scratch_context()); - - v8::Local<v8::Value> v8_data; - if (!v8_helper->CreateValueFromJson(v8_helper->scratch_context(), *body) - .ToLocal(&v8_data) || - !v8_data->IsObject()) { - std::string error = base::StrCat({trusted_bidding_signals_url.spec(), - " Unable to parse as a JSON object."}); - PostCallbackToUserThread(std::move(user_thread_task_runner), weak_instance, - nullptr, std::move(error)); - return; - } - - v8::Local<v8::Object> v8_object = v8_data.As<v8::Object>(); - - std::map<std::string, std::string> json_data; - for (const auto& key : trusted_bidding_signals_keys) { - v8::Local<v8::String> v8_key; - if (!v8_helper->CreateUtf8String(key).ToLocal(&v8_key)) { - PostCallbackToUserThread(std::move(user_thread_task_runner), - weak_instance, nullptr, absl::nullopt); - - return; - } - // Only the Get() call should be able to fail. - v8::Local<v8::Value> v8_value; - v8::Local<v8::Value> v8_string_value; - std::string value; - if (!v8_object->Get(v8_helper->scratch_context(), v8_key) - .ToLocal(&v8_value) || - !v8::JSON::Stringify(v8_helper->scratch_context(), v8_value) - .ToLocal(&v8_string_value) || - !gin::ConvertFromV8(v8_helper->isolate(), v8_string_value, &value)) { - continue; - } - json_data[key] = std::move(value); - } - - PostCallbackToUserThread(std::move(user_thread_task_runner), weak_instance, - std::make_unique<Result>(std::move(json_data)), - absl::nullopt); -} - -// static -void TrustedBiddingSignals::PostCallbackToUserThread( - scoped_refptr<base::SequencedTaskRunner> user_thread_task_runner, - base::WeakPtr<TrustedBiddingSignals> weak_instance, - std::unique_ptr<Result> result, - absl::optional<std::string> error_msg) { - user_thread_task_runner->PostTask( - FROM_HERE, - base::BindOnce(&TrustedBiddingSignals::DeliverCallbackOnUserThread, - weak_instance, std::move(result), std::move(error_msg))); -} - -void TrustedBiddingSignals::DeliverCallbackOnUserThread( - std::unique_ptr<Result> result, - absl::optional<std::string> error_msg) { - std::move(load_signals_callback_) - .Run(std::move(result), std::move(error_msg)); -} - -} // namespace auction_worklet
diff --git a/content/services/auction_worklet/trusted_bidding_signals.h b/content/services/auction_worklet/trusted_bidding_signals.h deleted file mode 100644 index 29900c9..0000000 --- a/content/services/auction_worklet/trusted_bidding_signals.h +++ /dev/null
@@ -1,121 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CONTENT_SERVICES_AUCTION_WORKLET_TRUSTED_BIDDING_SIGNALS_H_ -#define CONTENT_SERVICES_AUCTION_WORKLET_TRUSTED_BIDDING_SIGNALS_H_ - -#include <map> -#include <memory> -#include <string> -#include <vector> - -#include "base/callback.h" -#include "services/network/public/mojom/url_loader_factory.mojom-forward.h" -#include "third_party/abseil-cpp/absl/types/optional.h" -#include "url/gurl.h" -#include "v8/include/v8-forward.h" - -namespace auction_worklet { - -class AuctionDownloader; -class AuctionV8Helper; - -// Represents the trusted bidding signals that are part of the FLEDGE bidding -// system (https://github.com/WICG/turtledove/blob/main/FLEDGE.md). Fetches and -// parses the hosted JSON data files needed by the bidder worklets. -// -// TODO(mmenke): This class currently does 4 copies when loading the data (To V8 -// string, use V8's JSON parser, split data into V8 JSON subcomponent strings, -// convert to C++ strings), and 2 copies of each substring to use the data (To -// V8 per-key JSON string, use V8's JSON parser). Keeping the data stored as V8 -// JSON subcomponents would remove 2 copies, without too much complexity. Could -// even implement V8 deep-copy logic, to remove two more copies (counting the -// clone operation as a copy). -class TrustedBiddingSignals { - public: - // Contains the values returned by the server. - // - // This can be created and destroyed on any thread, but GetSignals() can only - // be used on the V8 thread. - class Result { - public: - explicit Result(std::map<std::string, std::string> json_data); - explicit Result(const Result&) = delete; - ~Result(); - Result& operator=(const Result&) = delete; - - // Get the signals associated with the provided `keys`. `v8_helper`'s - // Isolate must be active (in particular, this must be on the v8 thread), - // and `context` must be the active context. `keys` must be a subset of - // those provided when creating the TrustedBiddingSignals object. Always - // returns a non-empty value (which may be an Object with no fields). - v8::Local<v8::Object> GetSignals( - AuctionV8Helper* v8_helper, - v8::Local<v8::Context> context, - const std::vector<std::string>& trusted_bidding_signals_keys) const; - - private: - // Map of keys to their associated JSON data. - std::map<std::string, std::string> json_data_; - }; - - using LoadSignalsCallback = - base::OnceCallback<void(std::unique_ptr<Result> result, - absl::optional<std::string> error_msg)>; - - // Starts loading the JSON data on construction. `trusted_bidding_signals_url` - // must be the base URL (no query params added). Callback will be invoked - // asynchronously once the data has been fetched or an error has occurred. - // Fails if the URL already has a query param (or has a location or embedded - // credentials) or if the response is not JSON. If some or all keys are - // missing, still succeeds, and GetSignals() will populate them with nulls. - // - // There are no lifetime constraints of `url_loader_factory`. - TrustedBiddingSignals(network::mojom::URLLoaderFactory* url_loader_factory, - std::vector<std::string> trusted_bidding_signals_keys, - const std::string& hostname, - const GURL& trusted_bidding_signals_url, - scoped_refptr<AuctionV8Helper> v8_helper, - LoadSignalsCallback load_signals_callback); - explicit TrustedBiddingSignals(const TrustedBiddingSignals&) = delete; - TrustedBiddingSignals& operator=(const TrustedBiddingSignals&) = delete; - ~TrustedBiddingSignals(); - - private: - void OnDownloadComplete(std::vector<std::string> trusted_bidding_signals_keys, - std::unique_ptr<std::string> body, - absl::optional<std::string> error_msg); - - static void HandleDownloadResultOnV8Thread( - scoped_refptr<AuctionV8Helper> v8_helper, - const GURL& trusted_bidding_signals_url, - std::vector<std::string> trusted_bidding_signals_keys, - std::unique_ptr<std::string> body, - absl::optional<std::string> error_msg, - scoped_refptr<base::SequencedTaskRunner> user_thread_task_runner, - base::WeakPtr<TrustedBiddingSignals> weak_instance); - - // Called from V8 thread. - static void PostCallbackToUserThread( - scoped_refptr<base::SequencedTaskRunner> user_thread_task_runner, - base::WeakPtr<TrustedBiddingSignals> weak_instance, - std::unique_ptr<Result> result, - absl::optional<std::string> error_msg); - - // Called on user thread. - void DeliverCallbackOnUserThread(std::unique_ptr<Result>, - absl::optional<std::string> error_msg); - - const GURL trusted_bidding_signals_url_; // original, for error messages. - const scoped_refptr<AuctionV8Helper> v8_helper_; - - LoadSignalsCallback load_signals_callback_; - std::unique_ptr<AuctionDownloader> auction_downloader_; - - base::WeakPtrFactory<TrustedBiddingSignals> weak_ptr_factory{this}; -}; - -} // namespace auction_worklet - -#endif // CONTENT_SERVICES_AUCTION_WORKLET_TRUSTED_BIDDING_SIGNALS_H_
diff --git a/content/services/auction_worklet/trusted_bidding_signals_unittest.cc b/content/services/auction_worklet/trusted_bidding_signals_unittest.cc deleted file mode 100644 index e063d8b..0000000 --- a/content/services/auction_worklet/trusted_bidding_signals_unittest.cc +++ /dev/null
@@ -1,241 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "content/services/auction_worklet/trusted_bidding_signals.h" - -#include <string> -#include <utility> -#include <vector> - -#include "base/bind.h" -#include "base/run_loop.h" -#include "base/synchronization/waitable_event.h" -#include "base/test/task_environment.h" -#include "content/services/auction_worklet/auction_v8_helper.h" -#include "content/services/auction_worklet/worklet_test_util.h" -#include "net/http/http_status_code.h" -#include "services/network/test/test_url_loader_factory.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "url/gurl.h" -#include "v8/include/v8-context.h" -#include "v8/include/v8-forward.h" - -namespace auction_worklet { -namespace { - -// Common JSON used for most tests. Key 4 is deliberately skipped. -const char kBaseJson[] = R"( - { - "key1": 1, - "key2": [2], - "key3": null, - "key5": "value5", - "key 6": 6, - "key=7": 7, - "key,8": 8 - } -)"; - -const char kHostname[] = "publisher"; - -class TrustedBiddingSignalsTest : public testing::Test { - public: - TrustedBiddingSignalsTest() { - v8_helper_ = AuctionV8Helper::Create(AuctionV8Helper::CreateTaskRunner()); - } - - ~TrustedBiddingSignalsTest() override { task_environment_.RunUntilIdle(); } - - // Sets the HTTP response and then fetches bidding signals and waits for - // completion. Returns nullptr on failure. - std::unique_ptr<TrustedBiddingSignals::Result> - FetchBiddingSignalsWithResponse( - const GURL& url, - const std::string& response, - std::vector<std::string> trusted_bidding_signals_keys, - const std::string& hostname) { - AddJsonResponse(&url_loader_factory_, url, response); - return FetchBiddingSignals(trusted_bidding_signals_keys, hostname); - } - - // Fetches bidding signals and waits for completion. Returns nullptr on - // failure. - std::unique_ptr<TrustedBiddingSignals::Result> FetchBiddingSignals( - std::vector<std::string> trusted_bidding_signals_keys, - const std::string& hostname) { - CHECK(!load_signals_run_loop_); - - DCHECK(!load_signals_result_); - auto bidding_signals = std::make_unique<TrustedBiddingSignals>( - &url_loader_factory_, std::move(trusted_bidding_signals_keys), - std::move(hostname), base_url_, v8_helper_, - base::BindOnce(&TrustedBiddingSignalsTest::LoadSignalsCallback, - base::Unretained(this))); - load_signals_run_loop_ = std::make_unique<base::RunLoop>(); - load_signals_run_loop_->Run(); - load_signals_run_loop_.reset(); - return std::move(load_signals_result_); - } - - // Returns the results of calling TrustedBiddingSignals::Result::GetSignals() - // with `trusted_bidding_signals_keys`. Returns value as a JSON std::string, - // for easy testing. - std::string ExtractSignals( - TrustedBiddingSignals::Result* signals, - std::vector<std::string> trusted_bidding_signals_keys) { - base::RunLoop run_loop; - - std::string result; - v8_helper_->v8_runner()->PostTask( - FROM_HERE, - base::BindOnce( - [](scoped_refptr<AuctionV8Helper> v8_helper, - TrustedBiddingSignals::Result* signals, - std::vector<std::string> trusted_bidding_signals_keys, - std::string* result_out, base::OnceClosure quit_closure) { - AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper.get()); - v8::Isolate* isolate = v8_helper->isolate(); - // Could use the scratch context, but using a separate one more - // closely resembles actual use. - v8::Local<v8::Context> context = v8::Context::New(isolate); - v8::Context::Scope context_scope(context); - - v8::Local<v8::Value> value = signals->GetSignals( - v8_helper.get(), context, trusted_bidding_signals_keys); - - if (!v8_helper->ExtractJson(context, value, result_out)) { - *result_out = "JSON extraction failed."; - } - std::move(quit_closure).Run(); - }, - v8_helper_, signals, std::move(trusted_bidding_signals_keys), - &result, run_loop.QuitClosure())); - run_loop.Run(); - return result; - } - - protected: - void LoadSignalsCallback( - std::unique_ptr<TrustedBiddingSignals::Result> result, - absl::optional<std::string> error_msg) { - load_signals_result_ = std::move(result); - error_msg_ = std::move(error_msg); - EXPECT_EQ(load_signals_result_ == nullptr, error_msg_.has_value()); - load_signals_run_loop_->Quit(); - } - - base::test::TaskEnvironment task_environment_; - - // URL without query params attached. - const GURL base_url_ = GURL("https://url.test/"); - - // Reuseable run loop for loading the signals. It's always populated after - // creating the worklet, to cause a crash if the callback is invoked - // synchronously. - std::unique_ptr<base::RunLoop> load_signals_run_loop_; - std::unique_ptr<TrustedBiddingSignals::Result> load_signals_result_; - absl::optional<std::string> error_msg_; - - network::TestURLLoaderFactory url_loader_factory_; - scoped_refptr<AuctionV8Helper> v8_helper_; -}; - -TEST_F(TrustedBiddingSignalsTest, NetworkError) { - url_loader_factory_.AddResponse( - "https://url.test/?hostname=publisher&keys=key1", kBaseJson, - net::HTTP_NOT_FOUND); - EXPECT_FALSE(FetchBiddingSignals({"key1"}, kHostname)); - ASSERT_TRUE(error_msg_.has_value()); - EXPECT_EQ( - "Failed to load https://url.test/?hostname=publisher&keys=key1 " - "HTTP status = 404 Not Found.", - error_msg_.value()); -} - -TEST_F(TrustedBiddingSignalsTest, ResponseNotJson) { - EXPECT_FALSE(FetchBiddingSignalsWithResponse( - GURL("https://url.test/?hostname=publisher&keys=key1"), "Not Json", - {"key1"}, kHostname)); - ASSERT_TRUE(error_msg_.has_value()); - EXPECT_EQ("https://url.test/ Unable to parse as a JSON object.", - error_msg_.value()); -} - -TEST_F(TrustedBiddingSignalsTest, ResponseNotObject) { - EXPECT_FALSE(FetchBiddingSignalsWithResponse( - GURL("https://url.test/?hostname=publisher&keys=key1"), "42", {"key1"}, - kHostname)); - ASSERT_TRUE(error_msg_.has_value()); - EXPECT_EQ("https://url.test/ Unable to parse as a JSON object.", - error_msg_.value()); -} - -TEST_F(TrustedBiddingSignalsTest, KeyMissing) { - std::unique_ptr<TrustedBiddingSignals::Result> signals = - FetchBiddingSignalsWithResponse( - GURL("https://url.test/?hostname=publisher&keys=key4"), kBaseJson, - {"key4"}, kHostname); - ASSERT_TRUE(signals); - EXPECT_EQ(R"({"key4":null})", ExtractSignals(signals.get(), {"key4"})); -} - -TEST_F(TrustedBiddingSignalsTest, FetchOneKey) { - std::unique_ptr<TrustedBiddingSignals::Result> signals = - FetchBiddingSignalsWithResponse( - GURL("https://url.test/?hostname=publisher&keys=key1"), kBaseJson, - {"key1"}, kHostname); - ASSERT_TRUE(signals); - EXPECT_EQ(R"({"key1":1})", ExtractSignals(signals.get(), {"key1"})); -} - -TEST_F(TrustedBiddingSignalsTest, FetchMultipleKeys) { - std::unique_ptr<TrustedBiddingSignals::Result> signals = - FetchBiddingSignalsWithResponse( - GURL("https://url.test/?hostname=publisher&keys=key3,key1,key5,key2"), - kBaseJson, {"key3", "key1", "key5", "key2"}, kHostname); - ASSERT_TRUE(signals); - EXPECT_EQ(R"({"key1":1})", ExtractSignals(signals.get(), {"key1"})); - EXPECT_EQ(R"({"key2":[2]})", ExtractSignals(signals.get(), {"key2"})); - EXPECT_EQ(R"({"key3":null})", ExtractSignals(signals.get(), {"key3"})); - EXPECT_EQ(R"({"key5":"value5"})", ExtractSignals(signals.get(), {"key5"})); - EXPECT_EQ(R"({"key1":1,"key2":[2],"key3":null,"key5":"value5"})", - ExtractSignals(signals.get(), {"key1", "key2", "key3", "key5"})); -} - -TEST_F(TrustedBiddingSignalsTest, EscapeQueryParams) { - std::unique_ptr<TrustedBiddingSignals::Result> signals = - FetchBiddingSignalsWithResponse( - GURL("https://url.test/" - "?hostname=pub+li%26sher&keys=key+6,key%3D7,key%2C8"), - kBaseJson, {"key 6", "key=7", "key,8"}, "pub li&sher"); - ASSERT_TRUE(signals); - EXPECT_EQ(R"({"key 6":6})", ExtractSignals(signals.get(), {"key 6"})); - EXPECT_EQ(R"({"key=7":7})", ExtractSignals(signals.get(), {"key=7"})); - EXPECT_EQ(R"({"key,8":8})", ExtractSignals(signals.get(), {"key,8"})); -} - -// Testcase where the loader is deleted after it queued the parsing of -// the script on V8 thread, but before it gets to finish. -TEST_F(TrustedBiddingSignalsTest, DeleteBeforeCallback) { - GURL url("https://url.test/?hostname=publisher&keys=key1"); - - AddJsonResponse(&url_loader_factory_, url, kBaseJson); - - // Wedge the V8 thread to control when the JSON parsing takes place. - base::WaitableEvent* event_handle = WedgeV8Thread(v8_helper_.get()); - - auto bidding_signals = std::make_unique<TrustedBiddingSignals>( - &url_loader_factory_, std::vector<std::string>{"key1"}, "publisher", - base_url_, v8_helper_, - base::BindOnce([](std::unique_ptr<TrustedBiddingSignals::Result> result, - absl::optional<std::string> error_msg) { - ADD_FAILURE() << "Callback should not be invoked since loader deleted"; - })); - base::RunLoop().RunUntilIdle(); - bidding_signals.reset(); - event_handle->Signal(); -} - -} // namespace -} // namespace auction_worklet
diff --git a/content/services/auction_worklet/trusted_scoring_signals.cc b/content/services/auction_worklet/trusted_scoring_signals.cc deleted file mode 100644 index ffd05992..0000000 --- a/content/services/auction_worklet/trusted_scoring_signals.cc +++ /dev/null
@@ -1,271 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "content/services/auction_worklet/trusted_scoring_signals.h" - -#include <memory> -#include <set> -#include <string> - -#include "base/bind.h" -#include "base/callback.h" -#include "base/logging.h" -#include "base/strings/strcat.h" -#include "base/strings/stringprintf.h" -#include "content/services/auction_worklet/auction_downloader.h" -#include "content/services/auction_worklet/auction_v8_helper.h" -#include "gin/converter.h" -#include "net/base/escape.h" -#include "services/network/public/mojom/url_loader_factory.mojom-forward.h" -#include "url/gurl.h" -#include "v8/include/v8-context.h" -#include "v8/include/v8-json.h" -#include "v8/include/v8-object.h" -#include "v8/include/v8-primitive.h" - -namespace auction_worklet { - -namespace { - -// Creates a query param of the form `&<name>=<urls in comma-delimited list>`. -// Returns an empty string if `urls` is empty. `name` will not be escaped, but -// entries in `urls` will be. -std::string CreateQueryParam(const char* name, const std::set<GURL>& urls) { - if (urls.empty()) - return std::string(); - std::string query_param = base::StringPrintf("&%s=", name); - bool first_key = true; - for (const auto& url : urls) { - if (first_key) { - first_key = false; - } else { - query_param.append(","); - } - query_param.append( - net::EscapeQueryParamValue(url.spec(), /*use_plus=*/true)); - } - return query_param; -} - -// Extracts GURL/JSON key/value pairs from the object named `name` in -// `v8_object`, using values in `urls` as keys. Does not add entries to the map -// for keys with missing values. -std::map<GURL, std::string> ExtractUrlMap(AuctionV8Helper* v8_helper, - v8::Local<v8::Object> v8_object, - const char* name, - const std::set<GURL>& urls) { - std::map<GURL, std::string> out; - if (urls.empty()) - return out; - - v8::Local<v8::Value> named_object_value; - // Don't consider the entire object missing a fatal error. - if (!v8_object - ->Get(v8_helper->scratch_context(), - v8_helper->CreateStringFromLiteral(name)) - .ToLocal(&named_object_value) || - !named_object_value->IsObject()) { - return out; - } - - v8::Local<v8::Object> named_object = named_object_value.As<v8::Object>(); - for (const auto& url : urls) { - v8::Local<v8::String> v8_key; - if (!v8_helper->CreateUtf8String(url.spec()).ToLocal(&v8_key)) - continue; - - v8::Local<v8::Value> v8_value; - v8::Local<v8::Value> v8_string_value; - std::string value; - // Only the Get() call should be able to fail. - if (!named_object->Get(v8_helper->scratch_context(), v8_key) - .ToLocal(&v8_value) || - !v8::JSON::Stringify(v8_helper->scratch_context(), v8_value) - .ToLocal(&v8_string_value) || - !gin::ConvertFromV8(v8_helper->isolate(), v8_string_value, &value)) { - continue; - } - out[url] = std::move(value); - } - return out; -} - -} // namespace - -TrustedScoringSignals::Result::Result( - std::map<GURL, std::string> render_url_json_data, - std::map<GURL, std::string> ad_component_json_data) - : render_url_json_data_(std::move(render_url_json_data)), - ad_component_json_data_(std::move(ad_component_json_data)) {} - -TrustedScoringSignals::Result::~Result() = default; - -v8::Local<v8::Object> TrustedScoringSignals::Result::GetSignals( - AuctionV8Helper* v8_helper, - v8::Local<v8::Context> context, - const GURL& render_url, - const std::set<GURL>& ad_component_render_urls) const { - v8::Local<v8::Object> out = v8::Object::New(v8_helper->isolate()); - - // Create renderUrl sub-object, and add it to to `out`. - v8::Local<v8::Object> render_url_v8_object = - v8::Object::New(v8_helper->isolate()); - auto render_url_data = render_url_json_data_.find(render_url); - // InsertJsonValue() shouldn't be able to fail, but the first check might. - if (render_url_data == render_url_json_data_.end() || - !v8_helper->InsertJsonValue(context, render_url.spec(), - render_url_data->second, - render_url_v8_object)) { - bool result = v8_helper->InsertValue(render_url.spec(), - v8::Null(v8_helper->isolate()), - render_url_v8_object); - DCHECK(result); - } - bool result = v8_helper->InsertValue("renderUrl", render_url_v8_object, out); - DCHECK(result); - - // If there are any ad components, assemble and add an `adComponentRenderUrls` - // object as well. - if (!ad_component_render_urls.empty()) { - v8::Local<v8::Object> ad_components_v8_object = - v8::Object::New(v8_helper->isolate()); - for (const auto& url : ad_component_render_urls) { - auto data = ad_component_json_data_.find(url); - // InsertJsonValue() shouldn't be able to fail, but the first check might. - if (data == ad_component_json_data_.end() || - !v8_helper->InsertJsonValue(context, url.spec(), data->second, - ad_components_v8_object)) { - bool result = - v8_helper->InsertValue(url.spec(), v8::Null(v8_helper->isolate()), - ad_components_v8_object); - DCHECK(result); - } - } - bool result = v8_helper->InsertValue("adComponentRenderUrls", - ad_components_v8_object, out); - DCHECK(result); - } - - return out; -} - -TrustedScoringSignals::TrustedScoringSignals( - network::mojom::URLLoaderFactory* url_loader_factory, - std::set<GURL> render_urls, - std::set<GURL> ad_component_render_urls, - const std::string& hostname, - const GURL& trusted_scoring_signals_url, - scoped_refptr<AuctionV8Helper> v8_helper, - LoadSignalsCallback load_signals_callback) - : trusted_scoring_signals_url_(trusted_scoring_signals_url), - v8_helper_(std::move(v8_helper)), - load_signals_callback_(std::move(load_signals_callback)) { - DCHECK(v8_helper_); - // Allow `render_urls` or `ad_component_render_urls` to be empty, but not - // both. - DCHECK(!render_urls.empty() || !ad_component_render_urls.empty()); - DCHECK(load_signals_callback_); - - std::string query_params = - "hostname=" + net::EscapeQueryParamValue(hostname, true) + - CreateQueryParam("renderUrls", render_urls) + - CreateQueryParam("adComponentRenderUrls", ad_component_render_urls); - - GURL::Replacements replacements; - replacements.SetQueryStr(query_params); - GURL final_url = trusted_scoring_signals_url.ReplaceComponents(replacements); - - auction_downloader_ = std::make_unique<AuctionDownloader>( - url_loader_factory, final_url, AuctionDownloader::MimeType::kJson, - base::BindOnce(&TrustedScoringSignals::OnDownloadComplete, - base::Unretained(this), std::move(render_urls), - std::move(ad_component_render_urls))); -} - -TrustedScoringSignals::~TrustedScoringSignals() = default; - -void TrustedScoringSignals::OnDownloadComplete( - std::set<GURL> render_urls, - std::set<GURL> ad_component_render_urls, - std::unique_ptr<std::string> body, - absl::optional<std::string> error_msg) { - auction_downloader_.reset(); - - v8_helper_->v8_runner()->PostTask( - FROM_HERE, - base::BindOnce( - &TrustedScoringSignals::HandleDownloadResultOnV8Thread, v8_helper_, - trusted_scoring_signals_url_, std::move(render_urls), - std::move(ad_component_render_urls), std::move(body), - std::move(error_msg), base::SequencedTaskRunnerHandle::Get(), - weak_ptr_factory.GetWeakPtr())); -} - -// static -void TrustedScoringSignals::HandleDownloadResultOnV8Thread( - scoped_refptr<AuctionV8Helper> v8_helper, - const GURL& trusted_scoring_signals_url, - std::set<GURL> render_urls, - std::set<GURL> ad_component_render_urls, - std::unique_ptr<std::string> body, - absl::optional<std::string> error_msg, - scoped_refptr<base::SequencedTaskRunner> user_thread_task_runner, - base::WeakPtr<TrustedScoringSignals> weak_instance) { - if (!body) { - PostCallbackToUserThread(std::move(user_thread_task_runner), weak_instance, - nullptr, std::move(error_msg)); - return; - } - - DCHECK(!error_msg.has_value()); - - AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper.get()); - v8::Context::Scope context_scope(v8_helper->scratch_context()); - - v8::Local<v8::Value> v8_data; - if (!v8_helper->CreateValueFromJson(v8_helper->scratch_context(), *body) - .ToLocal(&v8_data) || - !v8_data->IsObject()) { - std::string error = base::StrCat({trusted_scoring_signals_url.spec(), - " Unable to parse as a JSON object."}); - PostCallbackToUserThread(std::move(user_thread_task_runner), weak_instance, - nullptr, std::move(error)); - return; - } - - v8::Local<v8::Object> v8_object = v8_data.As<v8::Object>(); - - std::map<GURL, std::string> render_url_json_data = - ExtractUrlMap(v8_helper.get(), v8_object, "renderUrls", render_urls); - std::map<GURL, std::string> ad_component_json_data = - ExtractUrlMap(v8_helper.get(), v8_object, "adComponentRenderUrls", - ad_component_render_urls); - - PostCallbackToUserThread( - std::move(user_thread_task_runner), weak_instance, - std::make_unique<Result>(std::move(render_url_json_data), - std::move(ad_component_json_data)), - absl::nullopt); -} - -// static -void TrustedScoringSignals::PostCallbackToUserThread( - scoped_refptr<base::SequencedTaskRunner> user_thread_task_runner, - base::WeakPtr<TrustedScoringSignals> weak_instance, - std::unique_ptr<Result> result, - absl::optional<std::string> error_msg) { - user_thread_task_runner->PostTask( - FROM_HERE, - base::BindOnce(&TrustedScoringSignals::DeliverCallbackOnUserThread, - weak_instance, std::move(result), std::move(error_msg))); -} - -void TrustedScoringSignals::DeliverCallbackOnUserThread( - std::unique_ptr<Result> result, - absl::optional<std::string> error_msg) { - std::move(load_signals_callback_) - .Run(std::move(result), std::move(error_msg)); -} - -} // namespace auction_worklet
diff --git a/content/services/auction_worklet/trusted_scoring_signals.h b/content/services/auction_worklet/trusted_scoring_signals.h deleted file mode 100644 index b0c52f1..0000000 --- a/content/services/auction_worklet/trusted_scoring_signals.h +++ /dev/null
@@ -1,130 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CONTENT_SERVICES_AUCTION_WORKLET_TRUSTED_SCORING_SIGNALS_H_ -#define CONTENT_SERVICES_AUCTION_WORKLET_TRUSTED_SCORING_SIGNALS_H_ - -#include <map> -#include <memory> -#include <set> -#include <string> - -#include "base/callback.h" -#include "services/network/public/mojom/url_loader_factory.mojom-forward.h" -#include "third_party/abseil-cpp/absl/types/optional.h" -#include "url/gurl.h" -#include "v8/include/v8-forward.h" - -namespace auction_worklet { - -class AuctionDownloader; -class AuctionV8Helper; - -// Represents the trusted scoring signals that are part of the FLEDGE bidding -// system (https://github.com/WICG/turtledove/blob/main/FLEDGE.md). Fetches and -// parses the hosted JSON data files needed by the seller worklets. -// -// TODO(mmenke): This class currently does 4 copies when loading the data (To V8 -// string, use V8's JSON parser, split data into V8 JSON subcomponent strings, -// convert to C++ strings), and 2 copies of each substring to use the data (To -// V8 per-key JSON string, use V8's JSON parser). Keeping the data stored as V8 -// JSON subcomponents would remove 2 copies, without too much complexity. Could -// even implement V8 deep-copy logic, to remove two more copies (counting the -// clone operation as a copy). -class TrustedScoringSignals { - public: - // Contains the values returned by the server. - // - // This can be created and destroyed on any thread, but GetSignals() can only - // be used on the V8 thread. - class Result { - public: - Result(std::map<GURL, std::string> render_url_json_data, - std::map<GURL, std::string> ad_component_json_data); - explicit Result(const Result&) = delete; - ~Result(); - Result& operator=(const Result&) = delete; - - // Retrieves the trusted scoring signals associated with the passed in urls, - // in the format expected by a worklet's scoreAd() method. `v8_helper`'s - // Isolate must be active (in particular, this must be on the v8 thread), - // and `context` must be the active context. `render_url` and - // `ad_component_render_urls` must be subsets of the corresponding sets of - // GURLs provided when creating the TrustedScoringSignals object. Always - // returns a non-empty value. - v8::Local<v8::Object> GetSignals( - AuctionV8Helper* v8_helper, - v8::Local<v8::Context> context, - const GURL& render_url, - const std::set<GURL>& ad_component_render_urls) const; - - private: - // Map of GURLs to their associated JSON data. - std::map<GURL, std::string> render_url_json_data_; - std::map<GURL, std::string> ad_component_json_data_; - }; - - using LoadSignalsCallback = - base::OnceCallback<void(std::unique_ptr<Result> result, - absl::optional<std::string> error_msg)>; - - // Starts loading the JSON data on construction. `trusted_scoring_signals_url` - // must be the base URL (no query params added). Callback will be invoked - // asynchronously once the data has been fetched or an error has occurred. - // Fails if the URL already has a query param (or has a location or embedded - // credentials) or if the response is not JSON. If some or all of the render - // URLs are missing, still succeeds, and GetSignals() will populate them with - // nulls. - // - // There are no lifetime constraints of `url_loader_factory`. - TrustedScoringSignals(network::mojom::URLLoaderFactory* url_loader_factory, - std::set<GURL> render_urls, - std::set<GURL> ad_component_render_urls, - const std::string& hostname, - const GURL& trusted_scoring_signals_url, - scoped_refptr<AuctionV8Helper> v8_helper, - LoadSignalsCallback load_signals_callback); - explicit TrustedScoringSignals(const TrustedScoringSignals&) = delete; - TrustedScoringSignals& operator=(const TrustedScoringSignals&) = delete; - ~TrustedScoringSignals(); - - private: - void OnDownloadComplete(std::set<GURL> render_urls, - std::set<GURL> ad_component_render_urls, - std::unique_ptr<std::string> body, - absl::optional<std::string> error_msg); - - static void HandleDownloadResultOnV8Thread( - scoped_refptr<AuctionV8Helper> v8_helper, - const GURL& trusted_scoring_signals_url, - std::set<GURL> render_urls, - std::set<GURL> ad_component_render_urls, - std::unique_ptr<std::string> body, - absl::optional<std::string> error_msg, - scoped_refptr<base::SequencedTaskRunner> user_thread_task_runner, - base::WeakPtr<TrustedScoringSignals> weak_instance); - - // Called from V8 thread. - static void PostCallbackToUserThread( - scoped_refptr<base::SequencedTaskRunner> user_thread_task_runner, - base::WeakPtr<TrustedScoringSignals> weak_instance, - std::unique_ptr<Result> result, - absl::optional<std::string> error_msg); - - // Called on user thread. - void DeliverCallbackOnUserThread(std::unique_ptr<Result>, - absl::optional<std::string> error_msg); - - const GURL trusted_scoring_signals_url_; // original, for error messages. - const scoped_refptr<AuctionV8Helper> v8_helper_; - - LoadSignalsCallback load_signals_callback_; - std::unique_ptr<AuctionDownloader> auction_downloader_; - - base::WeakPtrFactory<TrustedScoringSignals> weak_ptr_factory{this}; -}; - -} // namespace auction_worklet - -#endif // CONTENT_SERVICES_AUCTION_WORKLET_TRUSTED_SCORING_SIGNALS_H_
diff --git a/content/services/auction_worklet/trusted_scoring_signals_unittest.cc b/content/services/auction_worklet/trusted_scoring_signals_unittest.cc deleted file mode 100644 index 1ef2787..0000000 --- a/content/services/auction_worklet/trusted_scoring_signals_unittest.cc +++ /dev/null
@@ -1,384 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "content/services/auction_worklet/trusted_scoring_signals.h" - -#include <set> -#include <string> -#include <utility> - -#include "base/bind.h" -#include "base/run_loop.h" -#include "base/synchronization/waitable_event.h" -#include "base/test/bind.h" -#include "base/test/task_environment.h" -#include "content/services/auction_worklet/auction_v8_helper.h" -#include "content/services/auction_worklet/worklet_test_util.h" -#include "net/http/http_status_code.h" -#include "services/network/test/test_url_loader_factory.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "url/gurl.h" -#include "v8/include/v8-context.h" -#include "v8/include/v8-forward.h" - -namespace auction_worklet { -namespace { - -// Common JSON used by a number of tests. -const char kBaseJson[] = R"( - { - "renderUrls": { - "https://foo.test/": 1, - "https://bar.test/": [2], - "https://baz.test/": null, - "https://shared.test/": "render url" - }, - "adComponentRenderUrls": { - "https://foosub.test/": 2, - "https://barsub.test/": [3], - "https://bazsub.test/": null, - "https://shared.test/": "ad component url" - } - } -)"; - -const char kHostname[] = "publisher"; - -class TrustedScoringSignalsTest : public testing::Test { - public: - TrustedScoringSignalsTest() { - v8_helper_ = AuctionV8Helper::Create(AuctionV8Helper::CreateTaskRunner()); - } - - ~TrustedScoringSignalsTest() override { task_environment_.RunUntilIdle(); } - - // Sets the HTTP response and then fetches scoring signals and waits for - // completion. Returns nullptr on failure. - std::unique_ptr<TrustedScoringSignals::Result> - FetchScoringSignalsWithResponse(const GURL& url, - const std::string& response, - std::set<GURL> render_urls, - std::set<GURL> ad_component_render_urls, - const std::string& hostname) { - AddJsonResponse(&url_loader_factory_, url, response); - return FetchScoringSignals(render_urls, ad_component_render_urls, hostname); - } - - // Fetches scoring signals and waits for completion. Returns nullptr on - // failure. - std::unique_ptr<TrustedScoringSignals::Result> FetchScoringSignals( - std::set<GURL> render_urls, - std::set<GURL> ad_component_render_urls, - const std::string& hostname) { - CHECK(!load_signals_run_loop_); - - DCHECK(!load_signals_result_); - auto scoring_signals = std::make_unique<TrustedScoringSignals>( - &url_loader_factory_, std::move(render_urls), - std::move(ad_component_render_urls), std::move(hostname), base_url_, - v8_helper_, - base::BindOnce(&TrustedScoringSignalsTest::LoadSignalsCallback, - base::Unretained(this))); - load_signals_run_loop_ = std::make_unique<base::RunLoop>(); - load_signals_run_loop_->Run(); - load_signals_run_loop_.reset(); - return std::move(load_signals_result_); - } - - // Returns the results of calling TrustedScoringSignals::Result::GetSignals() - // with the provided parameters. Returns value as a JSON std::string, for easy - // testing. - std::string ExtractSignals(TrustedScoringSignals::Result* signals, - const GURL& render_url, - const std::set<GURL>& ad_component_render_urls) { - base::RunLoop run_loop; - - std::string result; - v8_helper_->v8_runner()->PostTask( - FROM_HERE, base::BindLambdaForTesting([&]() { - AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper_.get()); - v8::Isolate* isolate = v8_helper_->isolate(); - // Could use the scratch context, but using a separate one more - // closely resembles actual use. - v8::Local<v8::Context> context = v8::Context::New(isolate); - v8::Context::Scope context_scope(context); - - v8::Local<v8::Value> value = signals->GetSignals( - v8_helper_.get(), context, render_url, ad_component_render_urls); - - if (!v8_helper_->ExtractJson(context, value, &result)) { - result = "JSON extraction failed."; - } - run_loop.Quit(); - })); - run_loop.Run(); - return result; - } - - protected: - void LoadSignalsCallback( - std::unique_ptr<TrustedScoringSignals::Result> result, - absl::optional<std::string> error_msg) { - load_signals_result_ = std::move(result); - error_msg_ = std::move(error_msg); - EXPECT_EQ(load_signals_result_ == nullptr, error_msg_.has_value()); - load_signals_run_loop_->Quit(); - } - - base::test::TaskEnvironment task_environment_; - - // URL without query params attached. - const GURL base_url_ = GURL("https://url.test/"); - - // Reuseable run loop for loading the signals. It's always populated after - // creating the worklet, to cause a crash if the callback is invoked - // synchronously. - std::unique_ptr<base::RunLoop> load_signals_run_loop_; - std::unique_ptr<TrustedScoringSignals::Result> load_signals_result_; - absl::optional<std::string> error_msg_; - - network::TestURLLoaderFactory url_loader_factory_; - scoped_refptr<AuctionV8Helper> v8_helper_; -}; - -TEST_F(TrustedScoringSignalsTest, NetworkError) { - url_loader_factory_.AddResponse( - "https://url.test/" - "?hostname=publisher&renderUrls=https%3A%2F%2Ffoo.test%2F", - kBaseJson, net::HTTP_NOT_FOUND); - EXPECT_FALSE(FetchScoringSignals( - /*render_urls=*/{GURL("https://foo.test/")}, - /*ad_component_render_urls=*/{}, kHostname)); - ASSERT_TRUE(error_msg_.has_value()); - EXPECT_EQ( - "Failed to load " - "https://url.test/" - "?hostname=publisher&renderUrls=https%3A%2F%2Ffoo.test%2F " - "HTTP status = 404 Not Found.", - error_msg_.value()); -} - -TEST_F(TrustedScoringSignalsTest, ResponseNotJson) { - EXPECT_FALSE(FetchScoringSignalsWithResponse( - GURL("https://url.test/" - "?hostname=publisher&renderUrls=https%3A%2F%2Ffoo.test%2F"), - "Not Json", - /*render_urls=*/{GURL("https://foo.test/")}, - /*ad_component_render_urls=*/{}, kHostname)); - ASSERT_TRUE(error_msg_.has_value()); - EXPECT_EQ("https://url.test/ Unable to parse as a JSON object.", - error_msg_.value()); -} - -TEST_F(TrustedScoringSignalsTest, ResponseNotObject) { - EXPECT_FALSE(FetchScoringSignalsWithResponse( - GURL("https://url.test/" - "?hostname=publisher&renderUrls=https%3A%2F%2Ffoo.test%2F"), - "42", /*render_urls=*/{GURL("https://foo.test/")}, - /*ad_component_render_urls=*/{}, kHostname)); - ASSERT_TRUE(error_msg_.has_value()); - EXPECT_EQ("https://url.test/ Unable to parse as a JSON object.", - error_msg_.value()); -} - -TEST_F(TrustedScoringSignalsTest, ExpectedEntriesNotPresent) { - std::unique_ptr<TrustedScoringSignals::Result> signals = - FetchScoringSignalsWithResponse( - GURL("https://url.test/?hostname=publisher" - "&renderUrls=https%3A%2F%2Ffoo.test%2F" - "&adComponentRenderUrls=https%3A%2F%2Fbar.test%2F"), - R"({"foo":4,"bar":5})", - /*render_urls=*/{GURL("https://foo.test/")}, - /*ad_component_render_urls=*/{GURL("https://bar.test/")}, kHostname); - ASSERT_TRUE(signals); - EXPECT_EQ( - R"({"renderUrl":{"https://foo.test/":null},"adComponentRenderUrls":{"https://bar.test/":null}})", - ExtractSignals(signals.get(), /*render_url=*/GURL("https://foo.test/"), - /*ad_component_render_urls=*/{GURL("https://bar.test/")})); - EXPECT_FALSE(error_msg_.has_value()); -} - -TEST_F(TrustedScoringSignalsTest, NestedEntriesNotObjects) { - std::unique_ptr<TrustedScoringSignals::Result> signals = - FetchScoringSignalsWithResponse( - GURL("https://url.test/?hostname=publisher" - "&renderUrls=https%3A%2F%2Ffoo.test%2F" - "&adComponentRenderUrls=https%3A%2F%2Fbar.test%2F"), - R"({"renderUrls":4,"adComponentRenderUrls":5})", - /*render_urls=*/{GURL("https://foo.test/")}, - /*ad_component_render_urls=*/{GURL("https://bar.test/")}, kHostname); - ASSERT_TRUE(signals); - EXPECT_EQ( - R"({"renderUrl":{"https://foo.test/":null},"adComponentRenderUrls":{"https://bar.test/":null}})", - ExtractSignals(signals.get(), /*render_url=*/GURL("https://foo.test/"), - /*ad_component_render_urls=*/{GURL("https://bar.test/")})); - EXPECT_FALSE(error_msg_.has_value()); -} - -TEST_F(TrustedScoringSignalsTest, KeysMissing) { - std::unique_ptr<TrustedScoringSignals::Result> signals = - FetchScoringSignalsWithResponse( - GURL("https://url.test/?hostname=publisher" - "&renderUrls=https%3A%2F%2Ffoo.test%2F" - "&adComponentRenderUrls=https%3A%2F%2Fbar.test%2F"), - R"({"renderUrls":{"these":"are not"},")" - R"(adComponentRenderUrls":{"the values":"you're looking for"}})", - /*render_urls=*/{GURL("https://foo.test/")}, - /*ad_component_render_urls=*/{GURL("https://bar.test/")}, kHostname); - ASSERT_TRUE(signals); - EXPECT_EQ( - R"({"renderUrl":{"https://foo.test/":null},"adComponentRenderUrls":{"https://bar.test/":null}})", - ExtractSignals(signals.get(), /*render_url=*/GURL("https://foo.test/"), - /*ad_component_render_urls=*/{GURL("https://bar.test/")})); - EXPECT_FALSE(error_msg_.has_value()); -} - -TEST_F(TrustedScoringSignalsTest, FetchForOneRenderUrl) { - std::unique_ptr<TrustedScoringSignals::Result> signals = - FetchScoringSignalsWithResponse( - GURL("https://url.test/" - "?hostname=publisher&renderUrls=https%3A%2F%2Ffoo.test%2F"), - kBaseJson, - /*render_urls=*/{GURL("https://foo.test/")}, - /*ad_component_render_urls=*/{}, kHostname); - ASSERT_TRUE(signals); - EXPECT_EQ( - R"({"renderUrl":{"https://foo.test/":1}})", - ExtractSignals(signals.get(), /*render_url=*/GURL("https://foo.test/"), - /*ad_component_render_urls=*/{})); - EXPECT_FALSE(error_msg_.has_value()); -} - -// Currently, there's no case where a fetch will only be for ad components and -// not render URLs, but once requests are batched, it may be useful. That will -// require other API changes and a caching layer, of course. -TEST_F(TrustedScoringSignalsTest, FetchForOneAdComponentUrl) { - std::unique_ptr<TrustedScoringSignals::Result> signals = - FetchScoringSignalsWithResponse( - GURL("https://url.test/" - "?hostname=publisher&adComponentRenderUrls=https%3A%2F%2Ffoosub." - "test%2F"), - kBaseJson, - /*render_urls=*/{}, - /*ad_component_render_urls=*/{GURL("https://foosub.test/")}, - kHostname); - ASSERT_TRUE(signals); - // Currently there's no way to extract only an ad component value. This test - // is really just about the fetching and parsing logic. - EXPECT_EQ( - R"({"renderUrl":{"https://foo.test/":null},"adComponentRenderUrls":{"https://foosub.test/":2}})", - ExtractSignals( - signals.get(), /*render_url=*/GURL("https://foo.test/"), - /*ad_component_render_urls=*/{GURL("https://foosub.test/")})); - EXPECT_FALSE(error_msg_.has_value()); -} - -TEST_F(TrustedScoringSignalsTest, FetchMultipleUrls) { - // URLs are currently added in lexical order. - std::unique_ptr<TrustedScoringSignals::Result> signals = - FetchScoringSignalsWithResponse( - GURL("https://url.test/?hostname=publisher" - "&renderUrls=https%3A%2F%2Fbar.test%2F," - "https%3A%2F%2Fbaz.test%2F,https%3A%2F%2Ffoo.test%2F" - "&adComponentRenderUrls=https%3A%2F%2Fbarsub.test%2F," - "https%3A%2F%2Fbazsub.test%2F,https%3A%2F%2Ffoosub.test%2F"), - kBaseJson, - /*render_urls=*/ - {GURL("https://foo.test/"), GURL("https://bar.test/"), - GURL("https://baz.test/")}, - /*ad_component_render_urls=*/ - {GURL("https://foosub.test/"), GURL("https://barsub.test/"), - GURL("https://bazsub.test/")}, - kHostname); - ASSERT_TRUE(signals); - EXPECT_FALSE(error_msg_.has_value()); - EXPECT_EQ( - R"({"renderUrl":{"https://bar.test/":[2]},")" - R"(adComponentRenderUrls":{"https://barsub.test/":[3],"https://bazsub.test/":null,"https://foosub.test/":2}})", - ExtractSignals( - signals.get(), /*render_url=*/GURL("https://bar.test/"), - /*ad_component_render_urls=*/ - {GURL("https://foosub.test/"), GURL("https://barsub.test/"), - GURL("https://bazsub.test/")})); -} - -// Test when a single URL is used as both a `renderUrl` and -// `adComponentRenderUrl`. -TEST_F(TrustedScoringSignalsTest, FetchSharedUrl) { - // URLs are currently added in lexical order. - std::unique_ptr<TrustedScoringSignals::Result> signals = - FetchScoringSignalsWithResponse( - GURL("https://url.test/?hostname=publisher" - "&renderUrls=https%3A%2F%2Fshared.test%2F" - "&adComponentRenderUrls=https%3A%2F%2Fshared.test%2F"), - kBaseJson, - /*render_urls=*/ - {GURL("https://shared.test/")}, - /*ad_component_render_urls=*/ - {GURL("https://shared.test/")}, kHostname); - ASSERT_TRUE(signals); - EXPECT_FALSE(error_msg_.has_value()); - EXPECT_EQ( - R"({"renderUrl":{"https://shared.test/":"render url"},")" - R"(adComponentRenderUrls":{"https://shared.test/":"ad component url"}})", - ExtractSignals(signals.get(), /*render_url=*/GURL("https://shared.test/"), - /*ad_component_render_urls=*/ - {GURL("https://shared.test/")})); -} - -TEST_F(TrustedScoringSignalsTest, EscapeQueryParams) { - std::unique_ptr<TrustedScoringSignals::Result> signals = - FetchScoringSignalsWithResponse( - GURL("https://url.test/?hostname=pub+li%26sher" - "&renderUrls=https%3A%2F%2Ffoo.test%2F%3F%26%3D" - "&adComponentRenderUrls=https%3A%2F%2Fbar.test%2F%3F%26%3D"), - R"( - { - "renderUrls": { - "https://foo.test/?&=": 4 - }, - "adComponentRenderUrls": { - "https://bar.test/?&=": 5 - } - } -)", - /*render_urls=*/ - {GURL("https://foo.test/?&=")}, /*ad_component_render_urls=*/ - {GURL("https://bar.test/?&=")}, "pub li&sher"); - ASSERT_TRUE(signals); - EXPECT_EQ( - R"({"renderUrl":{"https://foo.test/?&=":4},"adComponentRenderUrls":{"https://bar.test/?&=":5}})", - ExtractSignals(signals.get(), /*render_url=*/ - GURL("https://foo.test/?&="), /*ad_component_render_urls=*/ - {GURL("https://bar.test/?&=")})); - EXPECT_FALSE(error_msg_.has_value()); -} - -// Testcase where the loader is deleted after it queued the parsing of -// the script on V8 thread, but before it gets to finish. -TEST_F(TrustedScoringSignalsTest, DeleteBeforeCallback) { - GURL url( - "https://url.test/" - "?hostname=publisher&renderUrls=https%3A%2F%2Ffoo.test%2F"); - - AddJsonResponse(&url_loader_factory_, url, kBaseJson); - - // Wedge the V8 thread to control when the JSON parsing takes place. - base::WaitableEvent* event_handle = WedgeV8Thread(v8_helper_.get()); - auto scoring_signals = std::make_unique<TrustedScoringSignals>( - &url_loader_factory_, - /*render_urls=*/std::set<GURL>{GURL("http://foo.test/")}, - /*ad_component_urls=*/std::set<GURL>(), "publisher", base_url_, - v8_helper_, - base::BindOnce([](std::unique_ptr<TrustedScoringSignals::Result> result, - absl::optional<std::string> error_msg) { - ADD_FAILURE() << "Callback should not be invoked since loader deleted"; - })); - base::RunLoop().RunUntilIdle(); - scoring_signals.reset(); - event_handle->Signal(); -} - -} // namespace -} // namespace auction_worklet
diff --git a/content/services/auction_worklet/trusted_signals.cc b/content/services/auction_worklet/trusted_signals.cc new file mode 100644 index 0000000..cae5f69 --- /dev/null +++ b/content/services/auction_worklet/trusted_signals.cc
@@ -0,0 +1,380 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/services/auction_worklet/trusted_signals.h" + +#include <memory> +#include <set> +#include <string> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/check.h" +#include "base/strings/strcat.h" +#include "base/strings/stringprintf.h" +#include "content/services/auction_worklet/auction_downloader.h" +#include "content/services/auction_worklet/auction_v8_helper.h" +#include "gin/converter.h" +#include "net/base/escape.h" +#include "services/network/public/mojom/url_loader_factory.mojom-forward.h" +#include "url/gurl.h" +#include "v8/include/v8-context.h" +#include "v8/include/v8-json.h" +#include "v8/include/v8-object.h" +#include "v8/include/v8-primitive.h" + +namespace auction_worklet { + +namespace { + +// Creates a query param of the form `&<name>=<values in comma-delimited list>`. +// Returns an empty string if `keys` is empty. `name` will not be escaped, but +// `values` will be. Each entry in `keys` will be added at most once. +std::string CreateQueryParam(const char* name, + const std::set<std::string>& keys) { + if (keys.empty()) + return std::string(); + + std::string query_param = base::StringPrintf("&%s=", name); + bool first_key = true; + for (const auto& key : keys) { + if (first_key) { + first_key = false; + } else { + query_param.append(","); + } + query_param.append(net::EscapeQueryParamValue(key, /*use_plus=*/true)); + } + return query_param; +} + +GURL SetQueryParam(const GURL& base_url, const std::string& new_query_params) { + GURL::Replacements replacements; + replacements.SetQueryStr(new_query_params); + return base_url.ReplaceComponents(replacements); +} + +// Extracts GURL/JSON key/value pairs from `v8_object`, using values in `keys` +// as keys. Does not add entries to the map for keys with missing values. +std::map<std::string, std::string> ParseKeyValueMap( + AuctionV8Helper* v8_helper, + v8::Local<v8::Object> v8_object, + const std::set<std::string>& keys) { + std::map<std::string, std::string> out; + if (keys.empty()) + return out; + + for (const auto& key : keys) { + v8::Local<v8::String> v8_key; + if (!v8_helper->CreateUtf8String(key).ToLocal(&v8_key)) + continue; + + v8::Local<v8::Value> v8_value; + v8::Local<v8::Value> v8_string_value; + std::string value; + // Only the Get() call should be able to fail. + if (!v8_object->Get(v8_helper->scratch_context(), v8_key) + .ToLocal(&v8_value) || + !v8::JSON::Stringify(v8_helper->scratch_context(), v8_value) + .ToLocal(&v8_string_value) || + !gin::ConvertFromV8(v8_helper->isolate(), v8_string_value, &value)) { + continue; + } + out[key] = std::move(value); + } + return out; +} + +// Extracts GURL/JSON key/value pairs from the object named `name` in +// `v8_object`, using values in `keys` as keys. Does not add entries to the map +// for keys with missing values. +std::map<std::string, std::string> ParseChildKeyValueMap( + AuctionV8Helper* v8_helper, + v8::Local<v8::Object> v8_object, + const char* name, + const std::set<std::string>& keys) { + std::map<std::string, std::string> out; + if (keys.empty()) + return out; + + v8::Local<v8::Value> named_object_value; + // Don't consider the entire object missing a fatal error. + if (!v8_object + ->Get(v8_helper->scratch_context(), + v8_helper->CreateStringFromLiteral(name)) + .ToLocal(&named_object_value) || + !named_object_value->IsObject()) { + return out; + } + + return ParseKeyValueMap(v8_helper, named_object_value.As<v8::Object>(), keys); +} + +// Takes a list of keys, a map of strings to JSON strings and creates a +// corresponding v8::Object from the entries with the provided keys. `keys` must +// not be empty. +v8::Local<v8::Object> CreateObjectFromMap( + const std::vector<std::string>& keys, + const std::map<std::string, std::string>& json_data, + AuctionV8Helper* v8_helper, + v8::Local<v8::Context> context) { + DCHECK(v8_helper->v8_runner()->RunsTasksInCurrentSequence()); + DCHECK(!keys.empty()); + + v8::Local<v8::Object> out = v8::Object::New(v8_helper->isolate()); + for (const auto& key : keys) { + auto data = json_data.find(key); + // InsertJsonValue() shouldn't be able to fail, but the first check might. + if (data == json_data.end() || + !v8_helper->InsertJsonValue(context, key, data->second, out)) { + bool result = + v8_helper->InsertValue(key, v8::Null(v8_helper->isolate()), out); + DCHECK(result); + } + } + return out; +} + +} // namespace + +TrustedSignals::Result::Result( + std::map<std::string, std::string> bidder_json_data) + : bidder_json_data_(std::move(bidder_json_data)) {} + +TrustedSignals::Result::Result( + std::map<std::string, std::string> render_url_json_data, + std::map<std::string, std::string> ad_component_json_data) + : render_url_json_data_(std::move(render_url_json_data)), + ad_component_json_data_(std::move(ad_component_json_data)) {} + +TrustedSignals::Result::~Result() = default; + +v8::Local<v8::Object> TrustedSignals::Result::GetBiddingSignals( + AuctionV8Helper* v8_helper, + v8::Local<v8::Context> context, + const std::vector<std::string>& bidding_signals_keys) const { + DCHECK(v8_helper->v8_runner()->RunsTasksInCurrentSequence()); + DCHECK(bidder_json_data_.has_value()); + + return CreateObjectFromMap(bidding_signals_keys, *bidder_json_data_, + v8_helper, context); +} + +v8::Local<v8::Object> TrustedSignals::Result::GetScoringSignals( + AuctionV8Helper* v8_helper, + v8::Local<v8::Context> context, + const GURL& render_url, + const std::vector<std::string>& ad_component_render_urls) const { + DCHECK(v8_helper->v8_runner()->RunsTasksInCurrentSequence()); + DCHECK(render_url_json_data_.has_value()); + DCHECK(ad_component_json_data_.has_value()); + + v8::Local<v8::Object> out = v8::Object::New(v8_helper->isolate()); + + // Create renderUrl sub-object, and add it to to `out`. + v8::Local<v8::Object> render_url_v8_object = + CreateObjectFromMap(std::vector<std::string>{render_url.spec()}, + *render_url_json_data_, v8_helper, context); + bool result = v8_helper->InsertValue("renderUrl", render_url_v8_object, out); + DCHECK(result); + + // If there are any ad components, assemble and add an `adComponentRenderUrls` + // object as well. + if (!ad_component_render_urls.empty()) { + v8::Local<v8::Object> ad_components_v8_object = CreateObjectFromMap( + ad_component_render_urls, *ad_component_json_data_, v8_helper, context); + bool result = v8_helper->InsertValue("adComponentRenderUrls", + ad_components_v8_object, out); + DCHECK(result); + } + + return out; +} + +std::unique_ptr<TrustedSignals> TrustedSignals::LoadBiddingSignals( + network::mojom::URLLoaderFactory* url_loader_factory, + const std::vector<std::string>& bidding_signals_keys, + const std::string& hostname, + const GURL& trusted_bidding_signals_url, + scoped_refptr<AuctionV8Helper> v8_helper, + LoadSignalsCallback load_signals_callback) { + DCHECK(!bidding_signals_keys.empty()); + + std::unique_ptr<TrustedSignals> trusted_signals = + base::WrapUnique(new TrustedSignals( + /*bidding_signals_keys=*/std::set<std::string>( + bidding_signals_keys.begin(), bidding_signals_keys.end()), + /*render_urls=*/absl::nullopt, + /*ad_component_render_urls=*/absl::nullopt, + trusted_bidding_signals_url, std::move(v8_helper), + std::move(load_signals_callback))); + + std::string query_params = + "hostname=" + net::EscapeQueryParamValue(hostname, /*use_plus=*/true) + + CreateQueryParam("keys", *trusted_signals->bidding_signals_keys_); + GURL full_signals_url = + SetQueryParam(trusted_bidding_signals_url, query_params); + trusted_signals->StartDownload(url_loader_factory, full_signals_url); + + return trusted_signals; +} + +std::unique_ptr<TrustedSignals> TrustedSignals::LoadScoringSignals( + network::mojom::URLLoaderFactory* url_loader_factory, + const std::vector<std::string>& render_urls, + const std::vector<std::string>& ad_component_render_urls, + const std::string& hostname, + const GURL& trusted_scoring_signals_url, + scoped_refptr<AuctionV8Helper> v8_helper, + LoadSignalsCallback load_signals_callback) { + DCHECK(!render_urls.empty()); + + std::unique_ptr<TrustedSignals> trusted_signals = + base::WrapUnique(new TrustedSignals( + /*bidding_signals_keys=*/absl::nullopt, + /*render_urls=*/ + std::set<std::string>(render_urls.begin(), render_urls.end()), + /*ad_component_render_urls=*/ + std::set<std::string>(ad_component_render_urls.begin(), + ad_component_render_urls.end()), + trusted_scoring_signals_url, std::move(v8_helper), + std::move(load_signals_callback))); + + std::string query_params = + "hostname=" + net::EscapeQueryParamValue(hostname, /*use_plus=*/true) + + CreateQueryParam("renderUrls", *trusted_signals->render_urls_) + + CreateQueryParam("adComponentRenderUrls", + *trusted_signals->ad_component_render_urls_); + GURL full_signals_url = + SetQueryParam(trusted_scoring_signals_url, query_params); + trusted_signals->StartDownload(url_loader_factory, full_signals_url); + + return trusted_signals; +} + +TrustedSignals::TrustedSignals( + absl::optional<std::set<std::string>> bidding_signals_keys, + absl::optional<std::set<std::string>> render_urls, + absl::optional<std::set<std::string>> ad_component_render_urls, + const GURL& trusted_signals_url, + scoped_refptr<AuctionV8Helper> v8_helper, + LoadSignalsCallback load_signals_callback) + : bidding_signals_keys_(std::move(bidding_signals_keys)), + render_urls_(std::move(render_urls)), + ad_component_render_urls_(std::move(ad_component_render_urls)), + trusted_signals_url_(trusted_signals_url), + v8_helper_(std::move(v8_helper)), + load_signals_callback_(std::move(load_signals_callback)) { + DCHECK(v8_helper_); + DCHECK(load_signals_callback_); + + // Either this should be for bidding signals or scoring signals. + DCHECK(bidding_signals_keys_ || (render_urls_ && ad_component_render_urls_)); + DCHECK(!bidding_signals_keys_ || + (!render_urls_ && !ad_component_render_urls_)); +} + +TrustedSignals::~TrustedSignals() = default; + +void TrustedSignals::StartDownload( + network::mojom::URLLoaderFactory* url_loader_factory, + const GURL& full_signals_url) { + auction_downloader_ = std::make_unique<AuctionDownloader>( + url_loader_factory, full_signals_url, AuctionDownloader::MimeType::kJson, + base::BindOnce(&TrustedSignals::OnDownloadComplete, + base::Unretained(this))); +} + +void TrustedSignals::OnDownloadComplete(std::unique_ptr<std::string> body, + absl::optional<std::string> error_msg) { + // The downloader's job is done, so clean it up. + auction_downloader_.reset(); + + // Key-related fields aren't needed after this call, so pass ownership of them + // over to the parser on the V8 thread. + v8_helper_->v8_runner()->PostTask( + FROM_HERE, + base::BindOnce(&TrustedSignals::HandleDownloadResultOnV8Thread, + v8_helper_, trusted_signals_url_, + std::move(bidding_signals_keys_), std::move(render_urls_), + std::move(ad_component_render_urls_), std::move(body), + std::move(error_msg), + base::SequencedTaskRunnerHandle::Get(), + weak_ptr_factory.GetWeakPtr())); +} + +// static +void TrustedSignals::HandleDownloadResultOnV8Thread( + scoped_refptr<AuctionV8Helper> v8_helper, + const GURL& signals_url, + absl::optional<std::set<std::string>> bidding_signals_keys, + absl::optional<std::set<std::string>> render_urls, + absl::optional<std::set<std::string>> ad_component_render_urls, + std::unique_ptr<std::string> body, + absl::optional<std::string> error_msg, + scoped_refptr<base::SequencedTaskRunner> user_thread_task_runner, + base::WeakPtr<TrustedSignals> weak_instance) { + if (!body) { + PostCallbackToUserThread(std::move(user_thread_task_runner), weak_instance, + nullptr, std::move(error_msg)); + return; + } + + DCHECK(!error_msg.has_value()); + + AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper.get()); + v8::Context::Scope context_scope(v8_helper->scratch_context()); + + v8::Local<v8::Value> v8_data; + if (!v8_helper->CreateValueFromJson(v8_helper->scratch_context(), *body) + .ToLocal(&v8_data) || + !v8_data->IsObject()) { + std::string error = base::StrCat( + {signals_url.spec(), " Unable to parse as a JSON object."}); + PostCallbackToUserThread(std::move(user_thread_task_runner), weak_instance, + nullptr, std::move(error)); + return; + } + + v8::Local<v8::Object> v8_object = v8_data.As<v8::Object>(); + + std::unique_ptr<Result> result; + + if (bidding_signals_keys) { + // Handle bidding signals case. + result = std::make_unique<Result>( + ParseKeyValueMap(v8_helper.get(), v8_object, *bidding_signals_keys)); + } else { + // Handle scoring signals case. + result = std::make_unique<Result>( + ParseChildKeyValueMap(v8_helper.get(), v8_object, "renderUrls", + *render_urls), + ParseChildKeyValueMap(v8_helper.get(), v8_object, + "adComponentRenderUrls", + *ad_component_render_urls)); + } + + PostCallbackToUserThread(std::move(user_thread_task_runner), weak_instance, + std::move(result), absl::nullopt); +} + +void TrustedSignals::PostCallbackToUserThread( + scoped_refptr<base::SequencedTaskRunner> user_thread_task_runner, + base::WeakPtr<TrustedSignals> weak_instance, + std::unique_ptr<Result> result, + absl::optional<std::string> error_msg) { + user_thread_task_runner->PostTask( + FROM_HERE, + base::BindOnce(&TrustedSignals::DeliverCallbackOnUserThread, + weak_instance, std::move(result), std::move(error_msg))); +} + +void TrustedSignals::DeliverCallbackOnUserThread( + std::unique_ptr<Result> result, + absl::optional<std::string> error_msg) { + std::move(load_signals_callback_) + .Run(std::move(result), std::move(error_msg)); +} + +} // namespace auction_worklet
diff --git a/content/services/auction_worklet/trusted_signals.h b/content/services/auction_worklet/trusted_signals.h new file mode 100644 index 0000000..5aeb5c4 --- /dev/null +++ b/content/services/auction_worklet/trusted_signals.h
@@ -0,0 +1,191 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_SERVICES_AUCTION_WORKLET_TRUSTED_SIGNALS_H_ +#define CONTENT_SERVICES_AUCTION_WORKLET_TRUSTED_SIGNALS_H_ + +#include <map> +#include <memory> +#include <set> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/memory/scoped_refptr.h" +#include "services/network/public/mojom/url_loader_factory.mojom-forward.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "url/gurl.h" +#include "v8/include/v8-forward.h" + +namespace auction_worklet { + +class AuctionDownloader; +class AuctionV8Helper; + +// Represents the trusted bidding/scoring signals that are part of the FLEDGE +// bidding system (https://github.com/WICG/turtledove/blob/main/FLEDGE.md). +// Fetches and parses the hosted JSON data files needed by worklets. There are +// separate methods for fetching bidding and scoring signals. A single +// TrustedSignals object can only be used to fetch bidding signals or scoring +// signals, even if a single URL is used for both types of signals. +// +// TODO(mmenke): This class currently does 4 copies when loading the data (To V8 +// string, use V8's JSON parser, split data into V8 JSON subcomponent strings, +// convert to C++ strings), and 2 copies of each substring to use the data (To +// V8 per-key JSON string, use V8's JSON parser). Keeping the data stored as V8 +// JSON subcomponents would remove 2 copies, without too much complexity. Could +// even implement V8 deep-copy logic, to remove two more copies (counting the +// clone operation as a copy). +class TrustedSignals { + public: + // Contains the values returned by the server. + // + // This can be created and destroyed on any thread, but GetSignals() can only + // be used on the V8 thread. + class Result { + public: + // Constructor for bidding signals. + explicit Result(std::map<std::string, std::string> bidder_json_data); + + // Constructor for scoring signals. + Result(std::map<std::string, std::string> render_url_json_data, + std::map<std::string, std::string> ad_component_json_data); + + explicit Result(const Result&) = delete; + + ~Result(); + + Result& operator=(const Result&) = delete; + + // Retrieves the trusted bidding signals associated with the passed in keys, + // in the format expected by a worklet's generateBid() method. `this` must + // have been generated by fetching bidding signals. `v8_helper`'s Isolate + // must be active (in particular, this must be on the v8 thread), and + // `context` must be the active context. `bidding_signals_keys` must be + // subsets of the keys provided when creating the TrustedSignals object. + // Always returns a non-empty value. + v8::Local<v8::Object> GetBiddingSignals( + AuctionV8Helper* v8_helper, + v8::Local<v8::Context> context, + const std::vector<std::string>& bidding_signals_keys) const; + + // Retrieves the trusted scoring signals associated with the passed in urls, + // in the format expected by a worklet's scoreAd() method. `this` must have + // been generated by fetching scoring signals. `v8_helper`'s Isolate must be + // active (in particular, this must be on the v8 thread), and `context` must + // be the active context. `render_url` and `ad_component_render_urls` must + // be subsets of the corresponding sets of GURLs provided when creating the + // TrustedSignals object. Always returns a non-empty value. + v8::Local<v8::Object> GetScoringSignals( + AuctionV8Helper* v8_helper, + v8::Local<v8::Context> context, + const GURL& render_url, + const std::vector<std::string>& ad_component_render_urls) const; + + private: + // Map of keys to the associated JSON data for trusted bidding signals. + absl::optional<std::map<std::string, std::string>> bidder_json_data_; + + // Map of keys to the associated JSON data for trusted scoring signals. + absl::optional<std::map<std::string, std::string>> render_url_json_data_; + absl::optional<std::map<std::string, std::string>> ad_component_json_data_; + }; + + using LoadSignalsCallback = + base::OnceCallback<void(std::unique_ptr<Result> result, + absl::optional<std::string> error_msg)>; + + explicit TrustedSignals(const TrustedSignals&) = delete; + TrustedSignals& operator=(const TrustedSignals&) = delete; + ~TrustedSignals(); + + // Constructs a TrustedSignals for fetching bidding signals, and starts the + // fetch. `trusted_bidding_signals_url` must be the base URL (no query params + // added). Callback will be invoked asynchronously once the data has been + // fetched or an error has occurred. De-duplicates keys when assembling the + // full URL for the fetch. Fails if the URL already has a query param (or has + // a location or embedded credentials) or if the response is not JSON. If some + // or all of the render URLs are missing, still succeeds, and GetSignals() + // will populate them with nulls. + // + // There are no lifetime constraints of `url_loader_factory`. + static std::unique_ptr<TrustedSignals> LoadBiddingSignals( + network::mojom::URLLoaderFactory* url_loader_factory, + const std::vector<std::string>& bidding_signals_keys, + const std::string& hostname, + const GURL& trusted_bidding_signals_url, + scoped_refptr<AuctionV8Helper> v8_helper, + LoadSignalsCallback load_signals_callback); + + // Just like LoadBiddingSignals() above, but for fetching seller signals. + static std::unique_ptr<TrustedSignals> LoadScoringSignals( + network::mojom::URLLoaderFactory* url_loader_factory, + const std::vector<std::string>& render_urls, + const std::vector<std::string>& ad_component_render_urls, + const std::string& hostname, + const GURL& trusted_scoring_signals_url, + scoped_refptr<AuctionV8Helper> v8_helper, + LoadSignalsCallback load_signals_callback); + + private: + TrustedSignals(absl::optional<std::set<std::string>> bidding_signals_keys, + absl::optional<std::set<std::string>> render_urls, + absl::optional<std::set<std::string>> ad_component_render_urls, + const GURL& trusted_signals_url, + scoped_refptr<AuctionV8Helper> v8_helper, + LoadSignalsCallback load_signals_callback); + + // Starts downloading `url`, which should be the bidding or scoring signals + // URL with the query parameter correctly set. + void StartDownload(network::mojom::URLLoaderFactory* url_loader_factory, + const GURL& full_signals_url); + + void OnDownloadComplete(std::unique_ptr<std::string> body, + absl::optional<std::string> error_msg); + + // Parses the response body on the V8 thread, and extracts values associated + // with the requested keys. + static void HandleDownloadResultOnV8Thread( + scoped_refptr<AuctionV8Helper> v8_helper, + const GURL& signals_url, + absl::optional<std::set<std::string>> bidding_signals_keys, + absl::optional<std::set<std::string>> render_urls, + absl::optional<std::set<std::string>> ad_component_render_urls, + std::unique_ptr<std::string> body, + absl::optional<std::string> error_msg, + scoped_refptr<base::SequencedTaskRunner> user_thread_task_runner, + base::WeakPtr<TrustedSignals> weak_instance); + + // Called from V8 thread. + static void PostCallbackToUserThread( + scoped_refptr<base::SequencedTaskRunner> user_thread_task_runner, + base::WeakPtr<TrustedSignals> weak_instance, + std::unique_ptr<Result> result, + absl::optional<std::string> error_msg); + + // Called on user thread. + void DeliverCallbackOnUserThread(std::unique_ptr<Result>, + absl::optional<std::string> error_msg); + + // Keys being fetched. For bidding signals, only `bidding_signals_keys_` is + // non-null. For scoring signals, only `render_urls_` and + // `ad_component_render_urls_` are non-null. These are cleared and ownership + // is passed to the V8 thread once the download completes, as they're no + // longer on the main thread after that point. + absl::optional<std::set<std::string>> bidding_signals_keys_; + absl::optional<std::set<std::string>> render_urls_; + absl::optional<std::set<std::string>> ad_component_render_urls_; + + const GURL trusted_signals_url_; // original, for error messages. + const scoped_refptr<AuctionV8Helper> v8_helper_; + + LoadSignalsCallback load_signals_callback_; + std::unique_ptr<AuctionDownloader> auction_downloader_; + + base::WeakPtrFactory<TrustedSignals> weak_ptr_factory{this}; +}; + +} // namespace auction_worklet + +#endif // CONTENT_SERVICES_AUCTION_WORKLET_TRUSTED_SIGNALS_H_
diff --git a/content/services/auction_worklet/trusted_signals_unittest.cc b/content/services/auction_worklet/trusted_signals_unittest.cc new file mode 100644 index 0000000..2babf9b --- /dev/null +++ b/content/services/auction_worklet/trusted_signals_unittest.cc
@@ -0,0 +1,577 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/services/auction_worklet/trusted_signals.h" + +#include <string> +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/run_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/test/bind.h" +#include "base/test/task_environment.h" +#include "content/services/auction_worklet/auction_v8_helper.h" +#include "content/services/auction_worklet/worklet_test_util.h" +#include "net/http/http_status_code.h" +#include "services/network/test/test_url_loader_factory.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" +#include "v8/include/v8-context.h" +#include "v8/include/v8-forward.h" + +namespace auction_worklet { +namespace { + +// Common JSON used for most bidding signals tests. Key 4 is deliberately +// skipped. +const char kBaseBiddingJson[] = R"( + { + "key1": 1, + "key2": [2], + "key3": null, + "key5": "value5", + "key 6": 6, + "key=7": 7, + "key,8": 8 + } +)"; + +// Common JSON used for most scoring signals tests. +const char kBaseScoringJson[] = R"( + { + "renderUrls": { + "https://foo.test/": 1, + "https://bar.test/": [2], + "https://baz.test/": null, + "https://shared.test/": "render url" + }, + "adComponentRenderUrls": { + "https://foosub.test/": 2, + "https://barsub.test/": [3], + "https://bazsub.test/": null, + "https://shared.test/": "ad component url" + } + } +)"; + +const char kHostname[] = "publisher"; + +class TrustedSignalsTest : public testing::Test { + public: + TrustedSignalsTest() { + v8_helper_ = AuctionV8Helper::Create(AuctionV8Helper::CreateTaskRunner()); + } + + ~TrustedSignalsTest() override { task_environment_.RunUntilIdle(); } + + // Sets the HTTP response and then fetches bidding signals and waits for + // completion. Returns nullptr on failure. + std::unique_ptr<TrustedSignals::Result> FetchBiddingSignalsWithResponse( + const GURL& url, + const std::string& response, + std::vector<std::string> trusted_bidding_signals_keys, + const std::string& hostname) { + AddJsonResponse(&url_loader_factory_, url, response); + return FetchBiddingSignals(trusted_bidding_signals_keys, hostname); + } + + // Fetches bidding signals and waits for completion. Returns nullptr on + // failure. + std::unique_ptr<TrustedSignals::Result> FetchBiddingSignals( + std::vector<std::string> trusted_bidding_signals_keys, + const std::string& hostname) { + CHECK(!load_signals_run_loop_); + + DCHECK(!load_signals_result_); + auto bidding_signals = TrustedSignals::LoadBiddingSignals( + &url_loader_factory_, std::move(trusted_bidding_signals_keys), + std::move(hostname), base_url_, v8_helper_, + base::BindOnce(&TrustedSignalsTest::LoadSignalsCallback, + base::Unretained(this))); + WaitForLoadComplete(); + return std::move(load_signals_result_); + } + + // Sets the HTTP response and then fetches scoring signals and waits for + // completion. Returns nullptr on failure. + std::unique_ptr<TrustedSignals::Result> FetchScoringSignalsWithResponse( + const GURL& url, + const std::string& response, + std::vector<std::string> render_urls, + std::vector<std::string> ad_component_render_urls, + const std::string& hostname) { + AddJsonResponse(&url_loader_factory_, url, response); + return FetchScoringSignals(render_urls, ad_component_render_urls, hostname); + } + + // Fetches scoring signals and waits for completion. Returns nullptr on + // failure. + std::unique_ptr<TrustedSignals::Result> FetchScoringSignals( + std::vector<std::string> render_urls, + std::vector<std::string> ad_component_render_urls, + const std::string& hostname) { + CHECK(!load_signals_run_loop_); + + DCHECK(!load_signals_result_); + auto scoring_signals = TrustedSignals::LoadScoringSignals( + &url_loader_factory_, std::move(render_urls), + std::move(ad_component_render_urls), std::move(hostname), base_url_, + v8_helper_, + base::BindOnce(&TrustedSignalsTest::LoadSignalsCallback, + base::Unretained(this))); + WaitForLoadComplete(); + return std::move(load_signals_result_); + } + + // Wait for LoadSignalsCallback to be invoked. + void WaitForLoadComplete() { + // Since LoadSignalsCallback is always invoked asynchronously, fine to + // create the RunLoop after creating the TrustedSignals object, which will + // ultimately trigger the invocation. + load_signals_run_loop_ = std::make_unique<base::RunLoop>(); + load_signals_run_loop_->Run(); + load_signals_run_loop_.reset(); + } + + // Returns the results of calling TrustedSignals::Result::GetBiddingSignals() + // with `trusted_bidding_signals_keys`. Returns value as a JSON std::string, + // for easy testing. + std::string ExtractBiddingSignals( + TrustedSignals::Result* signals, + std::vector<std::string> trusted_bidding_signals_keys) { + base::RunLoop run_loop; + + std::string result; + v8_helper_->v8_runner()->PostTask( + FROM_HERE, base::BindLambdaForTesting([&]() { + AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper_.get()); + v8::Isolate* isolate = v8_helper_->isolate(); + // Could use the scratch context, but using a separate one more + // closely resembles actual use. + v8::Local<v8::Context> context = v8::Context::New(isolate); + v8::Context::Scope context_scope(context); + + v8::Local<v8::Value> value = signals->GetBiddingSignals( + v8_helper_.get(), context, trusted_bidding_signals_keys); + + if (!v8_helper_->ExtractJson(context, value, &result)) { + result = "JSON extraction failed."; + } + run_loop.Quit(); + })); + run_loop.Run(); + return result; + } + + // Returns the results of calling TrustedSignals::Result::GetScoringSignals() + // with the provided parameters. Returns value as a JSON std::string, for easy + // testing. + std::string ExtractScoringSignals( + TrustedSignals::Result* signals, + const GURL& render_url, + const std::vector<std::string>& ad_component_render_urls) { + base::RunLoop run_loop; + + std::string result; + v8_helper_->v8_runner()->PostTask( + FROM_HERE, base::BindLambdaForTesting([&]() { + AuctionV8Helper::FullIsolateScope isolate_scope(v8_helper_.get()); + v8::Isolate* isolate = v8_helper_->isolate(); + // Could use the scratch context, but using a separate one more + // closely resembles actual use. + v8::Local<v8::Context> context = v8::Context::New(isolate); + v8::Context::Scope context_scope(context); + + v8::Local<v8::Value> value = signals->GetScoringSignals( + v8_helper_.get(), context, render_url, ad_component_render_urls); + + if (!v8_helper_->ExtractJson(context, value, &result)) { + result = "JSON extraction failed."; + } + run_loop.Quit(); + })); + run_loop.Run(); + return result; + } + + protected: + void LoadSignalsCallback(std::unique_ptr<TrustedSignals::Result> result, + absl::optional<std::string> error_msg) { + load_signals_result_ = std::move(result); + error_msg_ = std::move(error_msg); + EXPECT_EQ(load_signals_result_ == nullptr, error_msg_.has_value()); + load_signals_run_loop_->Quit(); + } + + base::test::TaskEnvironment task_environment_; + + // URL without query params attached. + const GURL base_url_ = GURL("https://url.test/"); + + // Reuseable run loop for loading the signals. It's always populated after + // creating the worklet, to cause a crash if the callback is invoked + // synchronously. + std::unique_ptr<base::RunLoop> load_signals_run_loop_; + std::unique_ptr<TrustedSignals::Result> load_signals_result_; + absl::optional<std::string> error_msg_; + + network::TestURLLoaderFactory url_loader_factory_; + scoped_refptr<AuctionV8Helper> v8_helper_; +}; + +TEST_F(TrustedSignalsTest, BiddingSignalsNetworkError) { + url_loader_factory_.AddResponse( + "https://url.test/?hostname=publisher&keys=key1", kBaseBiddingJson, + net::HTTP_NOT_FOUND); + EXPECT_FALSE(FetchBiddingSignals({"key1"}, kHostname)); + ASSERT_TRUE(error_msg_.has_value()); + EXPECT_EQ( + "Failed to load https://url.test/?hostname=publisher&keys=key1 " + "HTTP status = 404 Not Found.", + error_msg_.value()); +} + +TEST_F(TrustedSignalsTest, ScoringSignalsNetworkError) { + url_loader_factory_.AddResponse( + "https://url.test/" + "?hostname=publisher&renderUrls=https%3A%2F%2Ffoo.test%2F", + kBaseScoringJson, net::HTTP_NOT_FOUND); + EXPECT_FALSE(FetchScoringSignals( + /*render_urls=*/{"https://foo.test/"}, + /*ad_component_render_urls=*/{}, kHostname)); + ASSERT_TRUE(error_msg_.has_value()); + EXPECT_EQ( + "Failed to load " + "https://url.test/" + "?hostname=publisher&renderUrls=https%3A%2F%2Ffoo.test%2F " + "HTTP status = 404 Not Found.", + error_msg_.value()); +} + +TEST_F(TrustedSignalsTest, BiddingSignalsResponseNotJson) { + EXPECT_FALSE(FetchBiddingSignalsWithResponse( + GURL("https://url.test/?hostname=publisher&keys=key1"), "Not Json", + {"key1"}, kHostname)); + ASSERT_TRUE(error_msg_.has_value()); + EXPECT_EQ("https://url.test/ Unable to parse as a JSON object.", + error_msg_.value()); +} + +TEST_F(TrustedSignalsTest, ScoringSignalsResponseNotJson) { + EXPECT_FALSE(FetchScoringSignalsWithResponse( + GURL("https://url.test/" + "?hostname=publisher&renderUrls=https%3A%2F%2Ffoo.test%2F"), + "Not Json", + /*render_urls=*/{"https://foo.test/"}, + /*ad_component_render_urls=*/{}, kHostname)); + ASSERT_TRUE(error_msg_.has_value()); + EXPECT_EQ("https://url.test/ Unable to parse as a JSON object.", + error_msg_.value()); +} + +TEST_F(TrustedSignalsTest, BiddingSignalsResponseNotObject) { + EXPECT_FALSE(FetchBiddingSignalsWithResponse( + GURL("https://url.test/?hostname=publisher&keys=key1"), "42", {"key1"}, + kHostname)); + ASSERT_TRUE(error_msg_.has_value()); + EXPECT_EQ("https://url.test/ Unable to parse as a JSON object.", + error_msg_.value()); +} + +TEST_F(TrustedSignalsTest, ScoringSignalsResponseNotObject) { + EXPECT_FALSE(FetchScoringSignalsWithResponse( + GURL("https://url.test/" + "?hostname=publisher&renderUrls=https%3A%2F%2Ffoo.test%2F"), + "42", /*render_urls=*/{"https://foo.test/"}, + /*ad_component_render_urls=*/{}, kHostname)); + ASSERT_TRUE(error_msg_.has_value()); + EXPECT_EQ("https://url.test/ Unable to parse as a JSON object.", + error_msg_.value()); +} + +TEST_F(TrustedSignalsTest, ScoringSignalsExpectedEntriesNotPresent) { + std::unique_ptr<TrustedSignals::Result> signals = + FetchScoringSignalsWithResponse( + GURL("https://url.test/?hostname=publisher" + "&renderUrls=https%3A%2F%2Ffoo.test%2F" + "&adComponentRenderUrls=https%3A%2F%2Fbar.test%2F"), + R"({"foo":4,"bar":5})", + /*render_urls=*/{"https://foo.test/"}, + /*ad_component_render_urls=*/{"https://bar.test/"}, kHostname); + ASSERT_TRUE(signals); + EXPECT_EQ(R"({"renderUrl":{"https://foo.test/":null},)" + R"("adComponentRenderUrls":{"https://bar.test/":null}})", + ExtractScoringSignals( + signals.get(), /*render_url=*/GURL("https://foo.test/"), + /*ad_component_render_urls=*/{"https://bar.test/"})); + EXPECT_FALSE(error_msg_.has_value()); +} + +TEST_F(TrustedSignalsTest, ScoringSignalsNestedEntriesNotObjects) { + std::unique_ptr<TrustedSignals::Result> signals = + FetchScoringSignalsWithResponse( + GURL("https://url.test/?hostname=publisher" + "&renderUrls=https%3A%2F%2Ffoo.test%2F" + "&adComponentRenderUrls=https%3A%2F%2Fbar.test%2F"), + R"({"renderUrls":4,"adComponentRenderUrls":5})", + /*render_urls=*/{"https://foo.test/"}, + /*ad_component_render_urls=*/{"https://bar.test/"}, kHostname); + ASSERT_TRUE(signals); + EXPECT_EQ(R"({"renderUrl":{"https://foo.test/":null},)" + R"("adComponentRenderUrls":{"https://bar.test/":null}})", + ExtractScoringSignals( + signals.get(), /*render_url=*/GURL("https://foo.test/"), + /*ad_component_render_urls=*/{"https://bar.test/"})); + EXPECT_FALSE(error_msg_.has_value()); +} + +TEST_F(TrustedSignalsTest, BiddingSignalsKeyMissing) { + std::unique_ptr<TrustedSignals::Result> signals = + FetchBiddingSignalsWithResponse( + GURL("https://url.test/?hostname=publisher&keys=key4"), + kBaseBiddingJson, {"key4"}, kHostname); + ASSERT_TRUE(signals); + EXPECT_EQ(R"({"key4":null})", ExtractBiddingSignals(signals.get(), {"key4"})); +} + +TEST_F(TrustedSignalsTest, ScoringSignalsKeysMissing) { + std::unique_ptr<TrustedSignals::Result> signals = + FetchScoringSignalsWithResponse( + GURL("https://url.test/?hostname=publisher" + "&renderUrls=https%3A%2F%2Ffoo.test%2F" + "&adComponentRenderUrls=https%3A%2F%2Fbar.test%2F"), + R"({"renderUrls":{"these":"are not"},")" + R"(adComponentRenderUrls":{"the values":"you're looking for"}})", + /*render_urls=*/{"https://foo.test/"}, + /*ad_component_render_urls=*/{"https://bar.test/"}, kHostname); + ASSERT_TRUE(signals); + EXPECT_EQ(R"({"renderUrl":{"https://foo.test/":null},)" + R"("adComponentRenderUrls":{"https://bar.test/":null}})", + ExtractScoringSignals( + signals.get(), /*render_url=*/GURL("https://foo.test/"), + /*ad_component_render_urls=*/{"https://bar.test/"})); + EXPECT_FALSE(error_msg_.has_value()); +} + +TEST_F(TrustedSignalsTest, BiddingSignalsOneKey) { + std::unique_ptr<TrustedSignals::Result> signals = + FetchBiddingSignalsWithResponse( + GURL("https://url.test/?hostname=publisher&keys=key1"), + kBaseBiddingJson, {"key1"}, kHostname); + ASSERT_TRUE(signals); + EXPECT_EQ(R"({"key1":1})", ExtractBiddingSignals(signals.get(), {"key1"})); +} + +TEST_F(TrustedSignalsTest, ScoringSignalsForOneRenderUrl) { + std::unique_ptr<TrustedSignals::Result> signals = + FetchScoringSignalsWithResponse( + GURL("https://url.test/" + "?hostname=publisher&renderUrls=https%3A%2F%2Ffoo.test%2F"), + kBaseScoringJson, + /*render_urls=*/{"https://foo.test/"}, + /*ad_component_render_urls=*/{}, kHostname); + ASSERT_TRUE(signals); + EXPECT_EQ(R"({"renderUrl":{"https://foo.test/":1}})", + ExtractScoringSignals(signals.get(), + /*render_url=*/GURL("https://foo.test/"), + /*ad_component_render_urls=*/{})); + EXPECT_FALSE(error_msg_.has_value()); +} + +TEST_F(TrustedSignalsTest, BiddingSignalsMultipleKeys) { + std::unique_ptr<TrustedSignals::Result> signals = + FetchBiddingSignalsWithResponse( + GURL("https://url.test/?hostname=publisher&keys=key1,key2,key3,key5"), + kBaseBiddingJson, {"key3", "key1", "key5", "key2"}, kHostname); + ASSERT_TRUE(signals); + EXPECT_EQ(R"({"key1":1})", ExtractBiddingSignals(signals.get(), {"key1"})); + EXPECT_EQ(R"({"key2":[2]})", ExtractBiddingSignals(signals.get(), {"key2"})); + EXPECT_EQ(R"({"key3":null})", ExtractBiddingSignals(signals.get(), {"key3"})); + EXPECT_EQ(R"({"key5":"value5"})", + ExtractBiddingSignals(signals.get(), {"key5"})); + EXPECT_EQ( + R"({"key1":1,"key2":[2],"key3":null,"key5":"value5"})", + ExtractBiddingSignals(signals.get(), {"key1", "key2", "key3", "key5"})); +} + +TEST_F(TrustedSignalsTest, ScoringSignalsMultipleUrls) { + // URLs are currently added in lexical order. + std::unique_ptr<TrustedSignals::Result> signals = + FetchScoringSignalsWithResponse( + GURL("https://url.test/?hostname=publisher" + "&renderUrls=https%3A%2F%2Fbar.test%2F," + "https%3A%2F%2Fbaz.test%2F,https%3A%2F%2Ffoo.test%2F" + "&adComponentRenderUrls=https%3A%2F%2Fbarsub.test%2F," + "https%3A%2F%2Fbazsub.test%2F,https%3A%2F%2Ffoosub.test%2F"), + kBaseScoringJson, + /*render_urls=*/ + {"https://foo.test/", "https://bar.test/", "https://baz.test/"}, + /*ad_component_render_urls=*/ + {"https://foosub.test/", "https://barsub.test/", + "https://bazsub.test/"}, + kHostname); + ASSERT_TRUE(signals); + EXPECT_FALSE(error_msg_.has_value()); + EXPECT_EQ(R"({"renderUrl":{"https://bar.test/":[2]},")" + R"(adComponentRenderUrls":{"https://foosub.test/":2,)" + R"("https://barsub.test/":[3],"https://bazsub.test/":null}})", + ExtractScoringSignals( + signals.get(), /*render_url=*/GURL("https://bar.test/"), + /*ad_component_render_urls=*/ + {"https://foosub.test/", "https://barsub.test/", + "https://bazsub.test/"})); +} + +TEST_F(TrustedSignalsTest, BiddingSignalsDuplicateKeys) { + std::vector<std::string> bidder_signals{"key1", "key2", "key2", "key1", + "key2"}; + std::unique_ptr<TrustedSignals::Result> signals = + FetchBiddingSignalsWithResponse( + GURL("https://url.test/?hostname=publisher&keys=key1,key2"), + kBaseBiddingJson, bidder_signals, kHostname); + ASSERT_TRUE(signals); + EXPECT_EQ(R"({"key1":1,"key2":[2]})", + ExtractBiddingSignals(signals.get(), bidder_signals)); +} + +TEST_F(TrustedSignalsTest, ScoringSignalsDuplicateKeys) { + std::vector<std::string> ad_component_render_urls{ + "https://barsub.test/", "https://foosub.test/", "https://foosub.test/", + "https://barsub.test/"}; + std::unique_ptr<TrustedSignals::Result> signals = + FetchScoringSignalsWithResponse( + GURL("https://url.test/?hostname=publisher" + "&renderUrls=https%3A%2F%2Fbar.test%2F,https%3A%2F%2Ffoo.test%2F" + "&adComponentRenderUrls=https%3A%2F%2Fbarsub.test%2F," + "https%3A%2F%2Ffoosub.test%2F"), + kBaseScoringJson, + /*render_urls=*/ + {"https://foo.test/", "https://foo.test/", "https://bar.test/", + "https://bar.test/", "https://foo.test/"}, + ad_component_render_urls, kHostname); + ASSERT_TRUE(signals); + EXPECT_FALSE(error_msg_.has_value()); + EXPECT_EQ(R"({"renderUrl":{"https://bar.test/":[2]},")" + R"(adComponentRenderUrls":{)" + R"("https://barsub.test/":[3],"https://foosub.test/":2}})", + ExtractScoringSignals(signals.get(), + /*render_url=*/GURL("https://bar.test/"), + ad_component_render_urls)); +} + +// Test when a single URL is used as both a `renderUrl` and +// `adComponentRenderUrl`. +TEST_F(TrustedSignalsTest, ScoringSignalsSharedUrl) { + // URLs are currently added in lexical order. + std::unique_ptr<TrustedSignals::Result> signals = + FetchScoringSignalsWithResponse( + GURL("https://url.test/?hostname=publisher" + "&renderUrls=https%3A%2F%2Fshared.test%2F" + "&adComponentRenderUrls=https%3A%2F%2Fshared.test%2F"), + kBaseScoringJson, + /*render_urls=*/ + {"https://shared.test/"}, + /*ad_component_render_urls=*/ + {"https://shared.test/"}, kHostname); + ASSERT_TRUE(signals); + EXPECT_FALSE(error_msg_.has_value()); + EXPECT_EQ( + R"({"renderUrl":{"https://shared.test/":"render url"},")" + R"(adComponentRenderUrls":{"https://shared.test/":"ad component url"}})", + ExtractScoringSignals(signals.get(), + /*render_url=*/GURL("https://shared.test/"), + /*ad_component_render_urls=*/ + {"https://shared.test/"})); +} + +TEST_F(TrustedSignalsTest, BiddingSignalsEscapeQueryParams) { + std::unique_ptr<TrustedSignals::Result> signals = + FetchBiddingSignalsWithResponse( + GURL("https://url.test/" + "?hostname=pub+li%26sher&keys=key+6,key%2C8,key%3D7"), + kBaseBiddingJson, {"key 6", "key=7", "key,8"}, "pub li&sher"); + ASSERT_TRUE(signals); + EXPECT_EQ(R"({"key 6":6})", ExtractBiddingSignals(signals.get(), {"key 6"})); + EXPECT_EQ(R"({"key=7":7})", ExtractBiddingSignals(signals.get(), {"key=7"})); + EXPECT_EQ(R"({"key,8":8})", ExtractBiddingSignals(signals.get(), {"key,8"})); +} + +TEST_F(TrustedSignalsTest, ScoringSignalsEscapeQueryParams) { + std::unique_ptr<TrustedSignals::Result> signals = + FetchScoringSignalsWithResponse( + GURL("https://url.test/?hostname=pub+li%26sher" + "&renderUrls=https%3A%2F%2Ffoo.test%2F%3F%26%3D" + "&adComponentRenderUrls=https%3A%2F%2Fbar.test%2F%3F%26%3D"), + R"( + { + "renderUrls": { + "https://foo.test/?&=": 4 + }, + "adComponentRenderUrls": { + "https://bar.test/?&=": 5 + } + } +)", + /*render_urls=*/ + {"https://foo.test/?&="}, /*ad_component_render_urls=*/ + {"https://bar.test/?&="}, "pub li&sher"); + ASSERT_TRUE(signals); + EXPECT_EQ(R"({"renderUrl":{"https://foo.test/?&=":4},)" + R"("adComponentRenderUrls":{"https://bar.test/?&=":5}})", + ExtractScoringSignals( + signals.get(), /*render_url=*/ + GURL("https://foo.test/?&="), /*ad_component_render_urls=*/ + {"https://bar.test/?&="})); + EXPECT_FALSE(error_msg_.has_value()); +} + +// Testcase where the loader is deleted after it queued the parsing of +// the script on V8 thread, but before it gets to finish. +TEST_F(TrustedSignalsTest, BiddingSignalsDeleteBeforeCallback) { + GURL url("https://url.test/?hostname=publisher&keys=key1"); + + AddJsonResponse(&url_loader_factory_, url, kBaseBiddingJson); + + // Wedge the V8 thread to control when the JSON parsing takes place. + base::WaitableEvent* event_handle = WedgeV8Thread(v8_helper_.get()); + + auto bidding_signals = TrustedSignals::LoadBiddingSignals( + &url_loader_factory_, {"key1"}, "publisher", base_url_, v8_helper_, + base::BindOnce([](std::unique_ptr<TrustedSignals::Result> result, + absl::optional<std::string> error_msg) { + ADD_FAILURE() << "Callback should not be invoked since loader deleted"; + })); + base::RunLoop().RunUntilIdle(); + bidding_signals.reset(); + event_handle->Signal(); +} + +// Testcase where the loader is deleted after it queued the parsing of +// the script on V8 thread, but before it gets to finish. +TEST_F(TrustedSignalsTest, ScoringSignalsDeleteBeforeCallback) { + GURL url( + "https://url.test/" + "?hostname=publisher&renderUrls=https%3A%2F%2Ffoo.test%2F"); + + AddJsonResponse(&url_loader_factory_, url, kBaseScoringJson); + + // Wedge the V8 thread to control when the JSON parsing takes place. + base::WaitableEvent* event_handle = WedgeV8Thread(v8_helper_.get()); + auto scoring_signals = TrustedSignals::LoadScoringSignals( + &url_loader_factory_, + /*render_urls=*/{"http://foo.test/"}, + /*ad_component_render_urls=*/{}, "publisher", base_url_, v8_helper_, + base::BindOnce([](std::unique_ptr<TrustedSignals::Result> result, + absl::optional<std::string> error_msg) { + ADD_FAILURE() << "Callback should not be invoked since loader deleted"; + })); + base::RunLoop().RunUntilIdle(); + scoring_signals.reset(); + event_handle->Signal(); +} + +} // namespace +} // namespace auction_worklet
diff --git a/content/test/data/accessibility/mac/attributes/ax-has-popup-expected.txt b/content/test/data/accessibility/mac/attributes/ax-has-popup-expected.txt new file mode 100644 index 0000000..c356171d --- /dev/null +++ b/content/test/data/accessibility/mac/attributes/ax-has-popup-expected.txt
@@ -0,0 +1,2 @@ +has_popup.AXHasPopup=1 +not_applicable.AXHasPopup=n/a
diff --git a/content/test/data/accessibility/mac/attributes/ax-has-popup.html b/content/test/data/accessibility/mac/attributes/ax-has-popup.html new file mode 100644 index 0000000..07f1745 --- /dev/null +++ b/content/test/data/accessibility/mac/attributes/ax-has-popup.html
@@ -0,0 +1,8 @@ +<!-- +@SCRIPT: + has_popup.AXHasPopup + not_applicable.AXHasPopup +--> +<!DOCTYPE html> +<div id="has_popup" role="combobox" aria-haspopup="listbox"></div> +<div id="not_applicable"></div>
diff --git a/content/test/data/accessibility/mac/attributes/ax-popup-value-expected.txt b/content/test/data/accessibility/mac/attributes/ax-popup-value-expected.txt new file mode 100644 index 0000000..f80db6b --- /dev/null +++ b/content/test/data/accessibility/mac/attributes/ax-popup-value-expected.txt
@@ -0,0 +1,6 @@ +has_popup_menu.AXPopupValue='menu' +has_popup_listbox.AXPopupValue='listbox' +has_popup_tree.AXPopupValue='tree' +has_popup_grid.AXPopupValue='grid' +has_popup_dialog.AXPopupValue='dialog' +not_applicable.AXPopupValue=n/a
diff --git a/content/test/data/accessibility/mac/attributes/ax-popup-value.html b/content/test/data/accessibility/mac/attributes/ax-popup-value.html new file mode 100644 index 0000000..2ca576e --- /dev/null +++ b/content/test/data/accessibility/mac/attributes/ax-popup-value.html
@@ -0,0 +1,16 @@ +<!-- +@SCRIPT: + has_popup_menu.AXPopupValue + has_popup_listbox.AXPopupValue + has_popup_tree.AXPopupValue + has_popup_grid.AXPopupValue + has_popup_dialog.AXPopupValue + not_applicable.AXPopupValue +--> +<!DOCTYPE html> +<div id="has_popup_menu" role="combobox" aria-haspopup="menu"></div> +<div id="has_popup_listbox" role="combobox" aria-haspopup="listbox"></div> +<div id="has_popup_tree" role="combobox" aria-haspopup="tree"></div> +<div id="has_popup_grid" role="combobox" aria-haspopup="grid"></div> +<div id="has_popup_dialog" role="combobox" aria-haspopup="dialog"></div> +<div id="not_applicable"></div>
diff --git a/extensions/browser/notification_types.h b/extensions/browser/notification_types.h index 68b050a..62c1b32 100644 --- a/extensions/browser/notification_types.h +++ b/extensions/browser/notification_types.h
@@ -65,11 +65,6 @@ // TODO(https://crbug.com/1174750): Remove. NOTIFICATION_EXTENSION_OMNIBOX_SUGGESTIONS_READY, - // Sent when the user accepts the input in an extension omnibox keyword - // session. The source is the BrowserContext*. - // TODO(https://crbug.com/1174751): Remove. - NOTIFICATION_EXTENSION_OMNIBOX_INPUT_ENTERED, - // Sent when an omnibox extension has updated the default suggestion. The // source is the BrowserContext*. // TODO(https://crbug.com/1174752): Remove.
diff --git a/extensions/common/api/runtime.json b/extensions/common/api/runtime.json index 2b5f24b0..f5902a2 100644 --- a/extensions/common/api/runtime.json +++ b/extensions/common/api/runtime.json
@@ -311,7 +311,6 @@ ], "returns": { "$ref": "Port", - // TODO(robwu): This description should appear in the documentation, but the docserver does not render it. "description": "Port through which messages can be sent and received. The port's $(ref:Port onDisconnect) event is fired if the extension/app does not exist. " } },
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg index be97fa83..4e1d3e09 100644 --- a/infra/config/generated/luci/commit-queue.cfg +++ b/infra/config/generated/luci/commit-queue.cfg
@@ -78,6 +78,18 @@ owner_whitelist_group: "project-chromium-robot-committers" } builders { + name: "chrome/try/chromeos-octopus-chrome" + includable_only: true + owner_whitelist_group: "googlers" + owner_whitelist_group: "project-chromium-robot-committers" + } + builders { + name: "chrome/try/chromeos-octopus-compile-chrome" + includable_only: true + owner_whitelist_group: "googlers" + owner_whitelist_group: "project-chromium-robot-committers" + } + builders { name: "chrome/try/ipad-device" includable_only: true owner_whitelist_group: "googlers"
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg index e9f31226..94376b9 100644 --- a/infra/config/generated/luci/cr-buildbucket.cfg +++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -50245,7 +50245,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -50336,7 +50336,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -50427,7 +50427,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -50518,7 +50518,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -50783,7 +50783,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -50874,7 +50874,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -51071,7 +51071,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -51162,7 +51162,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -51253,7 +51253,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -51344,7 +51344,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -51435,7 +51435,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -51526,7 +51526,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -51617,7 +51617,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -51708,7 +51708,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -51799,7 +51799,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -51890,7 +51890,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -51981,7 +51981,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -52072,7 +52072,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -52163,7 +52163,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -52436,7 +52436,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -52537,7 +52537,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -52741,7 +52741,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -52842,7 +52842,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -53038,7 +53038,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -53130,7 +53130,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -53221,7 +53221,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -53312,7 +53312,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -53403,7 +53403,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -53494,7 +53494,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -53589,7 +53589,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -53684,7 +53684,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -53775,7 +53775,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -53869,7 +53869,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -54059,7 +54059,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -54150,7 +54150,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -54242,7 +54242,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -54333,7 +54333,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -54424,7 +54424,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -54515,7 +54515,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -54606,7 +54606,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -54697,7 +54697,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -54788,7 +54788,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -54879,7 +54879,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -54970,7 +54970,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -55061,7 +55061,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -55152,7 +55152,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -55243,7 +55243,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -55334,7 +55334,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -55425,7 +55425,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -55516,7 +55516,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -55607,7 +55607,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -55699,7 +55699,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -55790,7 +55790,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -55881,7 +55881,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -55973,7 +55973,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -56064,7 +56064,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -56155,7 +56155,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -56246,7 +56246,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -56336,7 +56336,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -56427,7 +56427,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -56518,7 +56518,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -56609,7 +56609,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -56700,7 +56700,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -57002,7 +57002,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -57093,7 +57093,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -57183,7 +57183,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -57274,7 +57274,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -57365,7 +57365,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -57456,7 +57456,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -57547,7 +57547,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -57641,7 +57641,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -57828,7 +57828,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -57919,7 +57919,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -58016,7 +58016,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -58107,7 +58107,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -58198,7 +58198,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -58373,7 +58373,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -58461,7 +58461,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -58549,7 +58549,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -58637,7 +58637,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -58726,7 +58726,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -58815,7 +58815,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -58904,7 +58904,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -58993,7 +58993,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -59084,7 +59084,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -59262,7 +59262,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -59448,7 +59448,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -59630,7 +59630,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -59721,7 +59721,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -59812,7 +59812,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -59903,7 +59903,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -59994,7 +59994,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -60085,7 +60085,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -60175,7 +60175,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -60265,7 +60265,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -60355,7 +60355,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -60445,7 +60445,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -60532,7 +60532,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -60619,7 +60619,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -60706,7 +60706,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -60793,7 +60793,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -60880,7 +60880,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -60967,7 +60967,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -61054,7 +61054,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -61141,7 +61141,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -61228,7 +61228,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -61315,7 +61315,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -61402,7 +61402,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -61489,7 +61489,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -61576,7 +61576,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -61663,7 +61663,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -61750,7 +61750,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -61837,7 +61837,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -61924,7 +61924,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -62011,7 +62011,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -62098,7 +62098,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -62185,7 +62185,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -62272,7 +62272,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -62359,7 +62359,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -62444,7 +62444,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -62529,7 +62529,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -62614,7 +62614,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -62699,7 +62699,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -62784,7 +62784,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -62869,7 +62869,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -62954,7 +62954,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -63039,7 +63039,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -63124,7 +63124,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -63209,7 +63209,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -63294,7 +63294,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -63379,7 +63379,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -63464,7 +63464,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -63551,7 +63551,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -63638,7 +63638,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -63725,7 +63725,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -63812,7 +63812,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -63899,7 +63899,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -63986,7 +63986,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -64073,7 +64073,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -64160,7 +64160,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -64247,7 +64247,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -64334,7 +64334,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -64421,7 +64421,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -64508,7 +64508,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -64595,7 +64595,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -64682,7 +64682,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -64769,7 +64769,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -64856,7 +64856,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -64941,7 +64941,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -65026,7 +65026,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -65113,7 +65113,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -65292,7 +65292,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -65385,7 +65385,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -65486,7 +65486,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -65582,7 +65582,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -65686,7 +65686,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -65779,7 +65779,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -65872,7 +65872,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -65965,7 +65965,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -66065,7 +66065,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -66158,7 +66158,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -66251,7 +66251,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -66344,7 +66344,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -66437,7 +66437,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -66527,7 +66527,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -66617,7 +66617,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -66708,7 +66708,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -66799,7 +66799,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -66893,7 +66893,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -67071,7 +67071,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -67249,7 +67249,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -67340,7 +67340,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -67431,7 +67431,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -67523,7 +67523,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -67614,7 +67614,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -67705,7 +67705,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -67796,7 +67796,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -67887,7 +67887,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -67978,7 +67978,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -68069,7 +68069,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -68167,7 +68167,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -68258,7 +68258,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -68349,7 +68349,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -68439,7 +68439,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -68530,7 +68530,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -68621,7 +68621,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -68716,7 +68716,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -68817,7 +68817,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -69195,7 +69195,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -69286,7 +69286,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -69377,7 +69377,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -69468,7 +69468,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -69553,7 +69553,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -69644,7 +69644,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -69735,7 +69735,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -69826,7 +69826,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -69917,7 +69917,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -70009,7 +70009,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -70108,7 +70108,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -70200,7 +70200,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -70291,7 +70291,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -70382,7 +70382,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -70563,7 +70563,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -70654,7 +70654,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -70745,7 +70745,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -70836,7 +70836,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -70884,37 +70884,33 @@ dimensions: "os:Ubuntu-18.04" dimensions: "pool:luci.chromium.try" exe { - cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" - cipd_version: "refs/heads/main" - cmd: "luciexe" + cipd_package: "infra/chromium/bootstrapper/${platform}" + cipd_version: "latest" + cmd: "bootstrapper" } properties: '{' - ' "$build/chromium_orchestrator": {' - ' "compilator": "linux-rel-compilator",' - ' "compilator_watcher_git_revision": "5fd7f4ae276865742fe632642ec4633dd9f81649"' + ' "$bootstrap/exe": {' + ' "exe": {' + ' "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",' + ' "cipd_version": "refs/heads/main",' + ' "cmd": [' + ' "luciexe"' + ' ]' + ' }' ' },' - ' "$build/code_coverage": {' - ' "coverage_test_types": [' - ' "unit",' - ' "overall"' - ' ],' - ' "use_clang_coverage": true' - ' },' - ' "$build/goma": {' - ' "enable_ats": true,' - ' "rpc_extra_params": "?prod",' - ' "server_host": "goma.chromium.org",' - ' "use_luci_auth": true' - ' },' - ' "$recipe_engine/resultdb/test_presentation": {' - ' "column_keys": [],' - ' "grouping_keys": [' - ' "status",' - ' "v.test_suite"' - ' ]' + ' "$bootstrap/properties": {' + ' "properties_file": "infra/config/generated/builders/try/linux-rel/properties.textpb",' + ' "top_level_project": {' + ' "ref": "refs/heads/main",' + ' "repo": {' + ' "host": "chromium.googlesource.com",' + ' "project": "chromium/src"' + ' }' + ' }' ' },' ' "builder_group": "tryserver.chromium.linux",' + ' "led_builder_is_bootstrapped": true,' ' "recipe": "chromium/orchestrator"' '}' execution_timeout_secs: 14400 @@ -70937,7 +70933,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -70987,38 +70983,33 @@ dimensions: "pool:luci.chromium.try" dimensions: "ssd:1" exe { - cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build" - cipd_version: "refs/heads/main" - cmd: "luciexe" + cipd_package: "infra/chromium/bootstrapper/${platform}" + cipd_version: "latest" + cmd: "bootstrapper" } properties: '{' - ' "$build/code_coverage": {' - ' "coverage_test_types": [' - ' "unit",' - ' "overall"' - ' ],' - ' "use_clang_coverage": true' + ' "$bootstrap/exe": {' + ' "exe": {' + ' "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",' + ' "cipd_version": "refs/heads/main",' + ' "cmd": [' + ' "luciexe"' + ' ]' + ' }' ' },' - ' "$build/goma": {' - ' "enable_ats": true,' - ' "jobs": 150,' - ' "rpc_extra_params": "?prod",' - ' "server_host": "goma.chromium.org",' - ' "use_luci_auth": true' - ' },' - ' "$recipe_engine/resultdb/test_presentation": {' - ' "column_keys": [],' - ' "grouping_keys": [' - ' "status",' - ' "v.test_suite"' - ' ]' + ' "$bootstrap/properties": {' + ' "properties_file": "infra/config/generated/builders/try/linux-rel-compilator/properties.textpb",' + ' "top_level_project": {' + ' "ref": "refs/heads/main",' + ' "repo": {' + ' "host": "chromium.googlesource.com",' + ' "project": "chromium/src"' + ' }' + ' }' ' },' ' "builder_group": "tryserver.chromium.linux",' - ' "orchestrator": {' - ' "builder_group": "tryserver.chromium.linux",' - ' "builder_name": "linux-rel"' - ' },' + ' "led_builder_is_bootstrapped": true,' ' "recipe": "chromium/compilator"' '}' execution_timeout_secs: 14400 @@ -71135,7 +71126,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -71229,7 +71220,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -71320,7 +71311,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -71414,7 +71405,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -71508,7 +71499,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -71596,7 +71587,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -71852,7 +71843,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -71944,7 +71935,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -72035,7 +72026,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -72125,7 +72116,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -72216,7 +72207,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -72307,7 +72298,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -72398,7 +72389,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -72489,7 +72480,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -72581,7 +72572,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -72672,7 +72663,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -72763,7 +72754,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -72854,7 +72845,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -72948,7 +72939,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -73138,7 +73129,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -73229,7 +73220,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -73321,7 +73312,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -73413,7 +73404,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -73595,7 +73586,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -73686,7 +73677,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -73781,7 +73772,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -73872,7 +73863,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -73967,7 +73958,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -74059,7 +74050,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -74153,7 +74144,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -74342,7 +74333,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -74433,7 +74424,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -74524,7 +74515,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -74615,7 +74606,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -74706,7 +74697,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -74796,7 +74787,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -74972,7 +74963,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -75145,7 +75136,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -75234,7 +75225,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -75322,7 +75313,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -75411,7 +75402,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -75504,7 +75495,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -75699,7 +75690,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -75793,7 +75784,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -75878,7 +75869,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -75966,7 +75957,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -76054,7 +76045,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -76143,7 +76134,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -76232,7 +76223,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -76321,7 +76312,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -76410,7 +76401,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -76504,7 +76495,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -76689,7 +76680,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -76778,7 +76769,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -76867,7 +76858,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -76956,7 +76947,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -77045,7 +77036,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -77134,7 +77125,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -77223,7 +77214,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -77312,7 +77303,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -77401,7 +77392,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -77491,7 +77482,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -77581,7 +77572,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -77670,7 +77661,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -77759,7 +77750,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -77847,7 +77838,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -78106,7 +78097,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -78559,7 +78550,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -78911,7 +78902,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -79003,7 +78994,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -79101,7 +79092,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -79281,7 +79272,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -79462,7 +79453,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -79550,7 +79541,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -79974,7 +79965,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -80062,7 +80053,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -80153,7 +80144,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -80244,7 +80235,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -80335,7 +80326,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -80426,7 +80417,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -80523,7 +80514,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -80614,7 +80605,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -80715,7 +80706,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -80910,7 +80901,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -81004,7 +80995,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -81095,7 +81086,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -81186,7 +81177,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -81278,7 +81269,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -81369,7 +81360,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -81461,7 +81452,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -81552,7 +81543,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -81643,7 +81634,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -81734,7 +81725,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -81825,7 +81816,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -81916,7 +81907,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms" @@ -82091,7 +82082,7 @@ } experiments { key: "luci.recipes.use_python3" - value: 5 + value: 25 } experiments { key: "luci.use_realms"
diff --git a/infra/config/recipes.star b/infra/config/recipes.star index d5124cf..c601e970 100644 --- a/infra/config/recipes.star +++ b/infra/config/recipes.star
@@ -138,7 +138,7 @@ name = "recipe:chromium/orchestrator", bootstrappable = True, experiments = { - "luci.recipes.use_python3": 5, + "luci.recipes.use_python3": 25, }, ) @@ -198,7 +198,7 @@ name = "recipe:chromium_trybot", bootstrappable = True, experiments = { - "luci.recipes.use_python3": 5, + "luci.recipes.use_python3": 25, }, )
diff --git a/infra/config/subprojects/chromium/try.star b/infra/config/subprojects/chromium/try.star index dfdaf3a2..cdb75df 100644 --- a/infra/config/subprojects/chromium/try.star +++ b/infra/config/subprojects/chromium/try.star
@@ -1361,6 +1361,7 @@ try_.chromium_linux_orchestrator_pair( name = "linux-rel", + bootstrap = True, branch_selector = branches.STANDARD_MILESTONE, main_list_view = "try", use_clang_coverage = True, @@ -2395,6 +2396,14 @@ ) chrome_internal_verifier( + builder = "chromeos-octopus-chrome", +) + +chrome_internal_verifier( + builder = "chromeos-octopus-compile-chrome", +) + +chrome_internal_verifier( builder = "ipad-device", )
diff --git a/ios/chrome/app/strings/resources/ios_chromium_strings_hu.xtb b/ios/chrome/app/strings/resources/ios_chromium_strings_hu.xtb index 6828633..7a0bebf1 100644 --- a/ios/chrome/app/strings/resources/ios_chromium_strings_hu.xtb +++ b/ios/chrome/app/strings/resources/ios_chromium_strings_hu.xtb
@@ -31,6 +31,7 @@ <translation id="2478931088402984578">Válassza ki a <ph name="BEGIN_BOLD" />Chromium<ph name="END_BOLD" /> böngészőt.</translation> <translation id="2567507405773541360">Még több mindent elvégezhet az egyszerű, biztonságos és minden eddiginél gyorsabb Chromium segítségével</translation> <translation id="2590893390871230428">Chromium-adatok szinkronizálása</translation> +<translation id="259094968798709429">A jelszavakat a <ph name="BEGIN_LINK" />Jelszókezelőbe<ph name="END_LINK" /> menti a rendszer, így bármely eszközén használhatja őket.</translation> <translation id="2650312721222849884">Kapcsolja be a szinkronizálást, hogy bárhol hozzáférhessen a lapjaihoz, ahol a Chromiumot használja.</translation> <translation id="2684230048001240293">A Chromiumot alapértelmezett böngészőként beállítva az összes eszközén szinkronizálhatja a lapfüleket, jelszavakat és fizetési adatokat</translation> <translation id="2730884209570016437">A Chromium nem tudja használni a kamerát, mivel egy másik alkalmazás használja</translation> @@ -44,6 +45,7 @@ <translation id="3639997914391704523">A Chromium ellenőrizni tudja a mentett jelszavakat, ha Ön bejelentkezik Google-fiókjával.</translation> <translation id="3805899903892079518">A Chromium nem fér hozzá az Ön képeihez és videóihoz. Engedélyezés iOS-en: Beállítások > Adatvédelem > Fényképek.</translation> <translation id="3946918322491238254">Továbbra is láthatja könyvjelzőit, előzményeit, jelszavait és más beállításait ezen az eszközön. A módosításokat azonban a böngésző nem szinkronizálja az Ön fiókjába.</translation> +<translation id="4043291146360695975">A jelszavakat csak ezen az eszközön menti a rendszer a Jelszókezelőbe.</translation> <translation id="4099085513035183040">A Chromium bétaverziója nem támogatja</translation> <translation id="4555020257205549924">Ha a funkció be van kapcsolva, a Chromium felajánlja a más nyelveken írt oldalak lefordítását a Google Fordító segítségével. <ph name="BEGIN_LINK" />További információ<ph name="END_LINK" />.</translation> <translation id="4585809515399340748">Mostantól bármikor használhatja a Chromiumot, amikor üzenetekben, dokumentumokban és más alkalmazásokban lévő linkekre koppint.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_chromium_strings_lt.xtb b/ios/chrome/app/strings/resources/ios_chromium_strings_lt.xtb index 986afcb..b70c83a 100644 --- a/ios/chrome/app/strings/resources/ios_chromium_strings_lt.xtb +++ b/ios/chrome/app/strings/resources/ios_chromium_strings_lt.xtb
@@ -31,6 +31,7 @@ <translation id="2478931088402984578">Pasirinkite <ph name="BEGIN_BOLD" />Chromium<ph name="END_BOLD" /></translation> <translation id="2567507405773541360">Nuveikite daugiau naudodami paprastą, saugią ir spartesnę nei bet kada anksčiau „Chromium“</translation> <translation id="2590893390871230428">Sinchronizuoti „Chromium“ duomenis</translation> +<translation id="259094968798709429">Slaptažodžiai saugomi <ph name="BEGIN_LINK" />Slaptažodžių tvarkytuvėje<ph name="END_LINK" />, todėl juos galite naudoti bet kuriame įrenginyje.</translation> <translation id="2650312721222849884">Kad matytumėte skirtukus iš visų įrenginių, kuriuose naudojate „Chromium“, įjunkite sinchronizavimą</translation> <translation id="2684230048001240293">Nustatykite „Chromium“ kaip numatytąjį nustatymą, kad būtų sinchronizuojami skirtukai, slaptažodžiai ir mokėjimo informacija visuose įrenginiuose</translation> <translation id="2730884209570016437">„Chromium“ negali naudoti fotoaparato, nes jį naudoja kita programa</translation> @@ -44,6 +45,7 @@ <translation id="3639997914391704523">„Chromium“ gali tikrinti jūsų slaptažodžius, kai prisijungiate naudodami „Google“ paskyrą.</translation> <translation id="3805899903892079518">„Chromium“ nepasiekia nuotraukų ar vaizdo įr. Įgalinkite prieigą apsil. „iOS“ nustatymai“ > „Privatumas“ > „Nuotraukos“.</translation> <translation id="3946918322491238254">Vis tiek galėsite peržiūrėti visas žymes, istoriją, slaptažodžius ir kitus nustatymus šiame įrenginyje. Jei atliksite pakeitimų, jie nebus sinchronizuojami su jūsų paskyra.</translation> +<translation id="4043291146360695975">Slaptažodžiai saugomi Slaptažodžių tvarkytuvėje tik šiame įrenginyje.</translation> <translation id="4099085513035183040">Nepalaikoma „Chromium“ beta versijoje</translation> <translation id="4555020257205549924">Kai ši funkcija bus įjungta, „Chromium“ siūlys versti kitomis kalbomis parašytus puslapius naudojant „Google“ vertėją. <ph name="BEGIN_LINK" />Sužinokite daugiau<ph name="END_LINK" /></translation> <translation id="4585809515399340748">Dabar galite naudoti „Chromium“ bet kada palietę nuorodas pranešimuose, dokumentuose ir kitose programose.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_chromium_strings_ms.xtb b/ios/chrome/app/strings/resources/ios_chromium_strings_ms.xtb index b099967..4d1ba94b 100644 --- a/ios/chrome/app/strings/resources/ios_chromium_strings_ms.xtb +++ b/ios/chrome/app/strings/resources/ios_chromium_strings_ms.xtb
@@ -31,6 +31,7 @@ <translation id="2478931088402984578">Pilih <ph name="BEGIN_BOLD" />Chromium<ph name="END_BOLD" /></translation> <translation id="2567507405773541360">Selesaikan lebih banyak tugas dengan Chromium yang mudah, selamat dan lebih pantas berbanding dahulu</translation> <translation id="2590893390871230428">Segerakkan Data Chromium Anda</translation> +<translation id="259094968798709429">Kata laluan disimpan pada <ph name="BEGIN_LINK" />Pengurus Kata Laluan<ph name="END_LINK" /> supaya anda boleh menggunakan kata laluan tersebut pada mana-mana peranti.</translation> <translation id="2650312721222849884">Untuk melihat tab anda daripada mana-mana tempat anda menggunakan Chromium, hidupkan penyegerakan</translation> <translation id="2684230048001240293">Tetapkan Chromium sebagai penyemak imbas lalai untuk menyegerakkan tab, kata laluan dan maklumat pembayaran anda pada semua peranti anda</translation> <translation id="2730884209570016437">Chromium tidak dapat menggunakan kamera anda kerana kamera sedang digunakan oleh aplikasi lain</translation> @@ -44,6 +45,7 @@ <translation id="3639997914391704523">Chromium boleh menyemak kata laluan anda apabila anda log masuk menggunakan Akaun Google anda.</translation> <translation id="3805899903892079518">Chromium tidak mempunyai akses kepada foto atau video anda. Dayakan akses dalam Tetapan iOS > Privasi > Foto.</translation> <translation id="3946918322491238254">Anda masih boleh melihat semua penanda halaman, sejarah, kata laluan dan tetapan anda yang lain pada peranti ini. Jika anda perubahan dibuat, perubahan itu tidak akan disegerakkan ke akaun anda.</translation> +<translation id="4043291146360695975">Kata laluan disimpan pada Pengurus Kata Laluan pada peranti ini sahaja.</translation> <translation id="4099085513035183040">Tidak disokong pada Chromium Beta</translation> <translation id="4555020257205549924">Apabila ciri ini dihidupkan, Chromium akan menawarkan untuk menterjemah halaman yang ditulis dalam bahasa lain menggunakan Terjemahan Google. <ph name="BEGIN_LINK" />Ketahui lebih lanjut<ph name="END_LINK" /></translation> <translation id="4585809515399340748">Kini, anda boleh menggunakan Chromium pada bila-bila masa anda mengetik pautan dalam mesej, dokumen dan apl lain.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_hu.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_hu.xtb index 370b883..42060627 100644 --- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_hu.xtb +++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_hu.xtb
@@ -50,6 +50,7 @@ <translation id="4214277427269650960">Jelentkezzen be erre a webhelyre és a Chrome-ba. A szinkronizálást később is bekapcsolhatja.</translation> <translation id="424864128008805179">Kijelentkezés a Chrome-ból</translation> <translation id="4249068189593983585">Tipp a Chrome használatához. A további lapbeállításokhoz érintse meg és tartsa lenyomva az eszköztárban lévő Lapok megjelenítése gombot, amely a képernyő alsó vagy felső részén található.</translation> +<translation id="4267862249323750454">A jelszavakat a <ph name="BEGIN_LINK" />Google Jelszókezelőbe<ph name="END_LINK" /> menti a rendszer, így bármely eszközén használhatja őket.</translation> <translation id="4523886039239821078">Bizonyos bővítmények a Chrome összeomlását eredményezik. Távolítsa el a következő(ke)t:</translation> <translation id="4633328489441962921">A Chrome nem tud frissítéseket keresni</translation> <translation id="4698415050768537821">A Chrome nem tudta ellenőrizni az összes jelszót. Próbálja újra holnap, vagy <ph name="BEGIN_LINK" />ellenőrizze jelszavait a Google-fiókjában<ph name="END_LINK" />.</translation> @@ -70,6 +71,7 @@ <translation id="6063091872902370735">Chrome-bejelentkezés engedélyezése</translation> <translation id="6181930887571472871">Váltás Chrome-ra</translation> <translation id="6238746320622508509">Az inkognitó lapok zárolásának engedélyezése a Chrome számára.</translation> +<translation id="6387994324662817823">A jelszavakat csak ezen az eszközön menti a rendszer a Google Jelszókezelőbe.</translation> <translation id="6427126399757991875">A szervezete beállítja a Chrome-ot…</translation> <translation id="6600954340915313787">A Chrome-ba másolva</translation> <translation id="6634107063912726160">Ha kijelentkezik, a Chrome nem fogja szinkronizálni az új adatokat az Ön Google-fiókjába. A korábban szinkronizált adatok megmaradnak a fiókban.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_lt.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_lt.xtb index 62dced6..3a51a21 100644 --- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_lt.xtb +++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_lt.xtb
@@ -50,6 +50,7 @@ <translation id="4214277427269650960">Prisijunkite prie šios svetainės ir „Chrome“. Sinchronizavimą galėsite įjungti vėliau.</translation> <translation id="424864128008805179">Atsijungti nuo „Chrome“?</translation> <translation id="4249068189593983585">„Chrome“ patarimas. Jei reikia daugiau skirtukų parinkčių, palieskite ir palaikykite mygtuką „Rodyti skirtukus“ įrankių juostoje, kuri yra ekrano apačioje arba viršuje.</translation> +<translation id="4267862249323750454">Slaptažodžiai saugomi <ph name="BEGIN_LINK" />„Google“ slaptažodžių tvarkytuvėje<ph name="END_LINK" />, todėl juos galite naudoti bet kuriame įrenginyje.</translation> <translation id="4523886039239821078">Dėl tam tikrų priedų „Chrome“ užstringa . Pašalinkite:</translation> <translation id="4633328489441962921">„Chrome“ nepavyko patikrinti, ar yra naujinių</translation> <translation id="4698415050768537821">„Chrome“ nepavyko patikrinti visų slaptažodžių. Bandykite dar kartą rytoj arba <ph name="BEGIN_LINK" />patikrinkite slaptažodžius „Google“ paskyroje<ph name="END_LINK" />.</translation> @@ -70,6 +71,7 @@ <translation id="6063091872902370735">Leisti „Chrome“ prisijungimą</translation> <translation id="6181930887571472871">Perjungimas į „Chrome“</translation> <translation id="6238746320622508509">Leiskite „Chrome“ užrakinti jūsų inkognito skirtukus.</translation> +<translation id="6387994324662817823">Slaptažodžiai saugomi „Google“ slaptažodžių tvarkytuvėje tik šiame įrenginyje.</translation> <translation id="6427126399757991875">Jūsų organizacija konfigūruoja „Chrome“...</translation> <translation id="6600954340915313787">Nukopijuota į „Chrome“</translation> <translation id="6634107063912726160">Kai atsijungsite, „Chrome“ nesinchronizuos jokių naujų duomenų su jūsų „Google“ paskyra. Anksčiau sinchronizuoti duomenys lieka paskyroje.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ms.xtb b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ms.xtb index a2abcf0d2..b964355a 100644 --- a/ios/chrome/app/strings/resources/ios_google_chrome_strings_ms.xtb +++ b/ios/chrome/app/strings/resources/ios_google_chrome_strings_ms.xtb
@@ -50,6 +50,7 @@ <translation id="4214277427269650960">Log masuk ke laman ini dan Chrome. Anda boleh menghidupkan penyegerakan kemudian.</translation> <translation id="424864128008805179">Log keluar daripada Chrome?</translation> <translation id="4249068189593983585">Petua Chrome. Untuk melihat lebih banyak pilihan tab, sentuh & tahan butang Tunjukkan Tab dalam bar alat, yang terletak di bahagian bawah atau atas skrin anda.</translation> +<translation id="4267862249323750454">Kata laluan disimpan pada <ph name="BEGIN_LINK" />Pengurus Kata Laluan Google<ph name="END_LINK" /> supaya anda boleh menggunakan kata laluan tersebut pada mana-mana peranti.</translation> <translation id="4523886039239821078">Beberapa tambahan menyebabkan Chrome ranap. Sila nyahpasang:</translation> <translation id="4633328489441962921">Chrome tidak dapat menyemak kemas kini</translation> <translation id="4698415050768537821">Chrome tidak dapat menyemak semua kata laluan. Cuba lagi esok atau <ph name="BEGIN_LINK" />semak kata laluan dalam Akaun Google anda.<ph name="END_LINK" /></translation> @@ -70,6 +71,7 @@ <translation id="6063091872902370735">Benarkan Log Masuk Chrome</translation> <translation id="6181930887571472871">Beralih kepada Chrome</translation> <translation id="6238746320622508509">Biarkan Chrome mengunci tab Inkognito anda.</translation> +<translation id="6387994324662817823">Kata laluan disimpan pada Pengurus Kata Laluan Google pada peranti ini sahaja.</translation> <translation id="6427126399757991875">Organisasi anda sedang menyediakan Chrome...</translation> <translation id="6600954340915313787">Disalin ke Chrome</translation> <translation id="6634107063912726160">Apabila anda log keluar, Chrome tidak akan menyegerakkan sebarang data baharu ke Akaun Google anda. Data yang disegerakkan sebelumnya akan dikekalkan dalam akaun.</translation>
diff --git a/ios/chrome/app/strings/resources/ios_strings_lo.xtb b/ios/chrome/app/strings/resources/ios_strings_lo.xtb index f44e1128..782e732 100644 --- a/ios/chrome/app/strings/resources/ios_strings_lo.xtb +++ b/ios/chrome/app/strings/resources/ios_strings_lo.xtb
@@ -168,6 +168,7 @@ <translation id="2584132361465095047">ເພີ່ມບັນຊີ...</translation> <translation id="2600682495497606169">ລຶບລ້າງຄຸກກີ້ເວັບໄຊ</translation> <translation id="2625189173221582860">ສຳເນົາລະຫັດຜ່ານແລ້ວ</translation> +<translation id="2626236249646841566">ກະລຸນາກວດສອບການເຊື່ອມຕໍ່ຂອງທ່ານແລ້ວລອງເຂົ້າສູ່ລະບົບອີກເທື່ອໜຶ່ງ.</translation> <translation id="2647269890314209800">ຄຸກກີ້ທີ່ໃຊ້ຢູ່</translation> <translation id="2648803196158606475">ລຶບລາຍການທີ່ອ່ານແລ້ວ</translation> <translation id="2653659639078652383">ສົ່ງ</translation> @@ -363,6 +364,7 @@ <translation id="4508750114462689118">ປິດໂປຣໂມການເຂົ້າສູ່ລະບົບ</translation> <translation id="4526249700380860531">ເບິ່ງ ແລະຈັດການລະຫັດຜ່ານໄດ້ຢູ່ທີ່ <ph name="BEGIN_LINK" />passwords.google.com<ph name="END_LINK" /></translation> <translation id="4536418791685807335">ລອງເຂົ້າສູ່ລະບົບອີກຄັ້ງ.</translation> +<translation id="4540780316273593836">ມີບາງຢ່າງຜິດພາດເກີດຂຶ້ນ</translation> <translation id="457386861538956877">ເພີ່ມເຕີມ...</translation> <translation id="4592368184551360546">ແປ້ນພິມ</translation> <translation id="461440297010471931">ກຳລັງຊອກຫາດ້ວຍ Google</translation> @@ -478,6 +480,7 @@ <translation id="5669528293118408608">www</translation> <translation id="567881659373499783">ລຸ້ນ <ph name="PRODUCT_VERSION" /></translation> <translation id="5690398455483874150">{count,plural, =1{ດຽວນີ້ກຳລັງສະແດງໜ້າຈໍ Chrome 1 ລາຍການ}other{ດຽວນີ້ກຳລັງສະແດງໜ້າຈໍ Chrome {count} ລາຍການ}}</translation> +<translation id="5704908597376970822">ບໍ່ສາມາດເຂົ້າສູ່ລະບົບໄດ້.</translation> <translation id="5706552126692816153">ນຳໃຊ້ເມື່ອ 1 ມື້ກ່ອນ</translation> <translation id="5711039611392265845">ສຳລັບການຕັ້ງຄ່າເພີ່ມເຕີມທີ່ກ່ຽວຂ້ອງກັບຄວາມເປັນສ່ວນຕົວ, ຄວາມປອດໄພ ແລະ ການເກັບກຳຂໍ້ມູນ, ກະລຸນາເບິ່ງ <ph name="BEGIN_LINK" />ການຊິ້ງຂໍ້ມູນ ແລະ ການບໍລິການຂອງ Google<ph name="END_LINK" />.</translation> <translation id="5724941645893276623">ເພື່ອທ່ອງເວັບແບບເປັນສ່ວນຕົວ, ກະລຸນາເພີ່ມແຖບໃໝ່</translation>
diff --git a/ios/chrome/app/strings/resources/ios_strings_th.xtb b/ios/chrome/app/strings/resources/ios_strings_th.xtb index c9f5b8d..605798c9 100644 --- a/ios/chrome/app/strings/resources/ios_strings_th.xtb +++ b/ios/chrome/app/strings/resources/ios_strings_th.xtb
@@ -168,6 +168,7 @@ <translation id="2584132361465095047">เพิ่มบัญชี…</translation> <translation id="2600682495497606169">ล้างคุกกี้ของเว็บไซต์</translation> <translation id="2625189173221582860">คัดลอกรหัสผ่านแล้ว</translation> +<translation id="2626236249646841566">ตรวจสอบการเชื่อมต่อและลองลงชื่อเข้าใช้อีกครั้ง</translation> <translation id="2647269890314209800">คุกกี้ที่ใช้งานอยู่</translation> <translation id="2648803196158606475">ลบรายการที่อ่านแล้ว</translation> <translation id="2653659639078652383">ส่ง</translation> @@ -363,6 +364,7 @@ <translation id="4508750114462689118">ปิดโปรโมชันการลงชื่อเข้าใช้</translation> <translation id="4526249700380860531">ดูและจัดการรหัสผ่านที่บันทึกไว้ที่ <ph name="BEGIN_LINK" />passwords.google.com<ph name="END_LINK" /></translation> <translation id="4536418791685807335">ลองลงชื่อเข้าใช้อีกครั้ง</translation> +<translation id="4540780316273593836">เกิดข้อผิดพลาด</translation> <translation id="457386861538956877">เพิ่มเติม...</translation> <translation id="4592368184551360546">แป้นพิมพ์</translation> <translation id="461440297010471931">กำลังค้นหาด้วย Google</translation> @@ -478,6 +480,7 @@ <translation id="5669528293118408608">www</translation> <translation id="567881659373499783">รุ่น <ph name="PRODUCT_VERSION" /></translation> <translation id="5690398455483874150">{count,plural, =1{กำลังแสดงหน้าต่าง Chrome 1 หน้าต่าง}other{กำลังแสดงหน้าต่าง Chrome {count} หน้าต่าง}}</translation> +<translation id="5704908597376970822">ลงชื่อเข้าใช้ไม่ได้</translation> <translation id="5706552126692816153">ใช้งานเมื่อ 1 วันที่ผ่านมา</translation> <translation id="5711039611392265845">ดูการตั้งค่าเพิ่มเติมเกี่ยวกับความเป็นส่วนตัว ความปลอดภัย และการรวบรวมข้อมูลได้ที่<ph name="BEGIN_LINK" />การซิงค์และบริการต่างๆ ของ Google<ph name="END_LINK" /></translation> <translation id="5724941645893276623">เพิ่มแท็บใหม่เพื่อท่องเว็บแบบส่วนตัว</translation>
diff --git a/ios/chrome/browser/autofill/BUILD.gn b/ios/chrome/browser/autofill/BUILD.gn index 21e5dd1..3dbe2d4 100644 --- a/ios/chrome/browser/autofill/BUILD.gn +++ b/ios/chrome/browser/autofill/BUILD.gn
@@ -317,6 +317,7 @@ "//components/test/data/autofill/heuristics/input/177_reichelt.html", "//components/test/data/autofill/heuristics/input/178_zip_file_extension.html", "//components/test/data/autofill/heuristics/input/179_twtcgirls.html", + "//components/test/data/autofill/heuristics/input/180_namesurname.html", "//components/test/data/autofill/heuristics/output/000_i18n_de.out", "//components/test/data/autofill/heuristics/output/001_i18n_de2.out", "//components/test/data/autofill/heuristics/output/002_i18n_en.out", @@ -496,6 +497,7 @@ "//components/test/data/autofill/heuristics/output/177_reichelt.out", "//components/test/data/autofill/heuristics/output/178_zip_file_extension.out", "//components/test/data/autofill/heuristics/output/179_twtcgirls.out", + "//components/test/data/autofill/heuristics/output/180_namesurname.out", ] # Generate a file containing the list of test input files. This list will
diff --git a/ios/chrome/browser/autofill/form_structure_browsertest.mm b/ios/chrome/browser/autofill/form_structure_browsertest.mm index e39027f..d6af29c 100644 --- a/ios/chrome/browser/autofill/form_structure_browsertest.mm +++ b/ios/chrome/browser/autofill/form_structure_browsertest.mm
@@ -158,6 +158,8 @@ autofill::features::kAutofillEnableSupportForParsingWithSharedLabels, // TODO(crbug.com/1150895) Remove once launched. autofill::features::kAutofillParsingPatternsLanguageDetection, + // TODO(crbug.com/1277480): Remove once launched. + autofill::features::kAutofillEnableNameSurenameParsing, // TODO(crbug.com/1190334): Remove once launched. autofill::features::kAutofillParseMerchantPromoCodeFields}, // Disabled
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm index 72c8318..14f5920 100644 --- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm +++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -3985,6 +3985,8 @@ UIViewController* lensViewController = [_lensController postCaptureViewControllerForImage:command.image]; + [lensViewController setModalPresentationStyle:UIModalPresentationFullScreen]; + // TODO(crbug.com/1234532): Integrate Lens with the browser's navigation // stack. [self presentViewController:lensViewController animated:YES completion:nil];
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller.mm b/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller.mm index 977abe90..59242e36 100644 --- a/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller.mm +++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller.mm
@@ -23,7 +23,6 @@ #import "ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_constants.h" #import "ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_controller_delegate.h" #import "ios/chrome/browser/ui/settings/password/passwords_table_view_constants.h" -#import "ios/chrome/browser/ui/table_view/cells/table_view_attributed_header_footer_item.h" #import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h" #import "ios/chrome/browser/ui/table_view/cells/table_view_link_header_footer_item.h" #import "ios/chrome/browser/ui/table_view/cells/table_view_text_edit_item.h" @@ -495,29 +494,13 @@ return item; } -- (TableViewAttributedHeaderFooterItem*)TLDMessageFooterItem { - TableViewAttributedHeaderFooterItem* item = - [[TableViewAttributedHeaderFooterItem alloc] initWithType:ItemTypeFooter]; - NSString* URLWithTLD = - [self.websiteTextItem.textFieldValue stringByAppendingString:@".com"]; +- (TableViewLinkHeaderFooterItem*)TLDMessageFooterItem { + TableViewLinkHeaderFooterItem* item = + [[TableViewLinkHeaderFooterItem alloc] initWithType:ItemTypeFooter]; item.text = l10n_util::GetNSStringF( IDS_IOS_SETTINGS_PASSWORDS_MISSING_TLD_DESCRIPTION, - base::SysNSStringToUTF16(URLWithTLD)); - - NSRange boldedRange = [item.text rangeOfString:URLWithTLD]; - DCHECK(boldedRange.location != NSNotFound); - UIFontDescriptor* baseDescriptor = [UIFontDescriptor - preferredFontDescriptorWithTextStyle:UIFontTextStyleFootnote]; - UIFontDescriptor* styleDescriptor = [baseDescriptor - fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold]; - UIFont* fontText = [UIFont fontWithDescriptor:styleDescriptor - size:kUseDefaultFontSize]; - NSDictionary* boldTextAttribute = [NSDictionary - dictionaryWithObjectsAndKeys:fontText, NSFontAttributeName, - [UIColor colorNamed:kTextSecondaryColor], - NSForegroundColorAttributeName, nil]; - item.customTextAttributesOnRange = - @{[NSValue valueWithRange:boldedRange] : boldTextAttribute}; + base::SysNSStringToUTF16([self.websiteTextItem.textFieldValue + stringByAppendingString:@".com"])); return item; }
diff --git a/ios/chrome/browser/ui/table_view/cells/BUILD.gn b/ios/chrome/browser/ui/table_view/cells/BUILD.gn index 106927b..0afe5f9 100644 --- a/ios/chrome/browser/ui/table_view/cells/BUILD.gn +++ b/ios/chrome/browser/ui/table_view/cells/BUILD.gn
@@ -6,8 +6,6 @@ sources = [ "table_view_activity_indicator_header_footer_item.h", "table_view_activity_indicator_header_footer_item.mm", - "table_view_attributed_header_footer_item.h", - "table_view_attributed_header_footer_item.mm", "table_view_cell.h", "table_view_cell.mm", "table_view_detail_icon_item.h",
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_attributed_header_footer_item.h b/ios/chrome/browser/ui/table_view/cells/table_view_attributed_header_footer_item.h deleted file mode 100644 index c4c08c61..0000000 --- a/ios/chrome/browser/ui/table_view/cells/table_view_attributed_header_footer_item.h +++ /dev/null
@@ -1,40 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef IOS_CHROME_BROWSER_UI_TABLE_VIEW_CELLS_TABLE_VIEW_ATTRIBUTED_HEADER_FOOTER_ITEM_H_ -#define IOS_CHROME_BROWSER_UI_TABLE_VIEW_CELLS_TABLE_VIEW_ATTRIBUTED_HEADER_FOOTER_ITEM_H_ - -#import <UIKit/UIKit.h> - -#import "ios/chrome/browser/ui/table_view/cells/table_view_header_footer_item.h" - -@class TableViewAttributedHeaderFooterView; - -// TableViewAttributedHeaderFooterItem is the model class corresponding to -// TableViewAttributedHeaderFooterView. -@interface TableViewAttributedHeaderFooterItem : TableViewHeaderFooterItem - -// The main text string. -@property(nonatomic, copy) NSString* text; - -// The text string attributes to be applied on a specific range of text. -@property(nonatomic, assign) - NSDictionary<NSValue*, NSDictionary*>* customTextAttributesOnRange; - -@end - -// UITableViewHeaderFooterView subclass containing a single UITextView. The text -// view is laid to fill the full width of the cell and it is wrapped as needed -// to fit in the cell. -@interface TableViewAttributedHeaderFooterView : UITableViewHeaderFooterView - -// Sets the |text| displayed by this cell. Adds custom attributes to text if -// provided. -- (void)setText:(NSString*)text - withCustomTextAttributes: - (NSDictionary<NSValue*, NSDictionary*>*)customTextAttributesOnRange; - -@end - -#endif // IOS_CHROME_BROWSER_UI_TABLE_VIEW_CELLS_TABLE_VIEW_ATTRIBUTED_HEADER_FOOTER_ITEM_H_
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_attributed_header_footer_item.mm b/ios/chrome/browser/ui/table_view/cells/table_view_attributed_header_footer_item.mm deleted file mode 100644 index 425788c..0000000 --- a/ios/chrome/browser/ui/table_view/cells/table_view_attributed_header_footer_item.mm +++ /dev/null
@@ -1,117 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "ios/chrome/browser/ui/table_view/cells/table_view_attributed_header_footer_item.h" - -#import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h" -#import "ios/chrome/common/ui/colors/semantic_color_names.h" - -#if !defined(__has_feature) || !__has_feature(objc_arc) -#error "This file requires ARC support." -#endif - -namespace { - -// Padding used on the top and bottom edges of the cell. -const CGFloat kVerticalPadding = 8; - -} // namespace - -@implementation TableViewAttributedHeaderFooterItem - -- (instancetype)initWithType:(NSInteger)type { - self = [super initWithType:type]; - if (self) { - self.cellClass = [TableViewAttributedHeaderFooterView class]; - } - return self; -} - -#pragma mark CollectionViewItem - -- (void)configureHeaderFooterView: - (TableViewAttributedHeaderFooterView*)headerFooter - withStyler:(ChromeTableViewStyler*)styler { - [super configureHeaderFooterView:headerFooter withStyler:styler]; - - [headerFooter setText:self.text - withCustomTextAttributes:self.customTextAttributesOnRange]; -} - -@end - -@interface TableViewAttributedHeaderFooterView () - -// UILabel corresponding to |text| from the item. -@property(nonatomic, readonly, strong) UILabel* textLabel; - -@end - -@implementation TableViewAttributedHeaderFooterView - -@synthesize textLabel = _textLabel; - -- (instancetype)initWithReuseIdentifier:(NSString*)reuseIdentifier { - self = [super initWithReuseIdentifier:reuseIdentifier]; - if (self) { - _textLabel = [[UILabel alloc] init]; - _textLabel.backgroundColor = UIColor.clearColor; - _textLabel.font = - [UIFont preferredFontForTextStyle:kTableViewSublabelFontStyle]; - - _textLabel.adjustsFontForContentSizeCategory = YES; - _textLabel.translatesAutoresizingMaskIntoConstraints = NO; - - [self.contentView addSubview:_textLabel]; - - [NSLayoutConstraint activateConstraints:@[ - [_textLabel.topAnchor constraintEqualToAnchor:self.contentView.topAnchor - constant:kVerticalPadding], - [_textLabel.bottomAnchor - constraintEqualToAnchor:self.contentView.bottomAnchor - constant:-kVerticalPadding], - [_textLabel.trailingAnchor - constraintEqualToAnchor:self.contentView.trailingAnchor - constant:-HorizontalPadding()], - [_textLabel.leadingAnchor - constraintEqualToAnchor:self.contentView.leadingAnchor - constant:HorizontalPadding()], - ]]; - } - return self; -} - -- (void)prepareForReuse { - [super prepareForReuse]; - self.textLabel.text = nil; - self.textLabel.attributedText = nil; -} - -#pragma mark - Properties - -- (void)setText:(NSString*)text - withCustomTextAttributes: - (NSDictionary<NSValue*, NSDictionary*>*)customTextAttributesOnRange { - NSDictionary* textAttributes = @{ - NSFontAttributeName : - [UIFont preferredFontForTextStyle:kTableViewSublabelFontStyle], - NSForegroundColorAttributeName : [UIColor colorNamed:kTextSecondaryColor] - }; - - NSMutableAttributedString* attributedText = - [[NSMutableAttributedString alloc] initWithString:text - attributes:textAttributes]; - - if ([customTextAttributesOnRange count] > 0) { - [customTextAttributesOnRange - enumerateKeysAndObjectsUsingBlock:^(NSValue* range, NSDictionary* attrs, - BOOL* stop) { - [attributedText setAttributes:attrs range:range.rangeValue]; - }]; - } - - self.textLabel.attributedText = attributedText; -} - -@end
diff --git a/ios/chrome/browser/web/web_performance_metrics/web_performance_metrics_java_script_feature.h b/ios/chrome/browser/web/web_performance_metrics/web_performance_metrics_java_script_feature.h index 4e2bda70..9bd7adb 100644 --- a/ios/chrome/browser/web/web_performance_metrics/web_performance_metrics_java_script_feature.h +++ b/ios/chrome/browser/web/web_performance_metrics/web_performance_metrics_java_script_feature.h
@@ -36,6 +36,11 @@ void LogRelativeFirstInputDelay(double value, bool is_main_frame, bool loaded_from_cache); + + // Logs the First Input Delay time across main and sub frames in UMA. + void LogAggregateFirstInputDelay(web::WebState* web_state, + double first_input_delay, + bool loaded_from_cache); }; #endif // IOS_CHROME_BROWSER_WEB_WEB_PERFORMANCE_METRICS_WEB_PERFORMANCE_METRICS_JAVA_SCRIPT_FEATURE_H_
diff --git a/ios/chrome/browser/web/web_performance_metrics/web_performance_metrics_java_script_feature.mm b/ios/chrome/browser/web/web_performance_metrics/web_performance_metrics_java_script_feature.mm index c2387456..debedce 100644 --- a/ios/chrome/browser/web/web_performance_metrics/web_performance_metrics_java_script_feature.mm +++ b/ios/chrome/browser/web/web_performance_metrics/web_performance_metrics_java_script_feature.mm
@@ -86,6 +86,8 @@ LogRelativeFirstInputDelay(value.value(), message.is_main_frame(), loaded_from_cache.value()); + LogAggregateFirstInputDelay(web_state, value.value(), + loaded_from_cache.value()); } } @@ -165,4 +167,31 @@ delta); } } +} + +void WebPerformanceMetricsJavaScriptFeature::LogAggregateFirstInputDelay( + web::WebState* web_state, + double first_input_delay, + bool loaded_from_cache) { + WebPerformanceMetricsTabHelper* tab_helper = + WebPerformanceMetricsTabHelper::FromWebState(web_state); + + if (!tab_helper) { + return; + } + + bool first_input_delay_has_been_logged = + tab_helper->GetFirstInputDelayLoggingStatus(); + + if (!first_input_delay_has_been_logged) { + base::TimeDelta delta = base::Milliseconds(first_input_delay); + if (loaded_from_cache) { + UMA_HISTOGRAM_TIMES("PageLoad.InteractiveTiming.FirstInputDelay." + "AfterBackForwardCacheRestore", + delta); + } else { + UMA_HISTOGRAM_TIMES("PageLoad.InteractiveTiming.FirstInputDelay4", delta); + } + tab_helper->SetFirstInputDelayLoggingStatus(true); + } } \ No newline at end of file
diff --git a/ios/chrome/browser/web/web_performance_metrics/web_performance_metrics_tab_helper.h b/ios/chrome/browser/web/web_performance_metrics/web_performance_metrics_tab_helper.h index 809ca6e..e1a1752 100644 --- a/ios/chrome/browser/web/web_performance_metrics/web_performance_metrics_tab_helper.h +++ b/ios/chrome/browser/web/web_performance_metrics/web_performance_metrics_tab_helper.h
@@ -38,6 +38,14 @@ void SetAggregateAbsoluteFirstContentfulPaint( double absolute_first_contentful_paint); + // If the web page has logged its First Input Delay, the function + // returns |true| otherwise it returns |false| + bool GetFirstInputDelayLoggingStatus() const; + + // Sets the boolean variable that indicates whether the First Input Delay + // has been logged in UMA for the current web page. + void SetFirstInputDelayLoggingStatus(bool first_input_delay_logging_status); + private: friend class web::WebStateUserData<WebPerformanceMetricsTabHelper>; @@ -58,6 +66,10 @@ double aggregate_absolute_first_contentful_paint_ = std::numeric_limits<double>::max(); + // Stores whether the First Input Delay has been logged to UMA for the + // current web page + bool first_input_delay_has_been_logged = false; + WEB_STATE_USER_DATA_KEY_DECL(); };
diff --git a/ios/chrome/browser/web/web_performance_metrics/web_performance_metrics_tab_helper.mm b/ios/chrome/browser/web/web_performance_metrics/web_performance_metrics_tab_helper.mm index 5550789..d9e7ab7 100644 --- a/ios/chrome/browser/web/web_performance_metrics/web_performance_metrics_tab_helper.mm +++ b/ios/chrome/browser/web/web_performance_metrics/web_performance_metrics_tab_helper.mm
@@ -19,6 +19,7 @@ web::WebState* web_state, web::NavigationContext* navigation_context) { SetAggregateAbsoluteFirstContentfulPaint(std::numeric_limits<double>::max()); + SetFirstInputDelayLoggingStatus(false); } void WebPerformanceMetricsTabHelper::WebStateDestroyed( @@ -37,4 +38,13 @@ aggregate_absolute_first_contentful_paint_ = absolute_first_contentful_paint; } -WEB_STATE_USER_DATA_KEY_IMPL(WebPerformanceMetricsTabHelper) +bool WebPerformanceMetricsTabHelper::GetFirstInputDelayLoggingStatus() const { + return first_input_delay_has_been_logged; +} + +void WebPerformanceMetricsTabHelper::SetFirstInputDelayLoggingStatus( + bool first_input_delay_logging_status) { + first_input_delay_has_been_logged = first_input_delay_logging_status; +} + +WEB_STATE_USER_DATA_KEY_IMPL(WebPerformanceMetricsTabHelper) \ No newline at end of file
diff --git a/media/cast/sender/fake_software_video_encoder.cc b/media/cast/sender/fake_software_video_encoder.cc index 06eb64d7..05d79300 100644 --- a/media/cast/sender/fake_software_video_encoder.cc +++ b/media/cast/sender/fake_software_video_encoder.cc
@@ -54,12 +54,11 @@ RtpTimeTicks::FromTimeDelta(video_frame->timestamp(), kVideoFrequency); encoded_frame->reference_time = reference_time; - base::DictionaryValue values; - values.SetBoolean("key", - encoded_frame->dependency == EncodedFrame::KEY); - values.SetInteger("ref", encoded_frame->referenced_frame_id.lower_32_bits()); - values.SetInteger("id", encoded_frame->frame_id.lower_32_bits()); - values.SetInteger("size", frame_size_); + base::Value values(base::Value::Type::DICTIONARY); + values.SetBoolKey("key", encoded_frame->dependency == EncodedFrame::KEY); + values.SetIntKey("ref", encoded_frame->referenced_frame_id.lower_32_bits()); + values.SetIntKey("id", encoded_frame->frame_id.lower_32_bits()); + values.SetIntKey("size", frame_size_); base::JSONWriter::Write(values, &encoded_frame->data); encoded_frame->data.resize( std::max<size_t>(encoded_frame->data.size(), frame_size_), ' ');
diff --git a/media/cast/test/simulator.cc b/media/cast/test/simulator.cc index 26d34110..3313967 100644 --- a/media/cast/test/simulator.cc +++ b/media/cast/test/simulator.cc
@@ -700,9 +700,9 @@ NetworkSimulationModel model = media::cast::LoadModel( cmd->GetSwitchValuePath(media::cast::kModelPath)); - base::DictionaryValue values; - values.SetBoolean("sim", true); - values.SetString("sim-id", sim_id); + base::Value values(base::Value::Type::DICTIONARY); + values.SetBoolKey("sim", true); + values.SetStringKey("sim-id", sim_id); std::string extra_data; base::JSONWriter::Write(values, &extra_data);
diff --git a/media/gpu/chromeos/platform_video_frame_utils.cc b/media/gpu/chromeos/platform_video_frame_utils.cc index abc6f3e..4936475 100644 --- a/media/gpu/chromeos/platform_video_frame_utils.cc +++ b/media/gpu/chromeos/platform_video_frame_utils.cc
@@ -43,15 +43,6 @@ namespace media { namespace { - -gfx::GpuMemoryBufferId GetNextGpuMemoryBufferId() { - static base::NoDestructor<base::Lock> id_lock; - static int next_gpu_memory_buffer_id = 0; - base::AutoLock lock(*id_lock); - CHECK_LT(next_gpu_memory_buffer_id, std::numeric_limits<int>::max()); - return gfx::GpuMemoryBufferId(next_gpu_memory_buffer_id++); -} - // GbmDeviceWrapper is a singleton that provides thread-safe access to a // ui::GbmDevice for the purposes of creating native BOs. The ui::GbmDevice is // initialized with the first non-vgem render node found that works starting at @@ -196,6 +187,14 @@ } } // namespace +gfx::GpuMemoryBufferId GetNextGpuMemoryBufferId() { + static base::NoDestructor<base::Lock> id_lock; + static int next_gpu_memory_buffer_id = 0; + base::AutoLock lock(*id_lock); + CHECK_LT(next_gpu_memory_buffer_id, std::numeric_limits<int>::max()); + return gfx::GpuMemoryBufferId(next_gpu_memory_buffer_id++); +} + scoped_refptr<VideoFrame> CreateGpuMemoryBufferVideoFrame( gpu::GpuMemoryBufferFactory* factory, VideoPixelFormat pixel_format,
diff --git a/media/gpu/chromeos/platform_video_frame_utils.h b/media/gpu/chromeos/platform_video_frame_utils.h index 8e1227ca9..11cdb11 100644 --- a/media/gpu/chromeos/platform_video_frame_utils.h +++ b/media/gpu/chromeos/platform_video_frame_utils.h
@@ -10,18 +10,19 @@ #include "media/gpu/media_gpu_export.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "ui/gfx/buffer_types.h" +#include "ui/gfx/gpu_memory_buffer.h" #include "ui/gfx/linux/native_pixmap_dmabuf.h" -namespace gfx { -struct GpuMemoryBufferHandle; -} // namespace gfx - namespace gpu { class GpuMemoryBufferFactory; } // namespace gpu namespace media { +// Returns a GpuMemoryBufferId that's guaranteed to be different from those +// returned by previous calls. This function is thread safe. +MEDIA_GPU_EXPORT gfx::GpuMemoryBufferId GetNextGpuMemoryBufferId(); + // Create GpuMemoryBuffer-based media::VideoFrame with |buffer_usage|. // See //media/base/video_frame.h for other parameters. // If |gpu_memory_buffer_factory| is not null, it's used to allocate the
diff --git a/media/gpu/chromeos/vd_video_decode_accelerator.cc b/media/gpu/chromeos/vd_video_decode_accelerator.cc index 84faa92..da3a79e 100644 --- a/media/gpu/chromeos/vd_video_decode_accelerator.cc +++ b/media/gpu/chromeos/vd_video_decode_accelerator.cc
@@ -8,7 +8,10 @@ #include <vector> #include "base/bind.h" +#include "base/callback_helpers.h" #include "base/location.h" +#include "gpu/ipc/common/gpu_memory_buffer_support.h" +#include "media/base/format_utils.h" #include "media/base/media_util.h" #include "media/base/video_color_space.h" #include "media/base/video_decoder_config.h" @@ -17,8 +20,11 @@ #include "media/base/video_transformation.h" #include "media/base/video_types.h" #include "media/base/waiting.h" +#include "media/gpu/buffer_validation.h" #include "media/gpu/chromeos/gpu_buffer_layout.h" #include "media/gpu/macros.h" +#include "ui/gfx/buffer_format_util.h" +#include "ui/gfx/gpu_memory_buffer.h" #include "ui/gl/gl_bindings.h" namespace media { @@ -45,13 +51,6 @@ return planes; } -std::vector<base::ScopedFD> ExtractFds(gfx::GpuMemoryBufferHandle gmb_handle) { - std::vector<base::ScopedFD> fds; - for (auto& plane : gmb_handle.native_pixmap_handle.planes) - fds.push_back(std::move(plane.fd)); - return fds; -} - // TODO(akahuang): Move this function to a utility file. template <class T> std::string VectorToString(const std::vector<T>& vec) { @@ -360,6 +359,8 @@ return; } + CHECK(media::VerifyGpuMemoryBufferHandle(pixel_format, coded_size_, + gmb_handle)); const uint64_t modifier = gmb_handle.type == gfx::NATIVE_PIXMAP ? gmb_handle.native_pixmap_handle.modifier : gfx::NativePixmapHandle::kNoModifier; @@ -400,19 +401,41 @@ if (!layout_) return; + CHECK(media::VerifyGpuMemoryBufferHandle(pixel_format, layout_->coded_size(), + gmb_handle)); + auto buffer_format = VideoPixelFormatToGfxBufferFormat(pixel_format); + CHECK(buffer_format); + // Usage is SCANOUT_VDA_WRITE because we are just wrapping the dmabuf in a + // GpuMemoryBuffer. This buffer is just for decoding purposes, so having + // the dmabufs mmapped is not necessary. + std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer = + gpu::GpuMemoryBufferSupport().CreateGpuMemoryBufferImplFromHandle( + std::move(gmb_handle), layout_->coded_size(), *buffer_format, + gfx::BufferUsage::SCANOUT_VDA_WRITE, base::NullCallback()); + if (!gpu_memory_buffer) { + VLOGF(1) << "Failed to create GpuMemoryBuffer. format: " + << gfx::BufferFormatToString(*buffer_format) + << ", coded_size: " << layout_->coded_size().ToString(); + return; + } + + const gpu::MailboxHolder mailbox_holder[VideoFrame::kMaxPlanes] = {}; // VideoFrame::WrapVideoFrame() will check whether the updated visible_rect // is sub rect of the original visible_rect. Therefore we set visible_rect // as large as coded_size to guarantee this condition. - scoped_refptr<VideoFrame> origin_frame = VideoFrame::WrapExternalDmabufs( - *layout_, gfx::Rect(coded_size_), coded_size_, - ExtractFds(std::move(gmb_handle)), base::TimeDelta()); - DmabufId dmabuf_id = DmabufVideoFramePool::GetDmabufId(*origin_frame); - auto res = frame_id_to_picture_id_.emplace(dmabuf_id, picture_buffer_id); - // |dmabuf_id| should not be inside the map before insertion. + scoped_refptr<VideoFrame> origin_frame = + VideoFrame::WrapExternalGpuMemoryBuffer( + gfx::Rect(layout_->coded_size()), layout_->coded_size(), + std::move(gpu_memory_buffer), mailbox_holder, base::NullCallback(), + base::TimeDelta()); + + auto res = frame_id_to_picture_id_.emplace( + origin_frame->GetGpuMemoryBuffer()->GetId(), picture_buffer_id); + // The frame ID should not be inside the map before insertion. DCHECK(res.second); // |wrapped_frame| is used to keep |origin_frame| alive until everyone - // released |wrapped_frame|. Then DmabufId will be available at + // released |wrapped_frame|. Then GpuMemoryBufferId will be available at // OnFrameReleased(). scoped_refptr<VideoFrame> wrapped_frame = VideoFrame::WrapVideoFrame( origin_frame, origin_frame->format(), origin_frame->visible_rect(), @@ -439,8 +462,7 @@ DVLOGF(4); DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_); - auto it = - frame_id_to_picture_id_.find(DmabufVideoFramePool::GetDmabufId(frame)); + auto it = frame_id_to_picture_id_.find(frame.GetGpuMemoryBuffer()->GetId()); if (it == frame_id_to_picture_id_.end()) { VLOGF(1) << "Failed to find the picture buffer id."; return absl::nullopt; @@ -470,8 +492,8 @@ DVLOGF(4); DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_); - auto it = frame_id_to_picture_id_.find( - DmabufVideoFramePool::GetDmabufId(*origin_frame)); + auto it = + frame_id_to_picture_id_.find(origin_frame->GetGpuMemoryBuffer()->GetId()); DCHECK(it != frame_id_to_picture_id_.end()); int32_t picture_buffer_id = it->second; frame_id_to_picture_id_.erase(it);
diff --git a/media/gpu/chromeos/vd_video_decode_accelerator.h b/media/gpu/chromeos/vd_video_decode_accelerator.h index 1a4dcc0..f0cd41c 100644 --- a/media/gpu/chromeos/vd_video_decode_accelerator.h +++ b/media/gpu/chromeos/vd_video_decode_accelerator.h
@@ -23,6 +23,7 @@ #include "media/gpu/media_gpu_export.h" #include "media/video/video_decode_accelerator.h" #include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/gfx/gpu_memory_buffer.h" namespace media { @@ -86,8 +87,6 @@ ImportFrameCb import_frame_cb) override; private: - using DmabufId = DmabufVideoFramePool::DmabufId; - VdVideoDecodeAccelerator( CreateVideoDecoderCb create_vd_cb, scoped_refptr<base::SequencedTaskRunner> task_runner); @@ -139,8 +138,9 @@ gfx::Size coded_size_; absl::optional<VideoFrameLayout> layout_; - // Mapping from VideoFrame's DmabufId to picture buffer id. - std::map<DmabufId, int32_t /* picture_buffer_id */> frame_id_to_picture_id_; + // Mapping from VideoFrame's GpuMemoryBufferId to picture buffer id. + std::map<gfx::GpuMemoryBufferId, int32_t /* picture_buffer_id */> + frame_id_to_picture_id_; // Record how many times the picture is sent to the client, and keep a refptr // of corresponding VideoFrame when the client owns the buffers. std::map<int32_t /* picture_buffer_id */,
diff --git a/media/gpu/test/video_player/test_vda_video_decoder.cc b/media/gpu/test/video_player/test_vda_video_decoder.cc index 3a671cb..140a58d 100644 --- a/media/gpu/test/video_player/test_vda_video_decoder.cc +++ b/media/gpu/test/video_player/test_vda_video_decoder.cc
@@ -44,13 +44,15 @@ OnProvidePictureBuffersCB on_provide_picture_buffers_cb, const gfx::ColorSpace& target_color_space, FrameRenderer* const frame_renderer, - gpu::GpuMemoryBufferFactory* gpu_memory_buffer_factory) + gpu::GpuMemoryBufferFactory* gpu_memory_buffer_factory, + bool linear_output) : use_vd_vda_(use_vd_vda), on_provide_picture_buffers_cb_(std::move(on_provide_picture_buffers_cb)), target_color_space_(target_color_space), frame_renderer_(frame_renderer), #if BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) gpu_memory_buffer_factory_(gpu_memory_buffer_factory), + linear_output_(linear_output), #endif // BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) decode_start_timestamps_(kTimestampCacheSize) { DCHECK_CALLED_ON_VALID_SEQUENCE(vda_wrapper_sequence_checker_); @@ -242,10 +244,11 @@ scoped_refptr<VideoFrame> video_frame; #if BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) - video_frame = CreatePlatformVideoFrame( + video_frame = CreateGpuMemoryBufferVideoFrame( gpu_memory_buffer_factory_, format, dimensions, visible_rect, visible_rect.size(), base::TimeDelta(), - gfx::BufferUsage::SCANOUT_VDA_WRITE); + linear_output_ ? gfx::BufferUsage::SCANOUT_CPU_READ_WRITE + : gfx::BufferUsage::SCANOUT_VDA_WRITE); #endif // BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) ASSERT_TRUE(video_frame) << "Failed to create video frame";
diff --git a/media/gpu/test/video_player/test_vda_video_decoder.h b/media/gpu/test/video_player/test_vda_video_decoder.h index bf78f5f0..362089dd 100644 --- a/media/gpu/test/video_player/test_vda_video_decoder.h +++ b/media/gpu/test/video_player/test_vda_video_decoder.h
@@ -37,7 +37,8 @@ OnProvidePictureBuffersCB on_provide_picture_buffers_cb, const gfx::ColorSpace& target_color_space, FrameRenderer* const frame_renderer, - gpu::GpuMemoryBufferFactory* gpu_memory_buffer_factory); + gpu::GpuMemoryBufferFactory* gpu_memory_buffer_factory, + bool linear_output = false); TestVDAVideoDecoder(const TestVDAVideoDecoder&) = delete; TestVDAVideoDecoder& operator=(const TestVDAVideoDecoder&) = delete; @@ -118,6 +119,9 @@ #if BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) // Owned by VideoDecoderClient. gpu::GpuMemoryBufferFactory* const gpu_memory_buffer_factory_; + // Whether the decoder output buffers should be allocated with a linear + // layout. + const bool linear_output_; #endif // BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) // Map of video frames the decoder uses as output, keyed on picture buffer id.
diff --git a/media/gpu/test/video_player/video_decoder_client.cc b/media/gpu/test/video_player/video_decoder_client.cc index cfc6f5c5..b2ea827 100644 --- a/media/gpu/test/video_player/video_decoder_client.cc +++ b/media/gpu/test/video_player/video_decoder_client.cc
@@ -204,7 +204,8 @@ // |*this|. The lifetime of |decoder_| must be shorter than |*this|. base::BindRepeating(&VideoDecoderClient::ResolutionChangeTask, base::Unretained(this)), - gfx::ColorSpace(), frame_renderer_.get(), gpu_memory_buffer_factory_); + gfx::ColorSpace(), frame_renderer_.get(), gpu_memory_buffer_factory_, + decoder_client_config_.linear_output); break; }
diff --git a/media/gpu/test/video_player/video_decoder_client.h b/media/gpu/test/video_player/video_decoder_client.h index f95b60cd..bc04c114 100644 --- a/media/gpu/test/video_player/video_decoder_client.h +++ b/media/gpu/test/video_player/video_decoder_client.h
@@ -43,6 +43,7 @@ // without waiting for the result of the previous decode requests. size_t max_outstanding_decode_requests = 1; DecoderImplementation implementation = DecoderImplementation::kVDA; + bool linear_output = false; }; // The video decoder client is responsible for the communication between the
diff --git a/media/gpu/test/video_player/video_player_test_environment.cc b/media/gpu/test/video_player/video_player_test_environment.cc index 147eb26..2848522 100644 --- a/media/gpu/test/video_player/video_player_test_environment.cc +++ b/media/gpu/test/video_player/video_player_test_environment.cc
@@ -27,6 +27,7 @@ const base::FilePath& video_metadata_path, ValidatorType validator_type, const DecoderImplementation implementation, + bool linear_output, const base::FilePath& output_folder, const FrameOutputConfig& frame_output_config) { auto video = std::make_unique<media::test::Video>( @@ -38,14 +39,15 @@ } return new VideoPlayerTestEnvironment(std::move(video), validator_type, - implementation, output_folder, - frame_output_config); + implementation, linear_output, + output_folder, frame_output_config); } VideoPlayerTestEnvironment::VideoPlayerTestEnvironment( std::unique_ptr<media::test::Video> video, ValidatorType validator_type, const DecoderImplementation implementation, + bool linear_output, const base::FilePath& output_folder, const FrameOutputConfig& frame_output_config) : VideoTestEnvironment( @@ -69,6 +71,7 @@ video_(std::move(video)), validator_type_(validator_type), implementation_(implementation), + linear_output_(linear_output), frame_output_config_(frame_output_config), output_folder_(output_folder), gpu_memory_buffer_factory_( @@ -100,6 +103,10 @@ return implementation_; } +bool VideoPlayerTestEnvironment::ShouldOutputLinearBuffers() const { + return linear_output_; +} + FrameOutputMode VideoPlayerTestEnvironment::GetFrameOutputMode() const { return frame_output_config_.output_mode; }
diff --git a/media/gpu/test/video_player/video_player_test_environment.h b/media/gpu/test/video_player/video_player_test_environment.h index daeb2c5..e4b6c5a3 100644 --- a/media/gpu/test/video_player/video_player_test_environment.h +++ b/media/gpu/test/video_player/video_player_test_environment.h
@@ -34,6 +34,7 @@ const base::FilePath& video_metadata_path, ValidatorType validator_type, const DecoderImplementation implementation, + bool linear_output, const base::FilePath& output_folder = base::FilePath(), const FrameOutputConfig& frame_output_config = FrameOutputConfig()); ~VideoPlayerTestEnvironment() override; @@ -46,6 +47,8 @@ ValidatorType GetValidatorType() const; // Return which implementation is used. DecoderImplementation GetDecoderImplementation() const; + // Returns whether the final output of the decoder should be linear buffers. + bool ShouldOutputLinearBuffers() const; // Get the frame output mode. FrameOutputMode GetFrameOutputMode() const; @@ -66,12 +69,14 @@ VideoPlayerTestEnvironment(std::unique_ptr<media::test::Video> video, ValidatorType validator_type, const DecoderImplementation implementation, + bool linear_output, const base::FilePath& output_folder, const FrameOutputConfig& frame_output_config); const std::unique_ptr<media::test::Video> video_; const ValidatorType validator_type_; const DecoderImplementation implementation_; + const bool linear_output_; const FrameOutputConfig frame_output_config_; const base::FilePath output_folder_;
diff --git a/media/gpu/video_decode_accelerator_perf_tests.cc b/media/gpu/video_decode_accelerator_perf_tests.cc index be515d43..6b35abc 100644 --- a/media/gpu/video_decode_accelerator_perf_tests.cc +++ b/media/gpu/video_decode_accelerator_perf_tests.cc
@@ -29,10 +29,11 @@ // documentation under docs/media/gpu/video_decoder_perf_test_usage.md when // making changes here. constexpr const char* usage_msg = - "usage: video_decode_accelerator_perf_tests\n" - " [-v=<level>] [--vmodule=<config>] [--output_folder]\n" - " ([--use-legacy]|[--use_vd]|[--use_vd_vda]) [--gtest_help]\n" - " [--help] [<video path>] [<video metadata path>]\n"; + R"(usage: video_decode_accelerator_perf_tests + [-v=<level>] [--vmodule=<config>] [--output_folder] + ([--use-legacy]|[--use_vd]|[--use_vd_vda]) [--linear_output] + [--gtest_help] [--help] [<video path>] [<video metadata path>] +)"; // Video decoder perf tests help message. constexpr const char* help_msg = @@ -56,6 +57,11 @@ " wrapper that translates to the VDA interface,\n" " used to test interaction with older components\n" " expecting the VDA interface.\n" + " --linear_output use linear buffers as the final output of the\n" + " decoder which may require the use of an image\n" + " processor internally. This flag only works in\n" + " conjunction with --use_vd_vda.\n" + " Disabled by default.\n" " --gtest_help display the gtest help and exit.\n" " --help display this help and exit.\n"; @@ -324,6 +330,7 @@ // Use the new VD-based video decoders if requested. VideoDecoderClientConfig config; config.implementation = g_env->GetDecoderImplementation(); + config.linear_output = g_env->ShouldOutputLinearBuffers(); auto video_player = VideoPlayer::Create( config, g_env->GetGpuMemoryBufferFactory(), std::move(frame_renderer), @@ -443,6 +450,7 @@ bool use_legacy = false; bool use_vd = false; bool use_vd_vda = false; + bool linear_output = false; media::test::DecoderImplementation implementation = media::test::DecoderImplementation::kVD; base::CommandLine::SwitchMap switches = cmd_line->GetSwitches(); @@ -464,6 +472,8 @@ } else if (it->first == "use_vd_vda") { use_vd_vda = true; implementation = media::test::DecoderImplementation::kVDVDA; + } else if (it->first == "linear_output") { + linear_output = true; } else { std::cout << "unknown option: --" << it->first << "\n" << media::test::usage_msg; @@ -486,6 +496,12 @@ << media::test::usage_msg; return EXIT_FAILURE; } + if (linear_output && !use_vd_vda) { + std::cout << "--linear_output must be used with the VDVDA (--use_vd_vda)\n" + "implementation.\n" + << media::test::usage_msg; + return EXIT_FAILURE; + } testing::InitGoogleTest(&argc, argv); @@ -498,8 +514,7 @@ media::test::VideoPlayerTestEnvironment::Create( video_path, video_metadata_path, /*validator_type=*/ media::test::VideoPlayerTestEnvironment::ValidatorType::kNone, - implementation, - base::FilePath(output_folder)); + implementation, linear_output, base::FilePath(output_folder)); if (!test_environment) return EXIT_FAILURE;
diff --git a/media/gpu/video_decode_accelerator_tests.cc b/media/gpu/video_decode_accelerator_tests.cc index ae5fc2a..c6d16267 100644 --- a/media/gpu/video_decode_accelerator_tests.cc +++ b/media/gpu/video_decode_accelerator_tests.cc
@@ -43,13 +43,14 @@ // Video decoder tests usage message. Make sure to also update the documentation // under docs/media/gpu/video_decoder_test_usage.md when making changes here. constexpr const char* usage_msg = - "usage: video_decode_accelerator_tests\n" - " [-v=<level>] [--vmodule=<config>]\n" - " [--validator_type=(none|md5|ssim)]\n" - " [--output_frames=(all|corrupt)] [--output_format=(png|yuv)]\n" - " [--output_limit=<number>] [--output_folder=<folder>]\n" - " ([--use-legacy]|[--use_vd]|[--use_vd_vda]) [--gtest_help]\n" - " [--help] [<video path>] [<video metadata path>]\n"; + R"(usage: video_decode_accelerator_tests + [-v=<level>] [--vmodule=<config>] + [--validator_type=(none|md5|ssim)] + [--output_frames=(all|corrupt)] [--output_format=(png|yuv)] + [--output_limit=<number>] [--output_folder=<folder>] + [--linear_output] ([--use-legacy]|[--use_vd]|[--use_vd_vda]) + [--gtest_help] [--help] [<video path>] [<video metadata path>] +)"; // Video decoder tests help message. constexpr const char* help_msg = @@ -74,6 +75,11 @@ " --use_vd_vda use the new VD-based video decoders with a\n" " wrapper that translates to the VDA interface,\n" " used to test interaction with older components\n" + " --linear_output use linear buffers as the final output of the\n" + " decoder which may require the use of an image\n" + " processor internally. This flag only works in\n" + " conjunction with --use_vd_vda.\n" + " Disabled by default.\n" " --output_frames write the selected video frames to disk, possible\n" " values are \"all|corrupt\".\n" " --output_format set the format of frames saved to disk, supported\n" @@ -155,6 +161,7 @@ base::NumberToString(g_env->Video()->FrameRate())); config.implementation = g_env->GetDecoderImplementation(); + config.linear_output = g_env->ShouldOutputLinearBuffers(); auto video_player = VideoPlayer::Create( config, g_env->GetGpuMemoryBufferFactory(), std::move(frame_renderer), @@ -520,6 +527,7 @@ bool use_legacy = false; bool use_vd = false; bool use_vd_vda = false; + bool linear_output = false; media::test::DecoderImplementation implementation = media::test::DecoderImplementation::kVD; base::CommandLine::SwitchMap switches = cmd_line->GetSwitches(); @@ -585,6 +593,8 @@ } else if (it->first == "use_vd_vda") { use_vd_vda = true; implementation = media::test::DecoderImplementation::kVDVDA; + } else if (it->first == "linear_output") { + linear_output = true; } else { std::cout << "unknown option: --" << it->first << "\n" << media::test::usage_msg; @@ -607,6 +617,12 @@ << media::test::usage_msg; return EXIT_FAILURE; } + if (linear_output && !use_vd_vda) { + std::cout << "--linear_output must be used with the VDVDA (--use_vd_vda)\n" + "implementation.\n" + << media::test::usage_msg; + return EXIT_FAILURE; + } testing::InitGoogleTest(&argc, argv); @@ -625,7 +641,7 @@ media::test::VideoPlayerTestEnvironment* test_environment = media::test::VideoPlayerTestEnvironment::Create( video_path, video_metadata_path, validator_type, implementation, - base::FilePath(output_folder), frame_output_config); + linear_output, base::FilePath(output_folder), frame_output_config); if (!test_environment) return EXIT_FAILURE;
diff --git a/net/base/schemeful_site.cc b/net/base/schemeful_site.cc index f6377c07..51df09e 100644 --- a/net/base/schemeful_site.cc +++ b/net/base/schemeful_site.cc
@@ -169,4 +169,9 @@ return site_as_origin_.host() == other.site_as_origin_.host(); } +std::ostream& operator<<(std::ostream& os, const SchemefulSite& ss) { + os << ss.Serialize(); + return os; +} + } // namespace net
diff --git a/net/base/schemeful_site.h b/net/base/schemeful_site.h index f118076..a7eda6f0 100644 --- a/net/base/schemeful_site.h +++ b/net/base/schemeful_site.h
@@ -214,9 +214,9 @@ // Provided to allow gtest to create more helpful error messages, instead of // printing hex. -inline void PrintTo(const SchemefulSite& ss, std::ostream* os) { - *os << ss.Serialize(); -} +// +// Also used so that SchemefulSites can be the arguments of DCHECK_EQ. +NET_EXPORT std::ostream& operator<<(std::ostream& os, const SchemefulSite& ss); } // namespace net
diff --git a/net/cookies/cookie_access_delegate.cc b/net/cookies/cookie_access_delegate.cc index 49e7974a..2b7b6698 100644 --- a/net/cookies/cookie_access_delegate.cc +++ b/net/cookies/cookie_access_delegate.cc
@@ -4,6 +4,8 @@ #include "net/cookies/cookie_access_delegate.h" +#include "base/stl_util.h" + namespace net { CookieAccessDelegate::CookieAccessDelegate() = default; @@ -14,4 +16,36 @@ return false; } +// static +absl::optional<CookiePartitionKey> +CookieAccessDelegate::CreateCookiePartitionKey( + const CookieAccessDelegate* delegate, + const NetworkIsolationKey& network_isolation_key) { + absl::optional<SchemefulSite> fps_owner_site = absl::nullopt; + if (delegate) { + absl::optional<SchemefulSite> top_frame_site = + network_isolation_key.GetTopFrameSite(); + if (!top_frame_site) + return absl::nullopt; + fps_owner_site = delegate->FindFirstPartySetOwner(top_frame_site.value()); + } + return CookiePartitionKey::FromNetworkIsolationKey( + network_isolation_key, base::OptionalOrNullptr(fps_owner_site)); +} + +// static +absl::optional<CookiePartitionKey> +CookieAccessDelegate::FirstPartySetifyPartitionKey( + const CookieAccessDelegate* delegate, + const CookiePartitionKey& cookie_partition_key) { + if (!delegate) + return cookie_partition_key; + absl::optional<SchemefulSite> fps_owner_site = + delegate->FindFirstPartySetOwner(cookie_partition_key.site()); + if (!fps_owner_site) + return cookie_partition_key; + return CookiePartitionKey::FromWire(fps_owner_site.value(), + cookie_partition_key.nonce()); +} + } // namespace net
diff --git a/net/cookies/cookie_access_delegate.h b/net/cookies/cookie_access_delegate.h index 1899a4d..3e5dd05 100644 --- a/net/cookies/cookie_access_delegate.h +++ b/net/cookies/cookie_access_delegate.h
@@ -11,6 +11,7 @@ #include "net/base/net_export.h" #include "net/cookies/canonical_cookie.h" #include "net/cookies/cookie_constants.h" +#include "net/cookies/cookie_partition_key.h" #include "net/cookies/same_party_context.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "url/gurl.h" @@ -63,6 +64,27 @@ const absl::optional<SchemefulSite>& top_frame_site, const std::set<SchemefulSite>& party_context) const = 0; + // Returns the owner of a `site`'s First-Party Set if `site` is in a + // non-trivial set. Returns nullopt otherwise. + virtual absl::optional<net::SchemefulSite> FindFirstPartySetOwner( + const net::SchemefulSite& site) const = 0; + + // Creates a CookiePartitionKey that takes whether the top-frame site is in a + // First-Party Set into account. If FPS are not enabled, it returns a cookie + // partition key that does not take FPS into account. + // + // Should always return nullopt if partitioned cookies are disabled or if + // the NIK has no top-frame site. + static absl::optional<CookiePartitionKey> CreateCookiePartitionKey( + const CookieAccessDelegate* delegate, + const NetworkIsolationKey& network_isolation_key); + + // Converts the CookiePartitionKey's site to its First-Party Set owner if + // the site is in a nontrivial set. + static absl::optional<CookiePartitionKey> FirstPartySetifyPartitionKey( + const CookieAccessDelegate* delegate, + const CookiePartitionKey& cookie_partition_key); + // Returns the First-Party Sets. virtual base::flat_map<net::SchemefulSite, std::set<net::SchemefulSite>> RetrieveFirstPartySets() const = 0;
diff --git a/net/cookies/cookie_partition_key.cc b/net/cookies/cookie_partition_key.cc index 16367d7e..569c6b8 100644 --- a/net/cookies/cookie_partition_key.cc +++ b/net/cookies/cookie_partition_key.cc
@@ -4,9 +4,11 @@ #include "net/cookies/cookie_partition_key.h" +#include <ostream> #include <tuple> #include "base/feature_list.h" +#include "base/stl_util.h" #include "net/base/features.h" #include "net/cookies/cookie_constants.h" @@ -83,22 +85,24 @@ } absl::optional<CookiePartitionKey> CookiePartitionKey::FromNetworkIsolationKey( - const NetworkIsolationKey& network_isolation_key) { - if (!base::FeatureList::IsEnabled(features::kPartitionedCookies)) { + const NetworkIsolationKey& network_isolation_key, + const SchemefulSite* first_party_set_owner_site) { + if (!base::FeatureList::IsEnabled(features::kPartitionedCookies)) return absl::nullopt; - } // TODO(crbug.com/1225444): Check if the top frame site is in a First-Party // Set or if it is an extension URL. - absl::optional<net::SchemefulSite> top_frame_site = - network_isolation_key.GetTopFrameSite(); - if (!top_frame_site) + const SchemefulSite* partition_key_site = + first_party_set_owner_site + ? first_party_set_owner_site + : base::OptionalOrNullptr(network_isolation_key.GetTopFrameSite()); + if (!partition_key_site) return absl::nullopt; absl::optional<base::UnguessableToken> nonce = network_isolation_key.GetNonce(); return absl::make_optional( - net::CookiePartitionKey(top_frame_site.value(), nonce)); + net::CookiePartitionKey(*partition_key_site, nonce)); } bool CookiePartitionKey::IsSerializeable() const { @@ -109,4 +113,9 @@ return !site_.opaque() && !nonce_.has_value(); } +std::ostream& operator<<(std::ostream& os, const CookiePartitionKey& cpk) { + os << cpk.site(); + return os; +} + } // namespace net
diff --git a/net/cookies/cookie_partition_key.h b/net/cookies/cookie_partition_key.h index 64f39bbe..620fb1ed 100644 --- a/net/cookies/cookie_partition_key.h +++ b/net/cookies/cookie_partition_key.h
@@ -59,8 +59,12 @@ // Create a cookie partition key from a request's NetworkIsolationKey. // + // `first_party_set_owner_site` should be nullptr if the NetworkIsolationKey's + // top-frame site is not in First-Party Set. Otherwise it should be the owner + // site of the top-frame site's set. static absl::optional<CookiePartitionKey> FromNetworkIsolationKey( - const NetworkIsolationKey& network_isolation_key); + const NetworkIsolationKey& network_isolation_key, + const SchemefulSite* first_party_set_owner_site = nullptr); // Create a new CookiePartitionKey from the site of an existing // CookiePartitionKey. This should only be used for sites of partition keys @@ -115,6 +119,10 @@ absl::optional<base::UnguessableToken> nonce_; }; +// Used so that CookiePartitionKeys can be the arguments of DCHECK_EQ. +NET_EXPORT std::ostream& operator<<(std::ostream& os, + const CookiePartitionKey& cpk); + } // namespace net #endif // NET_COOKIES_COOKIE_PARTITION_KEY_H_
diff --git a/net/cookies/cookie_partition_key_unittest.cc b/net/cookies/cookie_partition_key_unittest.cc index ed50953..d855883 100644 --- a/net/cookies/cookie_partition_key_unittest.cc +++ b/net/cookies/cookie_partition_key_unittest.cc
@@ -155,6 +155,45 @@ EXPECT_EQ(kNonce, got->nonce().value()); } +TEST_P(CookiePartitionKeyTest, FromNetworkIsolationKey_WithFirstPartySetOwner) { + SchemefulSite kTopLevelSite = SchemefulSite(GURL("https://setmember.com")); + SchemefulSite kFirstPartySetOwnerSite = + SchemefulSite(GURL("https://setowner.com")); + SchemefulSite kCookieSite = SchemefulSite(GURL("https://cookiesite.com")); + + { + // Without a nonce. + absl::optional<CookiePartitionKey> got = + CookiePartitionKey::FromNetworkIsolationKey( + NetworkIsolationKey(kTopLevelSite, kCookieSite), + &kFirstPartySetOwnerSite); + bool partitioned_cookies_enabled = PartitionedCookiesEnabled(); + EXPECT_EQ(partitioned_cookies_enabled, got.has_value()); + if (partitioned_cookies_enabled) { + EXPECT_FALSE(got->from_script()); + EXPECT_EQ(kFirstPartySetOwnerSite, got->site()); + EXPECT_FALSE(got->nonce()); + } + } + + { + // With a nonce. + base::UnguessableToken nonce = base::UnguessableToken::Create(); + absl::optional<CookiePartitionKey> got = + CookiePartitionKey::FromNetworkIsolationKey( + NetworkIsolationKey(kTopLevelSite, kCookieSite, &nonce), + &kFirstPartySetOwnerSite); + bool partitioned_cookies_enabled = PartitionedCookiesEnabled(); + EXPECT_EQ(partitioned_cookies_enabled, got.has_value()); + if (partitioned_cookies_enabled) { + EXPECT_FALSE(got->from_script()); + EXPECT_EQ(kFirstPartySetOwnerSite, got->site()); + EXPECT_TRUE(got->nonce()); + EXPECT_EQ(nonce, got->nonce().value()); + } + } +} + TEST_P(CookiePartitionKeyTest, FromWire) { struct TestCase { const GURL url;
diff --git a/net/cookies/cookie_partition_keychain.cc b/net/cookies/cookie_partition_keychain.cc index 8eb83dc..2ab53b6 100644 --- a/net/cookies/cookie_partition_keychain.cc +++ b/net/cookies/cookie_partition_keychain.cc
@@ -34,4 +34,23 @@ CookiePartitionKeychain::~CookiePartitionKeychain() = default; +CookiePartitionKeychain CookiePartitionKeychain::FirstPartySetify( + const CookieAccessDelegate* cookie_access_delegate) const { + if (!cookie_access_delegate || IsEmpty() || ContainsAllKeys()) + return *this; + std::vector<CookiePartitionKey> keys; + keys.reserve(PartitionKeys().size()); + for (const auto& key : PartitionKeys()) { + absl::optional<SchemefulSite> fps_owner = + cookie_access_delegate->FindFirstPartySetOwner(key.site()); + if (fps_owner) { + keys.push_back( + CookiePartitionKey::FromWire(fps_owner.value(), key.nonce())); + } else { + keys.push_back(key); + } + } + return CookiePartitionKeychain(keys); +} + } // namespace net
diff --git a/net/cookies/cookie_partition_keychain.h b/net/cookies/cookie_partition_keychain.h index f5e7f13..910ed58c 100644 --- a/net/cookies/cookie_partition_keychain.h +++ b/net/cookies/cookie_partition_keychain.h
@@ -8,6 +8,7 @@ #include <vector> #include "net/base/net_export.h" +#include "net/cookies/cookie_access_delegate.h" #include "net/cookies/cookie_partition_key.h" namespace net { @@ -46,6 +47,14 @@ : CookiePartitionKeychain(); } + // Takes a CookiePartitionKeychain which was created in a context that does + // not have access to sites' First-Party Set owners and converts it to the + // correct First-Party-Sets-aware CookiePartitionKeychain, replacing any + // CookiePartitionKeys whose sites which are members of a set with a new + // partition key containing the set's owner site. + CookiePartitionKeychain FirstPartySetify( + const CookieAccessDelegate* cookie_access_delegate) const; + // Temporary method used to record where we need to decide how to build the // CookiePartitionKeychain. //
diff --git a/net/cookies/cookie_partition_keychain_unittest.cc b/net/cookies/cookie_partition_keychain_unittest.cc index 770d8cd..c4d8955 100644 --- a/net/cookies/cookie_partition_keychain_unittest.cc +++ b/net/cookies/cookie_partition_keychain_unittest.cc
@@ -6,6 +6,7 @@ #define NET_COOKIES_COOKIE_PARTITION_KEYCHAIN_UNITTEST_H_ #include "net/cookies/cookie_partition_keychain.h" +#include "net/cookies/test_cookie_access_delegate.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -70,6 +71,69 @@ CookiePartitionKey::FromURLForTesting(GURL("https://www.foo.com")))); } +TEST(CookiePartitionKeychainTest, FirstPartySetify) { + const GURL kOwnerURL("https://owner.com"); + const SchemefulSite kOwnerSite(kOwnerURL); + const CookiePartitionKey kOwnerPartitionKey = + CookiePartitionKey::FromURLForTesting(kOwnerURL); + + const GURL kMemberURL("https://member.com"); + const SchemefulSite kMemberSite(kMemberURL); + const CookiePartitionKey kMemberPartitionKey = + CookiePartitionKey::FromURLForTesting(kMemberURL); + + const GURL kNonMemberURL("https://nonmember.com"); + const CookiePartitionKey kNonMemberPartitionKey = + CookiePartitionKey::FromURLForTesting(kNonMemberURL); + + TestCookieAccessDelegate delegate; + base::flat_map<SchemefulSite, std::set<SchemefulSite>> first_party_sets; + first_party_sets.insert(std::make_pair( + kOwnerSite, std::set<SchemefulSite>({kOwnerSite, kMemberSite}))); + delegate.SetFirstPartySets(first_party_sets); + + CookiePartitionKeychain empty_keychain; + EXPECT_TRUE(empty_keychain.FirstPartySetify(&delegate).IsEmpty()); + EXPECT_TRUE(empty_keychain.FirstPartySetify(nullptr).IsEmpty()); + + CookiePartitionKeychain contains_all_keys = + CookiePartitionKeychain::ContainsAll(); + EXPECT_TRUE(contains_all_keys.FirstPartySetify(&delegate).ContainsAllKeys()); + EXPECT_TRUE(contains_all_keys.FirstPartySetify(nullptr).ContainsAllKeys()); + + // An owner site of an FPS should not have its partition key changed. + CookiePartitionKeychain got = + CookiePartitionKeychain(kOwnerPartitionKey).FirstPartySetify(&delegate); + EXPECT_EQ(1u, got.PartitionKeys().size()); + EXPECT_EQ(kOwnerPartitionKey, got.PartitionKeys()[0]); + + // A member site should have its partition key changed to the owner site. + got = + CookiePartitionKeychain(kMemberPartitionKey).FirstPartySetify(&delegate); + EXPECT_EQ(1u, got.PartitionKeys().size()); + EXPECT_EQ(kOwnerPartitionKey, got.PartitionKeys()[0]); + + // A member site's partition key should not change if the CookieAccessDelegate + // is null. + got = CookiePartitionKeychain(kMemberPartitionKey).FirstPartySetify(nullptr); + EXPECT_EQ(1u, got.PartitionKeys().size()); + EXPECT_EQ(kMemberPartitionKey, got.PartitionKeys()[0]); + + // A non-member site should not have its partition key changed. + got = CookiePartitionKeychain(kNonMemberPartitionKey) + .FirstPartySetify(&delegate); + EXPECT_EQ(1u, got.PartitionKeys().size()); + EXPECT_EQ(kNonMemberPartitionKey, got.PartitionKeys()[0]); + + // A keychain that contains a member site and non-member site should be + // changed to include the owner site and the unmodified non-member site. + got = CookiePartitionKeychain({kMemberPartitionKey, kNonMemberPartitionKey}) + .FirstPartySetify(&delegate); + EXPECT_EQ(2u, got.PartitionKeys().size()); + EXPECT_EQ(kOwnerPartitionKey, got.PartitionKeys()[0]); + EXPECT_EQ(kNonMemberPartitionKey, got.PartitionKeys()[1]); +} + } // namespace net #endif // NET_COOKIES_COOKIE_PARTITION_KEYCHAIN_UNITTEST_H_
diff --git a/net/cookies/test_cookie_access_delegate.cc b/net/cookies/test_cookie_access_delegate.cc index cacdb8cc..074fd49 100644 --- a/net/cookies/test_cookie_access_delegate.cc +++ b/net/cookies/test_cookie_access_delegate.cc
@@ -4,6 +4,7 @@ #include "net/cookies/test_cookie_access_delegate.h" +#include "base/containers/contains.h" #include "net/base/schemeful_site.h" #include "net/cookies/cookie_constants.h" #include "net/cookies/cookie_util.h" @@ -55,6 +56,16 @@ return false; } +absl::optional<net::SchemefulSite> +TestCookieAccessDelegate::FindFirstPartySetOwner( + const net::SchemefulSite& site) const { + for (const auto& all_sets_iter : first_party_sets_) { + if (base::Contains(all_sets_iter.second, site)) + return all_sets_iter.first; + } + return absl::nullopt; +} + base::flat_map<net::SchemefulSite, std::set<net::SchemefulSite>> TestCookieAccessDelegate::RetrieveFirstPartySets() const { return first_party_sets_;
diff --git a/net/cookies/test_cookie_access_delegate.h b/net/cookies/test_cookie_access_delegate.h index 58a9d91e..cb54f09 100644 --- a/net/cookies/test_cookie_access_delegate.h +++ b/net/cookies/test_cookie_access_delegate.h
@@ -47,6 +47,8 @@ const std::set<net::SchemefulSite>& party_context) const override; bool IsInNontrivialFirstPartySet( const net::SchemefulSite& site) const override; + absl::optional<net::SchemefulSite> FindFirstPartySetOwner( + const net::SchemefulSite& site) const override; base::flat_map<net::SchemefulSite, std::set<net::SchemefulSite>> RetrieveFirstPartySets() const override; @@ -63,6 +65,9 @@ const std::string& site_for_cookies_scheme, bool require_secure_origin); + // Set the test delegate's First-Party Sets. The map is keyed on the set's + // owner site. The owner site should still be included in the std::set stored + // in the map. void SetFirstPartySets( const base::flat_map<net::SchemefulSite, std::set<net::SchemefulSite>>& sets);
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc index f3ee1e1..5289d0e 100644 --- a/net/url_request/url_request_http_job.cc +++ b/net/url_request/url_request_http_job.cc
@@ -241,6 +241,7 @@ throttling_entry_ = manager->RegisterRequestUrl(request->url()); ResetTimer(); + ComputeCookiePartitionKey(); } URLRequestHttpJob::~URLRequestHttpJob() { @@ -595,13 +596,9 @@ request_site, request_->isolation_info(), delegate, request_->force_ignore_top_frame_party_for_cookies())); - absl::optional<CookiePartitionKey> cookie_partition_key = - CookiePartitionKey::FromNetworkIsolationKey( - request_->isolation_info().network_isolation_key()); - cookie_store->GetCookieListWithOptionsAsync( request_->url(), options, - CookiePartitionKeychain::FromOptional(cookie_partition_key), + CookiePartitionKeychain::FromOptional(cookie_partition_key_), base::BindOnce(&URLRequestHttpJob::SetCookieHeaderAndStart, weak_factory_.GetWeakPtr(), options)); } else { @@ -781,9 +778,7 @@ std::unique_ptr<CanonicalCookie> cookie = net::CanonicalCookie::Create( request_->url(), cookie_string, base::Time::Now(), server_time, - net::CookiePartitionKey::FromNetworkIsolationKey( - request_->isolation_info().network_isolation_key()), - &returned_status); + cookie_partition_key_, &returned_status); absl::optional<CanonicalCookie> cookie_to_return = absl::nullopt; if (returned_status.IsInclude()) { @@ -1616,4 +1611,15 @@ network_quality_estimator->NotifyURLRequestDestroyed(*request()); } +void URLRequestHttpJob::ComputeCookiePartitionKey() { + const CookieStore* cookie_store = request_->context()->cookie_store(); + if (!cookie_store) { + cookie_partition_key_ = absl::nullopt; + return; + } + cookie_partition_key_ = CookieAccessDelegate::CreateCookiePartitionKey( + cookie_store->cookie_access_delegate(), + request_->isolation_info().network_isolation_key()); +} + } // namespace net
diff --git a/net/url_request/url_request_http_job.h b/net/url_request/url_request_http_job.h index c5dbf75..fd3cd48c 100644 --- a/net/url_request/url_request_http_job.h +++ b/net/url_request/url_request_http_job.h
@@ -206,6 +206,14 @@ // `override_response_info_::headers`. HttpResponseHeaders* GetResponseHeaders() const; + // Compute the `cookie_partition_key_` for the request. Partitioned cookies + // will be set using this key and only partitioned cookies with this partition + // key will be sent. + // Sets `cookie_partition_key_` to nullopt if cookie partitioning is not + // enabled, if the NIK has no top-frame site, or if the instance has no + // cookie store. + void ComputeCookiePartitionKey(); + RequestPriority priority_; HttpRequestInfo request_info_; @@ -275,6 +283,12 @@ ResponseHeadersCallback early_response_headers_callback_; ResponseHeadersCallback response_headers_callback_; + // Partitioned cookies will be set using this key and only partitioned cookies + // with this partition key will be sent. + // + // Unpartitioned cookies are unaffected by this field. + absl::optional<CookiePartitionKey> cookie_partition_key_; + base::WeakPtrFactory<URLRequestHttpJob> weak_factory_{this}; };
diff --git a/net/url_request/url_request_http_job_unittest.cc b/net/url_request/url_request_http_job_unittest.cc index db18485..5e2f3ca 100644 --- a/net/url_request/url_request_http_job_unittest.cc +++ b/net/url_request/url_request_http_job_unittest.cc
@@ -34,6 +34,7 @@ #include "net/cookies/cookie_monster.h" #include "net/cookies/cookie_store_test_callbacks.h" #include "net/cookies/cookie_store_test_helpers.h" +#include "net/cookies/test_cookie_access_delegate.h" #include "net/http/http_transaction_factory.h" #include "net/http/http_transaction_test_util.h" #include "net/http/transport_security_state.h" @@ -1859,4 +1860,121 @@ } } +// This class test partitioned cookies' interaction with First-Party Sets. +// When FPS is enabled, top-level sites that are in the same set share a cookie +// partition. +TEST_P(PartitionedCookiesURLRequestHttpJobTest, + PartitionedCookiesAndFirstPartySets) { + EmbeddedTestServer https_test(EmbeddedTestServer::TYPE_HTTPS); + https_test.AddDefaultHandlers(base::FilePath()); + ASSERT_TRUE(https_test.Start()); + + const GURL kOwnerURL("https://owner.com"); + const SchemefulSite kOwnerSite(kOwnerURL); + const url::Origin kOwnerOrigin = url::Origin::Create(kOwnerURL); + const IsolationInfo kOwnerIsolationInfo = + IsolationInfo::CreateForInternalRequest(kOwnerOrigin); + + const GURL kMemberURL("https://member.com"); + const SchemefulSite kMemberSite(kMemberURL); + const url::Origin kMemberOrigin = url::Origin::Create(kMemberURL); + const IsolationInfo kMemberIsolationInfo = + IsolationInfo::CreateForInternalRequest(kMemberOrigin); + + const GURL kNonMemberURL("https://nonmember.com"); + const url::Origin kNonMemberOrigin = url::Origin::Create(kNonMemberURL); + const IsolationInfo kNonMemberIsolationInfo = + IsolationInfo::CreateForInternalRequest(kNonMemberOrigin); + + base::flat_map<SchemefulSite, std::set<SchemefulSite>> first_party_sets; + first_party_sets.insert(std::make_pair( + kOwnerSite, std::set<SchemefulSite>({kOwnerSite, kMemberSite}))); + + TestURLRequestContext context; + CookieMonster cookie_monster(nullptr, nullptr); + auto cookie_access_delegate = std::make_unique<TestCookieAccessDelegate>(); + cookie_access_delegate->SetFirstPartySets(first_party_sets); + cookie_monster.SetCookieAccessDelegate(std::move(cookie_access_delegate)); + context.set_cookie_store(&cookie_monster); + + TestDelegate delegate; + std::unique_ptr<URLRequest> req(context.CreateRequest( + https_test.GetURL("/set-cookie?__Host-foo=0;SameSite=None;Secure;Path=/" + ";Partitioned;"), + DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS)); + + // Start with the set's owner as the top-level site. + req->set_isolation_info(kOwnerIsolationInfo); + req->Start(); + ASSERT_TRUE(req->is_pending()); + delegate.RunUntilComplete(); + + { + // Test the cookie is present in a request with the same top-frame site as + // when the cookie was set. + TestDelegate delegate; + std::unique_ptr<URLRequest> req(context.CreateRequest( + https_test.GetURL("/echoheader?Cookie"), DEFAULT_PRIORITY, &delegate, + TRAFFIC_ANNOTATION_FOR_TESTS)); + req->set_isolation_info(kOwnerIsolationInfo); + req->Start(); + delegate.RunUntilComplete(); + EXPECT_EQ("__Host-foo=0", delegate.data_received()); + } + + { + // Requests whose top-frame site are in the set should have access to the + // partitioned cookie. + TestDelegate delegate; + std::unique_ptr<URLRequest> req(context.CreateRequest( + https_test.GetURL("/echoheader?Cookie"), DEFAULT_PRIORITY, &delegate, + TRAFFIC_ANNOTATION_FOR_TESTS)); + req->set_isolation_info(kMemberIsolationInfo); + req->Start(); + delegate.RunUntilComplete(); + EXPECT_EQ("__Host-foo=0", delegate.data_received()); + } + + // Set a cookie from the member site. + req = context.CreateRequest( + https_test.GetURL("/set-cookie?__Host-bar=1;SameSite=None;Secure;Path=/" + ";Partitioned;"), + DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS); + req->set_isolation_info(kMemberIsolationInfo); + req->Start(); + ASSERT_TRUE(req->is_pending()); + delegate.RunUntilComplete(); + + { + // Check request whose top-frame site is the owner site has the cookie set + // on the member site. + TestDelegate delegate; + std::unique_ptr<URLRequest> req(context.CreateRequest( + https_test.GetURL("/echoheader?Cookie"), DEFAULT_PRIORITY, &delegate, + TRAFFIC_ANNOTATION_FOR_TESTS)); + req->set_isolation_info(kOwnerIsolationInfo); + req->Start(); + delegate.RunUntilComplete(); + EXPECT_EQ("__Host-foo=0; __Host-bar=1", delegate.data_received()); + } + + { + // Check that the cookies are not available when the top-frame site is not + // in the set. If partitioned cookies are disabled, then the cookies should + // be available. + TestDelegate delegate; + std::unique_ptr<URLRequest> req(context.CreateRequest( + https_test.GetURL("/echoheader?Cookie"), DEFAULT_PRIORITY, &delegate, + TRAFFIC_ANNOTATION_FOR_TESTS)); + req->set_isolation_info(kNonMemberIsolationInfo); + req->Start(); + delegate.RunUntilComplete(); + if (PartitionedCookiesEnabled()) { + EXPECT_EQ("None", delegate.data_received()); + } else { + EXPECT_EQ("__Host-foo=0; __Host-bar=1", delegate.data_received()); + } + } +} + } // namespace net
diff --git a/services/audio/output_device_mixer_manager.cc b/services/audio/output_device_mixer_manager.cc index 19765a66..68c18aba 100644 --- a/services/audio/output_device_mixer_manager.cc +++ b/services/audio/output_device_mixer_manager.cc
@@ -220,7 +220,7 @@ } output_params.set_frames_per_buffer(media::AudioLatency::GetRtcBufferSize( - output_params.sample_rate(), /*hardware_buffer_size=*/0)); + output_params.sample_rate(), output_params.frames_per_buffer())); // base::Unretained(this) is safe here, because |output_device_mixers_| // are owned by |this|.
diff --git a/services/device/serial/serial_port_manager_impl.cc b/services/device/serial/serial_port_manager_impl.cc index b91d21b8..022c8b367 100644 --- a/services/device/serial/serial_port_manager_impl.cc +++ b/services/device/serial/serial_port_manager_impl.cc
@@ -131,8 +131,8 @@ ui_task_runner_->PostTask( FROM_HERE, base::BindOnce( - &BluetoothSerialPortImpl::Open, - bluetooth_enumerator_->GetAdapter(), *address, std::move(options), + &SerialPortManagerImpl::OpenBluetoothSerialPortOnUI, + weak_factory_.GetWeakPtr(), *address, std::move(options), std::move(client), std::move(watcher), base::BindOnce(&OnPortOpened, std::move(callback), base::SequencedTaskRunnerHandle::Get()))); @@ -143,6 +143,17 @@ std::move(callback).Run(mojo::NullRemote()); } +void SerialPortManagerImpl::OpenBluetoothSerialPortOnUI( + const std::string& address, + mojom::SerialConnectionOptionsPtr options, + mojo::PendingRemote<mojom::SerialPortClient> client, + mojo::PendingRemote<mojom::SerialPortConnectionWatcher> watcher, + BluetoothSerialPortImpl::OpenCallback callback) { + BluetoothSerialPortImpl::Open(bluetooth_enumerator_->GetAdapter(), address, + std::move(options), std::move(client), + std::move(watcher), std::move(callback)); +} + void SerialPortManagerImpl::OnPortAdded(const mojom::SerialPortInfo& port) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); for (auto& client : clients_)
diff --git a/services/device/serial/serial_port_manager_impl.h b/services/device/serial/serial_port_manager_impl.h index 5f9402e..8cb5109c 100644 --- a/services/device/serial/serial_port_manager_impl.h +++ b/services/device/serial/serial_port_manager_impl.h
@@ -8,6 +8,7 @@ #include <memory> #include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" #include "base/scoped_multi_source_observation.h" #include "base/sequence_checker.h" #include "mojo/public/cpp/bindings/pending_receiver.h" @@ -16,6 +17,7 @@ #include "mojo/public/cpp/bindings/remote_set.h" #include "services/device/public/mojom/serial.mojom.h" #include "services/device/serial/bluetooth_serial_device_enumerator.h" +#include "services/device/serial/bluetooth_serial_port_impl.h" #include "services/device/serial/serial_device_enumerator.h" namespace base { @@ -71,6 +73,13 @@ void OnPortAdded(const mojom::SerialPortInfo& port) override; void OnPortRemoved(const mojom::SerialPortInfo& port) override; + void OpenBluetoothSerialPortOnUI( + const std::string& address, + mojom::SerialConnectionOptionsPtr options, + mojo::PendingRemote<mojom::SerialPortClient> client, + mojo::PendingRemote<mojom::SerialPortConnectionWatcher> watcher, + BluetoothSerialPortImpl::OpenCallback callback); + std::unique_ptr<SerialDeviceEnumerator> enumerator_; std::unique_ptr<BluetoothSerialDeviceEnumerator> bluetooth_enumerator_; base::ScopedMultiSourceObservation<SerialDeviceEnumerator, @@ -84,6 +93,7 @@ mojo::RemoteSet<mojom::SerialPortManagerClient> clients_; // See threading notes above for guidelines for checking sequence. SEQUENCE_CHECKER(sequence_checker_); + base::WeakPtrFactory<SerialPortManagerImpl> weak_factory_{this}; }; } // namespace device
diff --git a/services/network/cookie_access_delegate_impl.cc b/services/network/cookie_access_delegate_impl.cc index 8e875330..bbddfbc 100644 --- a/services/network/cookie_access_delegate_impl.cc +++ b/services/network/cookie_access_delegate_impl.cc
@@ -80,6 +80,14 @@ first_party_sets_->IsInNontrivialFirstPartySet(site); } +absl::optional<net::SchemefulSite> +CookieAccessDelegateImpl::FindFirstPartySetOwner( + const net::SchemefulSite& site) const { + if (!first_party_sets_) + return absl::nullopt; + return first_party_sets_->FindOwner(site); +} + base::flat_map<net::SchemefulSite, std::set<net::SchemefulSite>> CookieAccessDelegateImpl::RetrieveFirstPartySets() const { if (!first_party_sets_)
diff --git a/services/network/cookie_access_delegate_impl.h b/services/network/cookie_access_delegate_impl.h index 0a0c1b6..79b71fc 100644 --- a/services/network/cookie_access_delegate_impl.h +++ b/services/network/cookie_access_delegate_impl.h
@@ -59,6 +59,8 @@ const std::set<net::SchemefulSite>& party_context) const override; bool IsInNontrivialFirstPartySet( const net::SchemefulSite& site) const override; + absl::optional<net::SchemefulSite> FindFirstPartySetOwner( + const net::SchemefulSite& site) const override; base::flat_map<net::SchemefulSite, std::set<net::SchemefulSite>> RetrieveFirstPartySets() const override;
diff --git a/services/network/cookie_manager.cc b/services/network/cookie_manager.cc index c09630b..99fa5c19 100644 --- a/services/network/cookie_manager.cc +++ b/services/network/cookie_manager.cc
@@ -100,23 +100,60 @@ #endif cookie_store_->GetCookieListWithOptionsAsync( - url, cookie_options, cookie_partition_keychain, std::move(callback)); + url, cookie_options, + cookie_partition_keychain.FirstPartySetify( + cookie_store_->cookie_access_delegate()), + std::move(callback)); } void CookieManager::SetCanonicalCookie(const net::CanonicalCookie& cookie, const GURL& source_url, const net::CookieOptions& cookie_options, SetCanonicalCookieCallback callback) { - cookie_store_->SetCanonicalCookieAsync( - std::make_unique<net::CanonicalCookie>(cookie), source_url, - cookie_options, std::move(callback)); + std::unique_ptr<net::CanonicalCookie> cookie_ptr = + std::make_unique<net::CanonicalCookie>(cookie); + if (absl::optional<net::CookiePartitionKey> cookie_partition_key = + cookie.PartitionKey()) { + absl::optional<net::CookiePartitionKey> fps_cookie_partition_key = + net::CookieAccessDelegate::FirstPartySetifyPartitionKey( + cookie_store_->cookie_access_delegate(), + cookie_partition_key.value()); + if (fps_cookie_partition_key != cookie_partition_key) { + cookie_ptr = net::CanonicalCookie::FromStorage( + cookie.Name(), cookie.Value(), cookie.Domain(), cookie.Path(), + cookie.CreationDate(), cookie.ExpiryDate(), cookie.LastAccessDate(), + cookie.IsSecure(), cookie.IsHttpOnly(), cookie.SameSite(), + cookie.Priority(), cookie.IsSameParty(), fps_cookie_partition_key, + cookie.SourceScheme(), cookie.SourcePort()); + if (!cookie_ptr) { + std::move(callback).Run( + net::CookieAccessResult(net::CookieInclusionStatus( + net::CookieInclusionStatus::ExclusionReason:: + EXCLUDE_FAILURE_TO_STORE))); + return; + } + } + } + cookie_store_->SetCanonicalCookieAsync(std::move(cookie_ptr), source_url, + cookie_options, std::move(callback)); } void CookieManager::DeleteCanonicalCookie( const net::CanonicalCookie& cookie, DeleteCanonicalCookieCallback callback) { + std::unique_ptr<net::CanonicalCookie> cookie_ptr = + std::make_unique<net::CanonicalCookie>(cookie); + if (absl::optional<net::CookiePartitionKey> cookie_partition_key = + cookie.PartitionKey()) { + absl::optional<net::CookiePartitionKey> fps_cookie_partition_key = + net::CookieAccessDelegate::FirstPartySetifyPartitionKey( + cookie_store_->cookie_access_delegate(), + cookie_partition_key.value()); + if (fps_cookie_partition_key && !cookie_partition_key->site().opaque()) + DCHECK_EQ(cookie_partition_key.value(), fps_cookie_partition_key.value()); + } cookie_store_->DeleteCanonicalCookieAsync( - cookie, + *cookie_ptr, base::BindOnce( [](DeleteCanonicalCookieCallback callback, uint32_t num_deleted) { std::move(callback).Run(num_deleted > 0);
diff --git a/services/network/cookie_manager_unittest.cc b/services/network/cookie_manager_unittest.cc index 7545662..54912a1e 100644 --- a/services/network/cookie_manager_unittest.cc +++ b/services/network/cookie_manager_unittest.cc
@@ -2915,5 +2915,128 @@ ASSERT_EQ(1U, service_wrapper()->callback_count()); } +class FPSPartitionedCookiesCookieManagerTest : public CookieManagerTest { + public: + FPSPartitionedCookiesCookieManagerTest() + : owner_url_(GURL("https://owner.test")), + owner_site_(owner_url_), + owner_partition_key_( + net::CookiePartitionKey::FromURLForTesting(owner_url_)), + member_url_(GURL("https://member.test")), + member_site_(member_url_), + member_partition_key_( + net::CookiePartitionKey::FromURLForTesting(GURL(member_url_))), + non_member_url_(GURL("https://nonmember.test")), + non_member_partition_key_( + net::CookiePartitionKey::FromURLForTesting(non_member_url_)) { + delegate_ = std::make_unique<net::TestCookieAccessDelegate>(); + first_party_sets_.insert(std::make_pair( + owner_site_, + std::set<net::SchemefulSite>({owner_site_, member_site_}))); + delegate_->SetFirstPartySets(first_party_sets_); + cookie_store()->SetCookieAccessDelegate(std::move(delegate_)); + } + + protected: + const GURL owner_url_; + const net::SchemefulSite owner_site_; + const net::CookiePartitionKey owner_partition_key_; + + const GURL member_url_; + const net::SchemefulSite member_site_; + const net::CookiePartitionKey member_partition_key_; + + const GURL non_member_url_; + const net::CookiePartitionKey non_member_partition_key_; + + base::flat_map<net::SchemefulSite, std::set<net::SchemefulSite>> + first_party_sets_; + std::unique_ptr<net::TestCookieAccessDelegate> delegate_; +}; + +TEST_F(FPSPartitionedCookiesCookieManagerTest, GetCookieList) { + // Add unpartitioned cookie. + ASSERT_TRUE(SetCanonicalCookie( + *net::CanonicalCookie::CreateUnsafeCookieForTesting( + "__Host-unpartitioned", "1", kCookieDomain, "/", base::Time(), + base::Time(), base::Time(), + /*secure=*/true, /*httponly=*/false, net::CookieSameSite::LAX_MODE, + net::COOKIE_PRIORITY_MEDIUM, /*same_party=*/false, + /*partition_key=*/absl::nullopt), + "https", true)); + // Add partitioned cookies. One is in the First-Party Set's partition, the + // other is not. + ASSERT_TRUE(SetCanonicalCookie( + *net::CanonicalCookie::CreateUnsafeCookieForTesting( + "__Host-intheset", "2", kCookieDomain, "/", base::Time(), + base::Time(), base::Time(), + /*secure=*/true, /*httponly=*/false, net::CookieSameSite::LAX_MODE, + net::COOKIE_PRIORITY_MEDIUM, /*same_party=*/false, + absl::make_optional<net::CookiePartitionKey>(owner_partition_key_)), + "https", true)); + ASSERT_TRUE(SetCanonicalCookie( + *net::CanonicalCookie::CreateUnsafeCookieForTesting( + "__Host-nonmember", "4", kCookieDomain, "/", base::Time(), + base::Time(), base::Time(), + /*secure=*/true, /*httponly=*/false, net::CookieSameSite::LAX_MODE, + net::COOKIE_PRIORITY_MEDIUM, /*same_party=*/false, + absl::make_optional<net::CookiePartitionKey>( + non_member_partition_key_)), + "https", true)); + + // Query cookies when the top-level site is a member of the First-Party Set. + // It should have access to the cookie set with the owner site as a partition + // key the unpartitioned cookie. + auto cookies = service_wrapper()->GetCookieList( + GURL("https://foo_host.com/with/path"), + net::CookieOptions::MakeAllInclusive(), + net::CookiePartitionKeychain(member_partition_key_)); + EXPECT_EQ(2u, cookies.size()); + EXPECT_EQ("__Host-unpartitioned", cookies[0].Name()); + EXPECT_EQ("__Host-intheset", cookies[1].Name()); + EXPECT_EQ(owner_partition_key_, cookies[1].PartitionKey().value()); +} + +TEST_F(FPSPartitionedCookiesCookieManagerTest, SetCanonicalCookie) { + // Add unpartitioned cookie. + ASSERT_TRUE(service_wrapper()->SetCanonicalCookie( + *net::CanonicalCookie::CreateUnsafeCookieForTesting( + "__Host-unpartitioned", "1", kCookieDomain, "/", base::Time(), + base::Time(), base::Time(), + /*secure=*/true, /*httponly=*/false, net::CookieSameSite::LAX_MODE, + net::COOKIE_PRIORITY_MEDIUM, /*same_party=*/false, + /*partition_key=*/absl::nullopt), + "https", true)); + // Add partitioned cookies. One is in the First-Party Set's partition, and is + // first set with a member site as the partition key. SetCanonicalCookie + // should change the partition key to the First-Party Set's owner site. + ASSERT_TRUE(service_wrapper()->SetCanonicalCookie( + *net::CanonicalCookie::CreateUnsafeCookieForTesting( + "__Host-intheset", "2", kCookieDomain, "/", base::Time(), + base::Time(), base::Time(), + /*secure=*/true, /*httponly=*/false, net::CookieSameSite::LAX_MODE, + net::COOKIE_PRIORITY_MEDIUM, /*same_party=*/false, + absl::make_optional<net::CookiePartitionKey>(member_partition_key_)), + "https", true)); + ASSERT_TRUE(service_wrapper()->SetCanonicalCookie( + *net::CanonicalCookie::CreateUnsafeCookieForTesting( + "__Host-nonmember", "4", kCookieDomain, "/", base::Time(), + base::Time(), base::Time(), + /*secure=*/true, /*httponly=*/false, net::CookieSameSite::LAX_MODE, + net::COOKIE_PRIORITY_MEDIUM, /*same_party=*/false, + absl::make_optional<net::CookiePartitionKey>( + non_member_partition_key_)), + "https", true)); + + auto cookies = service_wrapper()->GetCookieList( + GURL("https://foo_host.com/with/path"), + net::CookieOptions::MakeAllInclusive(), + net::CookiePartitionKeychain(owner_partition_key_)); + EXPECT_EQ(2u, cookies.size()); + EXPECT_EQ("__Host-unpartitioned", cookies[0].Name()); + EXPECT_EQ("__Host-intheset", cookies[1].Name()); + EXPECT_EQ(owner_partition_key_, cookies[1].PartitionKey().value()); +} + } // namespace } // namespace network
diff --git a/services/network/first_party_sets/first_party_sets.cc b/services/network/first_party_sets/first_party_sets.cc index bd0092b..bd86b312 100644 --- a/services/network/first_party_sets/first_party_sets.cc +++ b/services/network/first_party_sets/first_party_sets.cc
@@ -231,6 +231,11 @@ return owner; } +const absl::optional<net::SchemefulSite> FirstPartySets::FindOwner( + const net::SchemefulSite& site) const { + return FindOwner(site, /*infer_singleton_sets=*/false); +} + bool FirstPartySets::IsInNontrivialFirstPartySet( const net::SchemefulSite& site) const { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/services/network/first_party_sets/first_party_sets.h b/services/network/first_party_sets/first_party_sets.h index 4fb8776..386031e 100644 --- a/services/network/first_party_sets/first_party_sets.h +++ b/services/network/first_party_sets/first_party_sets.h
@@ -51,18 +51,6 @@ // Has no effect if `kFirstPartySets` is disabled. void ParseAndSet(base::File sets_file); - // Returns whether the `site` is same-party with the `party_context`, and - // `top_frame_site` (if it is not nullptr). That is, is the `site`'s owner the - // same as the owners of every member of `party_context` and of - // `top_frame_site`? Note: if `site` is not a member of a First-Party Set - // (with more than one member), then this returns false. If `top_frame_site` - // is nullptr, then it is ignored. - bool IsContextSamePartyWithSite( - const net::SchemefulSite& site, - const net::SchemefulSite* top_frame_site, - const std::set<net::SchemefulSite>& party_context, - bool infer_singleton_sets) const; - // Computes the SameParty context, indicating whether `site` is same-party // with `top_frame_site` (if not nullptr) and `party_context`. The context // includes the real context type, plus some additional "hypothetical" context @@ -107,7 +95,26 @@ void SetOnSiteDataCleared( base::OnceCallback<void(const std::string&)> callback); + // Returns nullopt if First-Party Sets are disabled or if the input is not in + // a nontrivial set. + // If FPS are enabled and the input site is in a nontrivial set, then this + // returns the owner site of that set. + const absl::optional<net::SchemefulSite> FindOwner( + const net::SchemefulSite& site) const; + private: + // Returns whether the `site` is same-party with the `party_context`, and + // `top_frame_site` (if it is not nullptr). That is, is the `site`'s owner the + // same as the owners of every member of `party_context` and of + // `top_frame_site`? Note: if `site` is not a member of a First-Party Set + // (with more than one member), then this returns false. If `top_frame_site` + // is nullptr, then it is ignored. + bool IsContextSamePartyWithSite( + const net::SchemefulSite& site, + const net::SchemefulSite* top_frame_site, + const std::set<net::SchemefulSite>& party_context, + bool infer_singleton_sets) const; + // Parses the contents of `raw_sets` as a collection of First-Party Set // declarations, and assigns to `sets_`. void OnReadSetsFile(const std::string& raw_sets);
diff --git a/services/network/first_party_sets/first_party_sets_unittest.cc b/services/network/first_party_sets/first_party_sets_unittest.cc index de56f2b9..5e19e77 100644 --- a/services/network/first_party_sets/first_party_sets_unittest.cc +++ b/services/network/first_party_sets/first_party_sets_unittest.cc
@@ -26,6 +26,8 @@ using ::testing::UnorderedElementsAre; using ::testing::Value; +using Type = net::SamePartyContext::Type; + // Some of these tests overlap with FirstPartySetParser unittests, but // overlapping test coverage isn't the worst thing. @@ -124,8 +126,6 @@ } TEST_F(FirstPartySetsDisabledTest, ComputeContext_InfersSingletons) { - using SamePartyContextType = net::SamePartyContext::Type; - net::SchemefulSite member(GURL("https://member1.test")); net::SchemefulSite example(GURL("https://example.test")); net::SchemefulSite wss_member(GURL("wss://member1.test")); @@ -134,39 +134,42 @@ // Works if the site is provided with WSS scheme instead of HTTPS. EXPECT_THAT(sets.ComputeContext(wss_member, &member, {member, example}), - net::SamePartyContext(SamePartyContextType::kCrossParty, - SamePartyContextType::kCrossParty, - SamePartyContextType::kSameParty)); + net::SamePartyContext(Type::kCrossParty, Type::kCrossParty, + Type::kSameParty)); EXPECT_THAT(sets.ComputeContext(example, &member, {member}), - net::SamePartyContext(SamePartyContextType::kCrossParty)); + net::SamePartyContext(Type::kCrossParty)); EXPECT_THAT(sets.ComputeContext(member, &example, {member}), - net::SamePartyContext(SamePartyContextType::kCrossParty)); + net::SamePartyContext(Type::kCrossParty)); // Top&resource differs from Ancestors. EXPECT_THAT(sets.ComputeContext(member, &member, {example}), - net::SamePartyContext(SamePartyContextType::kCrossParty, - SamePartyContextType::kCrossParty, - SamePartyContextType::kSameParty)); + net::SamePartyContext(Type::kCrossParty, Type::kCrossParty, + Type::kSameParty)); // Metrics values infer singleton sets when appropriate. EXPECT_THAT(sets.ComputeContext(member, &member, {member}), - net::SamePartyContext(SamePartyContextType::kCrossParty, - SamePartyContextType::kSameParty, - SamePartyContextType::kSameParty)); + net::SamePartyContext(Type::kCrossParty, Type::kSameParty, + Type::kSameParty)); EXPECT_THAT(sets.ComputeContext(member, &example, {member}), - net::SamePartyContext(SamePartyContextType::kCrossParty)); + net::SamePartyContext(Type::kCrossParty)); EXPECT_THAT(sets.ComputeContext(example, &member, {member}), - net::SamePartyContext(SamePartyContextType::kCrossParty)); + net::SamePartyContext(Type::kCrossParty)); EXPECT_THAT(sets.ComputeContext(member, &member, {example}), - net::SamePartyContext(SamePartyContextType::kCrossParty, - SamePartyContextType::kCrossParty, - SamePartyContextType::kSameParty)); + net::SamePartyContext(Type::kCrossParty, Type::kCrossParty, + Type::kSameParty)); EXPECT_THAT(sets.ComputeContext(member, &member, {member, example}), - net::SamePartyContext(SamePartyContextType::kCrossParty, - SamePartyContextType::kCrossParty, - SamePartyContextType::kSameParty)); + net::SamePartyContext(Type::kCrossParty, Type::kCrossParty, + Type::kSameParty)); +} + +TEST_F(FirstPartySetsDisabledTest, FindOwner) { + sets().SetManuallySpecifiedSet("https://example.test,https://member.test"); + EXPECT_FALSE( + sets().FindOwner(net::SchemefulSite(GURL("https://example.test")))); + EXPECT_FALSE( + sets().FindOwner(net::SchemefulSite(GURL("https://member.test")))); } class FirstPartySetsEnabledTest : public FirstPartySetsTest { @@ -980,32 +983,37 @@ } }; -TEST_F(PopulatedFirstPartySetsTest, IsContextSamePartyWithSite_EmptyContext) { +TEST_F(PopulatedFirstPartySetsTest, ComputeContext_EmptyContext) { net::SchemefulSite example_site(GURL("https://example.test")); net::SchemefulSite nonmember(GURL("https://nonmember.test")); for (const net::SchemefulSite* top_frame : std::initializer_list<net::SchemefulSite*>{&example_site, nullptr}) { - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://nonmember.test")), top_frame, {}, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://nonmember.test")), + top_frame, {}) + .context_type(), + Type::kCrossParty); - EXPECT_TRUE(sets().IsContextSamePartyWithSite( - example_site, top_frame, {}, false /* infer_singleton_sets */)); + EXPECT_EQ(sets().ComputeContext(example_site, top_frame, {}).context_type(), + Type::kSameParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("http://example.test")), top_frame, {}, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("http://example.test")), + top_frame, {}) + .context_type(), + Type::kCrossParty); } - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - example_site, &nonmember, {}, false /* infer_singleton_sets */)); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - nonmember, &example_site, {}, false /* infer_singleton_sets */)); + EXPECT_EQ(sets().ComputeContext(example_site, &nonmember, {}).context_type(), + Type::kCrossParty); + EXPECT_EQ(sets().ComputeContext(nonmember, &example_site, {}).context_type(), + Type::kCrossParty); } -TEST_F(PopulatedFirstPartySetsTest, - IsContextSamePartyWithSite_ContextIsNonmember) { +TEST_F(PopulatedFirstPartySetsTest, ComputeContext_ContextIsNonmember) { std::set<net::SchemefulSite> context({ net::SchemefulSite(GURL("https://nonmember.test")), }); @@ -1014,33 +1022,50 @@ for (const net::SchemefulSite* top_frame : std::initializer_list<net::SchemefulSite*>{&example_site, nullptr}) { - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://example.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://example.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("http://example.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("http://example.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://member1.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://member1.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://foo.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ(sets() + .ComputeContext(net::SchemefulSite(GURL("https://foo.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://member2.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://member2.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://nonmember.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://nonmember.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); } } -TEST_F(PopulatedFirstPartySetsTest, IsContextSamePartyWithSite_ContextIsOwner) { +TEST_F(PopulatedFirstPartySetsTest, ComputeContext_ContextIsOwner) { std::set<net::SchemefulSite> context( {net::SchemefulSite(GURL("https://example.test"))}); @@ -1048,34 +1073,50 @@ for (const net::SchemefulSite* top_frame : std::initializer_list<net::SchemefulSite*>{&example_site, nullptr}) { - EXPECT_TRUE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://example.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://example.test")), + top_frame, context) + .context_type(), + Type::kSameParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("http://example.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("http://example.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_TRUE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://member1.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://member1.test")), + top_frame, context) + .context_type(), + Type::kSameParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://foo.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ(sets() + .ComputeContext(net::SchemefulSite(GURL("https://foo.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://member2.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://member2.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://nonmember.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://nonmember.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); } } -TEST_F(PopulatedFirstPartySetsTest, - IsContextSamePartyWithSite_ContextIsMember) { +TEST_F(PopulatedFirstPartySetsTest, ComputeContext_ContextIsMember) { std::set<net::SchemefulSite> context( {net::SchemefulSite(GURL("https://member1.test"))}); @@ -1083,38 +1124,57 @@ for (const net::SchemefulSite* top_frame : std::initializer_list<net::SchemefulSite*>{&example_site, nullptr}) { - EXPECT_TRUE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://example.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://example.test")), + top_frame, context) + .context_type(), + Type::kSameParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("http://example.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("http://example.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_TRUE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://example.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://example.test")), + top_frame, context) + .context_type(), + Type::kSameParty); - EXPECT_TRUE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://member1.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://member1.test")), + top_frame, context) + .context_type(), + Type::kSameParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://foo.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ(sets() + .ComputeContext(net::SchemefulSite(GURL("https://foo.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://member2.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://member2.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://nonmember.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://nonmember.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); } } -TEST_F(PopulatedFirstPartySetsTest, - IsContextSamePartyWithSite_ContextIsOwnerAndMember) { +TEST_F(PopulatedFirstPartySetsTest, ComputeContext_ContextIsOwnerAndMember) { std::set<net::SchemefulSite> context({ net::SchemefulSite(GURL("https://example.test")), net::SchemefulSite(GURL("https://member1.test")), @@ -1124,38 +1184,57 @@ for (const net::SchemefulSite* top_frame : std::initializer_list<net::SchemefulSite*>{&example_site, nullptr}) { - EXPECT_TRUE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://example.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://example.test")), + top_frame, context) + .context_type(), + Type::kSameParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("http://example.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("http://example.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_TRUE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://member1.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://member1.test")), + top_frame, context) + .context_type(), + Type::kSameParty); - EXPECT_TRUE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://member3.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://member3.test")), + top_frame, context) + .context_type(), + Type::kSameParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://foo.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ(sets() + .ComputeContext(net::SchemefulSite(GURL("https://foo.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://member2.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://member2.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://nonmember.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://nonmember.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); } } -TEST_F(PopulatedFirstPartySetsTest, - IsContextSamePartyWithSite_ContextMixesParties) { +TEST_F(PopulatedFirstPartySetsTest, ComputeContext_ContextMixesParties) { std::set<net::SchemefulSite> context({ net::SchemefulSite(GURL("https://example.test")), net::SchemefulSite(GURL("https://member1.test")), @@ -1166,34 +1245,51 @@ for (const net::SchemefulSite* top_frame : std::initializer_list<net::SchemefulSite*>{&example_site, nullptr}) { - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://example.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://example.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("http://example.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("http://example.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://member1.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://member1.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://foo.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ(sets() + .ComputeContext(net::SchemefulSite(GURL("https://foo.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://member2.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://member2.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://nonmember.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://nonmember.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); } } TEST_F(PopulatedFirstPartySetsTest, - IsContextSamePartyWithSite_ContextMixesMembersAndNonmembers) { + ComputeContext_ContextMixesMembersAndNonmembers) { std::set<net::SchemefulSite> context({ net::SchemefulSite(GURL("https://example.test")), net::SchemefulSite(GURL("https://member1.test")), @@ -1204,34 +1300,50 @@ for (const net::SchemefulSite* top_frame : std::initializer_list<net::SchemefulSite*>{&example_site, nullptr}) { - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://example.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://example.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("http://example.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("http://example.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://member1.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://member1.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://foo.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ(sets() + .ComputeContext(net::SchemefulSite(GURL("https://foo.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://member2.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://member2.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://nonmember.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://nonmember.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); } } -TEST_F(PopulatedFirstPartySetsTest, - IsContextSamePartyWithSite_ContextMixesSchemes) { +TEST_F(PopulatedFirstPartySetsTest, ComputeContext_ContextMixesSchemes) { std::set<net::SchemefulSite> context({ net::SchemefulSite(GURL("https://example.test")), net::SchemefulSite(GURL("https://member1.test")), @@ -1242,77 +1354,50 @@ for (const net::SchemefulSite* top_frame : std::initializer_list<net::SchemefulSite*>{&example_site, nullptr}) { - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://example.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://example.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("http://example.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("http://example.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://member1.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://member1.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://foo.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ(sets() + .ComputeContext(net::SchemefulSite(GURL("https://foo.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://member2.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://member2.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://nonmember.test")), top_frame, context, - false /* infer_singleton_sets */)); + EXPECT_EQ( + sets() + .ComputeContext(net::SchemefulSite(GURL("https://nonmember.test")), + top_frame, context) + .context_type(), + Type::kCrossParty); } } -TEST_F(PopulatedFirstPartySetsTest, - IsContextSamePartyWithSite_InfersSingletonSets) { - std::set<net::SchemefulSite> context({ - net::SchemefulSite(GURL("https://nonmember.test")), - }); - net::SchemefulSite nonmember(GURL("https://nonmember.test")); - net::SchemefulSite nonmember2(GURL("https://nonmember2.test")); - net::SchemefulSite member(GURL("https://member1.test")); - - EXPECT_TRUE(sets().IsContextSamePartyWithSite(nonmember, nullptr, {}, true)); - - EXPECT_TRUE( - sets().IsContextSamePartyWithSite(nonmember, &nonmember, {}, true)); - - EXPECT_TRUE( - sets().IsContextSamePartyWithSite(nonmember, nullptr, context, true)); - - EXPECT_TRUE( - sets().IsContextSamePartyWithSite(nonmember, &nonmember, context, true)); - - // Context mismatches. - EXPECT_FALSE(sets().IsContextSamePartyWithSite(nonmember, &nonmember, - {nonmember2}, true)); - - // Context mismatches (but is a member of some set). - EXPECT_FALSE( - sets().IsContextSamePartyWithSite(nonmember, &nonmember, {member}, true)); - - // Top frame mismatches. - EXPECT_FALSE( - sets().IsContextSamePartyWithSite(nonmember, &member, {nonmember}, true)); - - // Request URL mismatches. - EXPECT_FALSE(sets().IsContextSamePartyWithSite( - net::SchemefulSite(GURL("https://nonmember1.test")), &nonmember2, - {nonmember2}, true)); - - // Request URL mismatches (but is a member of some set). - EXPECT_FALSE( - sets().IsContextSamePartyWithSite(member, &nonmember, {nonmember}, true)); -} - TEST_F(PopulatedFirstPartySetsTest, ComputeContext) { - using SamePartyContextType = net::SamePartyContext::Type; - net::SchemefulSite nonmember(GURL("https://nonmember.test")); net::SchemefulSite nonmember1(GURL("https://nonmember1.test")); net::SchemefulSite member(GURL("https://member1.test")); @@ -1322,56 +1407,51 @@ // Works as usual for sites that are in First-Party sets. EXPECT_THAT(sets().ComputeContext(member, &member, {member}), - net::SamePartyContext(SamePartyContextType::kSameParty)); + net::SamePartyContext(Type::kSameParty)); EXPECT_THAT(sets().ComputeContext(owner, &member, {member}), - net::SamePartyContext(SamePartyContextType::kSameParty)); + net::SamePartyContext(Type::kSameParty)); EXPECT_THAT(sets().ComputeContext(member, &owner, {member}), - net::SamePartyContext(SamePartyContextType::kSameParty)); + net::SamePartyContext(Type::kSameParty)); EXPECT_THAT(sets().ComputeContext(member, &member, {owner}), - net::SamePartyContext(SamePartyContextType::kSameParty)); + net::SamePartyContext(Type::kSameParty)); EXPECT_THAT(sets().ComputeContext(member, &member, {member, owner}), - net::SamePartyContext(SamePartyContextType::kSameParty)); + net::SamePartyContext(Type::kSameParty)); // Works if the site is provided with WSS scheme instead of HTTPS. EXPECT_THAT(sets().ComputeContext(wss_member, &member, {member, owner}), - net::SamePartyContext(SamePartyContextType::kSameParty)); + net::SamePartyContext(Type::kSameParty)); EXPECT_THAT(sets().ComputeContext(nonmember, &member, {member}), - net::SamePartyContext(SamePartyContextType::kCrossParty)); + net::SamePartyContext(Type::kCrossParty)); EXPECT_THAT(sets().ComputeContext(member, &nonmember, {member}), - net::SamePartyContext(SamePartyContextType::kCrossParty)); + net::SamePartyContext(Type::kCrossParty)); EXPECT_THAT( sets().ComputeContext(wss_nonmember, &wss_member, {member, owner}), - net::SamePartyContext(SamePartyContextType::kCrossParty)); + net::SamePartyContext(Type::kCrossParty)); // Top&resource differs from Ancestors. EXPECT_THAT(sets().ComputeContext(member, &member, {nonmember}), - net::SamePartyContext(SamePartyContextType::kCrossParty, - SamePartyContextType::kCrossParty, - SamePartyContextType::kSameParty)); + net::SamePartyContext(Type::kCrossParty, Type::kCrossParty, + Type::kSameParty)); // Metrics values infer singleton sets when appropriate. EXPECT_THAT(sets().ComputeContext(nonmember, &nonmember, {nonmember}), - net::SamePartyContext(SamePartyContextType::kCrossParty, - SamePartyContextType::kSameParty, - SamePartyContextType::kSameParty)); + net::SamePartyContext(Type::kCrossParty, Type::kSameParty, + Type::kSameParty)); EXPECT_THAT(sets().ComputeContext(nonmember, &nonmember1, {nonmember}), - net::SamePartyContext(SamePartyContextType::kCrossParty)); + net::SamePartyContext(Type::kCrossParty)); EXPECT_THAT(sets().ComputeContext(nonmember1, &nonmember, {nonmember}), - net::SamePartyContext(SamePartyContextType::kCrossParty)); + net::SamePartyContext(Type::kCrossParty)); EXPECT_THAT(sets().ComputeContext(nonmember, &nonmember, {nonmember1}), - net::SamePartyContext(SamePartyContextType::kCrossParty, - SamePartyContextType::kCrossParty, - SamePartyContextType::kSameParty)); + net::SamePartyContext(Type::kCrossParty, Type::kCrossParty, + Type::kSameParty)); EXPECT_THAT(sets().ComputeContext(member, &member, {member, nonmember}), - net::SamePartyContext(SamePartyContextType::kCrossParty, - SamePartyContextType::kCrossParty, - SamePartyContextType::kSameParty)); + net::SamePartyContext(Type::kCrossParty, Type::kCrossParty, + Type::kSameParty)); EXPECT_THAT(sets().ComputeContext(nonmember, &nonmember, {member, nonmember}), - net::SamePartyContext(SamePartyContextType::kCrossParty, - SamePartyContextType::kCrossParty, - SamePartyContextType::kSameParty)); + net::SamePartyContext(Type::kCrossParty, Type::kCrossParty, + Type::kSameParty)); } TEST_F(PopulatedFirstPartySetsTest, IsInNontrivialFirstPartySet) { @@ -1394,6 +1474,35 @@ net::SchemefulSite(GURL("https://nonmember.test")))); } +TEST_F(PopulatedFirstPartySetsTest, FindOwner) { + const absl::optional<net::SchemefulSite> kSetOwner1 = + absl::make_optional(net::SchemefulSite(GURL("https://example.test"))); + const absl::optional<net::SchemefulSite> kSetOwner2 = + absl::make_optional(net::SchemefulSite(GURL("https://foo.test"))); + + struct TestCase { + const std::string url; + const absl::optional<net::SchemefulSite> expected; + } test_cases[] = { + {"https://example.test", kSetOwner1}, + // Insecure URL + {"http://example.test", absl::nullopt}, + // Test member + {"https://member1.test", kSetOwner1}, + {"http://member1.test", absl::nullopt}, + // Test another disjoint set + {"https://foo.test", kSetOwner2}, + {"https://member2.test", kSetOwner2}, + // Test a site not in a set + {"https://nonmember.test", absl::nullopt}, + }; + + for (const auto& test_case : test_cases) { + EXPECT_EQ(test_case.expected, + sets().FindOwner(net::SchemefulSite(GURL(test_case.url)))); + } +} + TEST_F(PopulatedFirstPartySetsTest, Sets_NonEmpty) { EXPECT_THAT( sets().Sets(),
diff --git a/services/network/network_service.h b/services/network/network_service.h index fca0ff5..89f5cf04 100644 --- a/services/network/network_service.h +++ b/services/network/network_service.h
@@ -321,6 +321,12 @@ std::unique_ptr<service_manager::BinderRegistry> registry_; + // Globally-scoped state for First-Party Sets. Must be above the `receiver_` + // so it's destroyed after, to make sure even when the reply callback owned by + // the `first_party_sets_` is never run when destroyed, the receiver which the + // reply callback associated with is already disconnected. + std::unique_ptr<FirstPartySets> first_party_sets_; + mojo::Receiver<mojom::NetworkService> receiver_{this}; mojo::Remote<mojom::URLLoaderNetworkServiceObserver> @@ -341,9 +347,6 @@ mojom::HttpAuthDynamicParamsPtr http_auth_dynamic_network_service_params_; mojom::HttpAuthStaticParamsPtr http_auth_static_network_service_params_; - // Globally-scoped state for First-Party Sets. - std::unique_ptr<FirstPartySets> first_party_sets_; - // NetworkContexts created by CreateNetworkContext(). They call into the // NetworkService when their connection is closed so that it can delete // them. It will also delete them when the NetworkService itself is torn
diff --git a/services/network/restricted_cookie_manager.cc b/services/network/restricted_cookie_manager.cc index 33104bcc..ddd5c039 100644 --- a/services/network/restricted_cookie_manager.cc +++ b/services/network/restricted_cookie_manager.cc
@@ -349,7 +349,8 @@ } void RestrictedCookieManager::ComputeCookiePartitionKey() { - cookie_partition_key_ = net::CookiePartitionKey::FromNetworkIsolationKey( + cookie_partition_key_ = net::CookieAccessDelegate::CreateCookiePartitionKey( + cookie_store_->cookie_access_delegate(), isolation_info_.network_isolation_key()); cookie_partition_keychain_ = net::CookiePartitionKeychain::FromOptional(cookie_partition_key_);
diff --git a/services/network/restricted_cookie_manager.h b/services/network/restricted_cookie_manager.h index 613397a..ea892b3 100644 --- a/services/network/restricted_cookie_manager.h +++ b/services/network/restricted_cookie_manager.h
@@ -179,11 +179,6 @@ return isolation_info_.top_frame_origin().value(); } - const absl::optional<net::CookiePartitionKey> CookiePartitionKey() const { - return net::CookiePartitionKey::FromNetworkIsolationKey( - isolation_info_.network_isolation_key()); - } - CookieAccesses* GetCookieAccessesForURLAndSite( const GURL& url, const net::SiteForCookies& site_for_cookies);
diff --git a/services/network/restricted_cookie_manager_unittest.cc b/services/network/restricted_cookie_manager_unittest.cc index cff8e32..3ec5579 100644 --- a/services/network/restricted_cookie_manager_unittest.cc +++ b/services/network/restricted_cookie_manager_unittest.cc
@@ -1543,13 +1543,13 @@ ::testing::Values(mojom::RestrictedCookieManagerRole::SCRIPT, mojom::RestrictedCookieManagerRole::NETWORK)); -class PartitionedCookiesEnabledRestrictedCookieManagerTest +class PartitionedCookiesRestrictedCookieManagerTest : public RestrictedCookieManagerTest { public: - PartitionedCookiesEnabledRestrictedCookieManagerTest() { + PartitionedCookiesRestrictedCookieManagerTest() { feature_list_.InitAndEnableFeature(net::features::kPartitionedCookies); } - ~PartitionedCookiesEnabledRestrictedCookieManagerTest() override = default; + ~PartitionedCookiesRestrictedCookieManagerTest() override = default; private: base::test::ScopedFeatureList feature_list_; @@ -1674,8 +1674,7 @@ } // Test Partitioned cookie behavior when feature is enabled. -TEST_P(PartitionedCookiesEnabledRestrictedCookieManagerTest, - PartitionedCookies) { +TEST_P(PartitionedCookiesRestrictedCookieManagerTest, PartitionedCookies) { const GURL kCookieURL("https://example.com"); const GURL kTopFrameURL("https://sub.foo.com"); const net::SiteForCookies kSiteForCookies = @@ -1785,8 +1784,7 @@ } } -TEST_P(PartitionedCookiesEnabledRestrictedCookieManagerTest, - PartitionKeyFromScript) { +TEST_P(PartitionedCookiesRestrictedCookieManagerTest, PartitionKeyFromScript) { const GURL kCookieURL("https://example.com"); const GURL kTopFrameURL("https://foo.com"); const net::SiteForCookies kSiteForCookies = @@ -1812,15 +1810,194 @@ kCookieURL, kSiteForCookies, kTopFrameOrigin, std::move(options)); ASSERT_EQ(1u, cookies.size()); EXPECT_TRUE(cookies[0].IsPartitioned()); - EXPECT_EQ(cookies[0].PartitionKey(), - net::CookiePartitionKey::FromNetworkIsolationKey( - kIsolationInfo.network_isolation_key())); + EXPECT_EQ(cookies[0].PartitionKey().value(), + net::CookiePartitionKey::FromURLForTesting(kTopFrameURL)); EXPECT_EQ("__Host-foo", cookies[0].Name()); } +// Tests the interaction between First-Party Sets and partitioned cookies in +// RestrictedCookieManager. When FPS are enabled, RestrictedCookieManager +// should use the same cookie partition key for all sites in +TEST_P(PartitionedCookiesRestrictedCookieManagerTest, + PartitionedCookiesAndFirstPartySets) { + const GURL kOwnerURL("https://owner.com"); + const net::SchemefulSite kOwnerSite(kOwnerURL); + const url::Origin kOwnerOrigin = url::Origin::Create(kOwnerURL); + const net::IsolationInfo kOwnerIsolationInfo = + net::IsolationInfo::CreateForInternalRequest(kOwnerOrigin); + const net::SiteForCookies kOwnerSiteForCookies = + net::SiteForCookies::FromUrl(kOwnerURL); + + const GURL kMemberURL("https://member.com"); + const net::SchemefulSite kMemberSite(kMemberURL); + const url::Origin kMemberOrigin = url::Origin::Create(kMemberURL); + const net::IsolationInfo kMemberIsolationInfo = + net::IsolationInfo::CreateForInternalRequest(kMemberOrigin); + const net::SiteForCookies kMemberSiteForCookies = + net::SiteForCookies::FromUrl(kMemberURL); + + const GURL kNonMemberURL("https://nonmember.com"); + const url::Origin kNonMemberOrigin = url::Origin::Create(kNonMemberURL); + const net::IsolationInfo kNonMemberIsolationInfo = + net::IsolationInfo::CreateForInternalRequest(kNonMemberOrigin); + const net::SiteForCookies kNonMemberSiteForCookies = + net::SiteForCookies::FromUrl(kNonMemberURL); + + const GURL kCookieURL("https://example.com"); + + base::flat_map<net::SchemefulSite, std::set<net::SchemefulSite>> + first_party_sets; + first_party_sets.insert(std::make_pair( + kOwnerSite, std::set<net::SchemefulSite>({kOwnerSite, kMemberSite}))); + auto cookie_access_delegate = + std::make_unique<net::TestCookieAccessDelegate>(); + cookie_access_delegate->SetFirstPartySets(first_party_sets); + cookie_monster_.SetCookieAccessDelegate(std::move(cookie_access_delegate)); + + // Set https://example.com cookie when the top-frame site is the owner + // of the set. + service_->OverrideIsolationInfoForTesting(kOwnerIsolationInfo); + sync_service_->SetCookieFromString( + kCookieURL, kOwnerSiteForCookies, kOwnerOrigin, + "__Host-foo=0; Secure; SameSite=None; Path=/; Partitioned"); + + { + // Check that its partition key is the set's owner site. + auto options = mojom::CookieManagerGetOptions::New(); + options->name = ""; + options->match_type = mojom::CookieMatchType::STARTS_WITH; + + net::CookieList cookies = sync_service_->GetAllForUrl( + kCookieURL, kOwnerSiteForCookies, kOwnerOrigin, std::move(options)); + ASSERT_EQ(1u, cookies.size()); + EXPECT_TRUE(cookies[0].IsPartitioned()); + EXPECT_EQ(net::CookiePartitionKey::FromURLForTesting(kOwnerURL), + cookies[0].PartitionKey()); + EXPECT_EQ("__Host-foo", cookies[0].Name()); + + auto listener = CreateCookieChangeListener(kCookieURL, kOwnerSiteForCookies, + kOwnerOrigin); + + // Update partitioned cookie Max-Age: None -> 7200. + sync_service_->SetCookieFromString( + kCookieURL, kOwnerSiteForCookies, kOwnerOrigin, + "__Host-foo=0; Secure; SameSite=None; Path=/; Partitioned; " + "Max-Age: 7200"); + + listener->WaitForChange(); + ASSERT_THAT(listener->observed_changes(), testing::SizeIs(1)); + } + + { + // Check that cookie is available to https://example.com when the top-frame + // site is a site that is a member of the set but not the owner. + service_->OverrideIsolationInfoForTesting(kMemberIsolationInfo); + auto options = mojom::CookieManagerGetOptions::New(); + options->name = ""; + options->match_type = mojom::CookieMatchType::STARTS_WITH; + + net::CookieList cookies = sync_service_->GetAllForUrl( + kCookieURL, kMemberSiteForCookies, kMemberOrigin, std::move(options)); + ASSERT_EQ(1u, cookies.size()); + EXPECT_TRUE(cookies[0].IsPartitioned()); + EXPECT_EQ(net::CookiePartitionKey::FromURLForTesting(kOwnerURL), + cookies[0].PartitionKey()); + EXPECT_EQ("__Host-foo", cookies[0].Name()); + + auto listener = CreateCookieChangeListener( + kCookieURL, kMemberSiteForCookies, kMemberOrigin); + + // Update partitioned cookie Max-Age: None -> 7200. + service_->OverrideIsolationInfoForTesting(kOwnerIsolationInfo); + sync_service_->SetCookieFromString( + kCookieURL, kOwnerSiteForCookies, kOwnerOrigin, + "__Host-foo=0; Secure; SameSite=None; Path=/; Partitioned; " + "Max-Age: 3600"); + + listener->WaitForChange(); + ASSERT_THAT(listener->observed_changes(), testing::SizeIs(1)); + } + + // Set https://example.com cookie when the top-frame site is a member of + // the set. + service_->OverrideIsolationInfoForTesting(kMemberIsolationInfo); + sync_service_->SetCookieFromString( + kCookieURL, kMemberSiteForCookies, kMemberOrigin, + "__Host-bar=1; Secure; SameSite=None; Path=/; Partitioned"); + + { + // Check that the new cookie's partition key is the set's owner site. + auto options = mojom::CookieManagerGetOptions::New(); + options->name = ""; + options->match_type = mojom::CookieMatchType::STARTS_WITH; + + net::CookieList cookies = sync_service_->GetAllForUrl( + kCookieURL, kMemberSiteForCookies, kMemberOrigin, std::move(options)); + ASSERT_EQ(2u, cookies.size()); + + EXPECT_EQ("__Host-foo", cookies[0].Name()); + EXPECT_EQ("__Host-bar", cookies[1].Name()); + for (const auto& cookie : cookies) { + EXPECT_TRUE(cookie.IsPartitioned()); + EXPECT_EQ(net::CookiePartitionKey::FromURLForTesting(kOwnerURL), + cookie.PartitionKey()); + } + } + + { + // Check that the owner site can also access both cookies. + service_->OverrideIsolationInfoForTesting(kOwnerIsolationInfo); + auto options = mojom::CookieManagerGetOptions::New(); + options->name = ""; + options->match_type = mojom::CookieMatchType::STARTS_WITH; + + net::CookieList cookies = sync_service_->GetAllForUrl( + kCookieURL, kOwnerSiteForCookies, kOwnerOrigin, std::move(options)); + ASSERT_EQ(2u, cookies.size()); + EXPECT_EQ("__Host-foo", cookies[0].Name()); + EXPECT_EQ("__Host-bar", cookies[1].Name()); + } + + { + // Check that the cookies are not available to https://example.com when the + // top-frame site is not a member of the set. + service_->OverrideIsolationInfoForTesting(kNonMemberIsolationInfo); + auto options = mojom::CookieManagerGetOptions::New(); + options->name = ""; + options->match_type = mojom::CookieMatchType::STARTS_WITH; + + net::CookieList cookies = + sync_service_->GetAllForUrl(kCookieURL, kNonMemberSiteForCookies, + kNonMemberOrigin, std::move(options)); + ASSERT_EQ(0u, cookies.size()); + + auto listener = CreateCookieChangeListener( + kCookieURL, kNonMemberSiteForCookies, kNonMemberOrigin); + + // Set a new listener with the original IsolationInfo, we wait for this + // listener to receive an event to verify that the second listener was + // either called or skipped. + service_->OverrideIsolationInfoForTesting(kOwnerIsolationInfo); + auto second_listener = CreateCookieChangeListener( + kCookieURL, kOwnerSiteForCookies, kOwnerOrigin); + + // Update partitioned cookie __Host-bar's Max-Age: None -> 3600. + service_->OverrideIsolationInfoForTesting(kOwnerIsolationInfo); + sync_service_->SetCookieFromString( + kCookieURL, kOwnerSiteForCookies, kOwnerOrigin, + "__Host-bar=1; Secure; SameSite=None; Path=/; Partitioned; " + "Max-Age: 3600"); + + // The listener set on the non-member top-frame site should not be able to + // observe the change. + second_listener->WaitForChange(); + ASSERT_THAT(listener->observed_changes(), testing::SizeIs(0)); + } +} + INSTANTIATE_TEST_SUITE_P( PartitionedCookies, - PartitionedCookiesEnabledRestrictedCookieManagerTest, + PartitionedCookiesRestrictedCookieManagerTest, ::testing::Values(mojom::RestrictedCookieManagerRole::SCRIPT, mojom::RestrictedCookieManagerRole::NETWORK));
diff --git a/services/proxy_resolver/proxy_resolver_v8_tracing.cc b/services/proxy_resolver/proxy_resolver_v8_tracing.cc index 5ef2d2b..c118c9f 100644 --- a/services/proxy_resolver/proxy_resolver_v8_tracing.cc +++ b/services/proxy_resolver/proxy_resolver_v8_tracing.cc
@@ -861,7 +861,7 @@ std::string Job::MakeDnsCacheKey(const std::string& host, net::ProxyResolveDnsOperation op) { - return base::StringPrintf("%d:%s", op, host.c_str()); + return base::StringPrintf("%d:%s", static_cast<int>(op), host.c_str()); } void Job::HandleAlertOrError(bool is_alert,
diff --git a/services/shape_detection/barcode_detection_impl_barhopper_unittest.cc b/services/shape_detection/barcode_detection_impl_barhopper_unittest.cc index d934ca7..644cd688 100644 --- a/services/shape_detection/barcode_detection_impl_barhopper_unittest.cc +++ b/services/shape_detection/barcode_detection_impl_barhopper_unittest.cc
@@ -15,25 +15,27 @@ namespace shape_detection { -struct TestParams { - std::string filename; - std::string expected_value; +constexpr struct TestParams { + base::FilePath::StringPieceType filename; + base::StringPiece expected_value; float x; float y; float width; float height; -} kTestParams[] = {{"codabar.png", "A6.2831853B", 24, 24, 448, 95}, - {"code_39.png", "CHROMIUM", 20, 20, 318, 75}, - {"code_93.png", "CHROMIUM", 20, 20, 216, 75}, - {"code_128.png", "Chromium", 20, 20, 246, 75}, - {"data_matrix.png", "Chromium", 11, 11, 53, 53}, - {"ean_8.png", "62831857", 14, 10, 134, 75}, - {"ean_13.png", "6283185307179", 27, 10, 190, 75}, - {"itf.png", "62831853071795", 10, 10, 135, 39}, - {"pdf417.png", "Chromium", 20, 20, 240, 44}, - {"qr_code.png", "https://chromium.org", 40, 40, 250, 250}, - {"upc_a.png", "628318530714", 23, 10, 190, 75}, - {"upc_e.png", "06283186", 23, 10, 102, 75}}; +} kTestParams[] = { + {FILE_PATH_LITERAL("codabar.png"), "A6.2831853B", 24, 24, 448, 95}, + {FILE_PATH_LITERAL("code_39.png"), "CHROMIUM", 20, 20, 318, 75}, + {FILE_PATH_LITERAL("code_93.png"), "CHROMIUM", 20, 20, 216, 75}, + {FILE_PATH_LITERAL("code_128.png"), "Chromium", 20, 20, 246, 75}, + {FILE_PATH_LITERAL("data_matrix.png"), "Chromium", 11, 11, 53, 53}, + {FILE_PATH_LITERAL("ean_8.png"), "62831857", 14, 10, 134, 75}, + {FILE_PATH_LITERAL("ean_13.png"), "6283185307179", 27, 10, 190, 75}, + {FILE_PATH_LITERAL("itf.png"), "62831853071795", 10, 10, 135, 39}, + {FILE_PATH_LITERAL("pdf417.png"), "Chromium", 20, 20, 240, 44}, + {FILE_PATH_LITERAL("qr_code.png"), "https://chromium.org", 40, 40, 250, + 250}, + {FILE_PATH_LITERAL("upc_a.png"), "628318530714", 23, 10, 190, 75}, + {FILE_PATH_LITERAL("upc_e.png"), "06283186", 23, 10, 102, 75}}; class BarcodeDetectionImplBarhopperTest : public testing::TestWithParam<struct TestParams> { @@ -58,14 +60,15 @@ return barcode_service; } - std::unique_ptr<SkBitmap> LoadTestImage(std::string filename) { + std::unique_ptr<SkBitmap> LoadTestImage( + base::FilePath::StringPieceType filename) { // Load image data from test directory. base::FilePath image_path; EXPECT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &image_path)); image_path = image_path.Append(FILE_PATH_LITERAL("services")) .Append(FILE_PATH_LITERAL("test")) .Append(FILE_PATH_LITERAL("data")) - .Append(FILE_PATH_LITERAL(filename)); + .Append(filename); EXPECT_TRUE(base::PathExists(image_path)); std::string image_data; EXPECT_TRUE(base::ReadFileToString(image_path, &image_data)); @@ -114,4 +117,4 @@ BarcodeDetectionImplBarhopperTest, testing::ValuesIn(kTestParams)); -} // namespace shape_detection \ No newline at end of file +} // namespace shape_detection
diff --git a/skia/BUILD.gn b/skia/BUILD.gn index dc47e2d0..e7ad2b92 100644 --- a/skia/BUILD.gn +++ b/skia/BUILD.gn
@@ -131,7 +131,7 @@ # Internal-facing config for Skia library code. config("skia_library_config") { - defines = [ "SK_IGNORE_GLYPH_HAS_PATH_FIX" ] + defines = [] # Skia uses C++17 language features in its internal code. Previously Skia was built with # "-std=c++17". See http://crbug.com/1257145 for why this was a bad idea.
diff --git a/testing/buildbot/chromium.mac.json b/testing/buildbot/chromium.mac.json index db608bb..711202a 100644 --- a/testing/buildbot/chromium.mac.json +++ b/testing/buildbot/chromium.mac.json
@@ -10299,7 +10299,7 @@ } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", - "shards": 12 + "shards": 25 }, "test_id_prefix": "ninja://:blink_web_tests/" },
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl index 58a1bdf..f11e76c7 100644 --- a/testing/buildbot/test_suite_exceptions.pyl +++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -265,6 +265,16 @@ 'shards': 25, }, }, + 'Mac11 Tests': { + 'swarming': { + 'dimension_sets': [ + { + 'gpu': None, + }, + ], + 'shards': 25, + }, + }, 'Mac11 Tests (dbg)': { 'experiment_percentage': 100, 'args': [
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index bda80d4..285d131 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json
@@ -8831,6 +8831,8 @@ { "platforms": [ "android", + "android_weblayer", + "android_webview", "chromeos", "chromeos_lacros", "ios", @@ -9153,35 +9155,5 @@ } ] } - ], - "WinDelaySpellcheckServiceInit": [ - { - "platforms": [ - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "WinDelaySpellcheckServiceInit" - ] - } - ] - } - ], - "XsurfaceMetricsReporting": [ - { - "platforms": [ - "android" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "XsurfaceMetricsReporting" - ] - } - ] - } ] }
diff --git a/third_party/blink/renderer/core/animation/css/css_animations.cc b/third_party/blink/renderer/core/animation/css/css_animations.cc index 231977e..55895d1 100644 --- a/third_party/blink/renderer/core/animation/css/css_animations.cc +++ b/third_party/blink/renderer/core/animation/css/css_animations.cc
@@ -202,7 +202,6 @@ StringKeyframeEffectModel* CreateKeyframeEffectModel( StyleResolver* resolver, Element& element, - const Element& animating_element, const ComputedStyle* style, const ComputedStyle* parent_style, const AtomicString& name, @@ -228,7 +227,7 @@ // existing animation matching name is canceled. const StyleRuleKeyframes* keyframes_rule = - resolver->FindKeyframesRule(&element, &animating_element, name); + resolver->FindKeyframesRule(&element, name); DCHECK(keyframes_rule); // 3. Let keyframes be an empty sequence of keyframe objects. @@ -652,7 +651,7 @@ timing.timing_function = Timing().timing_function; StyleRuleKeyframes* keyframes_rule = - resolver->FindKeyframesRule(&element, &animating_element, name); + resolver->FindKeyframesRule(&element, name); if (!keyframes_rule) continue; // Cancel the animation if there's no style rule for it. @@ -759,9 +758,9 @@ update.UpdateAnimation( existing_animation_index, animation, *MakeGarbageCollected<InertEffect>( - CreateKeyframeEffectModel( - resolver, element, animating_element, &style, - parent_style, name, keyframe_timing_function.get(), i), + CreateKeyframeEffectModel(resolver, element, &style, + parent_style, name, + keyframe_timing_function.get(), i), timing, is_paused, inherited_time, inherited_phase, timeline_duration, animation->playbackRate()), specified_timing, keyframes_rule, timeline, @@ -784,16 +783,15 @@ inherited_time = timeline->CurrentTime(); } } - update.StartAnimation( - name, name_index, i, - *MakeGarbageCollected<InertEffect>( - CreateKeyframeEffectModel(resolver, element, animating_element, - &style, parent_style, name, - keyframe_timing_function.get(), i), - timing, is_paused, inherited_time, inherited_phase, - timeline_duration, 1.0), - specified_timing, keyframes_rule, timeline, - animation_data->PlayStateList()); + update.StartAnimation(name, name_index, i, + *MakeGarbageCollected<InertEffect>( + CreateKeyframeEffectModel( + resolver, element, &style, parent_style, + name, keyframe_timing_function.get(), i), + timing, is_paused, inherited_time, + inherited_phase, timeline_duration, 1.0), + specified_timing, keyframes_rule, timeline, + animation_data->PlayStateList()); } } }
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc index 4045985..c1b7aaa79 100644 --- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc +++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
@@ -646,61 +646,48 @@ } // namespace -HeapVector<Member<RuleSet>> StyleResolver::UARulesForElement( - const Element& element, - ElementRuleCollector* collector) const { - HeapVector<Member<RuleSet>> ua_rules; +void StyleResolver::MatchUARules(const Element& element, + ElementRuleCollector& collector) { + collector.SetMatchingUARules(true); CSSDefaultStyleSheets& default_style_sheets = CSSDefaultStyleSheets::Instance(); if (!print_media_type_) { if (LIKELY(element.IsHTMLElement() || element.IsVTTElement())) { - ua_rules.push_back(default_style_sheets.DefaultHtmlStyle()); + MatchRuleSet(collector, default_style_sheets.DefaultHtmlStyle()); if (UNLIKELY(IsInMediaUAShadow(element))) { - ua_rules.push_back(default_style_sheets.DefaultMediaControlsStyle()); + MatchRuleSet(collector, + default_style_sheets.DefaultMediaControlsStyle()); } } else if (element.IsSVGElement()) { - ua_rules.push_back(default_style_sheets.DefaultSVGStyle()); + MatchRuleSet(collector, default_style_sheets.DefaultSVGStyle()); } else if (element.namespaceURI() == mathml_names::kNamespaceURI) { - ua_rules.push_back(default_style_sheets.DefaultMathMLStyle()); + MatchRuleSet(collector, default_style_sheets.DefaultMathMLStyle()); } } else { - ua_rules.push_back(default_style_sheets.DefaultPrintStyle()); + MatchRuleSet(collector, default_style_sheets.DefaultPrintStyle()); } // In quirks mode, we match rules from the quirks user agent sheet. if (GetDocument().InQuirksMode()) - ua_rules.push_back(default_style_sheets.DefaultHtmlQuirksStyle()); + MatchRuleSet(collector, default_style_sheets.DefaultHtmlQuirksStyle()); - // If document uses view source styles (in view source mode or in xml - // viewer mode), then we match rules from the view source style sheet. + // If document uses view source styles (in view source mode or in xml viewer + // mode), then we match rules from the view source style sheet. if (GetDocument().IsViewSource()) - ua_rules.push_back(default_style_sheets.DefaultViewSourceStyle()); + MatchRuleSet(collector, default_style_sheets.DefaultViewSourceStyle()); // If the system is in forced colors mode, match rules from the forced colors // style sheet. if (IsForcedColorsModeEnabled()) - ua_rules.push_back(default_style_sheets.DefaultForcedColorStyle()); + MatchRuleSet(collector, default_style_sheets.DefaultForcedColorStyle()); - if (element.IsPseudoElement() || - (collector && collector->IsCollectingForPseudoElement())) { + if (collector.IsCollectingForPseudoElement()) { if (RuleSet* default_pseudo_style = - default_style_sheets.DefaultPseudoElementStyleOrNull()) { - ua_rules.push_back( - default_style_sheets.DefaultPseudoElementStyleOrNull()); - } + default_style_sheets.DefaultPseudoElementStyleOrNull()) + MatchRuleSet(collector, default_pseudo_style); } - return ua_rules; -} - -void StyleResolver::MatchUARules(const Element& element, - ElementRuleCollector& collector) { - collector.SetMatchingUARules(true); - - for (auto& ua_rule : UARulesForElement(element, &collector)) - MatchRuleSet(collector, ua_rule); - collector.FinishAddingUARules(); collector.SetMatchingUARules(false); } @@ -1421,7 +1408,6 @@ StyleRuleKeyframes* StyleResolver::FindKeyframesRule( const Element* element, - const Element* animating_element, const AtomicString& animation_name) { HeapVector<Member<ScopedStyleResolver>, 8> resolvers; CollectScopedResolversForHostedShadowTrees(*element, resolvers); @@ -1440,15 +1426,6 @@ animation_name)) return keyframes_rule; - // Match UA keyframe rules after user and author rules. - for (auto& rule : UARulesForElement(*animating_element, nullptr)) { - auto keyframes_rules = rule->KeyframesRules(); - for (auto& keyframes_rule : keyframes_rules) { - if (keyframes_rule->GetName() == animation_name) - return keyframes_rule; - } - } - for (auto& resolver : resolvers) resolver->SetHasUnresolvedKeyframesRule(); return nullptr;
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.h b/third_party/blink/renderer/core/css/resolver/style_resolver.h index 1e65ce4a..66f6a02 100644 --- a/third_party/blink/renderer/core/css/resolver/style_resolver.h +++ b/third_party/blink/renderer/core/css/resolver/style_resolver.h
@@ -115,7 +115,6 @@ SelectorFilter& GetSelectorFilter() { return selector_filter_; } StyleRuleKeyframes* FindKeyframesRule(const Element*, - const Element* animating_element, const AtomicString& animation_name); // These methods will give back the set of rules that matched for a given @@ -286,10 +285,6 @@ bool IsForcedColorsModeEnabled() const; - HeapVector<Member<RuleSet>> UARulesForElement( - const Element& element, - ElementRuleCollector* collector) const; - MatchedPropertiesCache matched_properties_cache_; Member<Document> document_; scoped_refptr<const ComputedStyle> initial_style_;
diff --git a/third_party/blink/renderer/core/css/style_engine_test.cc b/third_party/blink/renderer/core/css/style_engine_test.cc index dd3e3042a..f7d9b78 100644 --- a/third_party/blink/renderer/core/css/style_engine_test.cc +++ b/third_party/blink/renderer/core/css/style_engine_test.cc
@@ -423,7 +423,7 @@ // There's no @keyframes rule named dummy-animation ASSERT_FALSE(GetStyleEngine().GetStyleResolver().FindKeyframesRule( - t5, t5, AtomicString("dummy-animation"))); + t5, AtomicString("dummy-animation"))); auto* keyframes_parsed_sheet = MakeGarbageCollected<StyleSheetContents>( MakeGarbageCollected<CSSParserContext>(GetDocument())); @@ -437,7 +437,7 @@ // is found with one keyframe. StyleRuleKeyframes* keyframes = GetStyleEngine().GetStyleResolver().FindKeyframesRule( - t5, t5, AtomicString("dummy-animation")); + t5, AtomicString("dummy-animation")); ASSERT_TRUE(keyframes); EXPECT_EQ(1u, keyframes->Keyframes().size()); @@ -450,7 +450,7 @@ // Author @keyframes rules take precedence; now there are two keyframes (from // and to). keyframes = GetStyleEngine().GetStyleResolver().FindKeyframesRule( - t5, t5, AtomicString("dummy-animation")); + t5, AtomicString("dummy-animation")); ASSERT_TRUE(keyframes); EXPECT_EQ(2u, keyframes->Keyframes().size()); @@ -458,7 +458,7 @@ UpdateAllLifecyclePhases(); keyframes = GetStyleEngine().GetStyleResolver().FindKeyframesRule( - t5, t5, AtomicString("dummy-animation")); + t5, AtomicString("dummy-animation")); ASSERT_TRUE(keyframes); EXPECT_EQ(1u, keyframes->Keyframes().size()); @@ -467,7 +467,7 @@ // Injected @keyframes rules are no longer available once removed. ASSERT_FALSE(GetStyleEngine().GetStyleResolver().FindKeyframesRule( - t5, t5, AtomicString("dummy-animation"))); + t5, AtomicString("dummy-animation"))); // Custom properties
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_context.cc b/third_party/blink/renderer/core/display_lock/display_lock_context.cc index 8b18f336..f528d2f 100644 --- a/third_party/blink/renderer/core/display_lock/display_lock_context.cc +++ b/third_party/blink/renderer/core/display_lock/display_lock_context.cc
@@ -724,10 +724,6 @@ needs_graphics_layer_rebuild_ = false; if (forced_graphics_layer_update_blocked_) { - // We only add an extra dirty bit to the compositing state, which is safe - // since we do this before updating the compositing state. - DisableCompositingQueryAsserts disabler; - auto* compositing_parent = layout_box->Layer()->EnclosingLayerWithCompositedLayerMapping( kIncludeSelf);
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc index 57c8d33..ebce19f 100644 --- a/third_party/blink/renderer/core/frame/local_frame_view.cc +++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -1323,20 +1323,12 @@ layout_object->StyleRef().HasStickyConstrainedPosition()); DCHECK(layout_object->HasLayer()); PaintLayer* layer = To<LayoutBoxModelObject>(layout_object.Get())->Layer(); - - if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { - DisableCompositingQueryAsserts disabler; - if (layer->IsPaintInvalidationContainer()) - continue; - } - // If the layer has no visible content, then we shouldn't invalidate; but // if we're not compositing-inputs-clean, then we can't query // layer->SubtreeIsInvisible() here. layout_object->SetSubtreeShouldCheckForPaintInvalidation(); if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled() && !layer->SelfOrDescendantNeedsRepaint()) { - DisableCompositingQueryAsserts disabler; // Paint properties of the layer relative to its containing graphics // layer may change if the paint properties escape the graphics layer's // property state. Need to check raster invalidation for relative paint
diff --git a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc index 7206e7f..5c18234e 100644 --- a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc +++ b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
@@ -680,12 +680,8 @@ if (LayoutObject* layout_object = GetLayoutObject()) { if (layout_object->IsCanvas()) { - if (old_size != Size()) { + if (old_size != Size()) To<LayoutHTMLCanvas>(layout_object)->CanvasSizeChanged(); - if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled() && - GetDocument().GetSettings()->GetAcceleratedCompositingEnabled()) - GetLayoutBox()->ContentChanged(kCanvasChanged); - } if (had_resource_provider) layout_object->SetShouldDoFullPaintInvalidation(); }
diff --git a/third_party/blink/renderer/core/inspector/inspector_css_agent.cc b/third_party/blink/renderer/core/inspector/inspector_css_agent.cc index 36b871e..d7cf3ab 100644 --- a/third_party/blink/renderer/core/inspector/inspector_css_agent.cc +++ b/third_party/blink/renderer/core/inspector/inspector_css_agent.cc
@@ -874,7 +874,6 @@ if (!response.IsSuccess()) return response; - Element* animating_element = element; PseudoId element_pseudo_id = element->GetPseudoId(); if (element_pseudo_id) { element = element->ParentOrShadowHostElement(); @@ -944,7 +943,7 @@ inherited_entries->fromJust()->emplace_back(std::move(entry)); } - *css_keyframes_rules = AnimationsForNode(element, animating_element); + *css_keyframes_rules = AnimationsForNode(element); return Response::Success(); } @@ -967,8 +966,7 @@ } std::unique_ptr<protocol::Array<protocol::CSS::CSSKeyframesRule>> -InspectorCSSAgent::AnimationsForNode(Element* element, - Element* animating_element) { +InspectorCSSAgent::AnimationsForNode(Element* element) { auto css_keyframes_rules = std::make_unique<protocol::Array<protocol::CSS::CSSKeyframesRule>>(); Document& document = element->GetDocument(); @@ -984,8 +982,8 @@ AtomicString animation_name(animation_data->NameList()[i]); if (animation_name == CSSAnimationData::InitialName()) continue; - StyleRuleKeyframes* keyframes_rule = style_resolver.FindKeyframesRule( - element, animating_element, animation_name); + StyleRuleKeyframes* keyframes_rule = + style_resolver.FindKeyframesRule(element, animation_name); if (!keyframes_rule) continue;
diff --git a/third_party/blink/renderer/core/inspector/inspector_css_agent.h b/third_party/blink/renderer/core/inspector/inspector_css_agent.h index f871eb2a..20f45af4 100644 --- a/third_party/blink/renderer/core/inspector/inspector_css_agent.h +++ b/third_party/blink/renderer/core/inspector/inspector_css_agent.h
@@ -297,10 +297,8 @@ std::unique_ptr<protocol::Array<protocol::CSS::StyleDeclarationEdit>>, HeapVector<Member<StyleSheetAction>>* actions); - // If the |animating_element| is a pseudo element, then |element| is a - // reference to its originating DOM element. std::unique_ptr<protocol::Array<protocol::CSS::CSSKeyframesRule>> - AnimationsForNode(Element* element, Element* animating_element); + AnimationsForNode(Element*); void CollectPlatformFontsForLayoutObject( LayoutObject*,
diff --git a/third_party/blink/renderer/core/layout/layout_block.cc b/third_party/blink/renderer/core/layout/layout_block.cc index 71881b0..985f7aa 100644 --- a/third_party/blink/renderer/core/layout/layout_block.cc +++ b/third_party/blink/renderer/core/layout/layout_block.cc
@@ -1137,24 +1137,9 @@ static void ProcessPositionedObjectRemoval( ContainingBlockState containing_block_state, LayoutObject* positioned_object) { - if (containing_block_state == kNewContainingBlock) { + if (containing_block_state == kNewContainingBlock) positioned_object->SetChildNeedsLayout(kMarkOnlyThis); - // The positioned object changing containing block may change paint - // invalidation container. - // Invalidate it (including non-compositing descendants) on its original - // paint invalidation container. - if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { - // This valid because we need to invalidate based on the current - // status. - DisableCompositingQueryAsserts compositing_disabler; - if (!positioned_object->IsPaintInvalidationContainer()) { - ObjectPaintInvalidator(*positioned_object) - .InvalidatePaintIncludingNonCompositingDescendants(); - } - } - } - // It is parent blocks job to add positioned child to positioned objects // list of its containing block. // Parent layout needs to be invalidated to ensure this happens.
diff --git a/third_party/blink/renderer/core/layout/layout_box_model_object.cc b/third_party/blink/renderer/core/layout/layout_box_model_object.cc index 1ae09fa..6e683b5b 100644 --- a/third_party/blink/renderer/core/layout/layout_box_model_object.cc +++ b/third_party/blink/renderer/core/layout/layout_box_model_object.cc
@@ -91,15 +91,6 @@ return *map; } -void LayoutBoxModelObject::ContentChanged(ContentChangeType change_type) { - NOT_DESTROYED(); - DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); - if (!HasLayer()) - return; - - Layer()->ContentChanged(change_type); -} - LayoutBoxModelObject::LayoutBoxModelObject(ContainerNode* node) : LayoutObject(node) {} @@ -169,14 +160,7 @@ IsStackingContext() != IsStackingContext(new_style)) && // ObjectPaintInvalidator requires this. IsRooted()) { - if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { - ObjectPaintInvalidator(*this).SlowSetPaintingLayerNeedsRepaint(); - } else { - // We need to invalidate based on the current compositing status. - DisableCompositingQueryAsserts compositing_disabler; - ObjectPaintInvalidator(*this) - .InvalidatePaintIncludingNonCompositingDescendants(); - } + ObjectPaintInvalidator(*this).SlowSetPaintingLayerNeedsRepaint(); } LayoutObject::StyleWillChange(diff, new_style);
diff --git a/third_party/blink/renderer/core/layout/layout_box_model_object.h b/third_party/blink/renderer/core/layout/layout_box_model_object.h index 476b806e..27f4af7 100644 --- a/third_party/blink/renderer/core/layout/layout_box_model_object.h +++ b/third_party/blink/renderer/core/layout/layout_box_model_object.h
@@ -511,8 +511,6 @@ LineDirectionMode, LinePositionMode = kPositionOnContainingLine) const = 0; - void ContentChanged(ContentChangeType); - // Returns true if the background is painted opaque in the given rect. // The query rect is given in local coordinate system. virtual bool BackgroundIsKnownToBeOpaqueInRect(const PhysicalRect&) const {
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc index c00ea09..818ee39 100644 --- a/third_party/blink/renderer/core/layout/layout_object.cc +++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -1731,10 +1731,6 @@ NOT_DESTROYED(); DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); LayoutBoxModelObject* container = nullptr; - // FIXME: CompositingState is not necessarily up to date for many callers of - // this function. - DisableCompositingQueryAsserts disabler; - if (PaintLayer* painting_layer = PaintingLayer()) { if (PaintLayer* compositing_layer = painting_layer
diff --git a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc index a2fc970..cbfdb54e 100644 --- a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc +++ b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
@@ -1850,8 +1850,6 @@ FramePaintTiming frame_paint_timing(context, GetLayoutObject().GetFrame()); - // https://code.google.com/p/chromium/issues/detail?id=343772 - DisableCompositingQueryAsserts disabler; #if DCHECK_IS_ON() // FIXME: once the state machine is ready, this can be removed and we can // refer to that instead.
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc b/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc index fd86cc2..b74023b 100644 --- a/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc +++ b/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc
@@ -184,8 +184,6 @@ compositor->ClearCompositingInputsRoot(); - DisableCompositingQueryAsserts disabler; - bool previously_needed_paint_offset_translation = layer->NeedsPaintOffsetTranslationForCompositing(); @@ -231,8 +229,6 @@ PaintLayer* enclosing_squashing_composited_layer = info.enclosing_squashing_composited_layer; - DisableCompositingQueryAsserts disabler; - if (layer->NeedsCompositingInputsUpdate()) { if (enclosing_stacking_composited_layer) { enclosing_stacking_composited_layer->GetCompositedLayerMapping()
diff --git a/third_party/blink/renderer/core/paint/compositing/graphics_layer_tree_builder.cc b/third_party/blink/renderer/core/paint/compositing/graphics_layer_tree_builder.cc index c5dd967..71cf70ac 100644 --- a/third_party/blink/renderer/core/paint/compositing/graphics_layer_tree_builder.cc +++ b/third_party/blink/renderer/core/paint/compositing/graphics_layer_tree_builder.cc
@@ -134,8 +134,6 @@ PaintLayerCompositor* inner_compositor = PaintLayerCompositor::FrameContentsCompositor(*embedded); if (inner_compositor) { - // Disabler required because inner frame might be throttled. - DisableCompositingQueryAsserts disabler; if (GraphicsLayer* inner_root_graphics_layer = inner_compositor->RootGraphicsLayer()) { // TODO(szager); Remove this after diagnosing crash
diff --git a/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc b/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc index 18e0ab2..c07e8766 100644 --- a/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc +++ b/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc
@@ -406,7 +406,6 @@ layer->SetNeedsRepaint(); // We need to check for raster invalidations due to content changing // composited layer backings. - DisableCompositingQueryAsserts compositing_disabler; switch (layer->GetCompositingState()) { case kPaintsIntoOwnBacking: layer->GetCompositedLayerMapping()->SetNeedsCheckRasterInvalidation();
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc index 65c63c6..1439498 100644 --- a/third_party/blink/renderer/core/paint/paint_layer.cc +++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -266,11 +266,6 @@ rare_data_->resource_info->ClearLayer(); } - if (GroupedMapping()) { - DisableCompositingQueryAsserts disabler; - SetGroupedMapping(nullptr, kInvalidateLayerAndRemoveFromMapping); - } - // Child layers will be deleted by their corresponding layout objects, so // we don't need to delete them ourselves. if (HasCompositedLayerMapping()) @@ -304,41 +299,10 @@ return GetLayoutObject().View()->Compositor(); } -void PaintLayer::ContentChanged(ContentChangeType change_type) { - DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); - - // updateLayerCompositingState will query compositingReasons for accelerated - // overflow scrolling. This is tripped by - // web_tests/compositing/content-changed-chicken-egg.html - DisableCompositingQueryAsserts disabler; - - if (Compositor()) { - SetNeedsCompositingInputsUpdate(); - - if (change_type == kCanvasContextChanged) { - // Although we're missing test coverage, we need to call - // GraphicsLayer::SetContentsToCcLayer with the new cc::Layer for this - // canvas. See http://crbug.com/349195 - if (HasCompositedLayerMapping()) { - GetCompositedLayerMapping()->SetNeedsGraphicsLayerUpdate( - kGraphicsLayerUpdateSubtree); - } - } - } -} - bool PaintLayer::PaintsWithFilters() const { if (!GetLayoutObject().HasFilterInducingProperty()) return false; - - if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { - // https://code.google.com/p/chromium/issues/detail?id=343759 - DisableCompositingQueryAsserts disabler; - return !GetCompositedLayerMapping() || - GetCompositingState() != kPaintsIntoOwnBacking; - } else { - return true; - } + return true; } PhysicalOffset PaintLayer::SubpixelAccumulation() const { @@ -1514,8 +1478,6 @@ if (LocalFrameView* frame_view = layout_object_->GetDocument().View()) frame_view->SetNeedsForcedCompositingUpdate(); - // We need the current compositing status. - DisableCompositingQueryAsserts disabler; if (IsPaintInvalidationContainer()) { // Our children will be reparented and contained by a new paint // invalidation container, so need paint invalidation. CompositingUpdate @@ -3165,7 +3127,6 @@ DCHECK(HasCompositedLayerMapping()); DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); - DisableCompositingQueryAsserts disabler; if (!layer_being_destroyed) { // We need to make sure our descendants get a geometry update. In principle, // we could call setNeedsGraphicsLayerUpdate on our children, but that would @@ -3549,19 +3510,6 @@ // to recompute the bit once scrollbars have been updated. UpdateSelfPaintingLayer(); - if (!diff.CompositingReasonsChanged() && - !RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { - // For querying stale GetCompositingState(). - DisableCompositingQueryAsserts disable; - - // Compositing inputs update is required when the PaintLayer is currently - // composited. This is because even style changes as simple as background - // color change, or pointer-events state change, can update compositing - // state. - if (old_style && GetCompositingState() == kPaintsIntoOwnBacking) - SetNeedsCompositingInputsUpdate(); - } - // HasAlphaChanged can affect whether a composited layer is opaque. if (diff.NeedsLayout() || diff.HasAlphaChanged()) SetNeedsCompositingInputsUpdate(); @@ -3810,21 +3758,6 @@ void PaintLayer::MarkCompositingContainerChainForNeedsRepaint() { PaintLayer* layer = this; while (true) { - if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { - // Need to access compositingState(). We've ensured correct flag setting - // when compositingState() changes. - DisableCompositingQueryAsserts disabler; - if (layer->GetCompositingState() == kPaintsIntoOwnBacking) - break; - if (CompositedLayerMapping* grouped_mapping = layer->GroupedMapping()) { - // TODO(wkorman): As we clean up the CompositedLayerMapping needsRepaint - // logic to delegate to scrollbars, we may be able to remove the line - // below as well. - grouped_mapping->OwningLayer().SetNeedsRepaint(); - break; - } - } - // For a non-self-painting layer having self-painting descendant, the // descendant will be painted through this layer's Parent() instead of // this layer's Container(), so in addition to the CompositingContainer() @@ -3957,28 +3890,12 @@ auto* stacking_context = AncestorStackingContext(); if (!stacking_context) return; - - if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { - // This invalidation code intentionally refers to stale state. - DisableCompositingQueryAsserts disabler; - - // Changes of stacking may result in graphics layers changing size - // due to new contents painting into them. - if (auto* mapping = stacking_context->GetCompositedLayerMapping()) - mapping->SetNeedsGraphicsLayerUpdate(kGraphicsLayerUpdateSubtree); - } - if (stacking_context->StackingNode()) stacking_context->StackingNode()->DirtyZOrderLists(); MarkAncestorChainForFlagsUpdate(); } -DisableCompositingQueryAsserts::DisableCompositingQueryAsserts() - : disabler_(&g_compositing_query_mode, kCompositingQueriesAreAllowed) { - DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); -} - void PaintLayer::Trace(Visitor* visitor) const { visitor->Trace(layout_object_); visitor->Trace(parent_); @@ -4018,10 +3935,6 @@ return; } - absl::optional<blink::DisableCompositingQueryAsserts> disabler; - if (!blink::RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) - disabler.emplace(); - if (blink::LocalFrame* frame = layer->GetLayoutObject().GetFrame()) { WTF::String output = ExternalRepresentation(frame,
diff --git a/third_party/blink/renderer/core/paint/paint_layer.h b/third_party/blink/renderer/core/paint/paint_layer.h index 7db90de14a0..5c973134 100644 --- a/third_party/blink/renderer/core/paint/paint_layer.h +++ b/third_party/blink/renderer/core/paint/paint_layer.h
@@ -104,21 +104,6 @@ kNegativeZOrderChildren | kNormalFlowChildren | kPositiveZOrderChildren }; -// FIXME: remove this once the compositing query DCHECKS are no longer hit. -class CORE_EXPORT DisableCompositingQueryAsserts { - STACK_ALLOCATED(); - - public: - DisableCompositingQueryAsserts(); - DisableCompositingQueryAsserts(const DisableCompositingQueryAsserts&) = - delete; - DisableCompositingQueryAsserts& operator=( - const DisableCompositingQueryAsserts&) = delete; - - private: - base::AutoReset<CompositingQueryMode> disabler_; -}; - struct CORE_EXPORT PaintLayerRareData final : public GarbageCollected<PaintLayerRareData> { public: @@ -340,11 +325,6 @@ PaintLayerCompositor* Compositor() const; - // Notification from the layoutObject that its content changed (e.g. current - // frame of image changed). Allows updates of layer content without - // invalidating paint. - void ContentChanged(ContentChangeType); - bool UpdateSize(); void UpdateSizeAndScrollingAfterLayout();
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc index 68af1ea..a7b42c8b 100644 --- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc +++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
@@ -343,42 +343,16 @@ GraphicsLayer* PaintLayerScrollableArea::GraphicsLayerForHorizontalScrollbar() const { - if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) - return nullptr; - - // See crbug.com/343132. - DisableCompositingQueryAsserts disabler; - - return Layer()->HasCompositedLayerMapping() - ? Layer() - ->GetCompositedLayerMapping() - ->LayerForHorizontalScrollbar() - : nullptr; + return nullptr; } GraphicsLayer* PaintLayerScrollableArea::GraphicsLayerForVerticalScrollbar() const { - if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) - return nullptr; - - // See crbug.com/343132. - DisableCompositingQueryAsserts disabler; - - return Layer()->HasCompositedLayerMapping() - ? Layer()->GetCompositedLayerMapping()->LayerForVerticalScrollbar() - : nullptr; + return nullptr; } GraphicsLayer* PaintLayerScrollableArea::GraphicsLayerForScrollCorner() const { - if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) - return nullptr; - - // See crbug.com/343132. - DisableCompositingQueryAsserts disabler; - - return Layer()->HasCompositedLayerMapping() - ? Layer()->GetCompositedLayerMapping()->LayerForScrollCorner() - : nullptr; + return nullptr; } bool PaintLayerScrollableArea::IsActive() const { @@ -2018,7 +1992,6 @@ // composited controls get correctly positioned on a compositor update. For // now, conservatively leaving this unchanged. if (Layer()->HasCompositedLayerMapping()) { - DisableCompositingQueryAsserts disabler; Layer()->GetCompositedLayerMapping()->PositionOverflowControlsLayers(); } } @@ -2494,7 +2467,6 @@ void PaintLayerScrollableArea::UpdateCompositingLayersAfterScroll() { DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); - DisableCompositingQueryAsserts disabler; PaintLayerCompositor* compositor = GetLayoutBox()->View()->Compositor(); if (!compositor || !compositor->InCompositingMode()) return;
diff --git a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc index af4ab1e..3b431c9 100644 --- a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc +++ b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
@@ -59,7 +59,6 @@ PaintLayer* paint_layer = To<LayoutBoxModelObject>(object).Layer(); - DisableCompositingQueryAsserts disabler; // This ensures that CompositingLayerPropertyUpdater::Update will // be called and update LayerState for the LayoutView. auto* mapping = paint_layer->GetCompositedLayerMapping(); @@ -412,8 +411,6 @@ if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) return; - DisableCompositingQueryAsserts disabler; - if (object.IsPaintInvalidationContainer()) { context.paint_invalidation_container = To<LayoutBoxModelObject>(&object); if (object.IsStackingContext() || object.IsSVGRoot()) { @@ -630,8 +627,6 @@ // Mark the previous paint invalidation container as needing // raster invalidation. This handles cases where raster invalidation // needs to happen but no compositing layers were added or removed. - DisableCompositingQueryAsserts disabler; - const auto* paint_invalidation_container = context.paint_invalidation_container->Layer(); if (!paint_invalidation_container->SelfNeedsRepaint()) {
diff --git a/third_party/blink/renderer/modules/ad_auction/navigator_auction.cc b/third_party/blink/renderer/modules/ad_auction/navigator_auction.cc index 29dc461..4b3971bc 100644 --- a/third_party/blink/renderer/modules/ad_auction/navigator_auction.cc +++ b/third_party/blink/renderer/modules/ad_auction/navigator_auction.cc
@@ -13,6 +13,7 @@ #include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom-blink.h" #include "third_party/blink/public/mojom/parakeet/ad_request.mojom-blink.h" #include "third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom-blink.h" +#include "third_party/blink/public/web/web_console_message.h" #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" #include "third_party/blink/renderer/bindings/core/v8/v8_union_usvstring_usvstringsequence.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_ad_properties.h" @@ -27,6 +28,7 @@ #include "third_party/blink/renderer/core/dom/dom_exception.h" #include "third_party/blink/renderer/core/frame/local_dom_window.h" #include "third_party/blink/renderer/core/frame/navigator.h" +#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" #include "third_party/blink/renderer/core/loader/document_loader.h" #include "third_party/blink/renderer/modules/ad_auction/ads.h" #include "third_party/blink/renderer/modules/ad_auction/validate_blink_interest_group.h" @@ -89,6 +91,14 @@ error.Utf8().c_str()); } +String WarningPermissionsPolicy(const String& feature, const String& api) { + return String::Format( + "In the future, feature %s will not be enabled by default by Permissions " + "Policy (thus calling %s will be rejected with NotAllowedError) in cross-" + "origin iframes or same-origin iframes nested in cross-origin iframes", + feature.Utf8().c_str(), api.Utf8().c_str()); +} + // JSON and Origin conversion helpers. bool Jsonify(const ScriptState& script_state, @@ -628,6 +638,43 @@ return true; } +// Modified from LocalFrame::CountUseIfFeatureWouldBeBlockedByPermissionsPolicy. +// Checks if a feature, which is currently available in all frames, would be +// blocked by our restricted permissions policy EnableForSelf. +// Returns true if the frame is cross-origin relative to the top-level document, +// or if it is same-origin with the top level, but is embedded in any way +// through a cross-origin frame. (A->B->A embedding) +bool FeatureWouldBeBlockedByRestrictedPermissionsPolicy(Navigator& navigator) { + const Frame* frame = navigator.DomWindow()->GetFrame(); + // Get the origin of the top-level document + const SecurityOrigin* top_origin = + frame->Tree().Top().GetSecurityContext()->GetSecurityOrigin(); + + // Walk up the frame tree looking for any cross-origin embeds. Even if this + // frame is same-origin with the top-level, if it is embedded by a cross- + // origin frame (like A->B->A) it would be blocked without a permissions + // policy. + while (!frame->IsMainFrame()) { + if (!frame->GetSecurityContext()->GetSecurityOrigin()->CanAccess( + top_origin)) { + return true; + } + frame = frame->Tree().Parent(); + } + return false; +} + +void AddWarningMessageToConsole(ScriptState* script_state, + const String& feature, + const String& api) { + auto* window = To<LocalDOMWindow>(ExecutionContext::From(script_state)); + WebLocalFrameImpl::FromFrame(window->GetFrame()) + ->AddMessageToConsole( + WebConsoleMessage(mojom::ConsoleMessageLevel::kWarning, + WarningPermissionsPolicy(feature, api)), + /*discard_duplicates=*/true); +} + } // namespace NavigatorAuction::NavigatorAuction(Navigator& navigator) @@ -712,11 +759,18 @@ const ExecutionContext* context = ExecutionContext::From(script_state); if (!context->IsFeatureEnabled( blink::mojom::PermissionsPolicyFeature::kJoinAdInterestGroup)) { - exception_state.ThrowException( - static_cast<int>(DOMExceptionCode::kNotAllowedError), + exception_state.ThrowDOMException( + DOMExceptionCode::kNotAllowedError, "Feature join-ad-interest-group is not enabled by Permissions Policy"); return; } + if (!base::FeatureList::IsEnabled( + blink::features::kAdInterestGroupAPIRestrictedPolicyByDefault) && + FeatureWouldBeBlockedByRestrictedPermissionsPolicy(navigator)) { + AddWarningMessageToConsole(script_state, "join-ad-interest-group", + "joinAdInterestGroup"); + } + return From(ExecutionContext::From(script_state), navigator) .joinAdInterestGroup(script_state, group, duration_seconds, exception_state); @@ -744,11 +798,18 @@ ExecutionContext* context = ExecutionContext::From(script_state); if (!context->IsFeatureEnabled( blink::mojom::PermissionsPolicyFeature::kJoinAdInterestGroup)) { - exception_state.ThrowException( - static_cast<int>(DOMExceptionCode::kNotAllowedError), + exception_state.ThrowDOMException( + DOMExceptionCode::kNotAllowedError, "Feature join-ad-interest-group is not enabled by Permissions Policy"); return; } + if (!base::FeatureList::IsEnabled( + blink::features::kAdInterestGroupAPIRestrictedPolicyByDefault) && + FeatureWouldBeBlockedByRestrictedPermissionsPolicy(navigator)) { + AddWarningMessageToConsole(script_state, "join-ad-interest-group", + "leaveAdInterestGroup"); + } + return From(ExecutionContext::From(script_state), navigator) .leaveAdInterestGroup(script_state, group, exception_state); } @@ -764,11 +825,18 @@ ExecutionContext* context = ExecutionContext::From(script_state); if (!context->IsFeatureEnabled( blink::mojom::PermissionsPolicyFeature::kJoinAdInterestGroup)) { - exception_state.ThrowException( - static_cast<int>(DOMExceptionCode::kNotAllowedError), + exception_state.ThrowDOMException( + DOMExceptionCode::kNotAllowedError, "Feature join-ad-interest-group is not enabled by Permissions Policy"); return; } + if (!base::FeatureList::IsEnabled( + blink::features::kAdInterestGroupAPIRestrictedPolicyByDefault) && + FeatureWouldBeBlockedByRestrictedPermissionsPolicy(navigator)) { + AddWarningMessageToConsole(script_state, "join-ad-interest-group", + "updateAdInterestGroups"); + } + return From(context, navigator).updateAdInterestGroups(); } @@ -810,11 +878,17 @@ const ExecutionContext* context = ExecutionContext::From(script_state); if (!context->IsFeatureEnabled( blink::mojom::PermissionsPolicyFeature::kRunAdAuction)) { - exception_state.ThrowException( - static_cast<int>(DOMExceptionCode::kNotAllowedError), + exception_state.ThrowDOMException( + DOMExceptionCode::kNotAllowedError, "Feature run-ad-auction is not enabled by Permissions Policy"); return ScriptPromise(); } + if (!base::FeatureList::IsEnabled( + blink::features::kAdInterestGroupAPIRestrictedPolicyByDefault) && + FeatureWouldBeBlockedByRestrictedPermissionsPolicy(navigator)) { + AddWarningMessageToConsole(script_state, "run-ad-auction", "runAdAuction"); + } + return From(ExecutionContext::From(script_state), navigator) .runAdAuction(script_state, config, exception_state); }
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc index 7141cc6..603ca6c 100644 --- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc +++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
@@ -1398,12 +1398,6 @@ marked_canvas_dirty_ = true; if (auto* cc_layer = CcLayer()) cc_layer->SetNeedsDisplay(); - if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { - LayoutBox* layout_box = canvas()->GetLayoutBox(); - auto* settings = canvas()->GetDocument().GetSettings(); - if (layout_box && settings->GetAcceleratedCompositingEnabled()) - layout_box->ContentChanged(change_type); - } DidDraw(draw_type); } }
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 index d049e7d..fa5949310 100644 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1280,7 +1280,7 @@ }, { name: "LayoutNGBlockFragmentation", - implied_by: ["LayoutNGFlexFragmentation", "LayoutNGGridFragmentation", "LayoutNGPrinting"], + implied_by: ["LayoutNGFlexFragmentation", "LayoutNGGridFragmentation", "LayoutNGPrinting", "LayoutNGTableFragmentation"], }, { name: "LayoutNGBlockInInline", @@ -1331,6 +1331,12 @@ status: "test" }, { + // Block fragmentation support in the table layout algorithm. + // https://drafts.csswg.org/css-tables-3/#fragmentation + name: "LayoutNGTableFragmentation", + depends_on: ["LayoutNG"], + }, + { name: "LayoutNGTextCombine", depends_on: ["LayoutNG"], status: "stable"
diff --git a/third_party/blink/renderer/platform/wtf/allocator/partitions.cc b/third_party/blink/renderer/platform/wtf/allocator/partitions.cc index 894d01db..e7d46e3 100644 --- a/third_party/blink/renderer/platform/wtf/allocator/partitions.cc +++ b/third_party/blink/renderer/platform/wtf/allocator/partitions.cc
@@ -83,7 +83,9 @@ base::FeatureList::IsEnabled( base::features::kPartitionAllocBackupRefPtr) && base::features::kBackupRefPtrEnabledProcessesParam.Get() == - base::features::BackupRefPtrEnabledProcesses::kBrowserAndRenderer; + base::features::BackupRefPtrEnabledProcesses::kBrowserAndRenderer && + base::features::kBackupRefPtrModeParam.Get() == + base::features::BackupRefPtrMode::kEnabled; #else false; #endif
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations index 85efbad..0244f755 100644 --- a/third_party/blink/web_tests/TestExpectations +++ b/third_party/blink/web_tests/TestExpectations
@@ -2985,6 +2985,15 @@ crbug.com/626703 external/wpt/preload/preload-resource-match.https.html [ Failure ] # ====== New tests from wpt-importer added here ====== +crbug.com/626703 [ Mac10.14 ] virtual/threaded/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-iframe.html [ Failure ] +crbug.com/626703 [ Mac10.15 ] virtual/threaded/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-iframe.html [ Failure ] +crbug.com/626703 [ Mac11 ] virtual/threaded/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-iframe.html [ Failure ] +crbug.com/626703 [ Mac11-arm64 ] virtual/threaded/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-iframe.html [ Failure ] +crbug.com/626703 virtual/threaded/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-quirks-mode.html [ Failure ] +crbug.com/626703 virtual/threaded/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-writing-mode-rl.html [ Failure ] +crbug.com/626703 virtual/threaded/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors.html [ Failure ] +crbug.com/626703 virtual/threaded/external/wpt/scroll-animations/css/at-scroll-timeline-frame-size-changed.html [ Failure ] +crbug.com/626703 virtual/threaded/external/wpt/scroll-animations/css/at-scroll-timeline-inline-orientation.html [ Failure ] crbug.com/626703 [ Mac10.15 ] virtual/plz-dedicated-worker/external/wpt/html/cross-origin-embedder-policy/reporting-subresource-corp.https.html [ Skip Timeout ] crbug.com/626703 [ Mac10.12 ] virtual/fenced-frame-mparch/wpt_internal/fenced_frame/presentation-receiver.https.html [ Crash Failure ] crbug.com/626703 [ Mac10.12 ] external/wpt/websockets/constructor/009.html?wpt_flags=h2 [ Crash Failure ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json index addd317..f248f37 100644 --- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json +++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -213054,6 +213054,86 @@ ] }, "scroll-animations": { + "css": { + "at-scroll-timeline-default-descriptors-iframe.html": [ + "ddb06693c77d80cdcde9b4cecb13cfdad7ef20bf", + [ + null, + [ + [ + "/scroll-animations/css/at-scroll-timeline-default-descriptors-iframe-ref.html", + "==" + ] + ], + {} + ] + ], + "at-scroll-timeline-default-descriptors-quirks-mode.html": [ + "1bba34d5866a2dfc581d8f1a97c16d0fd2344a6c", + [ + null, + [ + [ + "/scroll-animations/css/at-scroll-timeline-default-descriptors-ref.html", + "==" + ] + ], + {} + ] + ], + "at-scroll-timeline-default-descriptors-writing-mode-rl.html": [ + "4d276497bc685ceba6ebece2df70f8dc8478c351", + [ + null, + [ + [ + "/scroll-animations/css/at-scroll-timeline-default-descriptors-writing-mode-rl-ref.html", + "==" + ] + ], + {} + ] + ], + "at-scroll-timeline-default-descriptors.html": [ + "58225341938822b6d9ff5d13034b1dc7617cd183", + [ + null, + [ + [ + "/scroll-animations/css/at-scroll-timeline-default-descriptors-ref.html", + "==" + ] + ], + {} + ] + ], + "at-scroll-timeline-frame-size-changed.html": [ + "e41af998c26ef375e6ce990b6dfba90200796c3e", + [ + null, + [ + [ + "/scroll-animations/css/at-scroll-timeline-frame-size-changed-ref.html", + "==" + ] + ], + {} + ] + ], + "at-scroll-timeline-inline-orientation.html": [ + "ca6ef1c3f7ae8d7cf5edb4aa3097facdad0843f1", + [ + null, + [ + [ + "/scroll-animations/css/at-scroll-timeline-inline-orientation-ref.html", + "==" + ] + ], + {} + ] + ] + }, "scroll-timelines": { "animation-with-animatable-interface.html": [ "b04aaf2d336eeb3d7ddc8cfebbe4f902d3ef94f0", @@ -300692,6 +300772,26 @@ [] ], "css": { + "at-scroll-timeline-default-descriptors-iframe-ref.html": [ + "b05627d99400026dde2c568788a90a25af81f932", + [] + ], + "at-scroll-timeline-default-descriptors-ref.html": [ + "b72a4e8b733a57e119d01366cf44f32f28f0ca97", + [] + ], + "at-scroll-timeline-default-descriptors-writing-mode-rl-ref.html": [ + "2ea7d17577c2af1943f01784625a07dbea4e1605", + [] + ], + "at-scroll-timeline-frame-size-changed-ref.html": [ + "745c2f5472ee59f6a5bcaccff0a1181763bf720a", + [] + ], + "at-scroll-timeline-inline-orientation-ref.html": [ + "e87901e30a2c43b3d7e77c78805b1a6db87a920d", + [] + ], "scroll-timeline-cssom.tentative-expected.txt": [ "116f38ce142edb0efb004461c1a8a9a314c36283", [] @@ -369506,6 +369606,15 @@ {} ] ], + "user-select-001.html": [ + "35febc38bb0bebd0d1ada8c63a6ff4e27a8727f3", + [ + null, + { + "testdriver": true + } + ] + ], "webkit-appearance-parsing.html": [ "0f08eab222493a123fd5a44afce3689385a42390", [ @@ -432265,6 +432374,13 @@ {} ] ], + "coop-sandbox-redirects-cuts-opener.https.html": [ + "01f60b425dbea78909c075382246d475c341dfde", + [ + null, + {} + ] + ], "coop-sandbox.https.html": [ "6f250c1b0920808f519ee1278920215981fcd5e6", [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/user-select-001.html b/third_party/blink/web_tests/external/wpt/css/css-ui/user-select-001.html new file mode 100644 index 0000000..35febc38 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-ui/user-select-001.html
@@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html> + <meta charset="utf-8"> + <title>CSS Basic User Interface Test: Block children of a inline parent with "user-select:text" should be selectable even with a user-select: none ancestor</title> + <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com"> + <link rel="author" title="Mozilla" href="https://www.mozilla.org/"> + <link rel="help" href="https://drafts.csswg.org/css-ui/#propdef-user-select"> + <link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1743074"> + + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-actions.js"></script> + <script src='/resources/testdriver-vendor.js'></script> + + <style> + :root { + user-select: none; + } + </style> + + <span style="user-select: text"> + <div>Let's select this <b id="target">word</b></div> + </span> + + <script> + promise_test(async function() { + let target = document.getElementById("target"); + let actions = new test_driver.Actions(); + + // Simulate a double click to select a word. + await actions.pointerMove(5, 5, {origin: target}) + .pointerDown() + .pointerUp() + .pointerDown() + .pointerUp() + .send(); + assert_equals(window.getSelection().toString(), "word", + "The text 'word' should be selectable.") + }, "Select the text 'word'"); + </script> +</html>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-iframe-ref.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-iframe-ref.html new file mode 100644 index 0000000..b05627d99 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-iframe-ref.html
@@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>Reference for default @scroll-timeline</title> +<iframe width="400" height="400" srcdoc=' + <html> + <style> + html { + min-height: 100%; + padding-bottom: 100px; + } + + #box { + width: 100px; + height: 100px; + background-color: green; + transform: translateY(100px); + } + + * { + margin-top: 0px; + margin-bottom: 0px; + } + </style> + <script> + window.addEventListener("load", function() { + // Move the scroller to halfway. + const scroller = document.scrollingElement; + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + scroller.scrollTop = 0.5 * maxScroll; + }); + </script> + <div id="box"></div> + </html> +'></iframe>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-iframe.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-iframe.html new file mode 100644 index 0000000..ddb0669 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-iframe.html
@@ -0,0 +1,69 @@ +<!DOCTYPE HTML> +<html class="reftest-wait"> +<title>The default scroll-timeline at rule in the iframe</title> +<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-at-rule"> +<link rel="help" href="https://drafts.csswg.org/css-animations-2/#animation-timeline"> +<meta name="assert" content="CSS animation correctly updates values when using the default scroll-timeline at rule"> +<link rel="match" href="at-scroll-timeline-default-descriptors-iframe-ref.html"> + +<iframe id="target" width="400" height="400" srcdoc=' + <html> + <style> + @keyframes update { + from { transform: translateY(0px); } + to { transform: translateY(200px); } + } + @scroll-timeline test-timeline { + source: auto; + orientation: auto; + scroll-offsets: none; + } + html { + min-height: 100%; + padding-bottom: 100px; + } + #box { + width: 100px; + height: 100px; + background-color: green; + animation: update 1s linear; + animation-timeline: test-timeline; + } + #covered { + width: 100px; + height: 100px; + background-color: red; + } + + * { + margin-top: 0px; + margin-bottom: 0px; + } + </style> + <script> + window.addEventListener("load", function() { + const scroller = document.scrollingElement; + + // Move the scroller to the halfway point. + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + scroller.scrollTop = 0.5 * maxScroll; + + window.requestAnimationFrame(() => { + window.parent.postMessage("success", "*"); + }); + }); + </script> + <body> + <div id="box"></div> + <div id="covered"></div> + </body> + </html> +'></iframe> + +<script> + window.addEventListener("message", event => { + if (event.data == "success") { + document.documentElement.classList.remove("reftest-wait"); + } + }, false); +</script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-quirks-mode.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-quirks-mode.html new file mode 100644 index 0000000..1bba34d --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-quirks-mode.html
@@ -0,0 +1,60 @@ +<html class="reftest-wait"> +<title>The default scroll-timeline at rule in quirks mode</title> +<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-at-rule"> +<link rel="help" href="https://drafts.csswg.org/css-animations-2/#animation-timeline"> +<meta name="assert" content="CSS animation correctly updates values when using the default scroll-timeline at rule"> +<link rel="match" href="at-scroll-timeline-default-descriptors-ref.html"> + +<style> + @keyframes update { + from { transform: translateY(0px); } + to { transform: translateY(200px); } + } + + @scroll-timeline test-timeline { + source: auto; + orientation: auto; + scroll-offsets: none; + } + + html { + min-height: 100%; + padding-bottom: 100px; + } + + #box { + width: 100px; + height: 100px; + background-color: green; + animation: update 1s linear; + animation-timeline: test-timeline; + } + + #covered { + width: 100px; + height: 100px; + background-color: red; + } + + * { + margin-top: 0px; + margin-bottom: 0px; + } +</style> + +<div id="box"></div> +<div id="covered"></div> + +<script> + window.addEventListener('load', function() { + const scroller = document.scrollingElement; + + // Move the scroller to the halfway point. + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + scroller.scrollTop = 0.5 * maxScroll; + + window.requestAnimationFrame(() => { + document.documentElement.classList.remove("reftest-wait"); + }); + }); +</script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-ref.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-ref.html new file mode 100644 index 0000000..b72a4e8 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-ref.html
@@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>Reference for default @scroll-timeline</title> +<style> + html { + min-height: 100%; + padding-bottom: 100px; + } + + #box { + width: 100px; + height: 100px; + background-color: green; + transform: translateY(100px); + } + + * { + margin-top: 0px; + margin-bottom: 0px; + } +</style> + +<div id="box"></div> + +<script> + window.addEventListener('load', function() { + // Move the scroller to halfway. + const scroller = document.scrollingElement; + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + scroller.scrollTop = 0.5 * maxScroll; + }); +</script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-writing-mode-rl-ref.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-writing-mode-rl-ref.html new file mode 100644 index 0000000..2ea7d175 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-writing-mode-rl-ref.html
@@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>Reference for default @scroll-timeline with vertical-rl</title> +<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> +<style> + html { + min-block-size: 100%; + padding-block-end: 100px; + writing-mode: vertical-rl + } + + #box { + width: 100px; + height: 100px; + background-color: green; + transform: translateX(-100px); + } + + * { + margin-block: 0px; + } +</style> + +<div id="box"></div> + +<script> + window.addEventListener('load', function() { + // Move the scroller to halfway. + const scroller = document.scrollingElement; + const maxScroll = scroller.scrollWidth - scroller.clientWidth; + scroller.scrollLeft = -0.5 * maxScroll; + }); +</script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-writing-mode-rl.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-writing-mode-rl.html new file mode 100644 index 0000000..4d276497 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors-writing-mode-rl.html
@@ -0,0 +1,63 @@ +<!DOCTYPE HTML> +<html class="reftest-wait"> +<title>The default scroll-timeline at rule with writing-mode:vertical-rl</title> +<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> +<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-at-rule"> +<link rel="help" href="https://drafts.csswg.org/css-animations-2/#animation-timeline"> +<meta name="assert" content="CSS animation correctly updates values when using + the default scroll-timeline at rule with writing-mode:vertical-rl"> +<link rel="match" href="at-scroll-timeline-default-descriptors-writing-mode-rl-ref.html"> + +<style> + @keyframes update { + from { transform: translateX(0px); } + to { transform: translateX(-200px); } + } + + @scroll-timeline test-timeline { + source: auto; + orientation: auto; + scroll-offsets: none; + } + + html { + min-block-size: 100%; + padding-block-end: 100px; + writing-mode: vertical-rl; + } + + #box { + width: 100px; + height: 100px; + background-color: green; + animation: update 1s linear; + animation-timeline: test-timeline; + } + + #covered { + width: 100px; + height: 100px; + background-color: red; + } + + * { + margin-block: 0px; + } +</style> + +<div id="box"></div> +<div id="covered"></div> + +<script> + window.addEventListener('load', function() { + const scroller = document.scrollingElement; + + // Move the scroller to the halfway point. + const maxScroll = scroller.scrollWidth - scroller.clientWidth; + scroller.scrollLeft = -0.5 * maxScroll; + + window.requestAnimationFrame(() => { + document.documentElement.classList.remove("reftest-wait"); + }); + }); +</script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors.html new file mode 100644 index 0000000..5822534 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-default-descriptors.html
@@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html class="reftest-wait"> +<title>The default scroll-timeline at rule</title> +<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-at-rule"> +<link rel="help" href="https://drafts.csswg.org/css-animations-2/#animation-timeline"> +<meta name="assert" content="CSS animation correctly updates values when using the default scroll-timeline at rule"> +<link rel="match" href="at-scroll-timeline-default-descriptors-ref.html"> + +<style> + @keyframes update { + from { transform: translateY(0px); } + to { transform: translateY(200px); } + } + + @scroll-timeline test-timeline { + source: auto; + orientation: auto; + scroll-offsets: none; + } + + html { + min-height: 100%; + padding-bottom: 100px; + } + + #box { + width: 100px; + height: 100px; + background-color: green; + animation: update 1s linear; + animation-timeline: test-timeline; + } + + #covered { + width: 100px; + height: 100px; + background-color: red; + } + + * { + margin-top: 0px; + margin-bottom: 0px; + } +</style> + +<div id="box"></div> +<div id="covered"></div> + +<script> + window.addEventListener('load', function() { + const scroller = document.scrollingElement; + + // Move the scroller to the halfway point. + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + scroller.scrollTop = 0.5 * maxScroll; + + window.requestAnimationFrame(() => { + document.documentElement.classList.remove("reftest-wait"); + }); + }); +</script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-frame-size-changed-ref.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-frame-size-changed-ref.html new file mode 100644 index 0000000..745c2f54 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-frame-size-changed-ref.html
@@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>Reference for default @scroll-timeline</title> +<style> + html { + min-height: 100%; + padding-bottom: 50px; + } + + #box { + width: 100px; + height: 100px; + background-color: green; + transform: translateY(100px); + } + + * { + margin-top: 0px; + margin-bottom: 0px; + } +</style> + +<div id="box"></div> + +<script> + window.addEventListener('load', function() { + // Move the scroller to halfway. + const scroller = document.scrollingElement; + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + scroller.scrollTop = 0.5 * maxScroll; + }); +</script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-frame-size-changed.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-frame-size-changed.html new file mode 100644 index 0000000..e41af99 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-frame-size-changed.html
@@ -0,0 +1,68 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<title>The default scroll-timeline at rule when the frame size changed</title> +<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-at-rule"> +<link rel="help" href="https://drafts.csswg.org/css-animations-2/#animation-timeline"> +<meta name="assert" content="CSS animation correctly updates values when using + the default scroll-timeline at rule and update the + frame size"> +<link rel="match" href="at-scroll-timeline-frame-size-changed-ref.html"> + +<style> + @keyframes update { + from { transform: translateY(0px); } + to { transform: translateY(200px); } + } + + @scroll-timeline test-timeline { + source: auto; + orientation: auto; + scroll-offsets: none; + } + + html { + min-height: 100%; + padding-bottom: 100px; + } + + #box { + width: 100px; + height: 100px; + background-color: green; + animation: update 1s linear; + animation-timeline: test-timeline; + } + + #covered { + width: 100px; + height: 100px; + background-color: red; + } + + * { + margin-top: 0px; + margin-bottom: 0px; + } +</style> + +<div id="box"></div> +<div id="covered"></div> + +<script> + window.addEventListener('load', function() { + const scroller = document.scrollingElement; + + // Move the scroller to the 25% point. + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + scroller.scrollTop = 0.25 * maxScroll; + + window.requestAnimationFrame(() => { + // Update scroll range to make the current position become 50% point. + scroller.style.paddingBottom = "50px"; + + window.requestAnimationFrame(() => { + document.documentElement.classList.remove("reftest-wait"); + }); + }); + }); +</script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-inline-orientation-ref.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-inline-orientation-ref.html new file mode 100644 index 0000000..e87901e --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-inline-orientation-ref.html
@@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>Reference for @scroll-timeline with inline orientation</title> +<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> +<style> + html { + min-width: 100%; + padding-right: 100px; + } + + #box { + width: 100px; + height: 100px; + background-color: green; + transform: translateX(100px); + } + + * { + margin-left: 0px; + margin-right: 0px; + } +</style> + +<div id="box"></div> + +<script> + window.addEventListener('load', function() { + // Move the scroller to halfway. + const scroller = document.scrollingElement; + const maxScroll = scroller.scrollWidth - scroller.clientWidth; + scroller.scrollLeft = 0.5 * maxScroll; + }); +</script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-inline-orientation.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-inline-orientation.html new file mode 100644 index 0000000..ca6ef1c3 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/at-scroll-timeline-inline-orientation.html
@@ -0,0 +1,66 @@ +<!DOCTYPE HTML> +<html class="reftest-wait"> +<title>The scroll-timeline at rule with inline orientation and default source</title> +<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1"> +<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-at-rule"> +<link rel="help" href="https://drafts.csswg.org/scroll-animations-1/#descdef-scroll-timeline-orientation"> +<link rel="help" href="https://drafts.csswg.org/css-animations-2/#animation-timeline"> +<meta name="assert" content="CSS animation correctly updates values when using the inline orientation"> +<link rel="match" href="at-scroll-timeline-inline-orientation-ref.html"> + +<style> + @keyframes update { + from { transform: translateX(0px); } + to { transform: translateX(200px); } + } + + @scroll-timeline test-timeline { + source: auto; + orientation: inline; + scroll-offsets: none; + } + + html { + min-width: 100%; + padding-right: 100px; + font-size: 0; + } + + #box { + width: 100px; + height: 100px; + background-color: green; + animation: update 1s linear; + animation-timeline: test-timeline; + display: inline-block; + } + + #covered { + width: 100px; + height: 100px; + background-color: red; + display: inline-block; + } + + * { + margin-left: 0px; + margin-right: 0px; + } +</style> + +<div id="box"></div> +<div id="covered"></div> + +<script> + window.addEventListener('load', function() { + const scroller = document.scrollingElement; + + // Move the scroller to the halfway point. + const maxScroll = scroller.scrollWidth - scroller.clientWidth; + scroller.scrollLeft = 0.5 * maxScroll; + + window.requestAnimationFrame(() => { + document.documentElement.classList.remove("reftest-wait"); + }); + }); +</script>
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/service-workers/service-worker/resource-timing-fetch-variants.https-expected.txt b/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/service-workers/service-worker/resource-timing-fetch-variants.https-expected.txt deleted file mode 100644 index ed296ab9..0000000 --- a/third_party/blink/web_tests/platform/mac-mac11-arm64/external/wpt/service-workers/service-worker/resource-timing-fetch-variants.https-expected.txt +++ /dev/null
@@ -1,8 +0,0 @@ -This is a testharness.js-based test. -PASS Redirects done from within a service-worker should not be exposed to client ResourceTiming -PASS Connection info from within a service-worker should not be exposed to client ResourceTiming -PASS requestStart should never be before fetchStart -PASS Delay from within service-worker (after internal fetching) should be accessible through `responseStart` -PASS Delay from within service-worker (before internal fetching) should be measured before responseStart in the client ResourceTiming entry -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/platform/win/virtual/text-antialias/fallback-traits-fixup-expected.png b/third_party/blink/web_tests/platform/win/virtual/text-antialias/fallback-traits-fixup-expected.png index 1d052caf..7f9a2eae 100644 --- a/third_party/blink/web_tests/platform/win/virtual/text-antialias/fallback-traits-fixup-expected.png +++ b/third_party/blink/web_tests/platform/win/virtual/text-antialias/fallback-traits-fixup-expected.png Binary files differ
diff --git a/third_party/sqlite/fuzz/sql_generate_corpus.cc b/third_party/sqlite/fuzz/sql_generate_corpus.cc index a6eace4..65a9911 100644 --- a/third_party/sqlite/fuzz/sql_generate_corpus.cc +++ b/third_party/sqlite/fuzz/sql_generate_corpus.cc
@@ -255,7 +255,7 @@ table->col_types[*it]); it++; el->mutable_extra_exprs()->Reserve(cols.size() - 1); - for (size_t i = 0; i < cols.size() - 1; i++) { + for (size_t c = 0; c < cols.size() - 1; c++) { GenerateLiteralValue(el->mutable_extra_exprs()->Add()->mutable_lit_val(), table->col_types[*it]); it++;
diff --git a/tools/mac/power/benchmark.py b/tools/mac/power/benchmark.py index 2c138df0..fc50cd66 100755 --- a/tools/mac/power/benchmark.py +++ b/tools/mac/power/benchmark.py
@@ -93,6 +93,8 @@ # This is the average brightness from UMA data. driver.SetMainDisplayBrightness(65) + driver.WaitBatteryNotFull() + # Measure or Profile all defined scenarios. browser_factory = lambda browser_name: browsers.MakeBrowserDriver( browser_name,
diff --git a/tools/mac/power/driver.py b/tools/mac/power/driver.py index 00238b3e..ced1b58 100644 --- a/tools/mac/power/driver.py +++ b/tools/mac/power/driver.py
@@ -3,9 +3,11 @@ # found in the LICENSE file. import ctypes +import io import json import logging import os +import pandas as pd import signal import subprocess import sys @@ -65,6 +67,35 @@ DisplayServices.DisplayServicesSetBrightness(main_display, brightness_level / 100) + def WaitBatteryNotFull(self): + # Empirical evidence has shown that right after a full battery charge, the + # current capacity stays equal to the maximum capacity for several minutes, + # despite the fact that power is definitely consumed. To ensure that power + # consumption estimates from battery level are meaningful, wait until the + # battery is no longer reporting being fully charged before benchmarking. + + power_sampler_args = [ + self._power_sample_path, "--sample-on-notification", + "--samplers=battery", "--sample-count=1" + ] + + logging.info('Waiting for battery to no longer be full') + + while True: + power_sampler_output = subprocess.check_output(power_sampler_args) + power_sampler_data = pd.read_csv(io.BytesIO(power_sampler_output)) + max_capacity = power_sampler_data.iloc[0]['battery_max_capacity(Ah)'] + current_capacity = power_sampler_data.iloc[0][ + 'battery_current_capacity(Ah)'] + + logging.info( + f'Battery level is {100 * current_capacity / max_capacity:.2f}%') + + if max_capacity != current_capacity: + return + + logging.info('Battery is no longer full') + def CheckEnv(self, throw_on_bad_env: bool): """Verifies that the environment is conducive to proper profiling or measurements.
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml index ff08c55f..6afd030 100644 --- a/tools/metrics/actions/actions.xml +++ b/tools/metrics/actions/actions.xml
@@ -23926,6 +23926,18 @@ </description> </action> +<action name="SafeBrowsing.Download.WarningBypassed"> + <owner>xinghuilu@chromium.org</owner> + <owner>chrome-safebrowsing-alerts@google.com</owner> + <description>User bypasses a download warning.</description> +</action> + +<action name="SafeBrowsing.Download.WarningShown" not_user_triggered="true"> + <owner>xinghuilu@chromium.org</owner> + <owner>chrome-safebrowsing-alerts@google.com</owner> + <description>A download warning is shown.</description> +</action> + <action name="SafeBrowsing.Settings.DisableSafeBrowsingClicked"> <owner>xinghuilu@chromium.org</owner> <owner>chrome-safebrowsing-alerts@google.com</owner>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index 1141861..ba5499d 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml
@@ -20448,6 +20448,28 @@ <int value="4" label="RollbackDisallowed"/> </enum> +<enum name="DeviceActiveClientPsmResponse"> +<!-- This must be kept current with DeviceActivityClient::PsmResponse + located in ash/components/device_activity/device_activity_client.h --> + + <int value="0" label="Unknown"/> + <int value="1" label="Success"/> + <int value="2" label="Error"/> + <int value="3" label="Timeout"/> +</enum> + +<enum name="DeviceActiveClientState"> +<!-- This must be kept current with DeviceActivityClient::State + located in ash/components/device_activity/device_activity_client.h --> + + <int value="0" label="Unknown"/> + <int value="1" label="Idle"/> + <int value="2" label="CheckingMembershipOprf"/> + <int value="3" label="CheckingMembershipQuery"/> + <int value="4" label="CheckingIn"/> + <int value="5" label="HealthCheck"/> +</enum> + <enum name="DeviceIdMismatch"> <int value="0" label="BOTH_NONEMPTY"/> <int value="1" label="SYNC_EMPTY"/> @@ -82000,11 +82022,7 @@ resource"/> <int value="6" label="Abandoned because no content was visible at the beginning of - startup. Hint: often caused by showing the profile picker - instead of immediately opening a browser window on startup"/> - <int value="7" - label="Abandoned because the content was already painted before - profiling started"/> + startup"/> </enum> <enum name="StartupTabPreloaderLoadDecisionCause">
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml index 08ac0a92..0744350 100644 --- a/tools/metrics/histograms/metadata/ash/histograms.xml +++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -31,6 +31,21 @@ summary="Metrics related to in app to home gesture's nudes and usage."/> </variants> +<variants name="DeviceActiveClientState"> + <variant name="CheckingIn" + summary="The device active reporting client is in Checking In State"/> + <variant name="CheckingMembershipOprf" + summary="The device active reporting client is in Oprf State"/> + <variant name="CheckingMembershipQuery" + summary="The device active reporting client is in Query State"/> + <variant name="HealthCheck" + summary="The device active reporting client is in Health Check State"/> + <variant name="Idle" + summary="The device active reporting client is in Idle State"/> + <variant name="Unknown" + summary="The device active reporting client is in Unknown State"/> +</variants> + <variants name="DisplayModes"> <variant name="ClamshellMode" summary="Clamshell mode"/> <variant name="TabletMode" summary="Tablet mode"/> @@ -1318,6 +1333,50 @@ </summary> </histogram> +<histogram name="Ash.DeviceActiveClient.Duration.{DeviceActiveClientState}" + units="ms" expires_after="2022-11-01"> + <owner>hirthanan@google.com</owner> + <owner>chromeos-data-team@google.com</owner> + <summary> + Record the total duration to transition between DeviceActivityClient states. + States are defined in //ash/components/device_activity_client.h. ChromeOS + only. {DeviceActiveClientState} + </summary> + <token key="DeviceActiveClientState" variants="DeviceActiveClientState"/> +</histogram> + +<histogram name="Ash.DeviceActiveClient.QueryMembershipResult" + enum="BooleanSuccess" expires_after="2022-11-01"> + <owner>hirthanan@google.com</owner> + <owner>chromeos-data-team@google.com</owner> + <summary> + Whether the private set membership query response is true or false. + </summary> +</histogram> + +<histogram name="Ash.DeviceActiveClient.Response.{DeviceActiveClientState}" + enum="DeviceActiveClientPsmResponse" expires_after="2022-11-01"> + <owner>hirthanan@google.com</owner> + <owner>chromeos-data-team@google.com</owner> + <summary> + Record the PsmResponse whenever a response is received from the server. + PsmResponse is defined in //ash/components/device_activity_client.h. + ChromeOS only. {DeviceActiveClientState} + </summary> + <token key="DeviceActiveClientState" variants="DeviceActiveClientState"/> +</histogram> + +<histogram name="Ash.DeviceActiveClient.StateCount" + enum="DeviceActiveClientState" expires_after="2022-11-01"> + <owner>hirthanan@google.com</owner> + <owner>chromeos-data-team@google.com</owner> + <summary> + Recorded every time the DeviceActivityClient enters a new state in its FSM. + States are defined in //ash/components/device_activity_client.h. ChromeOS + only. + </summary> +</histogram> + <histogram name="Ash.Display.InternalDisplay.ActiveEffectiveResolution" enum="EffectiveResolution" expires_after="2022-04-03"> <owner>malaykeshav@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/chromeos/histograms.xml b/tools/metrics/histograms/metadata/chromeos/histograms.xml index 9bbd0104..7242b1ee 100644 --- a/tools/metrics/histograms/metadata/chromeos/histograms.xml +++ b/tools/metrics/histograms/metadata/chromeos/histograms.xml
@@ -2241,7 +2241,7 @@ </histogram> <histogram name="ChromeOS.WebAPK.{InstallType}.ArcInstallResult" - enum="WebApkArcInstallResultChromeOS" expires_after="2022-01-14"> + enum="WebApkArcInstallResultChromeOS" expires_after="2022-04-14"> <owner>tsergeant@chromium.org</owner> <owner>chromeos-apps-foundation-team@google.com</owner> <summary> @@ -2258,7 +2258,7 @@ </histogram> <histogram name="ChromeOS.WebAPK.{InstallType}.Result" - enum="WebApkInstallResultChromeOS" expires_after="2022-01-14"> + enum="WebApkInstallResultChromeOS" expires_after="2022-04-14"> <owner>tsergeant@chromium.org</owner> <owner>chromeos-apps-foundation-team@google.com</owner> <summary>
diff --git a/tools/metrics/histograms/metadata/cross_device/histograms.xml b/tools/metrics/histograms/metadata/cross_device/histograms.xml index f6b2501..d8fe0001 100644 --- a/tools/metrics/histograms/metadata/cross_device/histograms.xml +++ b/tools/metrics/histograms/metadata/cross_device/histograms.xml
@@ -1910,7 +1910,7 @@ </histogram> <histogram name="MultiDevice.SecureChannel.Nearby.OperationResult.{Function}" - enum="NearbyConnectionsStatus" expires_after="2022-01-01"> + enum="NearbyConnectionsStatus" expires_after="2023-01-01"> <owner>hansenmichael@google.com</owner> <owner>better-together-dev@google.com</owner> <summary>
diff --git a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml index ee779d6..769b417 100644 --- a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml +++ b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
@@ -9947,6 +9947,7 @@ <suffix name="ChromeUpdate" label=""/> <suffix name="Prefetch" label=""/> <suffix name="ReadingList" label=""/> + <suffix name="FeatureGuide" label=""/> <suffix name="Unknown" label=""/> <suffix name="WebUI" label=""/> <affected-histogram name="Notifications.Scheduler.IhnrActionButtonEvent"/>
diff --git a/tools/metrics/histograms/metadata/memory/histograms.xml b/tools/metrics/histograms/metadata/memory/histograms.xml index 042c1337..0b7ecbb7 100644 --- a/tools/metrics/histograms/metadata/memory/histograms.xml +++ b/tools/metrics/histograms/metadata/memory/histograms.xml
@@ -403,6 +403,22 @@ </summary> </histogram> +<histogram name="Memory.Browser.PrivateMemoryFootprint.HasZombieProfile" + units="MB" expires_after="2022-04-20"> + <owner>nicolaso@chromium.org</owner> + <owner>cbe-eng@google.com</owner> + <summary> + Same as Memory.Browser.PrivateMemoryFootprint, but only gets recorded after + a Profile becomes safe to delete during this browsing session. This lets us + measure the memory impact of destroying Profile objects and their + KeyedServices. + + This is not meant to be compared with Memory.Browser.PrivateMemoryFootprint, + as it measures a different scenario. Instead, it should be compared with + itself in an A/B test with the DestroyProfileOnBrowserClose variation. + </summary> +</histogram> + <histogram name="Memory.Browser.PrivateSwapFootprint" units="MB" expires_after="2023-01-10"> <owner>erikchen@chromium.org</owner> @@ -2682,6 +2698,22 @@ </summary> </histogram> +<histogram name="Memory.Total.PrivateMemoryFootprint.HasZombieProfile" + units="MB" expires_after="2022-04-20"> + <owner>nicolaso@chromium.org</owner> + <owner>cbe-eng@google.com</owner> + <summary> + Same as Memory.Total.PrivateMemoryFootprint, but only gets recorded after a + Profile becomes safe to delete during this browsing session. This lets us + measure the memory impact of destroying Profile objects and their + KeyedServices. + + This is not meant to be compared with Memory.Total.PrivateMemoryFootprint, + as it measures a different scenario. Instead, it should be compared with + itself in an A/B test with the DestroyProfileOnBrowserClose variation. + </summary> +</histogram> + <histogram name="Memory.Total.RendererMalloc" units="MB" expires_after="2022-10-01"> <owner>keishi@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/omnibox/histograms.xml b/tools/metrics/histograms/metadata/omnibox/histograms.xml index a7cda43c..2a180777 100644 --- a/tools/metrics/histograms/metadata/omnibox/histograms.xml +++ b/tools/metrics/histograms/metadata/omnibox/histograms.xml
@@ -507,7 +507,7 @@ </histogram> <histogram name="Omnibox.LocalHistoryZeroSuggest.AsyncDeleteTime" units="ms" - expires_after="2022-01-01"> + expires_after="2022-06-05"> <owner>mahmadi@chromium.org</owner> <owner>tommycli@chromium.org</owner> <summary> @@ -518,7 +518,7 @@ </histogram> <histogram name="Omnibox.LocalHistoryZeroSuggest.SearchTermsExtractedCount" - units="count" expires_after="2022-01-01"> + units="count" expires_after="2022-06-05"> <owner>mahmadi@chromium.org</owner> <owner>tommycli@chromium.org</owner> <summary> @@ -530,7 +530,7 @@ </histogram> <histogram name="Omnibox.LocalHistoryZeroSuggest.SearchTermsExtractionTime" - units="ms" expires_after="2022-01-01"> + units="ms" expires_after="2022-06-05"> <owner>mahmadi@chromium.org</owner> <owner>tommycli@chromium.org</owner> <summary> @@ -541,7 +541,7 @@ </histogram> <histogram name="Omnibox.LocalHistoryZeroSuggest.SyncDeleteTime" units="ms" - expires_after="2022-01-01"> + expires_after="2022-06-05"> <owner>mahmadi@chromium.org</owner> <owner>tommycli@chromium.org</owner> <summary>
diff --git a/tools/metrics/histograms/metadata/password/histograms.xml b/tools/metrics/histograms/metadata/password/histograms.xml index 4a49e6a..9e3459e 100644 --- a/tools/metrics/histograms/metadata/password/histograms.xml +++ b/tools/metrics/histograms/metadata/password/histograms.xml
@@ -2282,9 +2282,13 @@ </summary> <token key="Function"> <variant name="AddLoginAsync" summary="add a login to"/> + <variant name="FillMatchingLoginsAsync" + summary="fill matching logins from"/> <variant name="GetAllLoginsAsync" summary="retrieve all logins from"/> <variant name="GetAutofillableLoginsAsync" summary="retrieve autofillable logins from"/> + <variant name="GetLoginsAsync" + summary="retrieve logins filtered by signon realm from"/> <variant name="RemoveLoginAsync" summary="remove a login from"/> <variant name="UpdateLoginAsync" summary="update a login in"/> </token> @@ -2303,11 +2307,15 @@ <token key="Function"> <variant name="AddLoginAsync" summary="AddLoginAsync() succeeded to add a login to"/> + <variant name="FillMatchingLoginsAsync" + summary="succeeded in filling matching logins from"/> <variant name="GetAllLoginsAsync" summary="GetAllLoginsAsync() succeeded in retrieving all logins from"/> <variant name="GetAutofillableLoginsAsync" summary="GetAutofillableLoginsAsync() succeeded in retrieving autofillable logins from"/> + <variant name="GetLoginsAsync" + summary="succeeded in retrieving logins filtered by signon realm from"/> <variant name="RemoveLoginAsync" summary="RemoveLoginAsync() succeeded in removing a login from"/> <variant name="UpdateLoginAsync" @@ -2327,7 +2335,10 @@ <token key="Function"> <variant name=""/> <variant name=".AddLoginAsync" summary="for AddLoginAsync"/> + <variant name=".FillMatchingLoginsAsync" + summary="for FillMatchingLoginsAsync"/> <variant name=".GetAllLoginsAsync" summary="for GetAllLoginsAsync"/> + <variant name=".GetLoginsAsync" summary="for GetLoginsAsync"/> <variant name=".RemoveLoginAsync" summary="for RemoveLoginAsync"/> <variant name=".UpdateLoginAsync" summary="for UpdateLoginAsync"/> </token> @@ -2346,7 +2357,11 @@ <variant name="" summary="retrieving/adding/updating or removing logins from"/> <variant name=".AddLoginAsync" summary="adding a login to"/> + <variant name=".FillMatchingLoginsAsync" + summary="filling matching logins from"/> <variant name=".GetAllLoginsAsync" summary="retrieving all logins from"/> + <variant name=".GetLoginsAsync" + summary="retrieving logins filtered by signon realm from"/> <variant name=".RemoveLoginAsync" summary="removing a login from"/> <variant name=".UpdateLoginAsync" summary="updating a login in"/> </token>
diff --git a/tools/metrics/histograms/metadata/profile/histograms.xml b/tools/metrics/histograms/metadata/profile/histograms.xml index a2e82be2..15911f6 100644 --- a/tools/metrics/histograms/metadata/profile/histograms.xml +++ b/tools/metrics/histograms/metadata/profile/histograms.xml
@@ -220,6 +220,16 @@ </summary> </histogram> +<histogram name="Profile.DidDestroyProfileBeforeShutdown" enum="Boolean" + expires_after="2022-04-01"> + <owner>nicolaso@chromium.org</owner> + <owner>cbe-eng@google.com</owner> + <summary> + Whether any profile reached a refcount of 0 at any point, prior to exiting + Chrome. Recorded during teardown. + </summary> +</histogram> + <histogram name="Profile.EphemeralGuest.Signin" enum="BooleanProfileSignedIn" expires_after="2022-02-02"> <obsolete> @@ -837,37 +847,6 @@ </summary> </histogram> -<histogram name="ProfilePicker.FirstProfileTime.FirstWebContentsFinishReason" - enum="StartupProfilingFinishReason" expires_after="2022-04-03"> - <owner>dgn@chromium.org</owner> - <owner>chrome-signin-team@google.com</owner> - <summary> - [Desktop] The reason for which profile picker startup profiling was deemed - complete. Logged once per session when the user opens a profile from the - profile picker shown on startup. - - Used to understand user behavior shifts when - ProfilePicker.FirstProfileTime.FirstWebContentsNonEmptyPaint regresses - </summary> -</histogram> - -<histogram name="ProfilePicker.FirstProfileTime.FirstWebContentsNonEmptyPaint" - units="ms" expires_after="2022-04-03"> - <owner>dgn@chromium.org</owner> - <owner>chrome-signin-team@google.com</owner> - <summary> - Measure the elapsed time from when the user selects a profile on the startup - profile picker to the first non empty paint of the first web contents. This - is recorded when the user selects an existing profile from that profile - picker shown on startup. - - Together with ProfilePicker.StartupTime.FirstPaint.FromApplicationStart, - this metric is intended to capture the startup latency for a common case - where Startup.FirstWebContents.NonEmptyPaint3 is not recorded due to the - profile picker interrupting browser startup. - </summary> -</histogram> - <histogram name="ProfilePicker.NewProfileCreateShortcut" enum="BooleanCreated" expires_after="M90"> <owner>msalama@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/startup/histograms.xml b/tools/metrics/histograms/metadata/startup/histograms.xml index 120eb31..3fd91665 100644 --- a/tools/metrics/histograms/metadata/startup/histograms.xml +++ b/tools/metrics/histograms/metadata/startup/histograms.xml
@@ -119,13 +119,54 @@ <histogram name="Startup.Android.Cold.TimeToFirstVisibleContent" units="ms" expires_after="2022-06-05"> <owner>yfriedman@chromium.org</owner> + <owner>pasko@chromium.org</owner> + <owner>fredmello@chromium.org</owner> <owner>yashard@chromium.org</owner> <summary> - The time from the activity creation point to the moment Chrome appears - ready. The intent is to reflect Clank's perceived cold start performance - regardless of different launch paths. The recorded value is the minimum of + The time from Chrome tabbed activity creation to the moment the Chrome first + appears ready. + + This metric captures when the omnibox is painted and either a navigation has + committed or a Paint Preview is shown. The intent is to reflect Clank's + perceived cold start performance regardless of different launch paths, while + controlling for the factors that we have the most direct influence over and + minimizing external factors. Note that network connectivity is mostly but + not entirely omitted as it does impact the initial response time for a site + which impacts the omnibox UX and loading metric. + + This metric captures the value of the first emitted metric out of the 'Startup.Android.Cold.TimeToFirstNavigationCommit.Tabbed' and - 'Browser.PaintPreview.TabbedPlayer.TimeToFirstBitmap' metric values. + 'Browser.PaintPreview.TabbedPlayer.TimeToFirstBitmap' metrics and emits the + value. + + This metric is only recorded once per application lifetime during cold + startup i.e. when Chrome's native library is not loaded at first activity + creation. Specifically, it is recorded for the first foreground committed + http(s) URL in the primary frame if that first navigation is not: an error, + same-document navigation, or fragment navigation. + + This metric is expected be stable from milestone to milestone. Updating + Chrome or the Android system image on the device are expected to result in a + noticeable slowdown on the first cold startup after. The total counts + changes for this histogram are not of significant importance; however, a + non-trivial increase in total counts could signal more background kills, for + example. + + This metric is not recorded: + + - If another navigation occurs before the first navigation commit and the + Paint Preview show. + + - If the navigation is pre-warmed, is a background navigation, or fails to + be committed (e.g. cancelled, network error, or error due to lack of + connectivity). + + - If the activity was ever backgrounded between activity creation and this + metric reporting. + + - If the app opens to the Chrome Start page. + + - For WebApps, PWAs or WebApks. </summary> </histogram> @@ -140,6 +181,11 @@ page. The recorded value is the minimum of 'Startup.Android.Cold.TimeToFirstContentfulPaint.Tabbed' and 'Browser.PaintPreview.TabbedPlayer.TimeToFirstBitmap' metric values. + + Not to be mistaken with the Startup.Android.Cold.TimeToFirstVisibleContent + histogram, as this metric is intended for the use of the Paint Preview + owners and the other is intended to track Clank's perceived cold start + performance. </summary> </histogram>
diff --git a/tools/metrics/histograms/metadata/tab/OWNERS b/tools/metrics/histograms/metadata/tab/OWNERS new file mode 100644 index 0000000..55680ad1 --- /dev/null +++ b/tools/metrics/histograms/metadata/tab/OWNERS
@@ -0,0 +1,5 @@ +per-file OWNERS=file://tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS + +# Prefer sending CLs to the owners listed below. +# Use chromium-metrics-reviews@google.com as a backup. +olivierli@chromium.org
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config index af8951e3..8a9a6e4c 100644 --- a/tools/perf/expectations.config +++ b/tools/perf/expectations.config
@@ -194,6 +194,7 @@ crbug.com/1114681 [ win-laptop ] media.desktop/video.html?src=smpte_3840x2160_60fps_vp9.webm&seek [ Skip ] crbug.com/1095610 [ win-laptop ] media.desktop/video.html?src=garden2_10s.mp4&seek [ Skip ] crbug.com/1211828 [ win ] media.desktop/video.html?src=tulip0.av1.mp4 [ Skip ] +crbug.com/1277968 [ win-laptop ] media.desktop/video.html?src=garden2_10s.webm&seek [ Skip ] # Benchmark: media.mobile crbug.com/1202988 [ fuchsia-board-astro ] media.mobile/video.html?src=boat_1080p60fps_vp9.webm [ Skip ] @@ -221,6 +222,7 @@ crbug.com/1226854 [ chromeos ] rendering.desktop/text_scrollbar_* [ Skip ] crbug.com/1229671 [ win ] rendering.desktop/espn_2018 [ Skip ] crbug.com/1230060 [ win-laptop ] rendering.desktop/yahoo_news_2018 [ Skip ] +crbug.com/1277970 [ win-laptop ] rendering.desktop/yahoo_sports_2018 [ Skip ] # Benchmark: rendering.mobile crbug.com/785485 [ android-webview ] rendering.mobile/kevs_3d [ Skip ] @@ -380,6 +382,7 @@ crbug.com/1196039 [ desktop ] system_health.common_desktop/browse:tools:earth:2020 [ Skip ] crbug.com/1174108 [ win-laptop ] system_health.common_desktop/browse:tools:autocad:2021 [ Skip ] crbug.com/1211795 [ mac ] system_health.common_desktop/browse:tools:autocad:2021 [ Skip ] +crbug.com/1277966 [ win-laptop ] system_health.common_desktop/browse:tools:maps:2019 [ Skip ] # Benchmark: system_health.common_mobile crbug.com/1007355 [ android-go android-webview ] system_health.common_mobile/load:media:imgur:2018 [ Skip ] @@ -402,6 +405,7 @@ crbug.com/1197082 [ android-go android-webview ] system_health.common_mobile/browse:shopping:amazon:2019 [ Skip ] crbug.com/1233945 [ android-go ] system_health.common_mobile/browse:news:businessinsider:2021 [ Skip ] crbug.com/1241400 [ android-webview ] system_health.common_mobile/browse:news:businessinsider:2021 [ Skip ] +crbug.com/1277985 [ android-pixel-2 ] system_health.common_mobile/browse:media:googleplaystore:2019 [ Skip ] # Benchmark: system_health.memory_desktop crbug.com/649392 system_health.memory_desktop/play:media:google_play_music [ Skip ] @@ -570,6 +574,7 @@ crbug.com/1211795 [ mac ] v8.browsing_desktop/browse:media:pinterest:2018 [ Skip ] crbug.com/1211795 [ mac ] v8.browsing_desktop/browse:social:twitter_infinite_scroll:2018 [ Skip ] crbug.com/1211795 [ mac ] v8.browsing_desktop/browse:social:facebook_infinite_scroll:2018 [ Skip ] +crbug.com/1277978 [ mac ] v8.browsing_desktop/browse:tools:photoshop:2021 [ Skip ] # Benchmark v8.browsing_desktop-future (keep in sync with v8.browsing_desktop above) crbug.com/788796 [ linux ] v8.browsing_desktop-future/browse:media:imgur [ Skip ] @@ -603,6 +608,7 @@ crbug.com/1211795 [ mac ] v8.browsing_desktop-future/browse:media:pinterest:2018 [ Skip ] crbug.com/1211795 [ mac ] v8.browsing_desktop-future/browse:social:twitter_infinite_scroll:2018 [ Skip ] crbug.com/1211795 [ mac ] v8.browsing_desktop-future/browse:social:facebook_infinite_scroll:2018 [ Skip ] +crbug.com/1277978 [ mac ] v8.browsing_desktop-future/browse:tools:photoshop:2021 [ Skip ] # Benchmark: v8.browsing_mobile (keep in sync with v8.browsing_mobile-future below) crbug.com/958034 [ android-go android-webview ] v8.browsing_mobile/* [ Skip ] @@ -619,6 +625,9 @@ crbug.com/1197099 [ android-pixel-4 android-webview ] v8.browsing_mobile/browse:media:imgur:2019 [ Skip ] crbug.com/1233945 [ android-go ] v8.browsing_mobile/browse:news:businessinsider:2021 [ Skip ] crbug.com/1241400 [ android-webview ] v8.browsing_mobile/browse:news:businessinsider:2021 [ Skip ] +crbug.com/1277994 [ android-go ] v8.browsing_mobile/browse:shopping:avito:2019 [ Skip ] +crbug.com/1277994 [ android-go ] v8.browsing_mobile/browse:shopping:flipkart:2019 [ Skip ] +crbug.com/1277994 [ android-go ] v8.browsing_mobile/browse:shopping:amazon:2019 [ Skip ] # Benchmark: v8.browsing_mobile-future (keep in sync with v8.browsing_mobile above) crbug.com/958034 [ android-go android-webview ] v8.browsing_mobile-future/* [ Skip ]
diff --git a/ui/accessibility/ax_common.h b/ui/accessibility/ax_common.h index 38f1bfb0..c89561c 100644 --- a/ui/accessibility/ax_common.h +++ b/ui/accessibility/ax_common.h
@@ -5,6 +5,8 @@ #ifndef UI_ACCESSIBILITY_AX_COMMON_H_ #define UI_ACCESSIBILITY_AX_COMMON_H_ +#include "build/build_config.h" + #if (!defined(NDEBUG) || defined(ADDRESS_SANITIZER) || \ defined(LEAK_SANITIZER) || defined(MEMORY_SANITIZER) || \ defined(THREAD_SANITIZER) || defined(UNDEFINED_SANITIZER) || \
diff --git a/ui/accessibility/platform/BUILD.gn b/ui/accessibility/platform/BUILD.gn index b5ae41f..450b5a92 100644 --- a/ui/accessibility/platform/BUILD.gn +++ b/ui/accessibility/platform/BUILD.gn
@@ -34,6 +34,7 @@ # Used by by browser_accessibility_state_impl.cc. "ax_platform_node.cc", "ax_platform_node.h", + "ax_platform_node_delegate.cc", "ax_platform_node_delegate.h", # Used by browser_accessibility.cc.
diff --git a/ui/accessibility/platform/ax_platform_node_cocoa.mm b/ui/accessibility/platform/ax_platform_node_cocoa.mm index 96320a6..d53f0f4 100644 --- a/ui/accessibility/platform/ax_platform_node_cocoa.mm +++ b/ui/accessibility/platform/ax_platform_node_cocoa.mm
@@ -831,6 +831,13 @@ [axAttributes addObject:NSAccessibilityColumnHeaderUIElementsAttribute]; } + // Popup + if (_node->HasIntAttribute(ax::mojom::IntAttribute::kHasPopup)) { + [axAttributes addObjectsFromArray:@[ + NSAccessibilityHasPopupAttribute, NSAccessibilityPopupValueAttribute + ]]; + } + return axAttributes.autorelease(); } @@ -1029,15 +1036,10 @@ return @""; } -- (NSNumber*)AXRequired { - return [self accessibilityRequired]; -} - -- (NSString*)AXRole { - if (!_node) +- (NSNumber*)AXHasPopup { + if (![self instanceActive]) return nil; - - return [[self class] nativeRoleFromAXRole:_node->GetRole()]; + return @(_node->HasIntAttribute(ax::mojom::IntAttribute::kHasPopup)); } - (NSString*)AXInvalid { @@ -1052,6 +1054,39 @@ } } +- (NSString*)AXPopupValue { + if (![self instanceActive]) + return nil; + int hasPopup = _node->GetIntAttribute(ax::mojom::IntAttribute::kHasPopup); + switch (static_cast<ax::mojom::HasPopup>(hasPopup)) { + case ax::mojom::HasPopup::kFalse: + return @"false"; + case ax::mojom::HasPopup::kTrue: + return @"true"; + case ax::mojom::HasPopup::kMenu: + return @"menu"; + case ax::mojom::HasPopup::kListbox: + return @"listbox"; + case ax::mojom::HasPopup::kTree: + return @"tree"; + case ax::mojom::HasPopup::kGrid: + return @"grid"; + case ax::mojom::HasPopup::kDialog: + return @"dialog"; + } +} + +- (NSNumber*)AXRequired { + return [self accessibilityRequired]; +} + +- (NSString*)AXRole { + if (!_node) + return nil; + + return [[self class] nativeRoleFromAXRole:_node->GetRole()]; +} + - (NSString*)AXRoleDescription { return [self accessibilityRoleDescription]; }
diff --git a/ui/accessibility/platform/ax_platform_node_delegate.cc b/ui/accessibility/platform/ax_platform_node_delegate.cc new file mode 100644 index 0000000..b1d01d7 --- /dev/null +++ b/ui/accessibility/platform/ax_platform_node_delegate.cc
@@ -0,0 +1,45 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/accessibility/platform/ax_platform_node_delegate.h" + +namespace ui { + +gfx::Rect AXPlatformNodeDelegate::GetClippedScreenBoundsRect( + AXOffscreenResult* offscreen_result) const { + return GetBoundsRect(AXCoordinateSystem::kScreenDIPs, + AXClippingBehavior::kClipped, offscreen_result); +} + +gfx::Rect AXPlatformNodeDelegate::GetUnclippedScreenBoundsRect( + AXOffscreenResult* offscreen_result) const { + return GetBoundsRect(AXCoordinateSystem::kScreenDIPs, + AXClippingBehavior::kUnclipped, offscreen_result); +} + +gfx::Rect AXPlatformNodeDelegate::GetClippedRootFrameBoundsRect( + AXOffscreenResult* offscreen_result) const { + return GetBoundsRect(AXCoordinateSystem::kRootFrame, + AXClippingBehavior::kClipped, offscreen_result); +} + +gfx::Rect AXPlatformNodeDelegate::GetUnclippedRootFrameBoundsRect( + AXOffscreenResult* offscreen_result) const { + return GetBoundsRect(AXCoordinateSystem::kRootFrame, + AXClippingBehavior::kUnclipped, offscreen_result); +} + +gfx::Rect AXPlatformNodeDelegate::GetClippedFrameBoundsRect( + AXOffscreenResult* offscreen_result) const { + return GetBoundsRect(AXCoordinateSystem::kFrame, AXClippingBehavior::kClipped, + offscreen_result); +} + +gfx::Rect AXPlatformNodeDelegate::GetUnclippedFrameBoundsRect( + AXOffscreenResult* offscreen_result) const { + return GetBoundsRect(AXCoordinateSystem::kFrame, + AXClippingBehavior::kUnclipped, offscreen_result); +} + +} // namespace ui
diff --git a/ui/accessibility/platform/ax_platform_node_delegate.h b/ui/accessibility/platform/ax_platform_node_delegate.h index ab273dc8..3d1374c8 100644 --- a/ui/accessibility/platform/ax_platform_node_delegate.h +++ b/ui/accessibility/platform/ax_platform_node_delegate.h
@@ -365,8 +365,19 @@ const AXClippingBehavior clipping_behavior, AXOffscreenResult* offscreen_result = nullptr) const = 0; - virtual gfx::Rect GetClippedScreenBoundsRect( - AXOffscreenResult* offscreen_result = nullptr) const = 0; + // Derivative utils for AXPlatformNodeDelegate::GetBoundsRect + gfx::Rect GetClippedScreenBoundsRect( + AXOffscreenResult* offscreen_result = nullptr) const; + gfx::Rect GetUnclippedScreenBoundsRect( + AXOffscreenResult* offscreen_result = nullptr) const; + gfx::Rect GetClippedRootFrameBoundsRect( + ui::AXOffscreenResult* offscreen_result = nullptr) const; + gfx::Rect GetUnclippedRootFrameBoundsRect( + ui::AXOffscreenResult* offscreen_result = nullptr) const; + gfx::Rect GetClippedFrameBoundsRect( + ui::AXOffscreenResult* offscreen_result = nullptr) const; + gfx::Rect GetUnclippedFrameBoundsRect( + ui::AXOffscreenResult* offscreen_result = nullptr) const; // Return the bounds of the text range given by text offsets relative to // GetHypertext in the coordinate system indicated. If the clipping behavior
diff --git a/ui/accessibility/platform/ax_platform_node_delegate_base.cc b/ui/accessibility/platform/ax_platform_node_delegate_base.cc index 44593eff..4dbd1b1 100644 --- a/ui/accessibility/platform/ax_platform_node_delegate_base.cc +++ b/ui/accessibility/platform/ax_platform_node_delegate_base.cc
@@ -591,18 +591,6 @@ return gfx::Rect(); } -gfx::Rect AXPlatformNodeDelegateBase::GetClippedScreenBoundsRect( - AXOffscreenResult* offscreen_result) const { - return GetBoundsRect(AXCoordinateSystem::kScreenDIPs, - AXClippingBehavior::kClipped, offscreen_result); -} - -gfx::Rect AXPlatformNodeDelegateBase::GetUnclippedScreenBoundsRect( - AXOffscreenResult* offscreen_result) const { - return GetBoundsRect(AXCoordinateSystem::kScreenDIPs, - AXClippingBehavior::kUnclipped, offscreen_result); -} - gfx::NativeViewAccessible AXPlatformNodeDelegateBase::HitTestSync( int screen_physical_pixel_x, int screen_physical_pixel_y) const {
diff --git a/ui/accessibility/platform/ax_platform_node_delegate_base.h b/ui/accessibility/platform/ax_platform_node_delegate_base.h index f06fbc7..87fac77 100644 --- a/ui/accessibility/platform/ax_platform_node_delegate_base.h +++ b/ui/accessibility/platform/ax_platform_node_delegate_base.h
@@ -194,12 +194,6 @@ const AXClippingBehavior clipping_behavior, AXOffscreenResult* offscreen_result) const override; - // Derivative utils for AXPlatformNodeDelegate::GetBoundsRect - gfx::Rect GetClippedScreenBoundsRect( - AXOffscreenResult* offscreen_result = nullptr) const override; - gfx::Rect GetUnclippedScreenBoundsRect( - AXOffscreenResult* offscreen_result = nullptr) const; - // Do a *synchronous* hit test of the given location in global screen physical // pixel coordinates, and the node within this node's subtree (inclusive) // that's hit, if any.
diff --git a/ui/accessibility/platform/ax_private_attributes_mac.h b/ui/accessibility/platform/ax_private_attributes_mac.h index 28e2086..d341a715 100644 --- a/ui/accessibility/platform/ax_private_attributes_mac.h +++ b/ui/accessibility/platform/ax_private_attributes_mac.h
@@ -40,6 +40,8 @@ @"AXDetailsElements"; AX_EXPORT constexpr NSString* const NSAccessibilityDOMIdentifierAttribute = @"AXDOMIdentifier"; +AX_EXPORT constexpr NSString* const NSAccessibilityHasPopupAttribute = + @"AXHasPopup"; AX_EXPORT constexpr NSString* const NSAccessibilityInvalidAttribute = @"AXInvalid"; AX_EXPORT constexpr NSString* const @@ -65,6 +67,8 @@ @"AXMathPostscripts"; AX_EXPORT constexpr NSString* const NSAccessibilityMathPrescriptsAttribute = @"AXMathPrescripts"; +AX_EXPORT constexpr NSString* const NSAccessibilityPopupValueAttribute = + @"AXPopupValue"; #if defined(MAC_OS_X_VERSION_10_12) && \ (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12)
diff --git a/ui/accessibility/platform/inspect/ax_inspect_utils_mac.mm b/ui/accessibility/platform/inspect/ax_inspect_utils_mac.mm index 5d04853..d60b7815b 100644 --- a/ui/accessibility/platform/inspect/ax_inspect_utils_mac.mm +++ b/ui/accessibility/platform/inspect/ax_inspect_utils_mac.mm
@@ -49,6 +49,7 @@ NSAccessibilityAutocompleteValueAttribute, NSAccessibilityColumnHeaderUIElementsAttribute, NSAccessibilityDetailsElementsAttribute, + NSAccessibilityHasPopupAttribute, NSAccessibilityInvalidAttribute, NSAccessibilityMathFractionNumeratorAttribute, NSAccessibilityMathFractionDenominatorAttribute, @@ -61,6 +62,7 @@ NSAccessibilityMathOverAttribute, NSAccessibilityMathPostscriptsAttribute, NSAccessibilityMathPrescriptsAttribute, + NSAccessibilityPopupValueAttribute, NSAccessibilityRequiredAttributeChrome, NSAccessibilityRoleDescriptionAttribute, NSAccessibilityURLAttribute},
diff --git a/ui/base/test/ui_controls_aura.h b/ui/base/test/ui_controls_aura.h index a2085773..7ee40a0 100644 --- a/ui/base/test/ui_controls_aura.h +++ b/ui/base/test/ui_controls_aura.h
@@ -6,6 +6,7 @@ #define UI_BASE_TEST_UI_CONTROLS_AURA_H_ #include "base/callback_forward.h" +#include "build/build_config.h" #include "build/chromeos_buildflags.h" #include "ui/base/test/ui_controls.h" #include "ui/events/keycodes/keyboard_codes.h"
diff --git a/ui/color/BUILD.gn b/ui/color/BUILD.gn index 4f32f6c..e47770d 100644 --- a/ui/color/BUILD.gn +++ b/ui/color/BUILD.gn
@@ -92,6 +92,27 @@ ] } +if (is_win) { + component("accent_color_observer") { + sources = [ + "win/accent_color_observer.cc", + "win/accent_color_observer.h", + ] + + defines = [ "IS_COLOR_IMPL" ] + + public_deps = [ + "//base", + "//third_party/abseil-cpp:absl", + ] + + deps = [ + "//skia", + "//ui/gfx:color_utils", + ] + } +} + component("mixers") { sources = [ "color_mixers.cc",
diff --git a/ui/color/win/accent_color_observer.cc b/ui/color/win/accent_color_observer.cc new file mode 100644 index 0000000..d922a30 --- /dev/null +++ b/ui/color/win/accent_color_observer.cc
@@ -0,0 +1,94 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/color/win/accent_color_observer.h" + +#include "base/no_destructor.h" +#include "base/win/windows_version.h" +#include "skia/ext/skia_utils_win.h" +#include "ui/gfx/color_utils.h" + +namespace ui { + +// static +AccentColorObserver* AccentColorObserver::Get() { + static base::NoDestructor<AccentColorObserver> observer; + return observer.get(); +} + +AccentColorObserver::AccentColorObserver() { + if (base::win::GetVersion() >= base::win::Version::WIN8) { + dwm_key_ = std::make_unique<base::win::RegKey>( + HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\DWM", KEY_READ); + if (dwm_key_->Valid()) + OnDwmKeyUpdated(); + else + dwm_key_.reset(); + } +} + +AccentColorObserver::~AccentColorObserver() = default; + +base::CallbackListSubscription AccentColorObserver::Subscribe( + base::RepeatingClosure callback) { + return callbacks_.Add(std::move(callback)); +} + +void AccentColorObserver::OnDwmKeyUpdated() { + accent_border_color_ = absl::nullopt; + DWORD colorization_color, colorization_color_balance; + if ((dwm_key_->ReadValueDW(L"ColorizationColor", &colorization_color) == + ERROR_SUCCESS) && + (dwm_key_->ReadValueDW(L"ColorizationColorBalance", + &colorization_color_balance) == ERROR_SUCCESS)) { + // The accent border color is a linear blend between the colorization + // color and the neutral #d9d9d9. colorization_color_balance is the + // percentage of the colorization color in that blend. + // + // On Windows version 1611 colorization_color_balance can be 0xfffffff3 if + // the accent color is taken from the background and either the background + // is a solid color or was just changed to a slideshow. It's unclear what + // that value's supposed to mean, so change it to 80 to match Edge's + // behavior. + if (colorization_color_balance > 100) + colorization_color_balance = 80; + + // colorization_color's high byte is not an alpha value, so replace it + // with 0xff to make an opaque ARGB color. + SkColor input_color = SkColorSetA(colorization_color, 0xff); + + accent_border_color_ = + color_utils::AlphaBlend(input_color, SkColorSetRGB(0xd9, 0xd9, 0xd9), + colorization_color_balance / 100.0f); + } + + accent_color_ = absl::nullopt; + accent_color_inactive_ = absl::nullopt; + if (base::win::GetVersion() >= base::win::Version::WIN10) { + DWORD accent_color, color_prevalence; + bool use_dwm_frame_color = + dwm_key_->ReadValueDW(L"AccentColor", &accent_color) == ERROR_SUCCESS && + dwm_key_->ReadValueDW(L"ColorPrevalence", &color_prevalence) == + ERROR_SUCCESS && + color_prevalence == 1; + if (use_dwm_frame_color) { + accent_color_ = skia::COLORREFToSkColor(accent_color); + DWORD accent_color_inactive; + if (dwm_key_->ReadValueDW(L"AccentColorInactive", + &accent_color_inactive) == ERROR_SUCCESS) { + accent_color_inactive_ = skia::COLORREFToSkColor(accent_color_inactive); + } + } + } + + callbacks_.Notify(); + + // Watch for future changes. + if (!dwm_key_->StartWatching(base::BindOnce( + &AccentColorObserver::OnDwmKeyUpdated, base::Unretained(this)))) { + dwm_key_.reset(); + } +} + +} // namespace ui
diff --git a/ui/color/win/accent_color_observer.h b/ui/color/win/accent_color_observer.h new file mode 100644 index 0000000..20da4406 --- /dev/null +++ b/ui/color/win/accent_color_observer.h
@@ -0,0 +1,55 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_COLOR_WIN_ACCENT_COLOR_OBSERVER_H_ +#define UI_COLOR_WIN_ACCENT_COLOR_OBSERVER_H_ + +#include <memory> + +#include "base/callback_list.h" +#include "base/component_export.h" +#include "base/win/registry.h" +#include "third_party/abseil-cpp/absl/types/optional.h" +#include "third_party/skia/include/core/SkColor.h" + +namespace ui { + +// Monitors the HKCU\SOFTWARE\Microsoft\Windows\DWM reg key for changes and +// provides access to the accent color (and related colors), as well as support +// for firing callbacks when changes occur. +class COMPONENT_EXPORT(COLOR) AccentColorObserver { + public: + static AccentColorObserver* Get(); + + AccentColorObserver(); + AccentColorObserver(const AccentColorObserver&) = delete; + AccentColorObserver& operator=(const AccentColorObserver&) = delete; + ~AccentColorObserver(); + + // Registers `callback` to be called whenever the accent color changes. + base::CallbackListSubscription Subscribe(base::RepeatingClosure callback); + + absl::optional<SkColor> accent_color() const { return accent_color_; } + absl::optional<SkColor> accent_color_inactive() const { + return accent_color_inactive_; + } + absl::optional<SkColor> accent_border_color() const { + return accent_border_color_; + } + + private: + void OnDwmKeyUpdated(); + + // Registry key containing the params that determine the accent color. + std::unique_ptr<base::win::RegKey> dwm_key_; + + base::RepeatingClosureList callbacks_; + absl::optional<SkColor> accent_color_; + absl::optional<SkColor> accent_color_inactive_; + absl::optional<SkColor> accent_border_color_; +}; + +} // namespace ui + +#endif // UI_COLOR_WIN_ACCENT_COLOR_OBSERVER_H_
diff --git a/ui/events/blink/web_input_event.cc b/ui/events/blink/web_input_event.cc index a6b8059..35bf897f 100644 --- a/ui/events/blink/web_input_event.cc +++ b/ui/events/blink/web_input_event.cc
@@ -4,6 +4,7 @@ #include "ui/events/blink/web_input_event.h" +#include "build/build_config.h" #include "ui/base/ui_base_features.h" #include "ui/events/base_event_utils.h" #include "ui/events/blink/blink_event_util.h"
diff --git a/ui/gfx/color_analysis_fuzzer.cc b/ui/gfx/color_analysis_fuzzer.cc index ef2f6d8..a77e9a5 100644 --- a/ui/gfx/color_analysis_fuzzer.cc +++ b/ui/gfx/color_analysis_fuzzer.cc
@@ -3,20 +3,13 @@ // found in the LICENSE file. #include <fuzzer/FuzzedDataProvider.h> +#include <algorithm> #include <vector> #include "third_party/skia/include/core/SkBitmap.h" #include "ui/gfx/color_analysis.h" #include "ui/gfx/color_utils.h" -double ConsumeDouble(FuzzedDataProvider* provider) { - std::vector<uint8_t> v = provider->ConsumeBytes<uint8_t>(sizeof(double)); - if (v.size() == sizeof(double)) - return reinterpret_cast<double*>(v.data())[0]; - - return 0; -} - extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { FuzzedDataProvider provider(data, size); @@ -26,12 +19,17 @@ SkImageInfo info = SkImageInfo::MakeN32Premul(width, height); size_t expected_size = info.computeMinByteSize(); - color_utils::HSL upper_bound = {ConsumeDouble(&provider), - ConsumeDouble(&provider), - ConsumeDouble(&provider)}; - color_utils::HSL lower_bound = {ConsumeDouble(&provider), - ConsumeDouble(&provider), - ConsumeDouble(&provider)}; + const double lower_bound_hue = provider.ConsumeFloatingPointInRange(0.0, 1.0); + const double upper_bound_hue = provider.ConsumeFloatingPointInRange( + lower_bound_hue, lower_bound_hue + 1); + const double s1 = provider.ConsumeFloatingPointInRange(0.0, 1.0); + const double s2 = provider.ConsumeFloatingPointInRange(0.0, 1.0); + const double l1 = provider.ConsumeFloatingPointInRange(0.0, 1.0); + const double l2 = provider.ConsumeFloatingPointInRange(0.0, 1.0); + color_utils::HSL upper_bound = {upper_bound_hue, std::max(s1, s2), + std::max(l1, l2)}; + color_utils::HSL lower_bound = {lower_bound_hue, std::min(s1, s2), + std::min(l1, l2)}; bool find_closest = provider.ConsumeBool();
diff --git a/ui/gfx/font_list.cc b/ui/gfx/font_list.cc index 4155b8aba4..51d9349 100644 --- a/ui/gfx/font_list.cc +++ b/ui/gfx/font_list.cc
@@ -10,6 +10,7 @@ #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" +#include "build/build_config.h" #include "third_party/skia/include/core/SkFontMgr.h" #include "third_party/skia/include/core/SkTypeface.h" #include "ui/gfx/font_list_impl.h"
diff --git a/ui/gfx/ipc/gfx_param_traits.h b/ui/gfx/ipc/gfx_param_traits.h index e9ae63a2..362aa5a 100644 --- a/ui/gfx/ipc/gfx_param_traits.h +++ b/ui/gfx/ipc/gfx_param_traits.h
@@ -7,6 +7,7 @@ #include <string> +#include "build/build_config.h" #include "ipc/ipc_message_utils.h" #include "ipc/param_traits_macros.h" #include "ui/gfx/buffer_types.h"
diff --git a/ui/gl/sync_control_vsync_provider.h b/ui/gl/sync_control_vsync_provider.h index 67585dc..17675773 100644 --- a/ui/gl/sync_control_vsync_provider.h +++ b/ui/gl/sync_control_vsync_provider.h
@@ -8,6 +8,7 @@ #include <stdint.h> #include "base/containers/queue.h" +#include "build/build_config.h" #include "ui/gfx/vsync_provider.h" namespace gl {
diff --git a/ui/ozone/demo/demo_window.cc b/ui/ozone/demo/demo_window.cc index c84b022..2ddd848 100644 --- a/ui/ozone/demo/demo_window.cc +++ b/ui/ozone/demo/demo_window.cc
@@ -8,6 +8,7 @@ #include "base/logging.h" #include "base/threading/thread_task_runner_handle.h" +#include "build/build_config.h" #include "ui/events/event.h" #include "ui/events/keycodes/dom/dom_code.h" #include "ui/ozone/demo/renderer.h"
diff --git a/ui/ozone/platform/x11/gl_ozone_glx.cc b/ui/ozone/platform/x11/gl_ozone_glx.cc index 712a10f..44ecef0 100644 --- a/ui/ozone/platform/x11/gl_ozone_glx.cc +++ b/ui/ozone/platform/x11/gl_ozone_glx.cc
@@ -5,6 +5,7 @@ #include "ui/ozone/platform/x11/gl_ozone_glx.h" #include "base/command_line.h" +#include "build/build_config.h" #include "ui/gl/gl_context.h" #include "ui/gl/gl_context_glx.h" #include "ui/gl/gl_gl_api_implementation.h"
diff --git a/ui/views/animation/animation_builder.h b/ui/views/animation/animation_builder.h index 52d495f..2d5c5d5 100644 --- a/ui/views/animation/animation_builder.h +++ b/ui/views/animation/animation_builder.h
@@ -21,9 +21,6 @@ #include "ui/views/animation/animation_sequence_block.h" #include "ui/views/views_export.h" -// This AnimationBuilder API is currently in the experimental phase and only -// used within ui/views/examples/. - namespace ui { class Layer; }
diff --git a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_device_battery_info.html b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_device_battery_info.html index 344483b..927bce40 100644 --- a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_device_battery_info.html +++ b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_device_battery_info.html
@@ -1,44 +1,42 @@ <style include="cr-shared-style"> - --low-battery-style: { - --iron-icon-fill-color: var(--cros-icon-color-secondary); - color: var(--cros-icon-color-secondary); - }; - - --not-low-battery-style: { - --iron-icon-fill-color: var(--cros-icon-color-secondary); - color: var(--cros-icon-color-secondary); - }; - :host([is-default-low-battery_]) #defaultBatteryContainer { - @apply --low-battery-style; + --iron-icon-fill-color: var(--cros-icon-color-alert); + color: var(--cros-text-color-alert); } :host(:not([is-default-low-battery_])) #defaultBatteryContainer { - @apply --not-low-battery-style; + --iron-icon-fill-color: var(--cros-icon-color-secondary); + color: var(--cros-icon-color-secondary); } :host([is-left-bud-low-battery_]) #leftBudBatteryContainer { - @apply --low-battery-style; + --iron-icon-fill-color: var(--cros-icon-color-alert); + color: var(--cros-text-color-alert); } :host(:not([is-left-bud-low-battery_])) #leftBudBatteryContainer { - @apply --not-low-battery-style; + --iron-icon-fill-color: var(--cros-icon-color-secondary); + color: var(--cros-icon-color-secondary); } :host([is-case-low-battery_]) #caseBatteryContainer { - @apply --low-battery-style; + --iron-icon-fill-color: var(--cros-icon-color-alert); + color: var(--cros-text-color-alert); } :host(:not([is-case-low-battery_])) #caseBatteryContainer { - @apply --not-low-battery-style; + --iron-icon-fill-color: var(--cros-icon-color-secondary); + color: var(--cros-icon-color-secondary); } :host([is-right-bud-low-battery_]) #rightBudBatteryContainer { - @apply --low-battery-style; + --iron-icon-fill-color: var(--cros-icon-color-alert); + color: var(--cros-text-color-alert); } :host(:not([is-right-bud-low-battery_])) #rightBudBatteryContainer { - @apply --not-low-battery-style; + --iron-icon-fill-color: var(--cros-icon-color-secondary); + color: var(--cros-icon-color-secondary); } .battery-icon {
diff --git a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_item.html b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_item.html index 32693121..eebb09c 100644 --- a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_item.html +++ b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_item.html
@@ -30,15 +30,30 @@ } </style> -<div id="container" class="cr-row continuation" - on-click="onSelected_" - on-keydown="onKeydown_"> - <bluetooth-icon device="[[device]]"></bluetooth-icon> - <div id="deviceName" class="text-row"> - [[getDeviceName_(device.*)]] - <!-- TODO(crbug.com/1010321): Add A11Y. --> - <div id="secondaryLabel" class="secondary"> - [[secondaryLabel_]] +<div id="wrapper" focus-row-container> + <div id="container" + class="cr-row continuation" + actionable + focus-row-control + selectable + aria-label$="[[getAriaLabel_(device.*, itemIndex, listSize)]]" + role="button" + focus-type="rowWrapper" + on-keydown="onKeydown_" + on-click="onSelected_"> + <bluetooth-icon device="[[device]]"></bluetooth-icon> + <div aria-live="polite" + aria-label="[[getSecondaryAriaLabel_( + secondaryLabel_, pairingFailed_, device.*)]]" + class="text-row"> + <div id="deviceName" aria-hidden="true"> + [[getDeviceName_(device.*)]] + </div> + <div id="secondaryLabel" + aria-hidden="true" + class="secondary"> + [[secondaryLabel_]] + </div> </div> </div> </div>
diff --git a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_item.js b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_item.js index 21849587..f8d8430 100644 --- a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_item.js +++ b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_item.js
@@ -12,9 +12,8 @@ import {I18nBehavior, I18nBehaviorInterface} from '//resources/js/i18n_behavior.m.js'; import {html, mixinBehaviors, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js'; - +import {FocusRowBehavior} from 'chrome://resources/js/cr/ui/focus_row_behavior.m.js'; import {assertNotReached} from '../../../js/assert.m.js'; - import {DeviceItemState} from './bluetooth_types.js'; import {mojoString16ToString} from './bluetooth_utils.js'; @@ -24,7 +23,7 @@ * @extends {PolymerElement} */ const SettingsBluetoothPairingDeviceItemElementBase = - mixinBehaviors([I18nBehavior], PolymerElement); + mixinBehaviors([I18nBehavior, FocusRowBehavior], PolymerElement); /** @polymer */ export class SettingsBluetoothPairingDeviceItemElement extends @@ -50,6 +49,15 @@ value: DeviceItemState.DEFAULT, }, + /** The index of this item in its parent list, used for its a11y label. */ + itemIndex: Number, + + /** + * The total number of elements in this item's parent list, used for its + * a11y label. + */ + listSize: Number, + /** @private {string} */ secondaryLabel_: { type: String, @@ -131,6 +139,66 @@ detail: {device: this.device}, })); } + + /** + * @return {string} + * @private + */ + getAriaLabel_() { + return this.i18n( + this.getA11yLabelMessageId_(), this.itemIndex + 1, this.listSize, + this.getDeviceName_()); + } + + /** + * @return {string} + * @private + */ + getA11yLabelMessageId_() { + const deviceType = chromeos.bluetoothConfig.mojom.DeviceType; + switch (this.device.deviceType) { + case deviceType.kUnknown: + return 'bluetoothPairingDeviceItemA11YLabelUnknown'; + case deviceType.kComputer: + return 'bluetoothPairingDeviceItemA11YLabelComputer'; + case deviceType.kPhone: + return 'bluetoothPairingDeviceItemA11YLabelPhone'; + case deviceType.kHeadset: + return 'bluetoothPairingDeviceItemA11YLabelHeadset'; + case deviceType.kVideoCamera: + return 'bluetoothPairingDeviceItemA11YLabelVideoCamera'; + case deviceType.kGameController: + return 'bluetoothPairingDeviceItemA11YLabelGameContoller'; + case deviceType.kKeyboard: + return 'bluetoothPairingDeviceItemA11YLabelKeyboard'; + case deviceType.kMouse: + return 'bluetoothPairingDeviceItemA11YLabelMouse'; + case deviceType.kTablet: + return 'bluetoothPairingDeviceItemA11YLabelTablet'; + default: + assertNotReached(); + } + } + + /** + * @return {string} + * @private + */ + getSecondaryAriaLabel_() { + const deviceName = this.getDeviceName_(); + switch (this.deviceItemState) { + case DeviceItemState.FAILED: + return this.i18n( + 'bluetoothPairingDeviceItemSecondaryErrorA11YLabel', deviceName); + case DeviceItemState.PAIRING: + return this.i18n( + 'bluetoothPairingDeviceItemSecondaryPairingA11YLabel', deviceName); + case DeviceItemState.DEFAULT: + return ''; + default: + assertNotReached(); + } + } } customElements.define(
diff --git a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page.html b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page.html index 24a43839..39ec00ca 100644 --- a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page.html +++ b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page.html
@@ -14,22 +14,32 @@ margin: 20px 0 8px 0; } </style> -<bluetooth-base-page show-scan-progress button-bar-state="[[buttonBarState_]]"> +<bluetooth-base-page show-scan-progress + button-bar-state="[[buttonBarState_]]"> <div slot="page-body" id="pageBody"> <localized-link localized-string="[[i18nAdvanced('bluetoothPairingLearnMoreLabel')]]"> </localized-link> <div id="deviceListTitle">[[getDeviceListTitle_(devices.*)]]</div> - <!-- TODO(crbug.com/1010321): Fix item focus. --> <template is="dom-if" if="[[shouldShowDeviceList_(devices.*)]]"> <div id="container" class="layout vertical flex" scrollable no-bottom-scroll-border> - <iron-list items="[[devices]]" preserve-focus> + <iron-list items="[[devices]]" + scroll-target="container" + preserve-focus> <template> <bluetooth-pairing-device-item + item="[[item]]" device="[[item]]" device-item-state="[[getDeviceItemState_( - item, devicePendingPairing.*, failedPairingDeviceId)]]" > + item, devicePendingPairing.*, failedPairingDeviceId)]]" + tabindex$="[[tabIndex]]" + focus-row-index="[[index]]" + iron-list-tab-index="[[tabIndex]]" + last-focused="{{lastFocused_}}" + list-blurred="{{listBlurred_}}" + item-index="[[index]]" + list-size="[[devices.length]]" > </bluetooth-pairing-device-item> </template> </iron-list>
diff --git a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page.js b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page.js index abbc0ee..f191cc43 100644 --- a/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page.js +++ b/ui/webui/resources/cr_components/chromeos/bluetooth/bluetooth_pairing_device_selection_page.js
@@ -72,7 +72,19 @@ cancel: ButtonState.ENABLED, pair: ButtonState.HIDDEN, }, - } + }, + + /** + * Used by FocusRowBehavior to track the last focused element on a row. + * @private + */ + lastFocused_: Object, + + /** + * Used by FocusRowBehavior to track if the list has been blurred. + * @private + */ + listBlurred_: Boolean, }; }
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserFragmentLifecycleTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserFragmentLifecycleTest.java index 3194a89..bc26b25 100644 --- a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserFragmentLifecycleTest.java +++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserFragmentLifecycleTest.java
@@ -97,6 +97,39 @@ waitForTabToFinishRestore(getTab(), url); } + @Test + @SmallTest + @MinWebLayerVersion(98) + public void setMaxNavigationsPerTabForInstanceState() throws Throwable { + TestThreadUtils.runOnUiThreadBlocking( + () -> Browser.setMaxNavigationsPerTabForInstanceState(4)); + + // Navigate to 5 urls. + mActivityTestRule.launchShellWithUrl("about:blank"); + final String url1 = "data:text,foo"; + mActivityTestRule.navigateAndWait(getTab(), url1, false); + final String url2 = mActivityTestRule.getTestDataURL("simple_page.html"); + mActivityTestRule.navigateAndWait(getTab(), url2, false); + final String url3 = mActivityTestRule.getTestDataURL("simple_page2.html"); + mActivityTestRule.navigateAndWait(getTab(), url3, false); + final String url4 = mActivityTestRule.getTestDataURL("simple_page3.html"); + mActivityTestRule.navigateAndWait(getTab(), url4, false); + final String url5 = mActivityTestRule.getTestDataURL("simple_page4.html"); + mActivityTestRule.navigateAndWait(getTab(), url5, false); + + mActivityTestRule.recreateActivity(); + + // The max set to 4, so only 4 navigation entries should be persisted. + waitForTabToFinishRestore(getTab(), url5); + TestThreadUtils.runOnUiThreadBlocking(() -> { + final NavigationController navigationController = + mActivityTestRule.getActivity().getTab().getNavigationController(); + Assert.assertEquals(4, navigationController.getNavigationListSize()); + Assert.assertEquals( + Uri.parse(url5), navigationController.getNavigationEntryDisplayUri(3)); + }); + } + private void destroyFragment(CallbackHelper helper) { FragmentManager fm = mActivityTestRule.getActivity().getSupportFragmentManager(); fm.beginTransaction()
diff --git a/weblayer/browser/browser_impl.cc b/weblayer/browser/browser_impl.cc index da2c667..14910cd 100644 --- a/weblayer/browser/browser_impl.cc +++ b/weblayer/browser/browser_impl.cc
@@ -159,8 +159,10 @@ } ScopedJavaLocalRef<jbyteArray> BrowserImpl::GetMinimalPersistenceState( - JNIEnv* env) { - return base::android::ToJavaByteArray(env, GetMinimalPersistenceState()); + JNIEnv* env, + int max_navigations_per_tab) { + return base::android::ToJavaByteArray( + env, GetMinimalPersistenceState(max_navigations_per_tab, 0)); } void BrowserImpl::RestoreStateIfNecessary( @@ -215,8 +217,9 @@ #endif std::vector<uint8_t> BrowserImpl::GetMinimalPersistenceState( + int max_navigations_per_tab, int max_size_in_bytes) { - return PersistMinimalState(this, max_size_in_bytes); + return PersistMinimalState(this, max_navigations_per_tab, max_size_in_bytes); } void BrowserImpl::SetWebPreferences(blink::web_pref::WebPreferences* prefs) { @@ -343,7 +346,7 @@ std::vector<uint8_t> BrowserImpl::GetMinimalPersistenceState() { // 0 means use the default max. - return GetMinimalPersistenceState(0); + return GetMinimalPersistenceState(0, 0); } bool BrowserImpl::IsRestoringPreviousState() {
diff --git a/weblayer/browser/browser_impl.h b/weblayer/browser/browser_impl.h index aee7eb0f..5debb852 100644 --- a/weblayer/browser/browser_impl.h +++ b/weblayer/browser/browser_impl.h
@@ -78,7 +78,8 @@ base::android::ScopedJavaLocalRef<jbyteArray> GetBrowserPersisterCryptoKey( JNIEnv* env); base::android::ScopedJavaLocalRef<jbyteArray> GetMinimalPersistenceState( - JNIEnv* env); + JNIEnv* env, + int max_navigations_per_tab); void RestoreStateIfNecessary( JNIEnv* env, const base::android::JavaParamRef<jstring>& j_persistence_id, @@ -97,7 +98,8 @@ #endif // Used in tests to specify a non-default max (0 means use the default). - std::vector<uint8_t> GetMinimalPersistenceState(int max_size_in_bytes); + std::vector<uint8_t> GetMinimalPersistenceState(int max_navigations_per_tab, + int max_size_in_bytes); // Used by tests to specify a callback to listen to changes to visible // security state.
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java index 2f8e859..79ac46f 100644 --- a/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java +++ b/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java
@@ -208,7 +208,8 @@ BrowserImplJni.get().getBrowserPersisterCryptoKey(mNativeBrowser)); } else if (!hasPersistenceId) { outState.putByteArray(SAVED_STATE_MINIMAL_PERSISTENCE_STATE_KEY, - BrowserImplJni.get().getMinimalPersistenceState(mNativeBrowser)); + BrowserImplJni.get().getMinimalPersistenceState(mNativeBrowser, + WebLayerImpl.getMaxNavigationsPerTabForInstanceState())); } if (mWindowAndroid != null) { @@ -716,7 +717,7 @@ String getPersistenceId(long nativeBrowserImpl); void saveBrowserPersisterIfNecessary(long nativeBrowserImpl); byte[] getBrowserPersisterCryptoKey(long nativeBrowserImpl); - byte[] getMinimalPersistenceState(long nativeBrowserImpl); + byte[] getMinimalPersistenceState(long nativeBrowserImpl, int maxNavigationsPerTab); void restoreStateIfNecessary(long nativeBrowserImpl, String persistenceId, byte[] persistenceCryptoKey, byte[] minimalPersistenceState); void webPreferencesChanged(long nativeBrowserImpl);
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java index d081116..6774271 100644 --- a/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java +++ b/weblayer/browser/java/org/chromium/weblayer_private/WebLayerImpl.java
@@ -137,6 +137,9 @@ // //build/android/gyp/util/protoresources.py and WebViewChromiumFactoryProvider.java. private static final int REQUIRED_PACKAGE_IDENTIFIER = 36; + // 0 results in using the default value. + private static int sMaxNavigationsForInstanceState = 0; + private final ProfileManager mProfileManager = new ProfileManager(); private boolean mInited; @@ -546,6 +549,16 @@ return ObjectWrapper.wrap(ContextUtils.getApplicationContext()); } + public static int getMaxNavigationsPerTabForInstanceState() { + try { + return (WebLayerFactoryImpl.getClientMajorVersion() >= 98) + ? sClient.getMaxNavigationsPerTabForInstanceState() + : 0; + } catch (RemoteException e) { + throw new APICallException(e); + } + } + public static Intent createIntent() { if (sClient == null) { throw new IllegalStateException("WebLayer should have been initialized already.");
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayerClient.aidl b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayerClient.aidl index e8f34f60..c109ce7 100644 --- a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayerClient.aidl +++ b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/IWebLayerClient.aidl
@@ -19,4 +19,7 @@ Intent createRemoteMediaServiceIntent() = 7; int getPresentationApiNotificationId() = 8; int getRemotePlaybackApiNotificationId() = 9; + + // Added in Version 98. + int getMaxNavigationsPerTabForInstanceState() = 10; }
diff --git a/weblayer/browser/persistence/minimal_browser_persister.cc b/weblayer/browser/persistence/minimal_browser_persister.cc index 73fc83e9..7f8bdf6 100644 --- a/weblayer/browser/persistence/minimal_browser_persister.cc +++ b/weblayer/browser/persistence/minimal_browser_persister.cc
@@ -266,13 +266,16 @@ // commands, false if size exceeded. bool PersistTabStateSecondaryPass(const SessionID& browser_session_id, Tab* tab, + int max_navigations_per_tab, MinimalPersister* builder) { NavigationEntryIterator iterator(tab); if (iterator.at_end()) return true; const SessionID& session_id = GetSessionIDForTab(tab); - for (int i = 0; i < 5; ++i) { + // Subtract 1 from `max_navigations_per_tab` as the first pass wrote a + // navigation. + for (int i = 0; i < max_navigations_per_tab - 1; ++i) { // Skips the navigation that was written during the first pass. if (!iterator.Next()) return true; @@ -313,6 +316,7 @@ } // namespace std::vector<uint8_t> PersistMinimalState(BrowserImpl* browser, + int max_navigations_per_tab, int max_size_in_bytes) { MinimalPersister builder(max_size_in_bytes == 0 ? kMaxSizeInBytes : max_size_in_bytes); @@ -342,9 +346,15 @@ return builder.ToByteArray(); } + // Use a default of 5 for the max number of navigations to persist. + if (max_navigations_per_tab == 0) + max_navigations_per_tab = 5; + for (Tab* tab : tabs) { - if (!PersistTabStateSecondaryPass(browser_session_id, tab, &builder)) + if (!PersistTabStateSecondaryPass(browser_session_id, tab, + max_navigations_per_tab, &builder)) { return builder.ToByteArray(); + } } return builder.ToByteArray();
diff --git a/weblayer/browser/persistence/minimal_browser_persister.h b/weblayer/browser/persistence/minimal_browser_persister.h index 7cd7c75f..aef0422 100644 --- a/weblayer/browser/persistence/minimal_browser_persister.h +++ b/weblayer/browser/persistence/minimal_browser_persister.h
@@ -16,9 +16,12 @@ // Returns a byte array that can later be used to restore the state (Tabs and // navigations) of a Browser. This does not store the full state, only a // minimal state. For example, it may not include all tabs or all navigations. -// |max_size_in_bytes| is provided for tests and allows specifying the max. -// A value of 0 means use the default max. +// |max_navigations_per_tab| is the max number of navigations to persist per +// tab. Depending upon space requirements, the max number of navigations may not +// be honored. |max_size_in_bytes| is provided for tests and allows specifying +// the max. A value of 0 means use the default max. std::vector<uint8_t> PersistMinimalState(BrowserImpl* browser, + int max_navigations_per_tab = 0, int max_size_in_bytes = 0); // Restores the state previously created via PersistMinimalState(). When
diff --git a/weblayer/browser/persistence/minimal_browser_persister_browsertest.cc b/weblayer/browser/persistence/minimal_browser_persister_browsertest.cc index 19161328..1485d7c5 100644 --- a/weblayer/browser/persistence/minimal_browser_persister_browsertest.cc +++ b/weblayer/browser/persistence/minimal_browser_persister_browsertest.cc
@@ -49,10 +49,12 @@ // Persists the current state, then recreates the browser. See // BrowserImpl::GetMinimalPersistenceState() for details on // |max_size_in_bytes|, 0 means use the default value. - void RecreateBrowserFromCurrentState(int max_size_in_bytes = 0) { + void RecreateBrowserFromCurrentState( + int max_number_of_navigations_per_tab = 0, + int max_size_in_bytes = 0) { Browser::PersistenceInfo persistence_info; - persistence_info.minimal_state = - browser_impl()->GetMinimalPersistenceState(max_size_in_bytes); + persistence_info.minimal_state = browser_impl()->GetMinimalPersistenceState( + max_number_of_navigations_per_tab, max_size_in_bytes); tab_ = nullptr; browser_ = Browser::Create(GetProfile(), &persistence_info); // There is always at least one tab created (even if restore fails). @@ -145,6 +147,33 @@ EXPECT_EQ(url2(), nav_controller.GetEntryAtIndex(1)->GetURL()); } +IN_PROC_BROWSER_TEST_F(MinimalBrowserPersisterTest, NavigationOverflow) { + NavigateAndWaitForCompletion(url1(), tab_); + NavigateAndWaitForCompletion(url2(), tab_); + const GURL url3 = embedded_test_server()->GetURL("/simple_page3.html"); + NavigateAndWaitForCompletion(url3, tab_); + const GURL url4 = embedded_test_server()->GetURL("/simple_page4.html"); + NavigateAndWaitForCompletion(url4, tab_); + + ASSERT_NO_FATAL_FAILURE(RecreateBrowserFromCurrentState(3)); + + // As a max of 3 navigations was specified, only the last three navigations + // should be restored. + TabImpl* restored_tab = tab_; + EXPECT_EQ(restored_tab, browser_->GetActiveTab()); + TestNavigationObserver observer( + url4, TestNavigationObserver::NavigationEvent::kCompletion, restored_tab); + observer.Wait(); + ASSERT_EQ(3, + restored_tab->GetNavigationController()->GetNavigationListSize()); + content::NavigationController& nav_controller = + restored_tab->web_contents()->GetController(); + EXPECT_EQ(2, nav_controller.GetCurrentEntryIndex()); + EXPECT_EQ(url2(), nav_controller.GetEntryAtIndex(0)->GetURL()); + EXPECT_EQ(url3, nav_controller.GetEntryAtIndex(1)->GetURL()); + EXPECT_EQ(url4, nav_controller.GetEntryAtIndex(2)->GetURL()); +} + // crbug.com/1240904: test is flaky on linux and win. #if defined(OS_LINUX) || defined(OS_WIN) #define MAYBE_Overflow DISABLED_Overflow @@ -157,7 +186,7 @@ url_string.replace(0, data.size(), data); NavigateAndWaitForCompletion(GURL(url_string), tab_); - ASSERT_NO_FATAL_FAILURE(RecreateBrowserFromCurrentState(2048)); + ASSERT_NO_FATAL_FAILURE(RecreateBrowserFromCurrentState(0, 2048)); TabImpl* restored_tab = tab_; EXPECT_EQ(restored_tab, browser_->GetActiveTab());
diff --git a/weblayer/public/java/org/chromium/weblayer/Browser.java b/weblayer/public/java/org/chromium/weblayer/Browser.java index 333b14d6..0b960a2 100644 --- a/weblayer/public/java/org/chromium/weblayer/Browser.java +++ b/weblayer/public/java/org/chromium/weblayer/Browser.java
@@ -40,6 +40,31 @@ private final ObserverList<BrowserControlsOffsetCallback> mBrowserControlsOffsetCallbacks; private final ObserverList<BrowserRestoreCallback> mBrowserRestoreCallbacks; + private static int sMaxNavigationsPerTabForInstanceState; + + /** + * Sets the maximum number of navigations saved when persisting a Browsers instance state. The + * max applies to each Tab in the Browser. For example, if a value of 6 is supplied and the + * Browser has 4 tabs, then up to 24 navigation entries may be saved. The supplied value is + * a recommendation, for various reasons it may not be honored. A value of 0 results in + * using the default. + * + * @param value The maximum number of navigations to persist. + * + * @throws IllegalArgumentException If {@code value} is less than 0. + * + * @since 98 + */ + public static void setMaxNavigationsPerTabForInstanceState(int value) { + ThreadCheck.ensureOnUiThread(); + if (value < 0) throw new IllegalArgumentException("Max must be >= 0"); + sMaxNavigationsPerTabForInstanceState = value; + } + + static int getMaxNavigationsPerTabForInstanceState() { + return sMaxNavigationsPerTabForInstanceState; + } + // Constructor for test mocking. protected Browser() { mImpl = null;
diff --git a/weblayer/public/java/org/chromium/weblayer/WebLayer.java b/weblayer/public/java/org/chromium/weblayer/WebLayer.java index ba3452b3..86d5030 100644 --- a/weblayer/public/java/org/chromium/weblayer/WebLayer.java +++ b/weblayer/public/java/org/chromium/weblayer/WebLayer.java
@@ -826,6 +826,11 @@ // The id is part of the public library to avoid conflicts. return R.id.weblayer_remote_playback_api_notification; } + + @Override + public int getMaxNavigationsPerTabForInstanceState() { + return Browser.getMaxNavigationsPerTabForInstanceState(); + } } /** Utility class to use new APIs that were added in O (API level 26). */