diff --git a/.github/README.md b/.github/README-Copilot.md similarity index 89% rename from .github/README.md rename to .github/README-Copilot.md index cf597ce..cc23f34 100644 --- a/.github/README.md +++ b/.github/README-Copilot.md
@@ -9,6 +9,11 @@ IDE/agent integration. Please check with your organization before using GitHub Copilot. +Note: This README file is intentionally NOT named /.github/README.md to avoid +replacing the root level [README.md](../README.md) on +https://github.com/chromium/chromium. See +[github readme documentation](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-readmes) + ## Where is copilot-instructions.md? [`copilot-instructions.md`](../copilot-instructions.md) is typically a single instruction file that contains default instructions for a workspace. These
diff --git a/DEPS b/DEPS index 05c23934d..08975a7 100644 --- a/DEPS +++ b/DEPS
@@ -282,11 +282,11 @@ 'reclient_version': 're_client_version:0.185.0.db415f21-gomaip', # screen-ai CIPD packages - 'screen_ai_linux': 'version:140.20', - 'screen_ai_macos_amd64': 'version:140.20', - 'screen_ai_macos_arm64': 'version:140.20', - 'screen_ai_windows_amd64': 'version:140.20', - 'screen_ai_windows_386': 'version:140.20', + 'screen_ai_linux': 'version:140.21', + 'screen_ai_macos_amd64': 'version:140.21', + 'screen_ai_macos_arm64': 'version:140.21', + 'screen_ai_windows_amd64': 'version:140.21', + 'screen_ai_windows_386': 'version:140.21', # download libaom test data 'download_libaom_testdata': False, @@ -308,7 +308,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. - 'src_internal_revision': 'af90ff55ed4003f9e5ff3d7f84adf74ea2bcd44e', + 'src_internal_revision': '64463e9445c6e4d54c906a615bf7c2abd03dce9b', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Skia # and whatever else without interference from each other. @@ -380,7 +380,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling CrossBench # and whatever else without interference from each other. - 'crossbench_revision': '0a6aad6789b93b29db3637c43bcf53596f776e27', + 'crossbench_revision': '58b175282c85010daae2db859a574b37e7246163', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling CrossBench # and whatever else without interference from each other. @@ -392,7 +392,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling fuzztest # and whatever else without interference from each other. - 'fuzztest_revision': 'f67a6fe9ca3afc4d71997e75e8d87c571f0c30cb', + 'fuzztest_revision': '6a217df9edbe5705885457f10968ebe80aec9504', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling domato # and whatever else without interference from each other. @@ -400,7 +400,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': '2dbd599b9ff902288d48e6bf5c9caa94e32c46f8', + 'devtools_frontend_revision': '194a8d96af8d948fb6c0a42fb4f839d6e3531b53', # 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. @@ -1209,7 +1209,7 @@ 'packages': [ { 'package': 'chromium/chrome/android/orderfiles/arm64', - 'version': 'IW6Na8AUwsWPItO2Z1s4OvE4yihkHawFLcIfOgNJLcAC', + 'version': 'kmQehJLguK1Uow813geoXsibZZni-58aNHH3bYTIWXgC', }, ], 'condition': 'checkout_android and non_git_source', @@ -1231,7 +1231,7 @@ 'packages': [ { 'package': 'chromium/android_webview/tools/orderfiles/arm64', - 'version': '6ZHmb8OOPgzAjXoklu2_U4-E3IM_x2krcEn7rGICph4C', + 'version': 'DTBgMboDM1we6bW8Tcofh55wDktAFsfUfdqMiL3Ym-EC', }, ], 'condition': 'checkout_android and non_git_source', @@ -1270,7 +1270,7 @@ 'packages': [ { 'package': 'chromium/third_party/updater/chrome_linux64', - 'version': 'version:2@147.0.7683.0', + 'version': 'version:2@147.0.7703.0', }, ], }, @@ -1281,7 +1281,7 @@ 'packages': [ { 'package': 'chromium/third_party/updater/chrome_mac_universal', - 'version': 'version:2@147.0.7683.0', + 'version': 'version:2@147.0.7703.0', }, ], }, @@ -1303,7 +1303,7 @@ 'packages': [ { 'package': 'chromium/third_party/updater/chrome_win_arm64', - 'version': 'version:2@147.0.7683.0', + 'version': 'version:2@147.0.7703.0', }, ], }, @@ -1314,7 +1314,7 @@ 'packages': [ { 'package': 'chromium/third_party/updater/chrome_win_x86', - 'version': 'version:2@147.0.7683.0', + 'version': 'version:2@147.0.7703.0', }, ], }, @@ -1325,7 +1325,7 @@ 'packages': [ { 'package': 'chromium/third_party/updater/chrome_win_x86_64', - 'version': 'version:2@147.0.7683.0', + 'version': 'version:2@147.0.7703.0', }, ], }, @@ -1616,7 +1616,7 @@ 'packages': [ { 'package': 'chromium/chrome/test/data/variations/cipd', - 'version': '_-m72dgwiZn7mQQwHJ1fEU7FFnMxcdjxhnEr7ALnidEC', + 'version': 'Ph6_jM82kadxfsIBJKpOw2uMZEfftC_0YiW9m9uGj0gC', }, ], 'condition': 'non_git_source', @@ -1628,7 +1628,7 @@ 'src/clank': { 'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' + - '93136002dccd088d38584d5a14cd03c645b6f3e1', + '0f6bcbff8115cd83bf904013b823dbbff9b9736d', 'condition': 'checkout_android and checkout_src_internal', }, @@ -2958,7 +2958,7 @@ 'dep_type': 'cipd', }, - 'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@473fd805b316f18182343fb6131cb5b9d98bc071', + 'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@145852941ba897ac22dd6f569672f2d60d2b66d6', 'src/third_party/glslang/src': '{chromium_git}/external/github.com/KhronosGroup/glslang@e966816ab28ab7cb448d5b33270b43c941b343d4', 'src/third_party/spirv-cross/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Cross@b8fcf307f1f347089e3c46eb4451d27f32ebc8d3', 'src/third_party/spirv-headers/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Headers@f88a2d766840fc825af1fc065977953ba1fa4a91', @@ -2967,7 +2967,7 @@ 'src/third_party/vulkan-loader/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Loader@e0e501b0ba42df7b3af023470ad068c48a3ac4de', 'src/third_party/vulkan-tools/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Tools@7f423e2b242c154e6ace85c804c65462a7d41870', 'src/third_party/vulkan-utility-libraries/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@738ec97a3f659dd6469bff3c4078ef981b0a343f', - 'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@d2769c7598fdbbe769e74a12120773048a0b3a5b', + 'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@860f51d1f7df06432512ecb893bc5edeefd9deb1', 'src/third_party/vulkan_memory_allocator': Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'cb0597213b0fcb999caa9ed08c2f88dc45eb7d50',
diff --git a/android_webview/browser/aw_field_trials.cc b/android_webview/browser/aw_field_trials.cc index 8e44b49..8eb7a16 100644 --- a/android_webview/browser/aw_field_trials.cc +++ b/android_webview/browser/aw_field_trials.cc
@@ -140,6 +140,11 @@ aw_feature_overrides.DisableFeature( blink::features::kSecurePaymentConfirmationAvailabilityAPI); + // WebView does not support Secure Payment Confirmation, and thus should not + // expose the PaymentRequest.securePaymentConfirmationCapabilities API. + aw_feature_overrides.DisableFeature( + blink::features::kSecurePaymentConfirmationCapabilities); + // WebView does not support handling payment links. aw_feature_overrides.DisableFeature(blink::features::kPaymentLinkDetection);
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java index 525cd88..8aa9785 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwContents.java +++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java
@@ -1517,7 +1517,7 @@ try (DualTraceEvent e2 = DualTraceEvent.scoped("AwContents.createActivityWindow")) { final boolean listenToActivityState = false; activityWindow = - new ActivityWindowAndroid( + ActivityWindowAndroid.create( activity, listenToActivityState, IntentRequestTracker.createFromActivity(activity),
diff --git a/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt b/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt index aef6e27..29ce657 100644 --- a/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt +++ b/android_webview/test/data/web_tests/webexposed/global-interface-listing-expected.txt
@@ -6619,7 +6619,6 @@ method share method vibrate method webkitGetUserMedia - setter modelContext setter modelContextTesting interface NavigatorManagedData : EventTarget attribute @@toStringTag
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc index a60728d9..34057567 100644 --- a/ash/constants/ash_features.cc +++ b/ash/constants/ash_features.cc
@@ -1533,10 +1533,6 @@ // Enables or disables Orca on Demo mode. BASE_FEATURE(kOrcaSupportDemoMode, base::FEATURE_ENABLED_BY_DEFAULT); -// If enabled, OsSyncConsent Revamp will be shown. -// enabling this without enabling Lacros flag will have no effect -BASE_FEATURE(kOsSyncConsentRevamp, base::FEATURE_ENABLED_BY_DEFAULT); - // Enables Jelly colors and components to appear in the Parent Access Widget // if jelly-colors is also enabled. BASE_FEATURE(kParentAccessJelly, base::FEATURE_DISABLED_BY_DEFAULT); @@ -2944,10 +2940,6 @@ return base::FeatureList::IsEnabled(kOobeSplitModifierKeyboardInfo); } -bool IsOsSyncConsentRevampEnabled() { - return base::FeatureList::IsEnabled(kOsSyncConsentRevamp); -} - bool IsParentAccessJellyEnabled() { return base::FeatureList::IsEnabled(kParentAccessJelly); }
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h index 4d646e6..498d5a40 100644 --- a/ash/constants/ash_features.h +++ b/ash/constants/ash_features.h
@@ -694,8 +694,6 @@ BASE_DECLARE_FEATURE(kOrcaResizingSupport); COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOrcaUseAccountCapabilities); -COMPONENT_EXPORT(ASH_CONSTANTS) -COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kOsSyncConsentRevamp); COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kParentAccessJelly); COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kPcieBillboardNotification); @@ -1177,7 +1175,6 @@ COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeSplitModifierKeyboardInfoEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeInputMethodsEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOobeAutoEnrollmentCheckForcedEnabled(); -COMPONENT_EXPORT(ASH_CONSTANTS) bool IsOsSyncConsentRevampEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsParentAccessJellyEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsPcieBillboardNotificationEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsPciguardUiEnabled();
diff --git a/ash/drag_drop/drag_drop_controller_unittest.cc b/ash/drag_drop/drag_drop_controller_unittest.cc index b1961d0..b2c9e305 100644 --- a/ash/drag_drop/drag_drop_controller_unittest.cc +++ b/ash/drag_drop/drag_drop_controller_unittest.cc
@@ -38,6 +38,7 @@ #include "ui/aura/window_tree_host.h" #include "ui/base/clipboard/clipboard.h" #include "ui/base/clipboard/scoped_clipboard_writer.h" +#include "ui/base/clipboard/test/clipboard_test_util.h" #include "ui/base/data_transfer_policy/data_transfer_endpoint.h" #include "ui/base/data_transfer_policy/data_transfer_policy_controller.h" #include "ui/base/data_transfer_policy/mock_data_transfer_policy_controller.h" @@ -792,12 +793,11 @@ base::RunLoop().RunUntilIdle(); // Verify the clipboard contents haven't changed - std::string result; EXPECT_TRUE(cb->IsFormatAvailable(ui::ClipboardFormatType::PlainTextType(), ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr)); - cb->ReadAsciiText(ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr, - &result); + std::string result = ui::clipboard_test_util::ReadAsciiText( + cb, ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr); EXPECT_EQ(clip_str, result); // Destroy the clipboard here because ash doesn't delete it. // crbug.com/158150.
diff --git a/ash/webui/projector_app/resources/app/untrusted/BUILD.gn b/ash/webui/projector_app/resources/app/untrusted/BUILD.gn index 2cb0ab8..cf945e0 100644 --- a/ash/webui/projector_app/resources/app/untrusted/BUILD.gn +++ b/ash/webui/projector_app/resources/app/untrusted/BUILD.gn
@@ -15,6 +15,8 @@ "ash/webui/projector_app/public/mojom/projector_types.mojom-webui.ts", ] +ts_files = [ "untrusted_projector_browser_proxy.ts" ] + preprocess_if_expr("copy_mojo_ts") { visibility = [ ":build_ts" ] in_folder = root_gen_dir @@ -26,15 +28,25 @@ out_folder = "$target_gen_dir/preprocessed" } +preprocess_if_expr("copy_ts") { + visibility = [ ":build_ts" ] + in_folder = "." + in_files = ts_files + out_folder = "$target_gen_dir/preprocessed" +} + webui_ts_library("build_ts") { root_dir = "$target_gen_dir/preprocessed" out_dir = "$target_gen_dir/tsc" - in_files = mojo_files + in_files = mojo_files + ts_files composite = true deps = [ "//ui/webui/resources/mojo:build_ts" ] - extra_deps = [ ":copy_mojo_ts" ] + extra_deps = [ + ":copy_mojo_ts", + ":copy_ts", + ] } generate_grd("mojom_grdp") { @@ -48,13 +60,20 @@ input_files_base_dir = rebase_path("$target_gen_dir/tsc", root_build_dir) } +generate_grd("ts_grdp") { + grd_prefix = "ash_projector_app_untrusted" + out_grd = "$target_gen_dir/ts_resources.grdp" + deps = [ ":build_ts" ] + input_files = [ "untrusted_projector_browser_proxy.js" ] + input_files_base_dir = rebase_path("$target_gen_dir/tsc", root_build_dir) +} + generate_grd("build_untrusted_grd") { input_files = [ "index.html", "launch.js", "sandboxed_load_time_data.js", "untrusted_app_comm_factory.js", - "untrusted_projector_browser_proxy.js", "assets/icon_16.png", "assets/icon_32.png", "assets/icon_48.png", @@ -69,7 +88,13 @@ grd_prefix = "ash_projector_app_untrusted" out_grd = "$target_gen_dir/${grd_prefix}_resources.grd" - deps = [ ":mojom_grdp" ] - grdp_files = - [ "$target_gen_dir/projector_mojo_bindings_webui_resources.grdp" ] + deps = [ + ":mojom_grdp", + ":ts_grdp", + ] + + grdp_files = [ + "$target_gen_dir/projector_mojo_bindings_webui_resources.grdp", + "$target_gen_dir/ts_resources.grdp", + ] }
diff --git a/ash/webui/projector_app/resources/app/untrusted/untrusted_projector_browser_proxy.js b/ash/webui/projector_app/resources/app/untrusted/untrusted_projector_browser_proxy.ts similarity index 60% rename from ash/webui/projector_app/resources/app/untrusted/untrusted_projector_browser_proxy.js rename to ash/webui/projector_app/resources/app/untrusted/untrusted_projector_browser_proxy.ts index b32fb47..a73d755e 100644 --- a/ash/webui/projector_app/resources/app/untrusted/untrusted_projector_browser_proxy.js +++ b/ash/webui/projector_app/resources/app/untrusted/untrusted_projector_browser_proxy.ts
@@ -1,11 +1,11 @@ -// Copyright 2023 The Chromium Authors +// Copyright 2026 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import {UntrustedProjectorPageCallbackRouter, UntrustedProjectorPageHandlerFactory, UntrustedProjectorPageHandlerRemote, UntrustedProjectorPageRemote} from './ash/webui/projector_app/mojom/untrusted_projector.mojom-webui.js'; -import {JsNetErrorCode, PrefsThatProjectorCanAskFor, RequestType, XhrResponseCode} from './ash/webui/projector_app/public/mojom/projector_types.mojom-webui.js'; +import {UntrustedProjectorPageCallbackRouter, UntrustedProjectorPageHandlerFactory, UntrustedProjectorPageHandlerRemote} from './ash/webui/projector_app/mojom/untrusted_projector.mojom-webui.js'; +import {Account, JsNetErrorCode, NewScreencastPrecondition, PendingScreencast, PrefsThatProjectorCanAskFor, RequestType, VideoInfo, XhrResponseCode} from './ash/webui/projector_app/public/mojom/projector_types.mojom-webui.js'; -const booleanUserPrefs = new Map([ +const booleanUserPrefs = new Map<string, PrefsThatProjectorCanAskFor>([ [ 'ash.projector.creation_flow_enabled', PrefsThatProjectorCanAskFor.kProjectorCreationFlowEnabled, @@ -16,7 +16,7 @@ ], ]); -const intUserPrefs = new Map([ +const intUserPrefs = new Map<string, PrefsThatProjectorCanAskFor>([ [ 'ash.projector.gallery_onboarding_show_count', PrefsThatProjectorCanAskFor.kProjectorGalleryOnboardingShowCount, @@ -27,7 +27,7 @@ ], ]); -const requestMaps = new Map([ +const requestMaps = new Map<string, RequestType>([ [ 'POST', RequestType.kPost, @@ -46,7 +46,7 @@ ], ]); -const errorCodeMap = new Map([ +const errorCodeMap = new Map<XhrResponseCode, string>([ [ XhrResponseCode.kSuccess, '', @@ -69,43 +69,54 @@ ], ]); +export interface XhrResponseResult { + success: boolean; + response?: string; + error?: string; + errorCode: JsNetErrorCode; +} + export class UntrustedProjectorBrowserProxyImpl { + private pageHandlerRemote: UntrustedProjectorPageHandlerRemote; + private projectorCallbackRouter: UntrustedProjectorPageCallbackRouter; + constructor() { - this.pageHandlerFactory = UntrustedProjectorPageHandlerFactory.getRemote(); + const pageHandlerFactory = UntrustedProjectorPageHandlerFactory.getRemote(); this.pageHandlerRemote = new UntrustedProjectorPageHandlerRemote(); this.projectorCallbackRouter = new UntrustedProjectorPageCallbackRouter(); - this.pageHandlerFactory.create( + pageHandlerFactory.create( this.pageHandlerRemote.$.bindNewPipeAndPassReceiver(), this.projectorCallbackRouter.$.bindNewPipeAndPassRemote()); } - getProjectorCallbackRouter() { + getProjectorCallbackRouter(): UntrustedProjectorPageCallbackRouter { return this.projectorCallbackRouter; } - async getNewScreencastPreconditionState() { + async getNewScreencastPreconditionState(): + Promise<NewScreencastPrecondition> { const {precondition} = await this.pageHandlerRemote.getNewScreencastPrecondition(); return precondition; } - async shouldDownloadSoda() { + async shouldDownloadSoda(): Promise<boolean> { const {shouldDownload} = await this.pageHandlerRemote.shouldDownloadSoda(); return shouldDownload; } - async installSoda() { + async installSoda(): Promise<boolean> { const {triggered} = await this.pageHandlerRemote.installSoda(); return triggered; } - async getPendingScreencasts() { + async getPendingScreencasts(): Promise<PendingScreencast[]> { const {pendingScreencasts} = await this.pageHandlerRemote.getPendingScreencasts(); return pendingScreencasts; } - async getUserPref(userPref) { + async getUserPref(userPref: string): Promise<boolean|number> { const isBoolPref = booleanUserPrefs.has(userPref); const isIntPref = intUserPrefs.has(userPref); @@ -113,21 +124,17 @@ throw new Error(`Unsupported user preference: ${userPref}`); } - let mojoPref; - if (isBoolPref) { - mojoPref = booleanUserPrefs.get(userPref); - } else { - mojoPref = intUserPrefs.get(userPref); - } + const mojoPref = isBoolPref ? booleanUserPrefs.get(userPref)! : + intUserPrefs.get(userPref)!; const {value} = await this.pageHandlerRemote.getUserPref(mojoPref); if (isBoolPref && value.hasOwnProperty('boolValue')) { - return value.boolValue; + return value.boolValue!; } if (isIntPref && value.hasOwnProperty('intValue')) { - return value.intValue; + return value.intValue!; } throw new Error( @@ -135,7 +142,7 @@ userPref}`); } - async setUserPref(userPref, value) { + async setUserPref(userPref: string, value: boolean|number): Promise<boolean> { const isBoolPref = booleanUserPrefs.has(userPref); const isIntPref = intUserPrefs.has(userPref); @@ -143,26 +150,21 @@ throw new Error(`Unsupported user preference: ${userPref}`); } - let mojoPref; - const mojoValue = new Object(); - if (isBoolPref) { - mojoPref = booleanUserPrefs.get(userPref); - mojoValue.boolValue = value; - } else { - mojoPref = intUserPrefs.get(userPref); - mojoValue.intValue = value; - } + const mojoPref = isBoolPref ? booleanUserPrefs.get(userPref)! : + intUserPrefs.get(userPref)!; + const mojoValue = isBoolPref ? {boolValue: value as boolean} : + {intValue: value as number}; await this.pageHandlerRemote.setUserPref(mojoPref, mojoValue); return true; } - async openFeedbackDialog() { + async openFeedbackDialog(): Promise<void> { await this.pageHandlerRemote.openFeedbackDialog(); return; } - async startProjectorSession(storageDir) { + async startProjectorSession(storageDir: string): Promise<boolean> { const {success} = await this.pageHandlerRemote.startProjectorSession({ path: { path: storageDir, @@ -172,22 +174,23 @@ } async sendXhr( - url, method, requestBody, useCredentials, useApiKey, headers, - accountEmail) { + url: string, method: string, requestBody: string|null, + useCredentials: boolean, useApiKey: boolean, + headers: {[key: string]: string}|null, + accountEmail: string|null): Promise<XhrResponseResult> { if (!requestMaps.has(method)) { throw new Error(`Invalid request method. ${method}`); } - const requestMethod = requestMaps.get(method); + const requestMethod = requestMaps.get(method)!; const {response} = await this.pageHandlerRemote.sendXhr( url, requestMethod, requestBody, useCredentials, useApiKey, headers, accountEmail); - // TODO(b/237337607): Remove the success field and just pass response - // directly. + // TODO(crbug.com/237337607): Remove the success field and just pass + // response directly. - const errorCode = 'netErrorCode' in response ? response.netErrorCode : - JsNetErrorCode.kNoError; + const errorCode = response.netErrorCode ?? JsNetErrorCode.kNoError; return { success: response.responseCode === XhrResponseCode.kSuccess, @@ -197,22 +200,20 @@ }; } - async getAccounts() { + async getAccounts(): Promise<Account[]> { const {accounts} = await this.pageHandlerRemote.getAccounts(); return accounts; } - async getVideo(videoFileId, resourceKey) { + async getVideo(videoFileId: string, resourceKey: string|null): + Promise<VideoInfo> { const {result} = await this.pageHandlerRemote.getVideo(videoFileId, resourceKey); if ('errorMessage' in result) { return Promise.reject(result.errorMessage); } - return result.video; + return result.video!; } } -/** - * @type {UntrustedProjectorBrowserProxyImpl} - */ export const browserProxy = new UntrustedProjectorBrowserProxyImpl();
diff --git a/base/memory/memory_pressure_listener.h b/base/memory/memory_pressure_listener.h index 062e6833..24f8368 100644 --- a/base/memory/memory_pressure_listener.h +++ b/base/memory/memory_pressure_listener.h
@@ -29,57 +29,56 @@ class SingleThreadTaskRunner; enum class MemoryPressureListenerTag { + // Commented out values are deprecated. kTest = 0, - kHangWatcher = 1, + // kHangWatcher = 1, kMemBackend = 2, kLevelDb = 3, kSSLClientSessionCache = 4, kVulkanInProcessContextProvider = 5, - kDemuxerManager = 6, - kFrameEvictionManager = 7, + // kDemuxerManager = 6, + // kFrameEvictionManager = 7, kSlopBucket = 8, kDiscardableSharedMemoryManager = 9, - kSharedStorageManager = 10, - kStagingBufferPool = 11, + // kSharedStorageManager = 10, + // kStagingBufferPool = 11, kSharedDictionaryStorageOnDisk = 12, - kHttpNetworkSession = 13, + // kHttpNetworkSession = 13, kBlobMemoryController = 14, kQuicSessionPool = 15, - kImageDecodingStore = 16, - kCompositorGpuThread = 17, + // kImageDecodingStore = 16, + // kCompositorGpuThread = 17, kApplicationBreadcrumbsLogger = 18, - kSkiaOutputSurfaceImpl = 19, - kGpuImageDecodeCache = 20, - kResourcePool = 21, + // kSkiaOutputSurfaceImpl = 19, + // kGpuImageDecodeCache = 20, + // kResourcePool = 21, kOnDeviceTailModelService = 22, kGpuChannelManager = 23, - // Deprecated. // kSharedDictionaryManagerOnDisk = 24, kSharedDictionaryManager = 25, - kHistoryBackend = 26, - kMediaUrlIndex = 27, - kBFCachePolicy = 28, - kLayerTreeHostImpl = 29, - kCacheStorageManager = 30, - kPlayerCompositorDelegate = 31, - kNetworkServiceClient = 32, - kGpuChildThread = 33, - kNavigationEntryScreenshotManager = 34, + // kHistoryBackend = 26, + // kMediaUrlIndex = 27, + // kBFCachePolicy = 28, + // kLayerTreeHostImpl = 29, + // kCacheStorageManager = 30, + // kPlayerCompositorDelegate = 31, + // kNetworkServiceClient = 32, + // kGpuChildThread = 33, + // kNavigationEntryScreenshotManager = 34, kGlicKeyedService = 35, kRenderThreadImpl = 36, kSpareRenderProcessHostManagerImpl = 37, - kDOMStorageContextWrapper = 38, - kGpuProcessHost = 39, + // kDOMStorageContextWrapper = 38, + // kGpuProcessHost = 39, kPrerenderHostRegistry = 40, kUrgentPageDiscardingPolicy = 41, - kTabLoader = 42, + // kTabLoader = 42, kBackgroundTabLoadingPolicy = 43, - // Deprecated. // kThumbnailCache = 44, kUserspaceSwapPolicy = 45, kWorkingSetTrimmerPolicyChromeOS = 46, kLruRendererCache = 47, - kCastMemoryPressureControllerImpl = 48, + // kCastMemoryPressureControllerImpl = 48, kFontGlobalContext = 49, kClientDiscardableSharedMemoryManager = 50, kMemoryReclaimerPressureListener = 51, @@ -90,7 +89,7 @@ kPlainTextPainter = 56, kMemoryCache = 57, kResource = 58, - kResourceFetcher = 59, + // kResourceFetcher = 59, kGlicProfileManager = 60, kWebUIContentsPreloadManager = 61, kPaintPreviewTabService = 62,
diff --git a/cc/base/features.cc b/cc/base/features.cc index 2c553127..13e20ee 100644 --- a/cc/base/features.cc +++ b/cc/base/features.cc
@@ -25,12 +25,7 @@ // Whether the compositor should attempt to sync with the scroll handlers before // submitting a frame. -BASE_FEATURE(kSynchronizedScrolling, -#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) - base::FEATURE_DISABLED_BY_DEFAULT); -#else - base::FEATURE_ENABLED_BY_DEFAULT); -#endif +BASE_FEATURE(kSynchronizedScrolling, base::FEATURE_DISABLED_BY_DEFAULT); BASE_FEATURE(kDeferImplInvalidation, base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/cc/layers/picture_layer_impl.cc b/cc/layers/picture_layer_impl.cc index 52454da..14742d5 100644 --- a/cc/layers/picture_layer_impl.cc +++ b/cc/layers/picture_layer_impl.cc
@@ -378,14 +378,9 @@ break; } case TileDrawInfo::SOLID_COLOR_MODE: { - float alpha = draw_info.solid_color().fA * shared_quad_state->opacity; - if (alpha >= std::numeric_limits<float>::epsilon()) { - auto* quad = - render_pass->CreateAndAppendDrawQuad<viz::SolidColorDrawQuad>(); - quad->SetNew( - shared_quad_state, offset_geometry_rect, - offset_visible_geometry_rect, draw_info.solid_color(), - !layer_tree_impl()->settings().enable_edge_anti_aliasing); + if (auto* quad = AppendSolidColorQuad( + render_pass, shared_quad_state, offset_geometry_rect, + offset_visible_geometry_rect, draw_info.solid_color())) { ValidateQuadResources(quad); } has_draw_quad = true;
diff --git a/cc/layers/tile_based_layer_impl.h b/cc/layers/tile_based_layer_impl.h index 02a1c28..9e806a6 100644 --- a/cc/layers/tile_based_layer_impl.h +++ b/cc/layers/tile_based_layer_impl.h
@@ -6,6 +6,7 @@ #define CC_LAYERS_TILE_BASED_LAYER_IMPL_H_ #include <algorithm> +#include <limits> #include <memory> #include <utility> #include <vector> @@ -20,6 +21,7 @@ #include "cc/tiles/tiling_set_coverage_iterator.h" #include "cc/trees/layer_tree_impl.h" #include "components/viz/common/quads/debug_border_draw_quad.h" +#include "components/viz/common/quads/solid_color_draw_quad.h" namespace cc { @@ -93,6 +95,14 @@ return std::ranges::contains(last_append_quads_scales_, scale); } + // Appends a solid-color quad with color `color`. Returns the appended quad. + viz::SolidColorDrawQuad* AppendSolidColorQuad( + viz::CompositorRenderPass* render_pass, + viz::SharedQuadState* shared_quad_state, + const gfx::Rect& offset_geometry_rect, + const gfx::Rect& offset_visible_geometry_rect, + SkColor4f color); + private: // Invoked when the draw mode is DRAW_MODE_RESOURCELESS_SOFTWARE. virtual void AppendQuadsForResourcelessSoftwareDraw( @@ -414,6 +424,24 @@ } template <typename Tiling> +viz::SolidColorDrawQuad* TileBasedLayerImpl<Tiling>::AppendSolidColorQuad( + viz::CompositorRenderPass* render_pass, + viz::SharedQuadState* shared_quad_state, + const gfx::Rect& offset_geometry_rect, + const gfx::Rect& offset_visible_geometry_rect, + SkColor4f color) { + float alpha = color.fA * shared_quad_state->opacity; + if (alpha < std::numeric_limits<float>::epsilon()) { + return nullptr; + } + auto* quad = render_pass->CreateAndAppendDrawQuad<viz::SolidColorDrawQuad>(); + quad->SetNew(shared_quad_state, offset_geometry_rect, + offset_visible_geometry_rect, color, + !layer_tree_impl()->settings().enable_edge_anti_aliasing); + return quad; +} + +template <typename Tiling> std::optional<gfx::Rect> TileBasedLayerImpl<Tiling>::CalculateScaledCullRect( float max_contents_scale) const { const ScrollTree& scroll_tree =
diff --git a/cc/layers/tile_display_layer_impl.cc b/cc/layers/tile_display_layer_impl.cc index 565238e..2f44580 100644 --- a/cc/layers/tile_display_layer_impl.cc +++ b/cc/layers/tile_display_layer_impl.cc
@@ -5,7 +5,6 @@ #include "cc/layers/tile_display_layer_impl.h" #include <algorithm> -#include <limits> #include <memory> #include <utility> #include <variant> @@ -214,14 +213,8 @@ has_draw_quad = true; } else if (auto color = iter->solid_color()) { has_draw_quad = true; - const float alpha = color->fA * shared_quad_state->opacity; - if (alpha >= std::numeric_limits<float>::epsilon()) { - auto* quad = - render_pass->CreateAndAppendDrawQuad<viz::SolidColorDrawQuad>(); - quad->SetNew(shared_quad_state, offset_geometry_rect, - offset_visible_geometry_rect, *color, - !layer_tree_impl()->settings().enable_edge_anti_aliasing); - } + AppendSolidColorQuad(render_pass, shared_quad_state, offset_geometry_rect, + offset_visible_geometry_rect, *color); } else if (iter->is_oom()) { // Keep `has_draw_quad` false to end up checkerboarding below. }
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc index ccc7295..0acdebf 100644 --- a/cc/trees/layer_tree_host_impl.cc +++ b/cc/trees/layer_tree_host_impl.cc
@@ -1428,8 +1428,12 @@ if (expects_to_draw) { if (active_tree_->RootRenderSurface()) { - const gfx::Rect& viz_damage_rect = + gfx::Rect viz_damage_rect = active_tree_->RootRenderSurface()->GetDamageRect(); + // Add a 1px margin to the viz damage rect to filter out precision issues + // with transforms. This will be re-added once the larger damage + // discrepancies are fixed. + viz_damage_rect.Outset(1); // If Viz has MORE damage than the client expected, it's safe for // rendering (just potentially wasteful). If Viz has LESS damage, we might // miss redrawing some areas. @@ -1449,7 +1453,8 @@ // Force drawing, but assert in DCHECK builds. DUMP_WILL_BE_CHECK(has_damage) - << "crbug.com/454680865: Has no damage while expects_to_draw is set"; + << "crbug.com/454680865: Has no damage while expects_to_draw is set." + << " Client damage: " << root_layer_damage_rect_.ToString(); has_damage = true; }
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni index 84c6bef..c90e5c01 100644 --- a/chrome/android/chrome_java_sources.gni +++ b/chrome/android/chrome_java_sources.gni
@@ -10,6 +10,7 @@ "java/src/org/chromium/chrome/browser/ActivityUtils.java", "java/src/org/chromium/chrome/browser/AppHooks.java", "java/src/org/chromium/chrome/browser/AutofillOptionsLauncher.java", + "java/src/org/chromium/chrome/browser/BrowserExitReasonTracker.java", "java/src/org/chromium/chrome/browser/BrowserRestartActivity.java", "java/src/org/chromium/chrome/browser/ChromeActionModeHandler.java", "java/src/org/chromium/chrome/browser/ChromeActivitySessionTracker.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/BrowserExitReasonTracker.java b/chrome/android/java/src/org/chromium/chrome/browser/BrowserExitReasonTracker.java new file mode 100644 index 0000000..300befc --- /dev/null +++ b/chrome/android/java/src/org/chromium/chrome/browser/BrowserExitReasonTracker.java
@@ -0,0 +1,54 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser; + +import android.content.SharedPreferences; +import android.os.Build; +import android.os.Process; + +import org.chromium.base.shared_preferences.SharedPreferencesManager; +import org.chromium.build.annotations.NullMarked; +import org.chromium.chrome.browser.preferences.ChromePreferenceKeys; +import org.chromium.chrome.browser.preferences.ChromeSharedPreferences; +import org.chromium.components.crash.browser.ProcessExitReasonFromSystem; + +/** Records the SystemExitReason for the Browser process. */ +@NullMarked +public class BrowserExitReasonTracker { + private BrowserExitReasonTracker() {} + + public static void initForegroundBrowserProcess() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return; + + SharedPreferencesManager prefs = ChromeSharedPreferences.getInstance(); + if (prefs.contains(ChromePreferenceKeys.LAST_SESSION_BROWSER_EXIT_REASON)) { + int reason = prefs.readInt(ChromePreferenceKeys.LAST_SESSION_BROWSER_EXIT_REASON); + ProcessExitReasonFromSystem.recordAsEnumHistogram( + "Stability.Android.SystemExitReason.Browser2", reason); + } + SharedPreferences.Editor ed = prefs.getEditor(); + ed.remove(ChromePreferenceKeys.LAST_SESSION_BROWSER_EXIT_REASON); + // Store current PID for next session to detect how this one exited. + ed.putInt(ChromePreferenceKeys.LAST_SESSION_BROWSER_PID, Process.myPid()); + ed.apply(); + } + + public static void onBrowserProcessCreated() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return; + + SharedPreferencesManager prefs = ChromeSharedPreferences.getInstance(); + int previousPid = prefs.readInt(ChromePreferenceKeys.LAST_SESSION_BROWSER_PID); + if (previousPid != 0) { + int reason = ProcessExitReasonFromSystem.getExitReason(previousPid); + SharedPreferences.Editor ed = prefs.getEditor(); + ed.remove(ChromePreferenceKeys.LAST_SESSION_BROWSER_PID); + // If this is a background launch eg. JobScheduler, we may not load native and persist + // histograms. Store the histrogram value to shared prefs to record it on next + // foreground launch. + ed.putInt(ChromePreferenceKeys.LAST_SESSION_BROWSER_EXIT_REASON, reason); + ed.apply(); + } + } +}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplicationImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplicationImpl.java index dde1194..10798d0 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplicationImpl.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeApplicationImpl.java
@@ -95,6 +95,8 @@ if (!BuildConfig.IS_CHROME_BRANDED) { HierarchySnapshotter.initialize(); } + + BrowserExitReasonTracker.onBrowserProcessCreated(); } }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeWindow.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeWindow.java index 7f0fefb..f881e0e 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeWindow.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeWindow.java
@@ -18,6 +18,7 @@ import org.chromium.ui.base.IntentRequestTracker; import org.chromium.ui.insets.InsetObserver; import org.chromium.ui.modaldialog.ModalDialogManager; +import org.chromium.ui.permissions.ActivityAndroidPermissionDelegate; import java.lang.ref.WeakReference; import java.util.function.Supplier; @@ -62,6 +63,7 @@ super( activity, /* listenToActivityState= */ true, + new ActivityAndroidPermissionDelegate(new WeakReference<>(activity)), sKeyboardVisibilityDelegateFactory.create( new WeakReference<>(activity), manualFillingComponentSupplier), /* activityTopResumedSupported= */ true,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkActivity.java index a512e90..1e214ff 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/bookmarks/BookmarkActivity.java
@@ -81,7 +81,7 @@ /* context= */ this, /* componentName= */ parentComponent); mWindowAndroid = - new ActivityWindowAndroid( + ActivityWindowAndroid.create( this, /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(this),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActivity.java index ff45bc4..7dd0ee1 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActivity.java
@@ -111,9 +111,9 @@ IntentRequestTracker intentRequestTracker = IntentRequestTracker.createFromActivity(this); mWindowAndroid = - new ActivityWindowAndroid( + ActivityWindowAndroid.create( this, - false, + /* listenToActivityState= */ false, intentRequestTracker, getInsetObserver(), /* trackOcclusion= */ true);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuItemViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuItemViewBinder.java index 1300069..7fa67d6 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuItemViewBinder.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuItemViewBinder.java
@@ -16,6 +16,7 @@ import static org.chromium.ui.listmenu.ListMenuItemProperties.START_ICON_ID; import static org.chromium.ui.listmenu.ListMenuItemProperties.TITLE; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; @@ -78,7 +79,7 @@ } } else if (propertyKey == ICON_TINT_COLOR_STATE_LIST_ID) { @ColorRes int tintColorId = model.get(ICON_TINT_COLOR_STATE_LIST_ID); - if (tintColorId != 0) { + if (tintColorId != Resources.ID_NULL) { ImageViewCompat.setImageTintList( startIconView, AppCompatResources.getColorStateList(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java index 4a8602b..8f1766a 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java
@@ -4,6 +4,8 @@ package org.chromium.chrome.browser.contextualsearch; +import static org.chromium.build.NullUtil.assertNonNull; + import android.content.Context; import org.jni_zero.CalledByNative; @@ -38,12 +40,19 @@ import org.chromium.ui.base.WindowAndroid; import org.chromium.url.GURL; +import java.util.HashMap; +import java.util.Map; + /** Manages the enabling and disabling and gesture listeners for ContextualSearch on a given Tab. */ @NullMarked public class ContextualSearchTabHelper extends EmptyTabObserver implements NetworkChangeNotifier.ConnectionTypeObserver, TemplateUrlServiceObserver { private static final String TAG = "ContextualSearch"; + // A map of native helper objects to their Java counterparts allows unlimited scaling in number + // of tabs. + private static final Map<Long, ContextualSearchTabHelper> sNativeHelperMap = new HashMap<>(); + /** The Tab that this helper tracks. */ private final Tab mTab; @@ -148,7 +157,9 @@ // is initialized. Profile profile = tab.getProfile(); if (mNativeHelper == 0 && tab.getWebContents() != null) { - mNativeHelper = ContextualSearchTabHelperJni.get().init(this, profile); + mNativeHelper = ContextualSearchTabHelperJni.get().init(profile); + var oldValue = sNativeHelperMap.put(mNativeHelper, this); + assert oldValue == null; } if (profile != null && mTemplateUrlService == null) { mTemplateUrlService = TemplateUrlServiceFactory.getForProfile(profile); @@ -162,6 +173,8 @@ public void onDestroyed(Tab tab) { if (mNativeHelper != 0) { ContextualSearchTabHelperJni.get().destroy(mNativeHelper); + var oldValue = sNativeHelperMap.remove(mNativeHelper); + assert oldValue == this; mNativeHelper = 0; } if (mTemplateUrlService != null) { @@ -468,9 +481,14 @@ } } + @CalledByNative + private static ContextualSearchTabHelper getJavaObject(long nativeHelper) { + return assertNonNull(sNativeHelperMap.get(nativeHelper)); + } + @NativeMethods interface Natives { - long init(ContextualSearchTabHelper self, @JniType("Profile*") Profile profile); + long init(@JniType("Profile*") Profile profile); void installUnhandledTapNotifierIfNeeded( long nativeContextualSearchTabHelper,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceImpl.java index e2bab33..1c18420 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceImpl.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadServiceImpl.java
@@ -10,7 +10,6 @@ import android.content.ComponentName; import android.content.Intent; import android.os.PersistableBundle; -import android.os.Process; import androidx.annotation.StringDef; import androidx.annotation.VisibleForTesting; @@ -18,25 +17,16 @@ import org.jni_zero.CalledByNative; import org.jni_zero.JniType; -import org.chromium.base.ApplicationState; import org.chromium.base.ApplicationStatus; -import org.chromium.base.ApplicationStatus.ApplicationStateListener; import org.chromium.base.ContextUtils; import org.chromium.base.Log; import org.chromium.base.StreamUtil; -import org.chromium.base.ThreadUtils; import org.chromium.base.metrics.RecordHistogram; -import org.chromium.base.shared_preferences.SharedPreferencesManager; -import org.chromium.base.task.PostTask; -import org.chromium.base.task.TaskTraits; import org.chromium.build.annotations.NullMarked; import org.chromium.build.annotations.Nullable; import org.chromium.chrome.browser.base.SplitCompatIntentService; -import org.chromium.chrome.browser.preferences.ChromePreferenceKeys; -import org.chromium.chrome.browser.preferences.ChromeSharedPreferences; import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl; import org.chromium.components.background_task_scheduler.TaskIds; -import org.chromium.components.crash.browser.ProcessExitReasonFromSystem; import org.chromium.components.minidump_uploader.CrashFileManager; import org.chromium.components.minidump_uploader.MinidumpUploadCallable; import org.chromium.components.minidump_uploader.MinidumpUploadCallable.MinidumpUploadStatus; @@ -118,50 +108,10 @@ MinidumpUploadJobService.scheduleUpload(builder); } - private static ApplicationStateListener createApplicationStateListener() { - return newState -> { - ChromeSharedPreferences.getInstance() - .writeInt(ChromePreferenceKeys.LAST_SESSION_APPLICATION_STATE, newState); - }; - } - /** Stores the successes and failures from uploading crash to UMA, */ public static void storeBreakpadUploadStatsInUma(CrashUploadCountStore pref) { sBrowserCrashMetricsInitialized.set(true); - SharedPreferencesManager sharedPrefs = ChromeSharedPreferences.getInstance(); - int previousPid = sharedPrefs.readInt(ChromePreferenceKeys.LAST_SESSION_BROWSER_PID); - @ApplicationState - int applicationExitState = - sharedPrefs.readInt(ChromePreferenceKeys.LAST_SESSION_APPLICATION_STATE); - String umaSuffix; - if (applicationExitState == ApplicationState.HAS_RUNNING_ACTIVITIES) { - umaSuffix = "Foreground2"; - } else { - umaSuffix = "Background2"; - } - sharedPrefs.writeInt(ChromePreferenceKeys.LAST_SESSION_BROWSER_PID, Process.myPid()); - ApplicationStateListener appStateListener = createApplicationStateListener(); - appStateListener.onApplicationStateChange(ApplicationStatus.getStateForApplication()); - - if (ThreadUtils.runningOnUiThread()) { - ApplicationStatus.registerApplicationStateListener(appStateListener); - } else { - PostTask.postTask( - TaskTraits.UI_BEST_EFFORT, - () -> { - ApplicationStatus.registerApplicationStateListener(appStateListener); - }); - } - - if (previousPid != 0) { - int reason = ProcessExitReasonFromSystem.getExitReason(previousPid); - ProcessExitReasonFromSystem.recordAsEnumHistogram( - "Stability.Android.SystemExitReason.Browser", reason); - ProcessExitReasonFromSystem.recordAsEnumHistogram( - "Stability.Android.SystemExitReason.Browser." + umaSuffix, reason); - } - for (String type : TYPES) { for (int success = pref.getCrashSuccessUploadCount(type); success > 0; success--) { RecordHistogram.recordEnumeratedHistogram( @@ -319,17 +269,19 @@ * crashes by looking into the file contents. Because this code can execute in a context when * the main Chrome activity is no longer running, the counts are stored in shared preferences; * they are later read and recorded as metrics by the main Chrome activity. - * NOTE: This method should be called *after* renaming the file, since renaming occurs as a + * + * <p>NOTE: This method should be called *after* renaming the file, since renaming occurs as a * side-effect of a successful upload. + * * @param originalFilename The name of the successfully uploaded minidump, *prior* to uploading. */ public static void incrementCrashSuccessUploadCount(String originalFilename) { - final @ProcessType String process_type = + final @ProcessType String processType = getCrashType(getNewNameAfterSuccessfulUpload(originalFilename)); - if (ProcessType.BROWSER.equals(process_type)) { + if (ProcessType.BROWSER.equals(processType)) { sDidBrowserCrashRecently.set(true); } - CrashUploadCountStore.getInstance().incrementCrashSuccessUploadCount(process_type); + CrashUploadCountStore.getInstance().incrementCrashSuccessUploadCount(processType); } /** @@ -337,15 +289,17 @@ * by looking into the file contents. Because this code can execute in a context when the main * Chrome activity is no longer running, the counts are stored in shared preferences; they are * later read and recorded as metrics by the main Chrome activity. - * NOTE: This method should be called *prior* to renaming the file. + * + * <p>NOTE: This method should be called *prior* to renaming the file. + * * @param originalFilename The name of the successfully uploaded minidump, *prior* to uploading. */ public static void incrementCrashFailureUploadCount(String originalFilename) { - final @ProcessType String process_type = getCrashType(originalFilename); - if (ProcessType.BROWSER.equals(process_type)) { + final @ProcessType String processType = getCrashType(originalFilename); + if (ProcessType.BROWSER.equals(processType)) { sDidBrowserCrashRecently.set(true); } - CrashUploadCountStore.getInstance().incrementCrashFailureUploadCount(process_type); + CrashUploadCountStore.getInstance().incrementCrashFailureUploadCount(processType); } /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/device_lock/DeviceLockActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/device_lock/DeviceLockActivity.java index 0aba555..7066565 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/device_lock/DeviceLockActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/device_lock/DeviceLockActivity.java
@@ -69,7 +69,7 @@ mFrameLayout = new FrameLayout(this); setContentView(mFrameLayout); mWindowAndroid = - new ActivityWindowAndroid( + ActivityWindowAndroid.create( this, /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(this),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java index 680e174..c8ec5814 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/FirstRunActivity.java
@@ -859,7 +859,7 @@ @Override protected ActivityWindowAndroid createWindowAndroid() { - return new ActivityWindowAndroid( + return ActivityWindowAndroid.create( this, /* listenToActivityState= */ true, getIntentRequestTracker(),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryActivity.java index 4215593..b5c47ce 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryActivity.java
@@ -74,7 +74,7 @@ view, getEdgeToEdgeSupplier()); } mWindowAndroid = - new ActivityWindowAndroid( + ActivityWindowAndroid.create( this, /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(this),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/identity_disc/IdentityDiscController.java b/chrome/android/java/src/org/chromium/chrome/browser/identity_disc/IdentityDiscController.java index 56af298..471b807a 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/identity_disc/IdentityDiscController.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/identity_disc/IdentityDiscController.java
@@ -31,6 +31,7 @@ import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.settings.SettingsNavigationFactory; import org.chromium.chrome.browser.signin.SigninAndHistorySyncActivityLauncherImpl; +import org.chromium.chrome.browser.signin.services.BadgeConfig; import org.chromium.chrome.browser.signin.services.DisplayableProfileData; import org.chromium.chrome.browser.signin.services.IdentityServicesProvider; import org.chromium.chrome.browser.signin.services.ProfileDataCache; @@ -368,8 +369,9 @@ coreAccountInfo.getId(), mIdentityError == UserActionableError.NONE ? null - : ProfileDataCache.createToolbarIdentityDiscBadgeConfig( - mContext, R.drawable.ic_error_badge_16dp)); + : BadgeConfig.create(R.drawable.ic_error_badge_16dp) + .withToolbarIdentityDiscConfig() + .build(mContext)); } }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java index 4f5c323..cfeba724 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
@@ -45,6 +45,7 @@ import org.chromium.build.annotations.Nullable; import org.chromium.chrome.R; import org.chromium.chrome.browser.AppHooks; +import org.chromium.chrome.browser.BrowserExitReasonTracker; import org.chromium.chrome.browser.ChromeActivitySessionTracker; import org.chromium.chrome.browser.ChromeStrictMode; import org.chromium.chrome.browser.DefaultBrowserInfo; @@ -627,6 +628,8 @@ protected void addPerApplicationStartupDeferredTasks(List<Runnable> tasks, Profile profile) { tasks.add( () -> { + BrowserExitReasonTracker.initForegroundBrowserProcess(); + initAsyncDiskTask(); StorageSystem.recordStorageType();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/DocumentPictureInPictureActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/media/DocumentPictureInPictureActivity.java index fd18dc6..fe66b37e 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/media/DocumentPictureInPictureActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/media/DocumentPictureInPictureActivity.java
@@ -358,7 +358,7 @@ @Override protected ActivityWindowAndroid createWindowAndroid() { - return new ActivityWindowAndroid( + return ActivityWindowAndroid.create( this, /* listenToActivityState= */ true, getIntentRequestTracker(),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java index 7f0f6ad..749a3c8 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java
@@ -841,7 +841,7 @@ @Override protected ActivityWindowAndroid createWindowAndroid() { - return new ActivityWindowAndroid( + return ActivityWindowAndroid.create( this, /* listenToActivityState= */ true, getIntentRequestTracker(),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java index 6b8a40f59..3d64bac 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
@@ -89,6 +89,7 @@ import org.chromium.ui.base.ActivityWindowAndroid; import org.chromium.ui.edge_to_edge.EdgeToEdgeSystemBarColorHelper; import org.chromium.ui.modaldialog.ModalDialogManager; +import org.chromium.ui.permissions.ActivityAndroidPermissionDelegate; import org.chromium.url.GURL; import java.lang.annotation.Retention; @@ -274,6 +275,7 @@ return new ActivityWindowAndroid( this, /* listenToActivityState= */ true, + new ActivityAndroidPermissionDelegate(new WeakReference(this)), new ActivityKeyboardVisibilityDelegate(new WeakReference(this)), /* activityTopResumedSupported= */ false, getIntentRequestTracker(),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninAndHistorySyncActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninAndHistorySyncActivity.java index c27c079..2aeac01 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninAndHistorySyncActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninAndHistorySyncActivity.java
@@ -205,7 +205,7 @@ @Override protected ActivityWindowAndroid createWindowAndroid() { - return new ActivityWindowAndroid( + return ActivityWindowAndroid.create( this, /* listenToActivityState= */ true, getIntentRequestTracker(),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/AccountManagementFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/AccountManagementFragment.java index f437d86..a9a51b06 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/AccountManagementFragment.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/AccountManagementFragment.java
@@ -38,6 +38,7 @@ import org.chromium.chrome.browser.settings.SettingsNavigationFactory; import org.chromium.chrome.browser.settings.search.ChromeBaseSearchIndexProvider; import org.chromium.chrome.browser.signin.SigninAndHistorySyncActivityLauncherImpl; +import org.chromium.chrome.browser.signin.services.BadgeConfig; import org.chromium.chrome.browser.signin.services.DisplayableProfileData; import org.chromium.chrome.browser.signin.services.IdentityServicesProvider; import org.chromium.chrome.browser.signin.services.ProfileDataCache; @@ -335,10 +336,7 @@ accountPreference.setOnPreferenceClickListener( SyncSettingsUtils.toOnClickListener( - this, - () -> - SigninUtils.openSettingsForAccount( - getActivity(), coreAccountInfo.getEmail()))); + this, () -> SigninUtils.openSettingsForAllAccounts(getActivity()))); return accountPreference; } @@ -431,10 +429,9 @@ if (isChild && context != null) { mProfileDataCache.setBadge( assumeNonNull(childAccount).getId(), - ProfileDataCache - .createDefaultSizeChildAccountBadgeConfig( - context, - R.drawable.ic_account_child_20dp)); + BadgeConfig.create(R.drawable.ic_account_child_20dp) + .withDefaultSizeChildAccountConfig() + .build(context)); } }); }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/synced_set_up/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/sync/synced_set_up/OWNERS new file mode 100644 index 0000000..1f9c979 --- /dev/null +++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/synced_set_up/OWNERS
@@ -0,0 +1 @@ +file://components/sync_preferences/synced_set_up/OWNERS \ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeUtil.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeUtil.java index f0b596f..f034053 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeUtil.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/ReturnToChromeUtil.java
@@ -17,6 +17,7 @@ import androidx.annotation.IntDef; +import org.chromium.base.DeviceInfo; import org.chromium.base.IntentUtils; import org.chromium.base.metrics.RecordHistogram; import org.chromium.build.annotations.NullMarked; @@ -129,6 +130,11 @@ Bundle bundle, PersistableBundle persistableBundle, ChromeInactivityTracker inactivityTracker) { + // If the device is android desktop, don't show a NTP homepage. + if (ChromeFeatureList.sNtpSimplification.isEnabled() && DeviceInfo.isDesktop()) { + return false; + } + // If the current session is due to recreated, don't show a NTP homepage. if (isFromRecreate(bundle) || isFromUpdate(persistableBundle)) { return false;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java index cc3cebb5..793de97b 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -39,7 +39,6 @@ import org.chromium.base.CallbackController; import org.chromium.base.ContextUtils; import org.chromium.base.JavaExceptionReporter; -import org.chromium.base.Log; import org.chromium.base.TimeUtils; import org.chromium.base.TraceEvent; import org.chromium.base.ValueChangedCallback; @@ -253,8 +252,6 @@ TintObserver, MenuButtonDelegate, TabObscuringHandler.Observer { - private static final String TAG = "ToolbarManager"; - private final IncognitoStateProvider mIncognitoStateProvider; private final TopUiThemeColorProvider mTopUiThemeColorProvider; private @Nullable final AdjustedTopUiThemeColorProvider mAdjustedTopUiThemeColorProvider; @@ -362,7 +359,6 @@ private final SettableNonNullObservableSupplier<@ControlsPosition Integer> mToolbarPositionSupplier = ObservableSuppliers.createNonNull(ControlsPosition.NONE); private final OneshotSupplier<ChromeAndroidTask> mChromeAndroidTaskSupplier; - private final boolean mEnableLogs; private @MonotonicNonNull HomeButtonCoordinator mHomeButtonCoordinator; private @MonotonicNonNull ToggleTabStackButtonCoordinator mTabSwitcherButtonCoordinator; @@ -869,7 +865,6 @@ mToolbarLayout = mActivity.findViewById(R.id.toolbar); NewTabPageDelegate ntpDelegate = createNewTabPageDelegate(); mIsCustomTab = mToolbarLayout instanceof CustomTabToolbar; - mEnableLogs = ChromeFeatureList.sNewTabPageCustomizationV2EnableLogs.getValue(); mLocationBarModel = new LocationBarModel( @@ -3200,20 +3195,7 @@ * the new page. */ private void checkIfNtpShowingWithNoPendingLoad() { - GURL url = mLocationBarModel.getCurrentGurl(); - boolean isNtpUrl = UrlUtilities.isNtpUrl(url); - - if (mEnableLogs) { - Log.i( - TAG, - "Get NewTabPage for the current tab: [null: %b] [isNtpUrl: %b] [isEmpty: %b]" - + " [isValid: %b]", - getNewTabPageForCurrentTab() == null, - isNtpUrl, - url.isEmpty(), - url.isValid()); - } - + boolean isNtpUrl = UrlUtilities.isNtpUrl(mLocationBarModel.getCurrentGurl()); if (isNtpUrl && getNewTabPageForCurrentTab() != null) { mIsNtpWithFakeboxShowingSupplier.set(NewTabPage.isInSingleUrlBarMode(mIsTablet)); } else { @@ -3232,12 +3214,6 @@ mLocationBarModel.notifyNtpStartedLoading(); } - if (mEnableLogs) { - Log.i( - TAG, - "Check if Ntp loaded with current tab %s a NTP.", - ntp != null ? "is" : "isn't"); - } checkIfNtpShowingWithNoPendingLoad(); if (mToolbarPositionController != null) { @@ -3287,9 +3263,6 @@ if (updateUrl) { mLocationBarModel.notifyUrlChanged(false); updateButtonStatus(); - if (mEnableLogs) { - Log.i(TAG, "Update tab loading state to check if NTP showing."); - } checkIfNtpShowingWithNoPendingLoad(); } }
diff --git a/chrome/android/javatests/BUILD.gn b/chrome/android/javatests/BUILD.gn index 4276b24..e90f40653c 100644 --- a/chrome/android/javatests/BUILD.gn +++ b/chrome/android/javatests/BUILD.gn
@@ -1507,6 +1507,7 @@ "src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveButtonActionMenuRenderTest.java", "src/org/chromium/chrome/browser/toolbar/adaptive/OptionalNewTabButtonControllerPhoneTest.java", "src/org/chromium/chrome/browser/toolbar/adaptive/OptionalNewTabButtonControllerTabletTest.java", + "src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonCoordinatorTest.java", "src/org/chromium/chrome/browser/toolbar/top/BrandColorTest.java", "src/org/chromium/chrome/browser/toolbar/top/NavigationPopupTest.java", "src/org/chromium/chrome/browser/toolbar/top/TabSwitcherActionMenuBatchedPTTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/SelectFileDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/SelectFileDialogTest.java index 591e73d..b4efc98 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/SelectFileDialogTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/SelectFileDialogTest.java
@@ -38,12 +38,15 @@ import org.chromium.chrome.test.transit.FreshCtaTransitTestRule; import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.browser.test.util.DOMUtils; +import org.chromium.ui.base.ActivityKeyboardVisibilityDelegate; import org.chromium.ui.base.ActivityWindowAndroid; import org.chromium.ui.base.IntentRequestTracker; import org.chromium.ui.base.SelectFileDialog; import org.chromium.ui.insets.InsetObserver; +import org.chromium.ui.permissions.ActivityAndroidPermissionDelegate; import java.io.File; +import java.lang.ref.WeakReference; /** Integration test for select file dialog used for <input type="file" /> */ @RunWith(ChromeJUnit4ClassRunner.class) @@ -77,6 +80,9 @@ super( activity, /* listenToActivityState= */ true, + new ActivityAndroidPermissionDelegate(new WeakReference<>(activity)), + new ActivityKeyboardVisibilityDelegate(new WeakReference<>(activity)), + /* activityTopResumedSupported= */ false, IntentRequestTracker.createFromActivity(activity), insetObserver, /* trackOcclusion= */ true);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/app/edge_to_edge/EdgeToEdgeInstrumentationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/edge_to_edge/EdgeToEdgeInstrumentationTest.java index 9a01d7f..3d09383e 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/app/edge_to_edge/EdgeToEdgeInstrumentationTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/edge_to_edge/EdgeToEdgeInstrumentationTest.java
@@ -238,6 +238,7 @@ @Test @MediumTest + @DisabledTest(message = "crbug.com/455479243") public void testRotationToPortrait_WhileOptedIntoE2E() { activateFeatureToEdge(); rotate(Configuration.ORIENTATION_LANDSCAPE);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBaseTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBaseTest.java index 176c2cce..29b0aa3 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBaseTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBaseTest.java
@@ -187,7 +187,7 @@ () -> { mActivity = activityTestRule.getActivity(); mWindowAndroid = - new ActivityWindowAndroid( + ActivityWindowAndroid.create( mActivity, /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(mActivity),
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelEventFilterTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelEventFilterTest.java index 481ac97..c0a6461 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelEventFilterTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelEventFilterTest.java
@@ -276,7 +276,7 @@ () -> { mActivity = activityTestRule.getActivity(); mWindowAndroid = - new ActivityWindowAndroid( + ActivityWindowAndroid.create( mActivity, /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(mActivity),
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelManagerTest.java index ed2c6d8..ea54f33 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelManagerTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelManagerTest.java
@@ -177,7 +177,7 @@ ThreadUtils.runOnUiThreadBlocking( () -> { mActivity = activityTestRule.getActivity(); - return new ActivityWindowAndroid( + return ActivityWindowAndroid.create( mActivity, /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(mActivity),
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/identity_disc/IdentityDiscControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/identity_disc/IdentityDiscControllerTest.java index 70744828..ac18c444e7 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/identity_disc/IdentityDiscControllerTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/identity_disc/IdentityDiscControllerTest.java
@@ -57,6 +57,7 @@ import org.chromium.base.test.params.ParameterizedRunner; import org.chromium.base.test.util.ApplicationTestUtils; import org.chromium.base.test.util.CommandLineFlags; +import org.chromium.base.test.util.DisabledTest; import org.chromium.base.test.util.DoNotBatch; import org.chromium.base.test.util.Feature; import org.chromium.base.test.util.Features.DisableFeatures; @@ -429,6 +430,7 @@ @Test @MediumTest @SuppressWarnings("CheckReturnValue") + @DisabledTest(message = "crbug.com/489053128") public void testIdentityDiscWithSwitchToIncognito() { mSigninTestRule.addAccountThenSignin(TestAccounts.ACCOUNT1); ViewUtils.waitForVisibleView(withId(R.id.optional_toolbar_button));
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderUnitTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderUnitTest.java index 33a6bdf..875fcf0c 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderUnitTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/AndroidPaymentAppFinderUnitTest.java
@@ -114,7 +114,7 @@ mWindowAndroid = ThreadUtils.runOnUiThreadBlocking( () -> { - return new ActivityWindowAndroid( + return ActivityWindowAndroid.create( sActivity, /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(sActivity),
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonCoordinatorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonCoordinatorTest.java new file mode 100644 index 0000000..edfd4f8 --- /dev/null +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonCoordinatorTest.java
@@ -0,0 +1,104 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.toolbar.signin_button; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; + +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.not; + +import static org.chromium.chrome.browser.url_constants.UrlConstantResolver.getOriginalNativeNtpUrl; + +import androidx.test.filters.MediumTest; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.runner.RunWith; + +import org.chromium.base.test.util.CommandLineFlags; +import org.chromium.base.test.util.DoNotBatch; +import org.chromium.base.test.util.Features.EnableFeatures; +import org.chromium.chrome.R; +import org.chromium.chrome.browser.flags.ChromeSwitches; +import org.chromium.chrome.test.ChromeJUnit4ClassRunner; +import org.chromium.chrome.test.transit.ChromeTransitTestRules; +import org.chromium.chrome.test.transit.FreshCtaTransitTestRule; +import org.chromium.chrome.test.transit.ntp.RegularNewTabPageStation; +import org.chromium.chrome.test.transit.page.WebPageStation; +import org.chromium.chrome.test.util.NewTabPageTestUtils; +import org.chromium.chrome.test.util.browser.signin.SigninTestRule; +import org.chromium.components.signin.SigninFeatures; +import org.chromium.content_public.common.ContentUrlConstants; +import org.chromium.ui.test.util.ViewUtils; + +/** Integration tests for {@link SigninButtonCoordinator}. */ +@RunWith(ChromeJUnit4ClassRunner.class) +@DoNotBatch(reason = "This test relies on native initialization") +@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) +@EnableFeatures(SigninFeatures.SIGNIN_LEVEL_UP_BUTTON) +public class SigninButtonCoordinatorTest { + private final FreshCtaTransitTestRule mActivityTestRule = + ChromeTransitTestRules.freshChromeTabbedActivityRule(); + + private final SigninTestRule mSigninTestRule = new SigninTestRule(); + + @Rule + public final RuleChain mRuleChain = + RuleChain.outerRule(mSigninTestRule).around(mActivityTestRule); + + private RegularNewTabPageStation mPage; + + @Before + public void setUp() { + mPage = mActivityTestRule.startOnNtp(); + NewTabPageTestUtils.waitForNtpLoaded(mPage.getTab()); + } + + @Test + @MediumTest + public void testSigninButtonVisibleOnNtp() { + // Sign-in button should be visible on NTP. + ViewUtils.waitForVisibleView(allOf(withId(R.id.signin_button), isDisplayed())); + } + + @Test + @MediumTest + public void testSigninButtonHiddenOnNavigation() { + // Initially visible on NTP. + ViewUtils.waitForVisibleView(allOf(withId(R.id.signin_button), isDisplayed())); + + // Should be hidden on navigation away from NTP. + WebPageStation aboutBlank = + mPage.loadWebPageProgrammatically(ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL); + + onView(withId(R.id.signin_button)).check(matches(not(isDisplayed()))); + + // Should be visible again when navigating back to NTP. + aboutBlank.loadPageProgrammatically( + getOriginalNativeNtpUrl(), RegularNewTabPageStation.newBuilder()); + ViewUtils.waitForVisibleView(allOf(withId(R.id.signin_button), isDisplayed())); + } + + @Test + @MediumTest + public void testSigninButtonHiddenOnIncognitoNtp() { + // Initially visible on NTP. + ViewUtils.waitForVisibleView(allOf(withId(R.id.signin_button), isDisplayed())); + + mPage.openAppMenu().openNewIncognitoTab(); + + // Signin button should not be visible on incognito NTP. + // It may not be inflated yet in the new incognito tab, so we check for both the + // inflated view and its stub. + onView(anyOf(withId(R.id.signin_button), withId(R.id.signin_button_stub))) + .check(matches(not(isDisplayed()))); + } +}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperMultiInstanceUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperMultiInstanceUnitTest.java index 4509d48..f1635dd 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperMultiInstanceUnitTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperMultiInstanceUnitTest.java
@@ -203,7 +203,7 @@ doReturn(taskId).when(mActivity).getTaskId(); mIntentRequestTracker = IntentRequestTracker.createFromActivity(mActivity); mWindow = - new ActivityWindowAndroid( + ActivityWindowAndroid.create( mActivity, /* listenToActivityState= */ false, mIntentRequestTracker,
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperUnitTest.java index 2caa75cb..ae06e3e1 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperUnitTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareHelperUnitTest.java
@@ -75,7 +75,7 @@ public void setup() { mActivity = Robolectric.buildActivity(Activity.class).get(); mWindow = - new ActivityWindowAndroid( + ActivityWindowAndroid.create( mActivity, /* listenToActivityState= */ false, IntentRequestTracker.createFromActivity(mActivity),
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/tasks/ReturnToChromeUtilUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/tasks/ReturnToChromeUtilUnitTest.java index 69ebfbd..d0875b5 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/tasks/ReturnToChromeUtilUnitTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/tasks/ReturnToChromeUtilUnitTest.java
@@ -48,6 +48,7 @@ import org.robolectric.annotation.Config; import org.chromium.base.BaseSwitches; +import org.chromium.base.DeviceInfo; import org.chromium.base.IntentUtils; import org.chromium.base.SysUtils; import org.chromium.base.test.BaseRobolectricTestRunner; @@ -182,6 +183,13 @@ assertTrue( ReturnToChromeUtil.shouldShowNtpAsHomeSurfaceAtStartup( intent, null, /* persistableBundle= */ null, mInactivityTracker)); + + // Tests the case when the device is an Android desktop. + DeviceInfo.setIsDesktopForTesting(true); + assertFalse( + ReturnToChromeUtil.shouldShowNtpAsHomeSurfaceAtStartup( + intent, null, /* persistableBundle= */ null, mInactivityTracker)); + DeviceInfo.setIsDesktopForTesting(false); } @Test
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index 7a5e5447..cd8663f 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn
@@ -1701,7 +1701,6 @@ "//chrome/browser/collaboration/messaging", "//chrome/browser/commerce", "//chrome/browser/commerce:impl", - "//chrome/browser/companion/text_finder", "//chrome/browser/complex_tasks", "//chrome/browser/complex_tasks:impl", "//chrome/browser/consent_auditor", @@ -3268,6 +3267,7 @@ "//chrome/browser/commerce/merchant_viewer:merchant_signal_db", "//chrome/browser/commerce/merchant_viewer:merchant_viewer_data_manager", "//chrome/browser/commerce/merchant_viewer/android:jni_headers", + "//chrome/browser/companion/text_finder", "//chrome/browser/composeplate/android:jni_headers", "//chrome/browser/contextual_search", "//chrome/browser/device_reauth/android:jni_headers", @@ -3741,6 +3741,8 @@ "lifetime/application_lifetime_desktop.h", "lifetime/browser_close_manager.cc", "lifetime/browser_close_manager.h", + "lifetime/smart_restart_metrics_observer.cc", + "lifetime/smart_restart_metrics_observer.h", "media/audio_ducker.cc", "media/audio_ducker.h", "media/cast_mirroring_service_host.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index 74d9fc6..b9dc3284 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc
@@ -7034,13 +7034,6 @@ flag_descriptions::kPageContentCacheDescription, kOsAndroid, FEATURE_VALUE_TYPE(page_content_annotations::features::kPageContentCache)}, - {"page-visibility-page-content-annotations", - flag_descriptions::kPageVisibilityPageContentAnnotationsName, - flag_descriptions::kPageVisibilityPageContentAnnotationsDescription, - kOsDesktop | kOsAndroid, - FEATURE_VALUE_TYPE(page_content_annotations::features:: - kPageVisibilityPageContentAnnotations)}, - #if BUILDFLAG(IS_CHROMEOS) {"language-packs-in-settings", flag_descriptions::kLanguagePacksInSettingsName, @@ -13043,6 +13036,13 @@ flag_descriptions::kNtpSimplificationDescription, kOsAndroid, FEATURE_VALUE_TYPE(chrome::android::kNtpSimplification)}, #endif +#if BUILDFLAG(IS_ANDROID) + {"new-tab-page-customization-theme-sync", + flag_descriptions::kNewTabPageCustomizationThemeSyncName, + flag_descriptions::kNewTabPageCustomizationThemeSyncDescription, + kOsAndroid, + FEATURE_VALUE_TYPE(chrome::android::kNewTabPageCustomizationThemeSync)}, +#endif // Add new entries above this line. // NOTE: Adding a new flag requires adding a corresponding entry to enum // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
diff --git a/chrome/browser/actor/actor_keyed_service.cc b/chrome/browser/actor/actor_keyed_service.cc index 56a36ae..b0848041 100644 --- a/chrome/browser/actor/actor_keyed_service.cc +++ b/chrome/browser/actor/actor_keyed_service.cc
@@ -400,6 +400,9 @@ void ActorKeyedService::RequestTabObservation( tabs::TabInterface& tab, TaskId task_id, + std::optional<page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions> + screenshot_collection_options, base::OnceCallback<void(TabObservationResult)> callback) { TRACE_EVENT0("actor", "ActorKeyedService::RequestTabObservation"); const GURL& last_committed_url = tab.GetContents()->GetLastCommittedURL(); @@ -414,9 +417,11 @@ // kFullPageScreenshot being true implies // kGlicTabScreenshotPaintPreviewBackend is enabled. ? page_content_annotations::ScreenshotOptions::FullPage( - CreateOptionalPaintPreviewOptions().value()) + CreateOptionalPaintPreviewOptions().value(), + std::move(screenshot_collection_options)) : page_content_annotations::ScreenshotOptions::ViewportOnly( - CreateOptionalPaintPreviewOptions()); + CreateOptionalPaintPreviewOptions(), + std::move(screenshot_collection_options)); options.annotated_page_content_options = optimization_guide::ActionableAIPageContentOptions(
diff --git a/chrome/browser/actor/actor_keyed_service.h b/chrome/browser/actor/actor_keyed_service.h index 89185b9..45e996b 100644 --- a/chrome/browser/actor/actor_keyed_service.h +++ b/chrome/browser/actor/actor_keyed_service.h
@@ -27,6 +27,7 @@ #include "chrome/common/buildflags.h" #include "components/download/content/public/all_download_item_notifier.h" #include "components/keyed_service/core/keyed_service.h" +#include "components/page_content_annotations/content/page_context_fetcher.h" #include "components/sessions/core/session_id.h" #include "components/tabs/public/tab_interface.h" @@ -132,6 +133,9 @@ void RequestTabObservation( tabs::TabInterface& tab, TaskId task_id, + std::optional<page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions> + screenshot_collection_options, base::OnceCallback<void(TabObservationResult)> callback); // A TabObservationResult may return the successful side of the base::expected
diff --git a/chrome/browser/actor/actor_keyed_service_browsertest.cc b/chrome/browser/actor/actor_keyed_service_browsertest.cc index 5bd0d75..218b625f 100644 --- a/chrome/browser/actor/actor_keyed_service_browsertest.cc +++ b/chrome/browser/actor/actor_keyed_service_browsertest.cc
@@ -161,8 +161,8 @@ actor_keyed_service()->CreateTask(NoEnterprisePolicyChecker()); TestFuture<ActorKeyedService::TabObservationResult> future; - actor_keyed_service()->RequestTabObservation(*active_tab(), task_id, - future.GetCallback()); + actor_keyed_service()->RequestTabObservation( + *active_tab(), task_id, std::nullopt, future.GetCallback()); const ActorKeyedService::TabObservationResult& result = future.Get(); ASSERT_TRUE(result.has_value()); @@ -203,8 +203,8 @@ } TestFuture<ActorKeyedService::TabObservationResult> future; - actor_keyed_service()->RequestTabObservation(*active_tab(), task_id, - future.GetCallback()); + actor_keyed_service()->RequestTabObservation( + *active_tab(), task_id, std::nullopt, future.GetCallback()); const ActorKeyedService::TabObservationResult& result = future.Get(); std::optional<std::string> error_message = @@ -231,15 +231,17 @@ std::optional<size_t> /*index_of_failed_actions*/, std::vector<actor::ActionResultWithLatencyInfo>, actor::TaskId, bool /*skip_async_observation_information*/, + std::optional<page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions>, std::unique_ptr<optimization_guide::proto::ActionsResult>, std::unique_ptr<actor::AggregatedJournal::PendingAsyncEntry>> future; actor::BuildActionsResultWithObservations( *GetProfile(), base::TimeTicks::Now(), mojom::ActionResultCode::kOk, std::nullopt, std::vector<actor::ActionResultWithLatencyInfo>(), *task, - true, future.GetCallback()); + true, std::nullopt, future.GetCallback()); const std::unique_ptr<optimization_guide::proto::ActionsResult>& - actions_result = future.Get<6>(); + actions_result = future.Get<7>(); ASSERT_TRUE(actions_result); EXPECT_EQ(actions_result->action_result(), static_cast<int32_t>(mojom::ActionResultCode::kOk));
diff --git a/chrome/browser/actor/actor_proto_conversion.cc b/chrome/browser/actor/actor_proto_conversion.cc index 4be3546..afd520d 100644 --- a/chrome/browser/actor/actor_proto_conversion.cc +++ b/chrome/browser/actor/actor_proto_conversion.cc
@@ -1022,6 +1022,78 @@ } // namespace +std::optional< + page_content_annotations::ScreenshotOptions::ScreenshotCollectionOptions> +GetScreenshotCollectionOptions( + const optimization_guide::proto::Actions& actions) { + if (!actions.has_screenshot_options()) { + return std::nullopt; + } + const auto& screenshot_collection_options = actions.screenshot_options(); + page_content_annotations::ScreenshotOptions::ScreenshotCollectionOptions + screenshot_collection_options_value; + screenshot_collection_options_value.max_width = + screenshot_collection_options.max_width(); + screenshot_collection_options_value.max_height = + screenshot_collection_options.max_height(); + if (screenshot_collection_options.has_compression_quality()) { + switch (screenshot_collection_options.compression_quality()) { + case optimization_guide::proto::CompressionQuality:: + COMPRESSION_QUALITY_LOW: + screenshot_collection_options_value.screenshot_compression_quality = + page_content_annotations::ScreenshotOptions:: + ScreenshotCompressionQuality::kLow; + break; + case optimization_guide::proto::CompressionQuality:: + COMPRESSION_QUALITY_MEDIUM: + screenshot_collection_options_value.screenshot_compression_quality = + page_content_annotations::ScreenshotOptions:: + ScreenshotCompressionQuality::kMedium; + break; + case optimization_guide::proto::CompressionQuality:: + COMPRESSION_QUALITY_HIGH: + screenshot_collection_options_value.screenshot_compression_quality = + page_content_annotations::ScreenshotOptions:: + ScreenshotCompressionQuality::kHigh; + break; + case optimization_guide::proto::CompressionQuality:: + COMPRESSION_QUALITY_NONE: + screenshot_collection_options_value.screenshot_compression_quality = + page_content_annotations::ScreenshotOptions:: + ScreenshotCompressionQuality::kNone; + break; + default: + break; + } + } + if (screenshot_collection_options.has_screenshot_format()) { + switch (screenshot_collection_options.screenshot_format()) { + case optimization_guide::proto::ScreenshotImageFormat:: + SCREENSHOT_IMAGE_FORMAT_JPEG: + screenshot_collection_options_value.screenshot_image_format = + page_content_annotations::ScreenshotOptions::ScreenshotImageFormat:: + kJpeg; + break; + case optimization_guide::proto::ScreenshotImageFormat:: + SCREENSHOT_IMAGE_FORMAT_PNG: + screenshot_collection_options_value.screenshot_image_format = + page_content_annotations::ScreenshotOptions::ScreenshotImageFormat:: + kPng; + break; + case optimization_guide::proto::ScreenshotImageFormat:: + SCREENSHOT_IMAGE_FORMAT_WEBP: + screenshot_collection_options_value.screenshot_image_format = + page_content_annotations::ScreenshotOptions::ScreenshotImageFormat:: + kWebp; + break; + default: + break; + } + } + + return screenshot_collection_options_value; +} + void BuildActionsResultWithObservations( content::BrowserContext& browser_context, base::TimeTicks actions_start_time, @@ -1030,6 +1102,9 @@ std::vector<actor::ActionResultWithLatencyInfo> action_results, const ActorTask& task, bool skip_async_observation_information, + std::optional<page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions> + screenshot_collection_options, base::OnceCallback< void(base::TimeTicks actions_start_time, mojom::ActionResultCode result_code, @@ -1037,6 +1112,9 @@ std::vector<actor::ActionResultWithLatencyInfo> action_results, actor::TaskId task_id, bool skip_async_observation_information, + std::optional<page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions> + screenshot_collection_options, std::unique_ptr<apc::ActionsResult>, std::unique_ptr<actor::AggregatedJournal::PendingAsyncEntry>)> callback) { @@ -1204,20 +1282,22 @@ std::move(callback).Run(actions_start_time, result_code, index_of_failed_action, std::move(action_results), task.id(), skip_async_observation_information, - std::move(response), std::move(journal_entry)); + screenshot_collection_options, std::move(response), + std::move(journal_entry)); return; } base::RepeatingClosure barrier = base::BarrierClosure( tabs_to_fetch.size(), base::BindOnce(std::move(callback), actions_start_time, result_code, index_of_failed_action, action_results, task.id(), - skip_async_observation_information, std::move(response), + skip_async_observation_information, + screenshot_collection_options, std::move(response), std::move(journal_entry))); for (auto& [tab, tab_observation] : tabs_to_fetch) { // tab_observation can be Unretained because the underlying APC is owned // by the barrier which is ref-counted. actor_service->RequestTabObservation( - *tab, task.id(), + *tab, task.id(), screenshot_collection_options, base::BindOnce(FetchCallback, tab->GetHandle(), profile->GetWeakPtr(), task.id(), barrier, base::Unretained(tab_observation), action_results, actions_start_time,
diff --git a/chrome/browser/actor/actor_proto_conversion.h b/chrome/browser/actor/actor_proto_conversion.h index 6c6cb660..e609db3f 100644 --- a/chrome/browser/actor/actor_proto_conversion.h +++ b/chrome/browser/actor/actor_proto_conversion.h
@@ -16,6 +16,7 @@ #include "chrome/common/actor.mojom-forward.h" #include "chrome/common/actor/action_result.h" #include "components/optimization_guide/proto/features/actions_data.pb.h" +#include "components/page_content_annotations/content/page_context_fetcher.h" #include "components/tabs/public/tab_interface.h" #include "third_party/abseil-cpp/absl/container/flat_hash_set.h" @@ -29,10 +30,6 @@ class Actions; } // namespace optimization_guide::proto -namespace page_content_annotations { -struct FetchPageContextResult; -} // namespace page_content_annotations - namespace actor { class ActorTask; class ToolRequest; @@ -65,6 +62,9 @@ std::vector<actor::ActionResultWithLatencyInfo> action_results, const ActorTask& task, bool skip_async_observation_information, + std::optional<page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions> + screenshot_collection_options, base::OnceCallback< void(base::TimeTicks start_time, mojom::ActionResultCode result_code, @@ -72,10 +72,20 @@ std::vector<actor::ActionResultWithLatencyInfo> action_results, actor::TaskId task_id, bool skip_async_observation_information, + std::optional<page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions> + screenshot_collection_options, std::unique_ptr<optimization_guide::proto::ActionsResult>, std::unique_ptr<actor::AggregatedJournal::PendingAsyncEntry>)> callback); +// Converts the ScreenshotCollectionOptions proto to the +// FetchPageContextOptions::ScreenshotCollectionOptions struct. +std::optional< + page_content_annotations::ScreenshotOptions::ScreenshotCollectionOptions> +GetScreenshotCollectionOptions( + const optimization_guide::proto::Actions& actions); + // For testing: when set, the callback is used to fill in the TabObservation // using the resulting FetchPageContextResult allowing tests to verify error // handling of the fetch.
diff --git a/chrome/browser/actor/tools/attempt_form_filling_tool_browsertest.cc b/chrome/browser/actor/tools/attempt_form_filling_tool_browsertest.cc index 30f7821..ccff3aea 100644 --- a/chrome/browser/actor/tools/attempt_form_filling_tool_browsertest.cc +++ b/chrome/browser/actor/tools/attempt_form_filling_tool_browsertest.cc
@@ -224,7 +224,8 @@ ActorKeyedService::Get(browser()->profile()); TestFuture<ActorKeyedService::TabObservationResult> tab_observation_future; actor_keyed_service->RequestTabObservation( - *active_tab(), actor_task().id(), tab_observation_future.GetCallback()); + *active_tab(), actor_task().id(), std::nullopt, + tab_observation_future.GetCallback()); const ActorKeyedService::TabObservationResult& result = tab_observation_future.Get(); std::optional<std::string> error_message =
diff --git a/chrome/browser/actor/tools/attempt_login_tool.cc b/chrome/browser/actor/tools/attempt_login_tool.cc index 7daeff2..9d9fe4b 100644 --- a/chrome/browser/actor/tools/attempt_login_tool.cc +++ b/chrome/browser/actor/tools/attempt_login_tool.cc
@@ -269,9 +269,13 @@ return; } + // TODO(https://crbug.com/488443317): For federated credentials, consider + // providing to the client `IdentityRequestAccount::picture` and + // `IdentityProviderMetadata::brand_icon_url` in lieu of a favicon. base::flat_set<GURL> unique_sites; for (const auto& cred : credentials_) { - if (!cred.source_site_or_app.empty()) { + if (cred.type == actor_login::CredentialType::kPassword && + !cred.source_site_or_app.empty()) { unique_sites.insert(GURL(cred.source_site_or_app)); } }
diff --git a/chrome/browser/actor/tools/tab_management_tool_browsertest.cc b/chrome/browser/actor/tools/tab_management_tool_browsertest.cc index 1765aa2..8e18b9b 100644 --- a/chrome/browser/actor/tools/tab_management_tool_browsertest.cc +++ b/chrome/browser/actor/tools/tab_management_tool_browsertest.cc
@@ -135,7 +135,7 @@ TestFuture<ActorKeyedService::TabObservationResult> future; actor_keyed_service->RequestTabObservation( *tabs::TabInterface::GetFromContents(web_contents()), actor_task().id(), - future.GetCallback()); + std::nullopt, future.GetCallback()); const ActorKeyedService::TabObservationResult& observation_result = future.Get();
diff --git a/chrome/browser/android/contextualsearch/contextual_search_tab_helper.cc b/chrome/browser/android/contextualsearch/contextual_search_tab_helper.cc index 3e50274f6..0059d42 100644 --- a/chrome/browser/android/contextualsearch/contextual_search_tab_helper.cc +++ b/chrome/browser/android/contextualsearch/contextual_search_tab_helper.cc
@@ -21,12 +21,8 @@ using base::android::ScopedJavaLocalRef; using contextual_search::UnhandledTapWebContentsObserver; -ContextualSearchTabHelper::ContextualSearchTabHelper( - JNIEnv* env, - const jni_zero::JavaRef<jobject>& obj, - Profile* profile) - : weak_java_ref_(env, obj), - pref_change_registrar_(new PrefChangeRegistrar()) { +ContextualSearchTabHelper::ContextualSearchTabHelper(Profile* profile) + : pref_change_registrar_(new PrefChangeRegistrar()) { pref_change_registrar_->Init(profile->GetPrefs()); pref_change_registrar_->Add( prefs::kContextualSearchEnabled, @@ -46,16 +42,15 @@ void ContextualSearchTabHelper::OnContextualSearchPrefChanged() { JNIEnv* env = base::android::AttachCurrentThread(); - ScopedJavaLocalRef<jobject> jobj = weak_java_ref_.get(env); - Java_ContextualSearchTabHelper_onContextualSearchPrefChanged(env, jobj); + Java_ContextualSearchTabHelper_onContextualSearchPrefChanged( + env, GetJavaObject(env)); } void ContextualSearchTabHelper::OnShowUnhandledTapUIIfNeeded(int x_px, int y_px) { JNIEnv* env = base::android::AttachCurrentThread(); - ScopedJavaLocalRef<jobject> jobj = weak_java_ref_.get(env); - Java_ContextualSearchTabHelper_onShowUnhandledTapUiIfNeeded(env, jobj, x_px, - y_px); + Java_ContextualSearchTabHelper_onShowUnhandledTapUiIfNeeded( + env, GetJavaObject(env), x_px, y_px); } void ContextualSearchTabHelper::InstallUnhandledTapNotifierIfNeeded( @@ -88,13 +83,18 @@ delete this; } +ScopedJavaLocalRef<jobject> ContextualSearchTabHelper::GetJavaObject( + JNIEnv* env) const { + return Java_ContextualSearchTabHelper_getJavaObject( + env, reinterpret_cast<intptr_t>(this)); +} + static int64_t JNI_ContextualSearchTabHelper_Init(JNIEnv* env, - const JavaRef<jobject>& obj, Profile* profile) { CHECK(profile); - ContextualSearchTabHelper* tab = new ContextualSearchTabHelper( - env, obj, profile); - return reinterpret_cast<intptr_t>(tab); + ContextualSearchTabHelper* helper = + new ContextualSearchTabHelper(profile); + return reinterpret_cast<intptr_t>(helper); } DEFINE_JNI(ContextualSearchTabHelper)
diff --git a/chrome/browser/android/contextualsearch/contextual_search_tab_helper.h b/chrome/browser/android/contextualsearch/contextual_search_tab_helper.h index 38f6964b..7ebac271 100644 --- a/chrome/browser/android/contextualsearch/contextual_search_tab_helper.h +++ b/chrome/browser/android/contextualsearch/contextual_search_tab_helper.h
@@ -7,7 +7,7 @@ #include <memory> -#include "base/android/jni_weak_ref.h" +#include "base/android/scoped_java_ref.h" #include "base/memory/weak_ptr.h" #include "components/prefs/pref_change_registrar.h" @@ -17,9 +17,7 @@ // This coordinates Tab changes with Contextual Search. class ContextualSearchTabHelper { public: - ContextualSearchTabHelper(JNIEnv* env, - const jni_zero::JavaRef<jobject>& obj, - Profile* profile); + explicit ContextualSearchTabHelper(Profile* profile); void Destroy(JNIEnv* env); ContextualSearchTabHelper(const ContextualSearchTabHelper&) = delete; @@ -44,7 +42,8 @@ // position. void OnShowUnhandledTapUIIfNeeded(int x_px, int y_px); - JavaObjectWeakGlobalRef weak_java_ref_; + base::android::ScopedJavaLocalRef<jobject> GetJavaObject(JNIEnv* env) const; + std::unique_ptr<PrefChangeRegistrar> pref_change_registrar_; base::WeakPtrFactory<ContextualSearchTabHelper> weak_factory_{this};
diff --git a/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_reader.cc b/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_reader.cc index 3c998def..740b286 100644 --- a/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_reader.cc +++ b/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_reader.cc
@@ -83,7 +83,7 @@ } int64_t ArcContentFileSystemFileStreamReader::GetLength( - net::Int64CompletionOnceCallback callback) { + GetLengthCallback callback) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); file_system_operation_runner_util::GetFileSizeOnIOThread( arc_url_, @@ -133,13 +133,15 @@ } void ArcContentFileSystemFileStreamReader::OnGetFileSize( - net::Int64CompletionOnceCallback callback, + GetLengthCallback callback, int64_t size) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); if (size < 0) { CloseInternal(CloseStatus::kStatusError); + std::move(callback).Run(base::unexpected(net::ERR_FAILED)); + return; } - std::move(callback).Run(size < 0 ? net::ERR_FAILED : size); + std::move(callback).Run(size); } void ArcContentFileSystemFileStreamReader::OnOpenFileSession(
diff --git a/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_reader.h b/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_reader.h index 1ea1d59..6917545 100644 --- a/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_reader.h +++ b/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_reader.h
@@ -45,7 +45,7 @@ int Read(net::IOBuffer* buffer, int buffer_length, net::CompletionOnceCallback callback) override; - int64_t GetLength(net::Int64CompletionOnceCallback callback) override; + int64_t GetLength(GetLengthCallback callback) override; private: using CloseStatus = file_system_operation_runner_util::CloseStatus; @@ -63,7 +63,7 @@ std::optional<size_t> result); // Called when GetFileSize() completes. - void OnGetFileSize(net::Int64CompletionOnceCallback callback, int64_t size); + void OnGetFileSize(GetLengthCallback callback, int64_t size); // Called when opening file session completes. void OnOpenFileSession(scoped_refptr<net::IOBuffer> buf,
diff --git a/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_reader_unittest.cc b/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_reader_unittest.cc index 59207dd8..cfaa38e 100644 --- a/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_reader_unittest.cc +++ b/chrome/browser/ash/arc/fileapi/arc_content_file_system_file_stream_reader_unittest.cc
@@ -14,6 +14,7 @@ #include "base/compiler_specific.h" #include "base/functional/bind.h" #include "base/location.h" +#include "base/test/test_future.h" #include "chrome/browser/ash/arc/fileapi/arc_file_system_operation_runner.h" #include "chrome/test/base/testing_profile.h" #include "chromeos/ash/experiences/arc/session/arc_bridge_service.h" @@ -23,6 +24,7 @@ #include "components/keyed_service/content/browser_context_keyed_service_factory.h" #include "content/public/test/browser_task_environment.h" #include "net/base/io_buffer.h" +#include "net/base/net_errors.h" #include "net/base/test_completion_callback.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" @@ -262,9 +264,10 @@ ArcContentFileSystemFileStreamReader reader(GURL(kArcUrlFile), /*offset=*/0); - net::TestInt64CompletionCallback callback; - EXPECT_EQ(static_cast<int64_t>(strlen(kData)), - callback.GetResult(reader.GetLength(callback.callback()))); + base::test::TestFuture<base::expected<int64_t, net::Error>> future; + reader.GetLength(future.GetCallback()); + ASSERT_TRUE(future.Get().has_value()); + EXPECT_EQ(static_cast<int64_t>(strlen(kData)), future.Get().value()); } base::RunLoop().RunUntilIdle(); } @@ -283,8 +286,9 @@ { ArcContentFileSystemFileStreamReader reader( GURL("content://org.chromium.foo/error"), /*offset=*/0); - net::TestInt64CompletionCallback callback; - EXPECT_LT(callback.GetResult(reader.GetLength(callback.callback())), 0); + base::test::TestFuture<base::expected<int64_t, net::Error>> future; + reader.GetLength(future.GetCallback()); + EXPECT_FALSE(future.Get().has_value()); } base::RunLoop().RunUntilIdle(); }
diff --git a/chrome/browser/ash/arc/fileapi/arc_documents_provider_file_stream_reader.cc b/chrome/browser/ash/arc/fileapi/arc_documents_provider_file_stream_reader.cc index f368ebe3..c74c2e2b 100644 --- a/chrome/browser/ash/arc/fileapi/arc_documents_provider_file_stream_reader.cc +++ b/chrome/browser/ash/arc/fileapi/arc_documents_provider_file_stream_reader.cc
@@ -54,7 +54,7 @@ } int64_t ArcDocumentsProviderFileStreamReader::GetLength( - net::Int64CompletionOnceCallback callback) { + GetLengthCallback callback) { DCHECK_CURRENTLY_ON(BrowserThread::IO); if (!content_url_resolved_) { pending_operations_.emplace_back(base::BindOnce( @@ -104,7 +104,7 @@ } void ArcDocumentsProviderFileStreamReader::RunPendingGetLength( - net::Int64CompletionOnceCallback callback) { + GetLengthCallback callback) { DCHECK_CURRENTLY_ON(BrowserThread::IO); DCHECK(content_url_resolved_); // Create two copies of |callback| though it can still only called at most @@ -116,7 +116,8 @@ ? underlying_reader_->GetLength(std::move(split_callback.first)) : net::ERR_FILE_NOT_FOUND; if (result != net::ERR_IO_PENDING) - std::move(split_callback.second).Run(result); + std::move(split_callback.second) + .Run(base::unexpected(net::ERR_FILE_NOT_FOUND)); } } // namespace arc
diff --git a/chrome/browser/ash/arc/fileapi/arc_documents_provider_file_stream_reader.h b/chrome/browser/ash/arc/fileapi/arc_documents_provider_file_stream_reader.h index c122bfc..f0ccdb2 100644 --- a/chrome/browser/ash/arc/fileapi/arc_documents_provider_file_stream_reader.h +++ b/chrome/browser/ash/arc/fileapi/arc_documents_provider_file_stream_reader.h
@@ -42,14 +42,14 @@ int Read(net::IOBuffer* buffer, int buffer_length, net::CompletionOnceCallback callback) override; - int64_t GetLength(net::Int64CompletionOnceCallback callback) override; + int64_t GetLength(GetLengthCallback callback) override; private: void OnResolveToContentUrl(const GURL& content_url); void RunPendingRead(scoped_refptr<net::IOBuffer> buffer, int buffer_length, net::CompletionOnceCallback callback); - void RunPendingGetLength(net::Int64CompletionOnceCallback callback); + void RunPendingGetLength(GetLengthCallback callback); const int64_t offset_; bool content_url_resolved_;
diff --git a/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc index 2efe44d..b149e69c 100644 --- a/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc +++ b/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc
@@ -4350,7 +4350,7 @@ content::WebContents* web_contents = browser->GetActiveWebContents(); webapps::AppBannerManager* app_banner_manager = - webapps::AppBannerManagerDesktop::FromWebContents(web_contents); + webapps::AppBannerManager::FromWebContents(web_contents); if (!app_banner_manager) { return RespondNow(Error("Failed to create AppBannerManager")); }
diff --git a/chrome/browser/ash/file_system_provider/fileapi/buffering_file_stream_reader.cc b/chrome/browser/ash/file_system_provider/fileapi/buffering_file_stream_reader.cc index e8ffe7b..284909b 100644 --- a/chrome/browser/ash/file_system_provider/fileapi/buffering_file_stream_reader.cc +++ b/chrome/browser/ash/file_system_provider/fileapi/buffering_file_stream_reader.cc
@@ -63,8 +63,7 @@ return net::ERR_IO_PENDING; } -int64_t BufferingFileStreamReader::GetLength( - net::Int64CompletionOnceCallback callback) { +int64_t BufferingFileStreamReader::GetLength(GetLengthCallback callback) { const int64_t result = file_stream_reader_->GetLength(std::move(callback)); DCHECK_EQ(net::ERR_IO_PENDING, result);
diff --git a/chrome/browser/ash/file_system_provider/fileapi/buffering_file_stream_reader.h b/chrome/browser/ash/file_system_provider/fileapi/buffering_file_stream_reader.h index cb854e4f..53af584c 100644 --- a/chrome/browser/ash/file_system_provider/fileapi/buffering_file_stream_reader.h +++ b/chrome/browser/ash/file_system_provider/fileapi/buffering_file_stream_reader.h
@@ -45,7 +45,7 @@ int Read(net::IOBuffer* buf, int buf_len, net::CompletionOnceCallback callback) override; - int64_t GetLength(net::Int64CompletionOnceCallback callback) override; + int64_t GetLength(GetLengthCallback callback) override; private: // Copies data from the preloading buffer and updates the internal iterator.
diff --git a/chrome/browser/ash/file_system_provider/fileapi/buffering_file_stream_reader_unittest.cc b/chrome/browser/ash/file_system_provider/fileapi/buffering_file_stream_reader_unittest.cc index f2966fc..4831135 100644 --- a/chrome/browser/ash/file_system_provider/fileapi/buffering_file_stream_reader_unittest.cc +++ b/chrome/browser/ash/file_system_provider/fileapi/buffering_file_stream_reader_unittest.cc
@@ -17,6 +17,7 @@ #include "base/memory/ref_counted.h" #include "base/run_loop.h" #include "base/task/single_thread_task_runner.h" +#include "base/types/expected.h" #include "chrome/browser/ash/fileapi/file_system_backend.h" #include "content/public/test/browser_task_environment.h" #include "net/base/io_buffer.h" @@ -73,7 +74,7 @@ return net::ERR_IO_PENDING; } - int64_t GetLength(net::Int64CompletionOnceCallback callback) override { + int64_t GetLength(GetLengthCallback callback) override { DCHECK_EQ(net::OK, return_error_); base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce(std::move(callback), kFileSize)); @@ -348,14 +349,15 @@ new FakeFileStreamReader(nullptr, net::OK)), kPreloadingBufferLength, kFileSize); - std::vector<int64_t> get_length_log; - const int64_t result = - reader.GetLength(base::BindOnce(&LogValue<int64_t>, &get_length_log)); + std::vector<base::expected<int64_t, net::Error>> get_length_log; + const int64_t result = reader.GetLength(base::BindOnce( + &LogValue<base::expected<int64_t, net::Error>>, &get_length_log)); base::RunLoop().RunUntilIdle(); EXPECT_EQ(net::ERR_IO_PENDING, result); ASSERT_EQ(1u, get_length_log.size()); - EXPECT_EQ(kFileSize, get_length_log[0]); + ASSERT_TRUE(get_length_log[0].has_value()); + EXPECT_EQ(kFileSize, get_length_log[0].value()); } } // namespace ash::file_system_provider
diff --git a/chrome/browser/ash/file_system_provider/fileapi/file_stream_reader.cc b/chrome/browser/ash/file_system_provider/fileapi/file_stream_reader.cc index 9893662e..331a742 100644 --- a/chrome/browser/ash/file_system_provider/fileapi/file_stream_reader.cc +++ b/chrome/browser/ash/file_system_provider/fileapi/file_stream_reader.cc
@@ -27,10 +27,12 @@ namespace ash::file_system_provider { -// Converts net::CompletionOnceCallback to net::Int64CompletionOnceCallback. -void Int64ToIntCompletionOnceCallback(net::CompletionOnceCallback callback, - int64_t result) { - std::move(callback).Run(static_cast<int>(result)); +// Converts GetLengthCallback to net::CompletionOnceCallback for Read errors. +void GetLengthToIntCompletionOnceCallback( + net::CompletionOnceCallback callback, + base::expected<int64_t, net::Error> result) { + std::move(callback).Run(result.has_value() ? static_cast<int>(result.value()) + : result.error()); } class FileStreamReader::OperationRunner @@ -211,9 +213,8 @@ TRACE_EVENT_END("file_system_provider", perfetto::Track::FromPointer(this)); } -void FileStreamReader::Initialize( - base::OnceClosure pending_closure, - net::Int64CompletionOnceCallback error_callback) { +void FileStreamReader::Initialize(base::OnceClosure pending_closure, + GetLengthCallback error_callback) { DCHECK_EQ(NOT_INITIALIZED, state_); state_ = INITIALIZING; @@ -226,10 +227,9 @@ std::move(error_callback)))); } -void FileStreamReader::OnOpenFileCompleted( - base::OnceClosure pending_closure, - net::Int64CompletionOnceCallback error_callback, - base::File::Error result) { +void FileStreamReader::OnOpenFileCompleted(base::OnceClosure pending_closure, + GetLengthCallback error_callback, + base::File::Error result) { DCHECK_CURRENTLY_ON(BrowserThread::IO); DCHECK_EQ(INITIALIZING, state_); @@ -237,7 +237,8 @@ // Read() or GetLength() pending request. if (result != base::File::FILE_OK) { state_ = FAILED; - std::move(error_callback).Run(net::FileErrorToNetError(result)); + std::move(error_callback) + .Run(base::unexpected(net::FileErrorToNetError(result))); return; } @@ -255,7 +256,7 @@ void FileStreamReader::OnInitializeCompleted( base::OnceClosure pending_closure, - net::Int64CompletionOnceCallback error_callback, + GetLengthCallback error_callback, std::unique_ptr<EntryMetadata> metadata, base::File::Error result) { DCHECK_CURRENTLY_ON(BrowserThread::IO); @@ -264,7 +265,8 @@ // In case of an error, abort. if (result != base::File::FILE_OK) { state_ = FAILED; - std::move(error_callback).Run(net::FileErrorToNetError(result)); + std::move(error_callback) + .Run(base::unexpected(net::FileErrorToNetError(result))); return; } @@ -274,7 +276,8 @@ if (!expected_modification_time_.is_null() && *metadata->modification_time != expected_modification_time_) { state_ = FAILED; - std::move(error_callback).Run(net::ERR_UPLOAD_FILE_CHANGED); + std::move(error_callback) + .Run(base::unexpected(net::ERR_UPLOAD_FILE_CHANGED)); return; } @@ -303,7 +306,7 @@ base::WrapRefCounted(buffer), buffer_length, base::BindRepeating(&FileStreamReader::OnReadCompleted, weak_ptr_factory_.GetWeakPtr())), - base::BindOnce(&Int64ToIntCompletionOnceCallback, + base::BindOnce(&GetLengthToIntCompletionOnceCallback, base::BindOnce(&FileStreamReader::OnReadCompleted, weak_ptr_factory_.GetWeakPtr()))); break; @@ -331,7 +334,7 @@ TRACE_EVENT_END("file_system_provider", perfetto::Track::FromPointer(this)); } -int64_t FileStreamReader::GetLength(net::Int64CompletionOnceCallback callback) { +int64_t FileStreamReader::GetLength(GetLengthCallback callback) { DCHECK_CURRENTLY_ON(BrowserThread::IO); get_length_callback_ = std::move(callback); @@ -358,8 +361,9 @@ return net::ERR_IO_PENDING; } -void FileStreamReader::OnGetLengthCompleted(int64_t result) { - std::move(get_length_callback_).Run(result); +void FileStreamReader::OnGetLengthCompleted( + base::expected<int64_t, net::Error> result) { + std::move(get_length_callback_).Run(std::move(result)); } void FileStreamReader::ReadAfterInitialized( @@ -428,7 +432,8 @@ // In case of an error, abort. if (result != base::File::FILE_OK) { state_ = FAILED; - std::move(get_length_callback_).Run(net::FileErrorToNetError(result)); + std::move(get_length_callback_) + .Run(base::unexpected(net::FileErrorToNetError(result))); return; } @@ -437,7 +442,8 @@ DCHECK(metadata.get()); if (!expected_modification_time_.is_null() && *metadata->modification_time != expected_modification_time_) { - std::move(get_length_callback_).Run(net::ERR_UPLOAD_FILE_CHANGED); + std::move(get_length_callback_) + .Run(base::unexpected(net::ERR_UPLOAD_FILE_CHANGED)); return; }
diff --git a/chrome/browser/ash/file_system_provider/fileapi/file_stream_reader.h b/chrome/browser/ash/file_system_provider/fileapi/file_stream_reader.h index cc8b2f82..2c171e8 100644 --- a/chrome/browser/ash/file_system_provider/fileapi/file_stream_reader.h +++ b/chrome/browser/ash/file_system_provider/fileapi/file_stream_reader.h
@@ -12,8 +12,10 @@ #include "base/memory/scoped_refptr.h" #include "base/memory/weak_ptr.h" #include "base/time/time.h" +#include "base/types/expected.h" #include "net/base/completion_once_callback.h" #include "net/base/completion_repeating_callback.h" +#include "net/base/net_errors.h" #include "storage/browser/file_system/file_stream_reader.h" #include "storage/browser/file_system/file_system_url.h" @@ -43,7 +45,7 @@ int Read(net::IOBuffer* buf, int buf_len, net::CompletionOnceCallback callback) override; - int64_t GetLength(net::Int64CompletionOnceCallback callback) override; + int64_t GetLength(GetLengthCallback callback) override; private: // Helper class for executing operations on the provided file system. All @@ -58,23 +60,23 @@ // error. void OnReadCompleted(int result); - // Called when GetLength() operation is completed with either a success of an + // Called when GetLength() operation is completed with either a success or an // error. - void OnGetLengthCompleted(int64_t result); + void OnGetLengthCompleted(base::expected<int64_t, net::Error> result); // Initializes the reader by opening the file. When completed with success, // runs the |pending_closure|. Otherwise, calls the |error_callback|. void Initialize(base::OnceClosure pending_closure, - net::Int64CompletionOnceCallback error_callback); + GetLengthCallback error_callback); // Called when opening a file is completed with either a success or an error. void OnOpenFileCompleted(base::OnceClosure pending_closure, - net::Int64CompletionOnceCallback error_callback, + GetLengthCallback error_callback, base::File::Error result); // Called when initialization is completed with either a success or an error. void OnInitializeCompleted(base::OnceClosure pending_closure, - net::Int64CompletionOnceCallback error_callback, + GetLengthCallback error_callback, std::unique_ptr<EntryMetadata> metadata, base::File::Error result); @@ -102,7 +104,7 @@ void GetLengthAfterInitialized(); net::CompletionOnceCallback read_callback_; - net::Int64CompletionOnceCallback get_length_callback_; + GetLengthCallback get_length_callback_; storage::FileSystemURL url_; int64_t current_offset_; int64_t current_length_;
diff --git a/chrome/browser/ash/file_system_provider/fileapi/file_stream_reader_unittest.cc b/chrome/browser/ash/file_system_provider/fileapi/file_stream_reader_unittest.cc index d36ee060..b66e214 100644 --- a/chrome/browser/ash/file_system_provider/fileapi/file_stream_reader_unittest.cc +++ b/chrome/browser/ash/file_system_provider/fileapi/file_stream_reader_unittest.cc
@@ -20,6 +20,7 @@ #include "base/memory/weak_ptr.h" #include "base/numerics/safe_math.h" #include "base/run_loop.h" +#include "base/types/expected.h" #include "chrome/browser/ash/file_system_provider/fake_extension_provider.h" #include "chrome/browser/ash/file_system_provider/fake_provided_file_system.h" #include "chrome/browser/ash/file_system_provider/service.h" @@ -56,7 +57,11 @@ virtual ~EventLogger() = default; void OnRead(int result) { results_.push_back(result); } - void OnGetLength(int64_t result) { results_.push_back(result); } + void OnGetLength(base::expected<int64_t, net::Error> result) { + results_.push_back(result.has_value() + ? result.value() + : static_cast<int64_t>(result.error())); + } base::WeakPtr<EventLogger> GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr();
diff --git a/chrome/browser/ash/fileapi/diversion_file_manager.cc b/chrome/browser/ash/fileapi/diversion_file_manager.cc index 5251959f..7eb9e21 100644 --- a/chrome/browser/ash/fileapi/diversion_file_manager.cc +++ b/chrome/browser/ash/fileapi/diversion_file_manager.cc
@@ -328,7 +328,7 @@ int Read(net::IOBuffer* buf, int buf_len, net::CompletionOnceCallback callback) override; - int64_t GetLength(net::Int64CompletionOnceCallback callback) override; + int64_t GetLength(GetLengthCallback callback) override; // storage::FileStreamWriter overrides. int Write(net::IOBuffer* buf, @@ -376,16 +376,18 @@ return net::ERR_IO_PENDING; } -int64_t DiversionFileManager::Worker::GetLength( - net::Int64CompletionOnceCallback callback) { +int64_t DiversionFileManager::Worker::GetLength(GetLengthCallback callback) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); CHECK_EQ(role_, Role::kReader); - static constexpr auto reply = [](net::Int64CompletionOnceCallback callback, + static constexpr auto reply = [](GetLengthCallback callback, const Tmpfile& tmpfile, int ignored) { - std::move(callback).Run((tmpfile.net_error < 0) - ? static_cast<int64_t>(tmpfile.net_error) - : tmpfile.file_size); + if (tmpfile.net_error < 0) { + std::move(callback).Run( + base::unexpected(static_cast<net::Error>(tmpfile.net_error))); + } else { + std::move(callback).Run(tmpfile.file_size); + } }; entry_->Enqueue(
diff --git a/chrome/browser/autofill/actor/actor_form_filling_service.h b/chrome/browser/autofill/actor/actor_form_filling_service.h index f2cbe0e8..c607efd 100644 --- a/chrome/browser/autofill/actor/actor_form_filling_service.h +++ b/chrome/browser/autofill/actor/actor_form_filling_service.h
@@ -19,19 +19,38 @@ namespace autofill { -// Interface for `actor::ExecutionEngine` to communicate with Autofill -// functionality. +// Interface for the actor tooling to communicate with Autofill functionality +// for form filling. +// +// Each instance of this interface is associated with a single high level +// request from the actor to fill forms, though that request may be for multiple +// fills across multiple types of data and form sections. class ActorFormFillingService { public: virtual ~ActorFormFillingService() = default; - // Creates a filling proposal for each of the "actor form"s defined in - // `fill_requests` and calls callback with it. - // Here an "actor form" is the union of one or more sections of a `FormData` - // and the section is identified by the `FieldGlobalId` of an arbitrary - // field inside that section. + // Represents a request from the actor to fill one or more form sections. + // + // The RequestedData identifies the 'type' of data to fill (e.g., shipping + // address, billing address, or credit card). Each FieldGlobalId in the vector + // identifies a 'trigger' field in a form section that the actor wants to + // fill. Multiple trigger fields are supported to allow the actor to indicate + // that these form sections should be filled with the same data (i.e., that + // they are part of the same overall form). using FillRequest = std::pair<ActorFormFillingRequest::RequestedData, std::vector<FieldGlobalId>>; + + // Retrieves Autofill suggestions for a set of fill requests from the actor. + // + // For each FillRequest, Autofill data will be retrieved based on the + // RequestedData type and the trigger fields identified by the + // FieldGlobalIds. The suggestions are returned via the callback, in the same + // order as the FillRequests. If an error occurs, the callback will be invoked + // with an ActorFormFillingError. + // + // The returned suggestions are expected to be shown to the user in a UX, + // from which the user will make selections (one per fill request). The + // selected suggestions should subsequently be passed to FillSuggestions(). virtual void GetSuggestions( const tabs::TabInterface& tab, base::span<const FillRequest> fill_requests, @@ -39,8 +58,12 @@ void(base::expected<std::vector<ActorFormFillingRequest>, ActorFormFillingError>)> callback) = 0; - // Attempts to fill `chosen_suggestions` and notifies `callback` with the - // result. + // Attempts to fill the `chosen_suggestions` into their corresponding form + // sections. The suggestions must have been obtained from a prior call to + // GetSuggestions(). + // + // If successful, the callback will be invoked with a void value. If an error + // occurs, the callback will be invoked with an ActorFormFillingError. virtual void FillSuggestions( const tabs::TabInterface& tab, base::span<const ActorFormFillingSelection> chosen_suggestions, @@ -65,7 +88,7 @@ virtual void ClearFormPreview(const tabs::TabInterface& tab, int form_index) = 0; - // Fills the with the given `selection` . + // Fills the form with the given `selection` . // `form_index` corresponds to the vector of ActorFormFillingRequests // retrieved by GetSuggestions(). virtual void FillForm(const tabs::TabInterface& tab,
diff --git a/chrome/browser/banners/android/ambient_badge_manager_browsertest.cc b/chrome/browser/banners/android/ambient_badge_manager_browsertest.cc index 6797c71a..a19cd88 100644 --- a/chrome/browser/banners/android/ambient_badge_manager_browsertest.cc +++ b/chrome/browser/banners/android/ambient_badge_manager_browsertest.cc
@@ -87,7 +87,7 @@ web_contents, std::make_unique<ChromeAppBannerManagerAndroid>(*web_contents)), mock_segmentation_(segmentation_platform_service) { - SetTriggeringDisabledForTesting(false); + app_banner_manager()->SetTriggeringDisabledForTesting(false); } TestAppBannerManager(const TestAppBannerManager&) = delete; @@ -107,13 +107,15 @@ protected: Profile* profile() { - return Profile::FromBrowserContext(web_contents()->GetBrowserContext()); + return Profile::FromBrowserContext( + app_banner_manager()->web_contents()->GetBrowserContext()); } void MaybeShowAmbientBadge( const InstallBannerConfig& install_config) override { ambient_badge_test_ = std::make_unique<TestAmbientBadgeManager>( - web_contents(), mock_segmentation_, profile()->GetPrefs()); + app_banner_manager()->web_contents(), mock_segmentation_, + profile()->GetPrefs()); ambient_badge_test_->WaitForState(target_badge_state_, std::move(on_badge_done_)); @@ -136,7 +138,8 @@ base::BindOnce(&AppBannerManagerAndroid::CreateAddToHomescreenParams, install_config, native_java_app_data) .Then(base::BindOnce( - &PwaBottomSheetController::MaybeShow, web_contents(), + &PwaBottomSheetController::MaybeShow, + app_banner_manager()->web_contents(), install_config.web_app_data, /*expand_sheet=*/false, base::BindRepeating(&TestAppBannerManager::OnInstallEvent, GetAndroidWeakPtr(),
diff --git a/chrome/browser/banners/app_banner_manager_browsertest.cc b/chrome/browser/banners/app_banner_manager_browsertest.cc index 4bf9d7f5..f09d734 100644 --- a/chrome/browser/banners/app_banner_manager_browsertest.cc +++ b/chrome/browser/banners/app_banner_manager_browsertest.cc
@@ -71,20 +71,25 @@ // TODO(http://crbug.com/329145718): Use AppBannerManagerNoFakeBrowserTest style // instead of overriding like this. // TODO(http://crbug.com/322342499): Completely remove this class. -class AppBannerManagerTest : public AppBannerManager, - public AppBannerManager::Delegate, +class AppBannerManagerTest : public AppBannerManager::Delegate, private AppBannerManager::Observer { public: explicit AppBannerManagerTest(content::WebContents* web_contents) - : AppBannerManager(this, web_contents) { - AddObserver(this); - SetTriggeringDisabledForTesting(false); + : app_banner_manager_(AppBannerManager::Create(this, web_contents)) { + app_banner_manager_->AddObserver(this); + app_banner_manager_->SetTriggeringDisabledForTesting(false); } AppBannerManagerTest(const AppBannerManagerTest&) = delete; AppBannerManagerTest& operator=(const AppBannerManagerTest&) = delete; - ~AppBannerManagerTest() override { RemoveObserver(this); } + ~AppBannerManagerTest() override { + app_banner_manager_->RemoveObserver(this); + } + + AppBannerManager* app_banner_manager() const { + return app_banner_manager_.get(); + } bool banner_shown() { return banner_shown_.get() && *banner_shown_; } @@ -94,13 +99,21 @@ void clear_will_show() { banner_shown_.reset(); } - State state() { return AppBannerManager::state(); } + State state() { return app_banner_manager_->state(); } // Configures a callback to be invoked when the app banner flow finishes. void PrepareDone(base::OnceClosure on_done) { on_done_ = std::move(on_done); } void OnMlInstallPrediction(std::string result_label) override {} + bool IsPromptAvailableForTesting() const { + return app_banner_manager_->IsPromptAvailableForTesting(); + } + + InstallableWebAppCheckResult GetInstallableWebAppCheckResult() const { + return app_banner_manager_->GetInstallableWebAppCheckResult(); + } + protected: bool CanRequestAppBanner() const override { return true; } @@ -149,7 +162,8 @@ const InstallBannerConfig& config) override { // Fake the call to ReportStatus here - this is usually called in // platform-specific code which is not exposed here. - ReportStatus(InstallableStatusCode::SHOWING_WEB_APP_BANNER); + app_banner_manager_->ReportStatus( + InstallableStatusCode::SHOWING_WEB_APP_BANNER); ASSERT_FALSE(banner_shown_.get()); banner_shown_ = std::make_unique<bool>(true); install_source_ = install_source; @@ -189,6 +203,7 @@ base::OnceClosure on_done_; private: + std::unique_ptr<AppBannerManager> app_banner_manager_; std::unique_ptr<bool> banner_shown_; std::optional<WebappInstallSource> install_source_; }; @@ -550,7 +565,7 @@ RunBannerTest(web_contents(), manager.get(), page_url, InstallableStatusCode::NO_MANIFEST); std::optional<WebAppBannerData> banner = - manager->GetCurrentWebAppBannerData(); + manager->app_banner_manager()->GetCurrentWebAppBannerData(); // Check the default manifest was populated. ASSERT_TRUE(banner); EXPECT_TRUE(blink::IsDefaultManifest(banner->manifest(), page_url)); @@ -572,7 +587,7 @@ webapps::InstallableWebAppCheckResult::kNo); // The banner will be the default one for the current page. std::optional<WebAppBannerData> banner = - manager->GetCurrentWebAppBannerData(); + manager->app_banner_manager()->GetCurrentWebAppBannerData(); ASSERT_TRUE(banner); EXPECT_TRUE(blink::IsDefaultManifest(banner->manifest(), url)); } @@ -684,7 +699,7 @@ // Dismiss the banner. base::RunLoop run_loop; manager->PrepareDone(run_loop.QuitClosure()); - manager->SendBannerDismissed(); + manager->app_banner_manager()->SendBannerDismissed(); // Wait for OnBannerPromptReply event. run_loop.Run(); @@ -781,8 +796,10 @@ // Expect the installation config to be empty, as the page is not eligible // for installation. - EXPECT_EQ(manager->GetCurrentWebAppBannerData(), std::nullopt); - EXPECT_EQ(manager->GetCurrentBannerConfig(), std::nullopt); + EXPECT_EQ(manager->app_banner_manager()->GetCurrentWebAppBannerData(), + std::nullopt); + EXPECT_EQ(manager->app_banner_manager()->GetCurrentBannerConfig(), + std::nullopt); // Expect RENDERER_CANCELLED to be called when an existing call is terminated. histograms.ExpectUniqueSample(kInstallableStatusCodeHistogram, @@ -1085,8 +1102,10 @@ EXPECT_EQ(manager->GetInstallableWebAppCheckResult(), InstallableWebAppCheckResult::kYes_Promotable); - ASSERT_TRUE(manager->GetCurrentBannerConfig()); - EXPECT_EQ(manager->GetCurrentBannerConfig()->GetWebOrNativeAppName(), + ASSERT_TRUE(manager->app_banner_manager()->GetCurrentBannerConfig()); + EXPECT_EQ(manager->app_banner_manager() + ->GetCurrentBannerConfig() + ->GetWebOrNativeAppName(), u"Manifest test app"); } @@ -1128,9 +1147,11 @@ AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED); EXPECT_EQ(manager->GetInstallableWebAppCheckResult(), InstallableWebAppCheckResult::kYes_Promotable); - ASSERT_TRUE(manager->GetCurrentBannerConfig()); - EXPECT_EQ(manager->GetCurrentBannerConfig()->GetWebOrNativeAppName(), - u"TestApp"); + ASSERT_TRUE(manager->app_banner_manager()->GetCurrentBannerConfig()); + EXPECT_EQ(manager->app_banner_manager() + ->GetCurrentBannerConfig() + ->GetWebOrNativeAppName(), + u"TestApp"); } IN_PROC_BROWSER_TEST_P(AppBannerManagerBrowserTest, ImplicitNameDocumentTitle) { @@ -1146,9 +1167,11 @@ AppBannerManager::State::PENDING_PROMPT_NOT_CANCELED); EXPECT_EQ(manager->GetInstallableWebAppCheckResult(), InstallableWebAppCheckResult::kYes_Promotable); - ASSERT_TRUE(manager->GetCurrentBannerConfig()); - EXPECT_EQ(manager->GetCurrentBannerConfig()->GetWebOrNativeAppName(), - u"Web app banner test page"); + ASSERT_TRUE(manager->app_banner_manager()->GetCurrentBannerConfig()); + EXPECT_EQ(manager->app_banner_manager() + ->GetCurrentBannerConfig() + ->GetWebOrNativeAppName(), + u"Web app banner test page"); } #if !BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/banners/app_banner_manager_desktop.cc b/chrome/browser/banners/app_banner_manager_desktop.cc index cf832118..5a46c5d 100644 --- a/chrome/browser/banners/app_banner_manager_desktop.cc +++ b/chrome/browser/banners/app_banner_manager_desktop.cc
@@ -85,8 +85,8 @@ AppBannerManagerDesktop::AppBannerManagerDesktop( content::WebContents* web_contents) - : AppBannerManager(this, web_contents), - content::WebContentsUserData<AppBannerManagerDesktop>(*web_contents) { + : content::WebContentsUserData<AppBannerManagerDesktop>(*web_contents), + app_banner_manager_(AppBannerManager::Create(this, web_contents)) { Profile* profile = Profile::FromBrowserContext(web_contents->GetBrowserContext()); extension_registry_ = extensions::ExtensionRegistry::Get(profile); @@ -136,6 +136,7 @@ void AppBannerManagerDesktop::InvalidateWeakPtrsForThisNavigation() { weak_factory_.InvalidateWeakPtrs(); } + void AppBannerManagerDesktop::ResetCurrentPageData() {} void AppBannerManagerDesktop::InstallableWebAppStatusUpdate() {} @@ -147,8 +148,8 @@ #if BUILDFLAG(IS_CHROMEOS) if (base::EqualsASCII(platform, kPlatformPlay) && - arc::IsArcAllowedForProfile( - Profile::FromBrowserContext(web_contents()->GetBrowserContext()))) { + arc::IsArcAllowedForProfile(Profile::FromBrowserContext( + app_banner_manager_->web_contents()->GetBrowserContext()))) { return true; } #endif // BUILDFLAG(IS_CHROMEOS) @@ -172,8 +173,8 @@ #if BUILDFLAG(IS_CHROMEOS) if (base::EqualsASCII(platform, kPlatformPlay)) { - ArcAppListPrefs* arc_app_list_prefs = - ArcAppListPrefs::Get(web_contents()->GetBrowserContext()); + ArcAppListPrefs* arc_app_list_prefs = ArcAppListPrefs::Get( + app_banner_manager_->web_contents()->GetBrowserContext()); return arc_app_list_prefs && arc_app_list_prefs->GetPackage(id) != nullptr; } #endif // BUILDFLAG(IS_CHROMEOS) @@ -194,8 +195,9 @@ } web_app::WebAppRegistrar& AppBannerManagerDesktop::registrar() const { - auto* provider = web_app::WebAppProvider::GetForWebApps( - Profile::FromBrowserContext(web_contents()->GetBrowserContext())); + auto* provider = + web_app::WebAppProvider::GetForWebApps(Profile::FromBrowserContext( + app_banner_manager_->web_contents()->GetBrowserContext())); DCHECK(provider); return provider->registrar_unsafe(); } @@ -203,8 +205,9 @@ void AppBannerManagerDesktop::ShowBannerUi(WebappInstallSource install_source, const InstallBannerConfig& config) { AppBannerSettingsHelper::RecordBannerEvent( - web_contents(), config, - AppBannerSettingsHelper::APP_BANNER_EVENT_DID_SHOW, GetCurrentTime()); + app_banner_manager_->web_contents(), config, + AppBannerSettingsHelper::APP_BANNER_EVENT_DID_SHOW, + app_banner_manager_->GetCurrentTime()); TrackDisplayEvent(DISPLAY_EVENT_WEB_APP_BANNER_CREATED); CreateWebApp(install_source, base::BindOnce(&AppBannerManagerDesktop::DidFinishCreatingWebApp, @@ -215,26 +218,29 @@ void AppBannerManagerDesktop::OnWebAppInstalledWithOsHooks( const webapps::AppId& installed_app_id) { - if (!validated_url()) { + std::optional<GURL> validated_url = app_banner_manager_->validated_url(); + if (!validated_url) { return; } std::optional<webapps::AppId> app_id = registrar().FindBestAppWithUrlInScope( - validated_url().value(), web_app::WebAppFilter::OpensInDedicatedWindow()); + validated_url.value(), web_app::WebAppFilter::OpensInDedicatedWindow()); if (installed_app_id != app_id) { return; } - OnInstall(registrar().GetEffectiveDisplayModeFromManifest(*app_id), - /*set_current_web_app_not_installable=*/true); + app_banner_manager_->OnInstall( + registrar().GetEffectiveDisplayModeFromManifest(*app_id), + /*set_current_web_app_not_installable=*/true); } void AppBannerManagerDesktop::OnWebAppWillBeUninstalled( const webapps::AppId& app_id) { - if (!validated_url()) { + std::optional<GURL> validated_url = app_banner_manager_->validated_url(); + if (!validated_url) { return; } // WebAppTabHelper has a app_id but it is reset during // OnWebAppWillBeUninstalled so use IsUrlInAppScope() instead. - if (registrar().IsUrlInAppScope(validated_url().value(), app_id)) { + if (registrar().IsUrlInAppScope(validated_url.value(), app_id)) { uninstalling_app_id_ = app_id; } } @@ -243,7 +249,7 @@ const webapps::AppId& app_id, webapps::WebappUninstallSource uninstall_source) { if (uninstalling_app_id_ == app_id) { - RecheckInstallabilityForLoadedPage(); + app_banner_manager_->RecheckInstallabilityForLoadedPage(); } } @@ -254,7 +260,7 @@ void AppBannerManagerDesktop::CreateWebApp( WebappInstallSource install_source, web_app::WebAppInstalledCallback install_callback) { - content::WebContents* contents = web_contents(); + content::WebContents* contents = app_banner_manager_->web_contents(); DCHECK(contents); web_app::CreateWebAppFromManifest(contents, install_source, @@ -266,7 +272,7 @@ base::WeakPtr<AppBannerManagerDesktop> is_navigation_current, const webapps::AppId& app_id, webapps::InstallResultCode code) { - content::WebContents* contents = web_contents(); + content::WebContents* contents = app_banner_manager_->web_contents(); if (!contents) return; @@ -274,14 +280,14 @@ // all other errors. if (code == webapps::InstallResultCode::kSuccessNewInstall) { if (is_navigation_current) { - SendBannerAccepted(); + app_banner_manager_->SendBannerAccepted(); } TrackUserResponse(USER_RESPONSE_WEB_APP_ACCEPTED); AppBannerSettingsHelper::RecordBannerInstallEvent(contents, manifest_id.spec()); } else if (code == webapps::InstallResultCode::kUserInstallDeclined) { if (is_navigation_current) { - SendBannerDismissed(); + app_banner_manager_->SendBannerDismissed(); } TrackUserResponse(USER_RESPONSE_WEB_APP_DISMISSED); AppBannerSettingsHelper::RecordBannerDismissEvent(contents,
diff --git a/chrome/browser/banners/app_banner_manager_desktop.h b/chrome/browser/banners/app_banner_manager_desktop.h index 1757edf..0f0ac1c9 100644 --- a/chrome/browser/banners/app_banner_manager_desktop.h +++ b/chrome/browser/banners/app_banner_manager_desktop.h
@@ -28,8 +28,7 @@ // Manages web app banners for desktop platforms. class AppBannerManagerDesktop - : public AppBannerManager, - public AppBannerManager::Delegate, + : public AppBannerManager::Delegate, public content::WebContentsUserData<AppBannerManagerDesktop>, public web_app::WebAppInstallManagerObserver { public: @@ -44,6 +43,10 @@ virtual TestAppBannerManagerDesktop* AsTestAppBannerManagerDesktopForTesting(); + AppBannerManager* app_banner_manager() const { + return app_banner_manager_.get(); + } + protected: explicit AppBannerManagerDesktop(content::WebContents* web_contents); @@ -105,6 +108,8 @@ void DidCreateWebAppFromMLDialog(const webapps::AppId& app_id, webapps::InstallResultCode code); + std::unique_ptr<AppBannerManager> app_banner_manager_; + raw_ptr<extensions::ExtensionRegistry> extension_registry_; webapps::AppId uninstalling_app_id_;
diff --git a/chrome/browser/banners/test_app_banner_manager_desktop.cc b/chrome/browser/banners/test_app_banner_manager_desktop.cc index 981405c..30cfea4 100644 --- a/chrome/browser/banners/test_app_banner_manager_desktop.cc +++ b/chrome/browser/banners/test_app_banner_manager_desktop.cc
@@ -27,15 +27,16 @@ TestAppBannerManagerDesktop::TestAppBannerManagerDesktop( content::WebContents* web_contents) - : AppBannerManagerDesktop(web_contents) { + : AppBannerManagerDesktop(web_contents), + content::WebContentsObserver(web_contents) { // Ensure no real instance exists. This must be the only instance to avoid // observers of AppBannerManager left observing the wrong one. DCHECK_EQ(AppBannerManagerDesktop::FromWebContents(web_contents), nullptr); - AddObserver(this); + app_banner_manager()->AddObserver(this); } TestAppBannerManagerDesktop::~TestAppBannerManagerDesktop() { - RemoveObserver(this); + app_banner_manager()->RemoveObserver(this); } static std::unique_ptr<AppBannerManagerDesktop> CreateTestAppBannerManager( @@ -75,7 +76,7 @@ run_loop.Run(); } CHECK(!installable_check_in_progress_); - return IsPromotableWebApp(); + return app_banner_manager()->IsPromotableWebApp(); } void TestAppBannerManagerDesktop::SetBannerPromptReplyCallback( @@ -89,7 +90,7 @@ } AppBannerManager::State TestAppBannerManagerDesktop::state() { - return AppBannerManager::state(); + return app_banner_manager()->state(); } void TestAppBannerManagerDesktop::AwaitAppInstall() { @@ -128,8 +129,6 @@ RunInstallableQuitClosureIfNeeded(); return; } - - AppBannerManagerDesktop::DidFinishLoad(render_frame_host, validated_url); } void TestAppBannerManagerDesktop::RunInstallableQuitClosureIfNeeded() {
diff --git a/chrome/browser/banners/test_app_banner_manager_desktop.h b/chrome/browser/banners/test_app_banner_manager_desktop.h index 7ffb5d6..6366c5a 100644 --- a/chrome/browser/banners/test_app_banner_manager_desktop.h +++ b/chrome/browser/banners/test_app_banner_manager_desktop.h
@@ -9,6 +9,7 @@ #include "base/values.h" #include "chrome/browser/banners/app_banner_manager_desktop.h" +#include "content/public/browser/web_contents_observer.h" namespace content { class WebContents; @@ -19,7 +20,8 @@ // Provides the ability to await the results of the installability check that // happens for every page load. class TestAppBannerManagerDesktop : public AppBannerManagerDesktop, - private AppBannerManager::Observer { + private AppBannerManager::Observer, + private content::WebContentsObserver { public: explicit TestAppBannerManagerDesktop(content::WebContents* web_contents); @@ -62,12 +64,18 @@ TestAppBannerManagerDesktop* AsTestAppBannerManagerDesktopForTesting() override; + bool IsPromptAvailableForTesting() const { + return app_banner_manager()->IsPromptAvailableForTesting(); + } + + InstallableWebAppCheckResult GetInstallableWebAppCheckResult() const { + return app_banner_manager()->GetInstallableWebAppCheckResult(); + } + const base::ListValue& debug_log() const { return debug_log_; } protected: - // AppBannerManager: - // TODO(http://crbug.com/322342499): When AppBannerManager is devirtualized, - // listen to WebContentsObserver::DidFinishLoad directly instead. + // WebContentsObserver: void DidFinishLoad(content::RenderFrameHost* render_frame_host, const GURL& validated_url) override;
diff --git a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStacker.java b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStacker.java index 3b2060f..40f1faa 100644 --- a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStacker.java +++ b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStacker.java
@@ -45,7 +45,6 @@ @IntDef({ LayerType.PROGRESS_BAR, LayerType.TABSTRIP_TOOLBAR, - LayerType.TABSTRIP_TOOLBAR_BELOW_READALOUD, LayerType.READ_ALOUD_PLAYER, LayerType.BOTTOM_TOOLBAR, LayerType.BOTTOM_CHIN, @@ -56,11 +55,8 @@ // The progress bar during page loading. This layer has a height of 0 and overlaps the next // visible layer in the stack. int PROGRESS_BAR = 0; - int TABSTRIP_TOOLBAR = 1; int READ_ALOUD_PLAYER = 2; - // Temporary layer that allows us to flag guard the new behavior of stacking the tabstrip - // toolbar below, rather than above, the readadloud player. - int TABSTRIP_TOOLBAR_BELOW_READALOUD = 3; + int TABSTRIP_TOOLBAR = 3; int BOTTOM_TOOLBAR = 4; int BOTTOM_CHIN = 5; // Bottom sheet as a browser control layer. This is used to position bottom sheet in @@ -125,9 +121,8 @@ new int[] { LayerType.BOTTOM_SHEET, LayerType.PROGRESS_BAR, - LayerType.TABSTRIP_TOOLBAR, LayerType.READ_ALOUD_PLAYER, - LayerType.TABSTRIP_TOOLBAR_BELOW_READALOUD, + LayerType.TABSTRIP_TOOLBAR, LayerType.BOTTOM_TOOLBAR, LayerType.BOTTOM_CHIN, LayerType.TEST_BOTTOM_LAYER
diff --git a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStackerUnitTest.java b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStackerUnitTest.java index cf67cbb..92ef78d 100644 --- a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStackerUnitTest.java +++ b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BottomControlsStackerUnitTest.java
@@ -49,7 +49,7 @@ public class BottomControlsStackerUnitTest { private static final @LayerType int ZERO_HEIGHT_TOP_LAYER = LayerType.PROGRESS_BAR; private static final @LayerType int TOP_LAYER = LayerType.READ_ALOUD_PLAYER; - private static final @LayerType int MID_LAYER = LayerType.TABSTRIP_TOOLBAR_BELOW_READALOUD; + private static final @LayerType int MID_LAYER = LayerType.TABSTRIP_TOOLBAR; private static final @LayerType int BOTTOM_LAYER = LayerType.TEST_BOTTOM_LAYER; @Mock BrowserControlsSizer mBrowserControlsSizer;
diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc index 49e256f..b7f1d3a 100644 --- a/chrome/browser/chrome_browser_main.cc +++ b/chrome/browser/chrome_browser_main.cc
@@ -158,6 +158,7 @@ #include "chrome/browser/apps/app_service/app_service_proxy_factory.h" #include "chrome/browser/apps/app_service/publishers/publisher_host_factory_impl.h" #include "chrome/browser/headless/chrome_browser_main_extra_parts_headless.h" +#include "chrome/browser/lifetime/smart_restart_metrics_observer.h" #include "chrome/browser/profiles/delete_profile_helper.h" #include "chrome/browser/resource_coordinator/tab_manager.h" #include "chrome/browser/resources_integrity.h" @@ -1643,6 +1644,15 @@ // We setup to observe to the initial page load here to defer running // task posted via PostAfterStartupTask until its complete. AfterStartupTaskUtils::StartMonitoringStartup(); + +#if !BUILDFLAG(IS_ANDROID) + // Initialize the observer for smart restart metrics on desktop. + if (base::FeatureList::IsEnabled(features::kSmartRestartMetrics)) { + smart_restart_metrics_observer_ = + std::make_unique<smart_restart::SmartRestartMetricsObserver>( + UpgradeDetector::GetInstance()); + } +#endif } int ChromeBrowserMainParts::PreMainMessageLoopRunImpl() {
diff --git a/chrome/browser/chrome_browser_main.h b/chrome/browser/chrome_browser_main.h index b7d705c..ac29656b 100644 --- a/chrome/browser/chrome_browser_main.h +++ b/chrome/browser/chrome_browser_main.h
@@ -45,6 +45,10 @@ class SyntheticTrialSyncer; } +namespace smart_restart { +class SmartRestartMetricsObserver; +} // namespace smart_restart + class ChromeBrowserMainParts : public content::BrowserMainParts { public: static std::unique_ptr<content::BrowserMainParts> Create( @@ -216,6 +220,12 @@ // created. // Must be deleted before `browser_process_`. std::unique_ptr<ProfileInitManager> profile_init_manager_; + +#if !BUILDFLAG(IS_ANDROID) + // Observer that records metrics related to "Smart Restart" opportunities. + std::unique_ptr<smart_restart::SmartRestartMetricsObserver> + smart_restart_metrics_observer_; +#endif }; #endif // CHROME_BROWSER_CHROME_BROWSER_MAIN_H_
diff --git a/chrome/browser/companion/text_finder/BUILD.gn b/chrome/browser/companion/text_finder/BUILD.gn index 9ef749c..9b87708 100644 --- a/chrome/browser/companion/text_finder/BUILD.gn +++ b/chrome/browser/companion/text_finder/BUILD.gn
@@ -4,9 +4,7 @@ assert(is_win || is_mac || is_linux || is_chromeos || is_android) -import("//mojo/public/tools/bindings/mojom.gni") - -static_library("text_finder") { +source_set("text_finder") { sources = [ "text_finder.cc", "text_finder.h", @@ -18,11 +16,14 @@ "text_highlighter_manager.h", ] - deps = [ + public_deps = [ "//base", "//content/public/browser", + "//content/public/common", "//ui/gfx/geometry", ] + + deps = [ "//services/service_manager/public/cpp" ] } source_set("unit_tests") {
diff --git a/chrome/browser/companion/text_finder/text_finder_manager.h b/chrome/browser/companion/text_finder/text_finder_manager.h index 44a40b90..cf85502d 100644 --- a/chrome/browser/companion/text_finder/text_finder_manager.h +++ b/chrome/browser/companion/text_finder/text_finder_manager.h
@@ -12,17 +12,21 @@ #include "base/gtest_prod_util.h" #include "base/memory/weak_ptr.h" #include "base/unguessable_token.h" -#include "chrome/browser/companion/text_finder/text_finder.h" #include "content/public/browser/page_user_data.h" #include "mojo/public/cpp/bindings/remote.h" #include "third_party/blink/public/mojom/annotation/annotation.mojom.h" namespace companion { +class TextFinder; + class TextFinderManager : public content::PageUserData<TextFinderManager> { public: using AllDoneCallback = base::OnceCallback<void( const std::vector<std::pair<std::string, bool>>&)>; + // Same as TextFinder::FinishedCallback. + using FinishedCallback = + base::OnceCallback<void(std::pair<std::string, bool>)>; ~TextFinderManager() override; @@ -36,7 +40,7 @@ // disconnection. Returns the id associated with the created text finder. std::optional<base::UnguessableToken> CreateTextFinder( const std::string& text_directive, - TextFinder::FinishedCallback callback); + FinishedCallback callback); // Creates multiple text finders for a vector of text directives. Calls // `all_done_callback` when all text finders finish searching (via
diff --git a/chrome/browser/companion/text_finder/text_highlighter_manager.cc b/chrome/browser/companion/text_finder/text_highlighter_manager.cc index bf270ba6..565ae49 100644 --- a/chrome/browser/companion/text_finder/text_highlighter_manager.cc +++ b/chrome/browser/companion/text_finder/text_highlighter_manager.cc
@@ -6,9 +6,6 @@ #include <memory> -#include "base/barrier_callback.h" -#include "base/functional/bind.h" -#include "base/unguessable_token.h" #include "chrome/browser/companion/text_finder/text_highlighter.h" #include "content/public/browser/page.h" #include "content/public/browser/page_user_data.h"
diff --git a/chrome/browser/companion/text_finder/text_highlighter_manager.h b/chrome/browser/companion/text_finder/text_highlighter_manager.h index 1790840d..492bc7e 100644 --- a/chrome/browser/companion/text_finder/text_highlighter_manager.h +++ b/chrome/browser/companion/text_finder/text_highlighter_manager.h
@@ -7,7 +7,6 @@ #include "base/gtest_prod_util.h" #include "base/memory/weak_ptr.h" -#include "base/unguessable_token.h" #include "content/public/browser/page_user_data.h" #include "mojo/public/cpp/bindings/remote.h" #include "third_party/blink/public/mojom/annotation/annotation.mojom.h"
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_composebox_handler.cc b/chrome/browser/contextual_tasks/contextual_tasks_composebox_handler.cc index 4d661e5..5adcf1e 100644 --- a/chrome/browser/contextual_tasks/contextual_tasks_composebox_handler.cc +++ b/chrome/browser/contextual_tasks/contextual_tasks_composebox_handler.cc
@@ -757,9 +757,9 @@ create_client_to_aim_request_info->query_start_time = base::Time::Now(); create_client_to_aim_request_info->active_tool = - GetInputState().active_tool; + input_state_model()->GetInputState().active_tool; create_client_to_aim_request_info->active_model = - GetInputState().active_model; + input_state_model()->GetInputState().active_model; if (auto active_tab_context_id = GetActiveTabContextId(); active_tab_context_id.has_value()) {
diff --git a/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.cc b/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.cc index b52aa39d9..f8ddb0d 100644 --- a/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.cc +++ b/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.cc
@@ -288,8 +288,8 @@ base::BindOnce( &ExperimentalActorPerformActionsFunction::OnActionsFinished, this, task_id, start_time, skip_async_observation_information, - actor::mojom::ActionResultCode::kArgumentsInvalid, requests.error(), - std::move(empty_results))); + std::nullopt, actor::mojom::ActionResultCode::kArgumentsInvalid, + requests.error(), std::move(empty_results))); return RespondLater(); } @@ -297,7 +297,8 @@ task_id, std::move(requests.value()), actor::ActorTaskMetadata(actions), base::BindOnce( &ExperimentalActorPerformActionsFunction::OnActionsFinished, this, - task_id, start_time, skip_async_observation_information)); + task_id, start_time, skip_async_observation_information, + std::nullopt)); return RespondLater(); } @@ -306,6 +307,9 @@ actor::TaskId task_id, base::TimeTicks start_time, bool skip_async_observation_information, + std::optional<page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions> + screenshot_collection_options, actor::mojom::ActionResultCode result_code, std::optional<size_t> index_of_failed_action, std::vector<actor::ActionResultWithLatencyInfo> action_results) { @@ -321,11 +325,12 @@ // Note: the arguments in this function are mostly unused other than the // response proto. - OnObservationResult(start_time, - actor::mojom::ActionResultCode::kTaskWentAway, - index_of_failed_action, action_results, task_id, - skip_async_observation_information, std::move(response), - /*journal_entry=*/nullptr); + OnObservationResult( + start_time, actor::mojom::ActionResultCode::kTaskWentAway, + index_of_failed_action, action_results, task_id, + skip_async_observation_information, + std::move(screenshot_collection_options), std::move(response), + /*journal_entry=*/nullptr); return; } @@ -339,17 +344,19 @@ // Note: the arguments in this function are mostly unused other than the // response proto. - OnObservationResult(start_time, - actor::mojom::ActionResultCode::kTaskWentAway, - index_of_failed_action, action_results, task_id, - skip_async_observation_information, std::move(response), - /*journal_entry=*/nullptr); + OnObservationResult( + start_time, actor::mojom::ActionResultCode::kTaskWentAway, + index_of_failed_action, action_results, task_id, + skip_async_observation_information, + std::move(screenshot_collection_options), std::move(response), + /*journal_entry=*/nullptr); return; } actor::BuildActionsResultWithObservations( *browser_context(), start_time, result_code, index_of_failed_action, std::move(action_results), *task, skip_async_observation_information, + std::move(screenshot_collection_options), base::BindOnce( &ExperimentalActorPerformActionsFunction::OnObservationResult, this)); } @@ -361,6 +368,9 @@ std::vector<actor::ActionResultWithLatencyInfo> action_results, actor::TaskId task_id, bool skip_async_observation_information, + std::optional<page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions> + screenshot_collection_options, std::unique_ptr<optimization_guide::proto::ActionsResult> response, std::unique_ptr<actor::AggregatedJournal::PendingAsyncEntry> journal_entry) { @@ -434,7 +444,7 @@ // TODO(dtapuska): We may want to add an optional task_id to the API so // we can attribute this tab observation to an appropriate task. actor_service->RequestTabObservation( - *tab, actor::TaskId(), + *tab, actor::TaskId(), std::nullopt, base::BindOnce(&ExperimentalActorRequestTabObservationFunction:: OnObservationFinished, this));
diff --git a/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.h b/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.h index e0ba1f4..f12d0ce0 100644 --- a/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.h +++ b/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.h
@@ -78,6 +78,9 @@ actor::TaskId task_id, base::TimeTicks start_time, bool skip_async_observation_information, + std::optional<page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions> + screenshot_collection_options, actor::mojom::ActionResultCode result_code, std::optional<size_t> index_of_failed_action, std::vector<actor::ActionResultWithLatencyInfo> action_results); @@ -88,6 +91,9 @@ std::vector<actor::ActionResultWithLatencyInfo> action_results, actor::TaskId task_id, bool skip_async_observation_information, + std::optional<page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions> + screenshot_collection_options, std::unique_ptr<optimization_guide::proto::ActionsResult> response, std::unique_ptr<actor::AggregatedJournal::PendingAsyncEntry> journal_entry);
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json index da6b60985..cd91f77 100644 --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json
@@ -4982,6 +4982,11 @@ "expiry_milestone": 150 }, { + "name": "fullscreen-refactoring", + "owners": [ "scottyoder@google.com", "meridian@google.com" ], + "expiry_milestone": 152 + }, + { "name": "fullscreen-scroll-threshold", "owners": [ "scottyoder@google.com", "alionadangla@chromium.org", "bling-flags@google.com" ], "expiry_milestone": 145 @@ -6757,6 +6762,11 @@ "expiry_milestone": 150 }, { + "name": "new-tab-page-customization-theme-sync", + "owners": ["hanxi@google.com", "xinyiji@google.com", "clank-start@google.com"], + "expiry_milestone": 160 + }, + { "name": "new-tab-page-customization-v2", "owners": ["hanxi@chromium.org", "clank-start@google.com"], "expiry_milestone": 150 @@ -7523,11 +7533,6 @@ "expiry_milestone": 155 }, { - "name": "page-visibility-page-content-annotations", - "owners": [ "sophiechang@chromium.org", "mcrouse@chromium.org", "chrome-intelligence-core@google.com"], - "expiry_milestone": 130 - }, - { "name": "paint-preview-demo", "owners": [ "ckitagawa@chromium.org", "fredmello@chromium.org", "chrome-fdt@google.com" ], "expiry_milestone": 160 @@ -8485,7 +8490,7 @@ "thefrog@chromium.org", "chrome-counter-abuse-alerts@google.com" ], - "expiry_milestone": 134 + "expiry_milestone": 149 }, { "name": "safe-browsing-scam-detection-keyboard-lock-trigger-android",
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h index 7bbcfda4..271c4e33 100644 --- a/chrome/browser/flag_descriptions.h +++ b/chrome/browser/flag_descriptions.h
@@ -3428,12 +3428,6 @@ inline constexpr char kPageContentCacheDescription[] = "Enables caching of the annotated page content and screenshot"; -inline constexpr char kPageVisibilityPageContentAnnotationsName[] = - "Page visibility content annotations"; -inline constexpr char kPageVisibilityPageContentAnnotationsDescription[] = - "Enables annotating the page visibility model for each page load " - "on-device."; - inline constexpr char kParallelDownloadingName[] = "Parallel downloading"; inline constexpr char kParallelDownloadingDescription[] = "Enable parallel downloading to accelerate download speed."; @@ -5530,6 +5524,12 @@ "Prevents UI jank when a navigation is 'captured', causing a new " "app to be opened."; +inline constexpr char kNewTabPageCustomizationThemeSyncName[] = + "New Tab Page Customization Theme Sync"; +inline constexpr char kNewTabPageCustomizationThemeSyncDescription[] = + "Allows users to sync their New Tab page customizations settings " + "(appearance)."; + inline constexpr char kNotificationPermissionRationaleName[] = "Notification Permission Rationale UI"; inline constexpr char kNotificationPermissionRationaleDescription[] =
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc index 3d3bf704..7b3ce2d 100644 --- a/chrome/browser/flags/android/chrome_feature_list.cc +++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -373,6 +373,7 @@ &kMvcUpdateViewWhenModelChanged, &kNavBarColorAnimation, &kNewTabPageCustomizationForMvt, + &kNewTabPageCustomizationThemeSync, &kNewTabPageCustomizationV2, &kNotificationPermissionVariant, &kNotificationTrampoline, @@ -709,6 +710,7 @@ BASE_FEATURE(kMvcUpdateViewWhenModelChanged, base::FEATURE_ENABLED_BY_DEFAULT); BASE_FEATURE(kNavBarColorAnimation, base::FEATURE_ENABLED_BY_DEFAULT); BASE_FEATURE(kNewTabPageCustomizationForMvt, base::FEATURE_ENABLED_BY_DEFAULT); +BASE_FEATURE(kNewTabPageCustomizationThemeSync, base::FEATURE_DISABLED_BY_DEFAULT); BASE_FEATURE(kNewTabPageCustomizationV2, base::FEATURE_DISABLED_BY_DEFAULT); BASE_FEATURE(kNotificationPermissionVariant, base::FEATURE_DISABLED_BY_DEFAULT); BASE_FEATURE(kNotificationTrampoline, base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h index 97eb9578..f207027b 100644 --- a/chrome/browser/flags/android/chrome_feature_list.h +++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -202,6 +202,7 @@ BASE_DECLARE_FEATURE(kMvcUpdateViewWhenModelChanged); BASE_DECLARE_FEATURE(kNavBarColorAnimation); BASE_DECLARE_FEATURE(kNewTabPageCustomizationForMvt); +BASE_DECLARE_FEATURE(kNewTabPageCustomizationThemeSync); BASE_DECLARE_FEATURE(kNewTabPageCustomizationV2); BASE_DECLARE_FEATURE(kNotificationPermissionVariant); BASE_DECLARE_FEATURE(kNotificationTrampoline);
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java index 12e182b3..ee5e60b 100644 --- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java +++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -505,6 +505,8 @@ public static final String MVC_UPDATE_VIEW_WHEN_MODEL_CHANGED = "MvcUpdateViewWhenModelChanged"; public static final String NAV_BAR_COLOR_ANIMATION = "NavBarColorAnimation"; public static final String NEW_TAB_PAGE_CUSTOMIZATION_FOR_MVT = "NewTabPageCustomizationForMvt"; + public static final String NEW_TAB_PAGE_CUSTOMIZATION_THEME_SYNC = + "NewTabPageCustomizationThemeSync"; public static final String NEW_TAB_PAGE_CUSTOMIZATION_V2 = "NewTabPageCustomizationV2"; public static final String NOTIFICATION_PERMISSION_VARIANT = "NotificationPermissionVariant"; public static final String NOTIFICATION_TRAMPOLINE = "NotificationTrampoline"; @@ -989,6 +991,8 @@ newCachedFlag(NAV_BAR_COLOR_ANIMATION, /* defaultValue= */ true); public static final CachedFlag sNewTabPageCustomizationForMvt = newCachedFlag(NEW_TAB_PAGE_CUSTOMIZATION_FOR_MVT, true); + public static final CachedFlag sNewTabPageCustomizationThemeSync = + newCachedFlag(NEW_TAB_PAGE_CUSTOMIZATION_THEME_SYNC, /* defaultValue= */ false); public static final CachedFlag sNewTabPageCustomizationV2 = newCachedFlag(NEW_TAB_PAGE_CUSTOMIZATION_V2, false, true); public static final CachedFlag sNotificationTrampoline = @@ -1230,6 +1234,7 @@ sMvcUpdateViewWhenModelChanged, sNavBarColorAnimation, sNewTabPageCustomizationForMvt, + sNewTabPageCustomizationThemeSync, sNewTabPageCustomizationV2, sNotificationTrampoline, sNotificationTrampolineNoNewTask,
diff --git a/chrome/browser/glic/BUILD.gn b/chrome/browser/glic/BUILD.gn index 84fbc3d8..0e9f85d0 100644 --- a/chrome/browser/glic/BUILD.gn +++ b/chrome/browser/glic/BUILD.gn
@@ -481,6 +481,30 @@ overridden_deps = [ "//third_party/blink/public/mojom:mojom_platform" ] component_deps = [ "//content/public/common" ] + cpp_typemaps = [ + { + types = [ + { + mojom = "glic.mojom.ScreenshotCollectionOptions" + cpp = "::page_content_annotations::ScreenshotOptions::ScreenshotCollectionOptions" + }, + { + mojom = "glic.mojom.ScreenshotImageFormat" + cpp = "::page_content_annotations::ScreenshotOptions::ScreenshotImageFormat" + }, + { + mojom = "glic.mojom.ScreenshotCompressionQuality" + cpp = "::page_content_annotations::ScreenshotOptions::ScreenshotCompressionQuality" + }, + ] + traits_headers = [ + "//components/page_content_annotations/content/page_context_fetcher.h", + "host/glic_mojom_traits.h", + ] + traits_sources = [ "host/glic_mojom_traits.cc" ] + traits_public_deps = [ "//components/page_content_annotations/content" ] + }, + ] } source_set("unit_tests") {
diff --git a/chrome/browser/glic/actor/glic_actor_task_lifecycle_browsertest.cc b/chrome/browser/glic/actor/glic_actor_task_lifecycle_browsertest.cc index 8e30909..62feb056 100644 --- a/chrome/browser/glic/actor/glic_actor_task_lifecycle_browsertest.cc +++ b/chrome/browser/glic/actor/glic_actor_task_lifecycle_browsertest.cc
@@ -6,9 +6,27 @@ #include "content/public/test/browser_test.h" namespace mojo { + +base::Value ConvertToValue(const page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions& in) { + base::Value raw_out(base::Value::Type::DICT); + base::DictValue& out = raw_out.GetDict(); + out.Set("maxWidth", static_cast<int>(in.max_width.value_or(0))); + out.Set("maxHeight", static_cast<int>(in.max_height.value_or(0))); + out.Set("screenshotImageFormat", + static_cast<int>(in.screenshot_image_format.value_or( + page_content_annotations::ScreenshotOptions:: + ScreenshotImageFormat::kJpeg))); + out.Set("screenshotCompressionQuality", + static_cast<int>(in.screenshot_compression_quality.value_or( + page_content_annotations::ScreenshotOptions:: + ScreenshotCompressionQuality::kMedium))); + return raw_out; +} + template <> struct TypeConverter<base::Value, glic::mojom::GetTabContextOptions> { - static base::Value Convert(const glic::mojom::GetTabContextOptions in) { + static base::Value Convert(const glic::mojom::GetTabContextOptions& in) { base::Value raw_out(base::Value::Type::DICT); base::DictValue& out = raw_out.GetDict(); out.Set("includeInnerText", in.include_inner_text); @@ -20,6 +38,8 @@ out.Set("pdfSizeLimit", static_cast<int>(in.pdf_size_limit)); out.Set("annotatedPageContentMode", static_cast<int>(in.annotated_page_content_mode)); + out.Set("screenshotCollectionOptions", + ConvertToValue(in.screenshot_collection_options)); return raw_out; } };
diff --git a/chrome/browser/glic/actor/glic_actor_task_manager.cc b/chrome/browser/glic/actor/glic_actor_task_manager.cc index 008e50b..d4bf9b9 100644 --- a/chrome/browser/glic/actor/glic_actor_task_manager.cc +++ b/chrome/browser/glic/actor/glic_actor_task_manager.cc
@@ -23,6 +23,7 @@ #include "chrome/browser/actor/ui/actor_ui_state_manager_interface.h" #include "chrome/browser/glic/actor/glic_actor_policy_checker.h" #include "chrome/browser/glic/host/context/glic_tab_data.h" +#include "chrome/browser/glic/host/glic_mojom_traits.h" #include "chrome/browser/glic/public/glic_keyed_service.h" #include "chrome/browser/glic/public/glic_keyed_service_factory.h" #include "chrome/browser/profiles/profile.h" @@ -34,6 +35,7 @@ #include "chrome/common/actor_webui.mojom.h" #include "chrome/common/chrome_features.h" #include "components/optimization_guide/proto/features/actions_data.pb.h" +#include "components/page_content_annotations/content/page_context_fetcher.h" #include "components/sessions/core/session_id.h" #include "components/tabs/public/tab_interface.h" #include "content/public/browser/navigation_controller.h" @@ -73,6 +75,7 @@ return nullptr; } + } // namespace GlicActorTaskManager::GlicActorTaskManager( @@ -146,6 +149,9 @@ actor::TaskId task_id, base::TimeTicks start_time, bool skip_async_observation_information, + std::optional<page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions> + screenshot_collection_options, actor::mojom::ActionResultCode result_code, std::optional<size_t> index_of_failed_action, std::vector<actor::ActionResultWithLatencyInfo> action_results) { @@ -190,6 +196,7 @@ &GlicActorTaskManager::PerformActionsFinished, weak_ptr_factory_.GetWeakPtr(), std::move(callback), task_id, start_time, skip_async_observation_information, + std::move(screenshot_collection_options), actor::mojom::ActionResultCode::kRendererCrashed, index_of_failed_action, std::move(action_results)); ReloadCrashedTab(*crashed_tab, task->id(), @@ -201,6 +208,7 @@ actor::BuildActionsResultWithObservations( *profile_, start_time, result_code, index_of_failed_action, std::move(action_results), *task, skip_async_observation_information, + screenshot_collection_options, base::BindOnce(&GlicActorTaskManager::DidFinishBuildObservation, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } @@ -213,6 +221,9 @@ std::vector<actor::ActionResultWithLatencyInfo> action_results, actor::TaskId task_id, bool skip_async_observation_information, + std::optional<page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions> + screenshot_collection_options, std::unique_ptr<optimization_guide::proto::ActionsResult> result, std::unique_ptr<actor::AggregatedJournal::PendingAsyncEntry> journal_entry) { @@ -240,7 +251,8 @@ auto retry_perform_actions_finished = base::BindOnce( &GlicActorTaskManager::PerformActionsFinished, weak_ptr_factory_.GetWeakPtr(), std::move(callback), task_id, - start_time, skip_async_observation_information, result_code, + start_time, skip_async_observation_information, + std::move(screenshot_collection_options), result_code, index_of_failed_action, std::move(action_results)); base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( @@ -348,12 +360,14 @@ bool skip_async_observation_information = actions.has_skip_async_observation_collection() && actions.skip_async_observation_collection(); + attempted_observation_retry_ = false; actor_keyed_service_->PerformActions( task_id, std::move(requests.value()), actor::ActorTaskMetadata(actions), base::BindOnce(&GlicActorTaskManager::PerformActionsFinished, GetWeakPtr(), std::move(callback), task_id, start_time, - skip_async_observation_information)); + skip_async_observation_information, + actor::GetScreenshotCollectionOptions(actions))); } void GlicActorTaskManager::CancelActions( @@ -546,8 +560,10 @@ std::move(callback), CreateTabData(tab_of_resumed_task), resume_response_code); - actor_keyed_service_->RequestTabObservation(*tab_of_resumed_task, task_id, - std::move(observation_callback)); + actor_keyed_service_->RequestTabObservation( + *tab_of_resumed_task, task_id, + context_options.screenshot_collection_options, + std::move(observation_callback)); } bool GlicActorTaskManager::IsActuating() const {
diff --git a/chrome/browser/glic/actor/glic_actor_task_manager.h b/chrome/browser/glic/actor/glic_actor_task_manager.h index 26e7500b..eb7cb76 100644 --- a/chrome/browser/glic/actor/glic_actor_task_manager.h +++ b/chrome/browser/glic/actor/glic_actor_task_manager.h
@@ -18,6 +18,7 @@ #include "chrome/common/actor/task_id.h" #include "chrome/common/actor_webui.mojom.h" #include "components/optimization_guide/proto/features/actions_data.pb.h" +#include "components/page_content_annotations/content/page_context_fetcher.h" #include "components/tabs/public/tab_interface.h" class Profile; @@ -78,6 +79,9 @@ actor::TaskId task_id, base::TimeTicks start_time, bool skip_async_observation_information, + std::optional<page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions> + screenshot_collection_options, actor::mojom::ActionResultCode result_code, std::optional<size_t> index_of_failed_action, std::vector<actor::ActionResultWithLatencyInfo> action_results); @@ -89,6 +93,9 @@ std::vector<actor::ActionResultWithLatencyInfo> action_results, actor::TaskId task_id, bool skip_async_observation_information, + std::optional<page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions> + screenshot_collection_options, std::unique_ptr<optimization_guide::proto::ActionsResult> result, std::unique_ptr<actor::AggregatedJournal::PendingAsyncEntry> journal_entry);
diff --git a/chrome/browser/glic/host/OWNERS b/chrome/browser/glic/host/OWNERS index 66e1a54..b3f0d06f 100644 --- a/chrome/browser/glic/host/OWNERS +++ b/chrome/browser/glic/host/OWNERS
@@ -12,3 +12,6 @@ per-file *.mojom=set noparent per-file *.mojom=file://ipc/SECURITY_OWNERS + +per-file *_mojom_traits*.*=set noparent +per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
diff --git a/chrome/browser/glic/host/context/glic_page_context_fetcher.cc b/chrome/browser/glic/host/context/glic_page_context_fetcher.cc index 24f7aaee..66ea621 100644 --- a/chrome/browser/glic/host/context/glic_page_context_fetcher.cc +++ b/chrome/browser/glic/host/context/glic_page_context_fetcher.cc
@@ -13,11 +13,13 @@ #include "chrome/browser/actor/aggregated_journal.h" #include "chrome/browser/glic/host/context/glic_tab_data.h" #include "chrome/browser/glic/host/glic.mojom.h" +#include "chrome/browser/glic/host/glic_mojom_traits.h" #include "chrome/browser/page_content_annotations/multi_source_page_context_fetcher.h" #include "chrome/common/actor/journal_details_builder.h" #include "components/content_extraction/content/browser/inner_text.h" #include "components/favicon/content/content_favicon_driver.h" #include "components/optimization_guide/content/browser/page_content_proto_provider.h" +#include "components/page_content_annotations/content/page_context_fetcher.h" #include "components/tabs/public/tab_interface.h" #include "mojo/public/cpp/base/proto_wrapper.h" #include "mojo/public/cpp/base/proto_wrapper_passkeys.h" @@ -187,7 +189,8 @@ // Disable paint preview backend for glic, and capture the viewport only. options.screenshot_options = page_content_annotations::ScreenshotOptions::ViewportOnly( - /*paint_preview_options=*/std::nullopt); + /*paint_preview_options=*/std::nullopt, + tab_context_options.screenshot_collection_options); } const bool on_critical_path = true;
diff --git a/chrome/browser/glic/host/glic.mojom b/chrome/browser/glic/host/glic.mojom index ee4bdb9..edae0bc 100644 --- a/chrome/browser/glic/host/glic.mojom +++ b/chrome/browser/glic/host/glic.mojom
@@ -489,6 +489,51 @@ bool enable_skills; }; +// @generate glic_api +// Enum to specify the image format of the screenshot. +[Stable, Extensible] +enum ScreenshotImageFormat { + // JPEG screenshot format. This is the default format. + [Default] kJpeg = 0, + // PNG screenshot format. + kPng = 1, + // WEBP screenshot format. + kWebp = 2, +}; + +// @generate glic_api +// Enum to specify the compression quality of the screenshot. Depending on +// screenshot format, the compression quality may not be respected or may mean +// something different. +[Stable, Extensible] +enum ScreenshotCompressionQuality { + // No compression. + [Default] kNone = 0, + // Low compression quality. + kLow = 1, + // Medium compression quality. + kMedium = 2, + // High compression quality. + kHigh = 3, +}; + +struct ScreenshotCollectionOptions { + // Screenshot will be scaled to fit the max width and height while + // maintaining the aspect ratio. + // If set to 0, the screenshot will be captured without limiting the width + // (so long as the height is not limited). + uint32 max_width; + // Screenshot will be scaled to fit the max width and height while maintaining + // the aspect ratio. + // If set to 0, the screenshot will be captured without limiting the height + // (so long as the width is not limited). + uint32 max_height; + // The format of the screenshot. + ScreenshotImageFormat screenshot_image_format; + // The compression quality of the screenshot. + ScreenshotCompressionQuality screenshot_compression_quality; +}; + // Options for getting tab context. struct GetTabContextOptions { // Whether to include inner text in the response. @@ -511,6 +556,8 @@ // // If invalid, falls back to using ANNOTATED_PAGE_CONTENT_MODE_DEFAULT. uint32 annotated_page_content_mode = 0; + // The screenshot collection options to use for the screenshot. + ScreenshotCollectionOptions screenshot_collection_options; }; // Options for requesting pin candidates. @@ -1929,4 +1976,6 @@ kShareAdditionalImageContext = 6, // Enables the PDF Zero State Web UI. kPdfZeroState = 7, + // Indicates that the host supports the invoke mechanism. + kInvoke = 8, };
diff --git a/chrome/browser/glic/host/glic_api_browsertest.cc b/chrome/browser/glic/host/glic_api_browsertest.cc index e2cf3d31..da58d184 100644 --- a/chrome/browser/glic/host/glic_api_browsertest.cc +++ b/chrome/browser/glic/host/glic_api_browsertest.cc
@@ -3655,6 +3655,8 @@ expected_capabilities.Append( std::to_underlying(mojom::HostCapability::kPdfZeroState)); } + expected_capabilities.Append( + std::to_underlying(mojom::HostCapability::kInvoke)); ExecuteJsTest({.params = base::Value(std::move(expected_capabilities))}); }
diff --git a/chrome/browser/glic/host/glic_mojom_traits.cc b/chrome/browser/glic/host/glic_mojom_traits.cc new file mode 100644 index 0000000..b58865f7 --- /dev/null +++ b/chrome/browser/glic/host/glic_mojom_traits.cc
@@ -0,0 +1,33 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/glic/host/glic_mojom_traits.h" + +namespace mojo { + +// static +bool StructTraits< + glic::mojom::ScreenshotCollectionOptionsDataView, + page_content_annotations::ScreenshotOptions::ScreenshotCollectionOptions>:: + Read(const glic::mojom::ScreenshotCollectionOptionsDataView data, + page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions* out) { + out->max_width = data.max_width(); + out->max_height = data.max_height(); + page_content_annotations::ScreenshotOptions::ScreenshotImageFormat + screenshot_image_format; + if (!data.ReadScreenshotImageFormat(&screenshot_image_format)) { + return false; + } + out->screenshot_image_format = screenshot_image_format; + page_content_annotations::ScreenshotOptions::ScreenshotCompressionQuality + screenshot_compression_quality; + if (!data.ReadScreenshotCompressionQuality(&screenshot_compression_quality)) { + return false; + } + out->screenshot_compression_quality = screenshot_compression_quality; + return true; +} + +} // namespace mojo
diff --git a/chrome/browser/glic/host/glic_mojom_traits.h b/chrome/browser/glic/host/glic_mojom_traits.h new file mode 100644 index 0000000..eefaaac --- /dev/null +++ b/chrome/browser/glic/host/glic_mojom_traits.h
@@ -0,0 +1,138 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_GLIC_HOST_GLIC_MOJOM_TRAITS_H_ +#define CHROME_BROWSER_GLIC_HOST_GLIC_MOJOM_TRAITS_H_ + +#include "base/notreached.h" +#include "chrome/browser/glic/host/glic.mojom.h" +#include "components/page_content_annotations/content/page_context_fetcher.h" +#include "mojo/public/cpp/bindings/enum_traits.h" +#include "mojo/public/cpp/bindings/struct_traits.h" + +namespace mojo { + +template <> +struct StructTraits< + glic::mojom::ScreenshotCollectionOptionsDataView, + page_content_annotations::ScreenshotOptions::ScreenshotCollectionOptions> { + static uint32_t max_width(const page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions& in) { + return in.max_width.value_or(0); + } + static uint32_t max_height(const page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions& in) { + return in.max_height.value_or(0); + } + static page_content_annotations::ScreenshotOptions::ScreenshotImageFormat + screenshot_image_format(const page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions& in) { + return in.screenshot_image_format.value_or( + page_content_annotations::ScreenshotOptions::ScreenshotImageFormat:: + kJpeg); + } + static page_content_annotations::ScreenshotOptions:: + ScreenshotCompressionQuality + screenshot_compression_quality( + const page_content_annotations::ScreenshotOptions:: + ScreenshotCollectionOptions& in) { + return in.screenshot_compression_quality.value_or( + page_content_annotations::ScreenshotOptions:: + ScreenshotCompressionQuality::kMedium); + } + static bool Read( + const glic::mojom::ScreenshotCollectionOptionsDataView data, + page_content_annotations::ScreenshotOptions::ScreenshotCollectionOptions* + out); +}; + +template <> +struct EnumTraits< + glic::mojom::ScreenshotImageFormat, + page_content_annotations::ScreenshotOptions::ScreenshotImageFormat> { + static glic::mojom::ScreenshotImageFormat ToMojom( + page_content_annotations::ScreenshotOptions::ScreenshotImageFormat + screenshot_image_format) { + switch (screenshot_image_format) { + case page_content_annotations::ScreenshotOptions::ScreenshotImageFormat:: + kJpeg: + return glic::mojom::ScreenshotImageFormat::kJpeg; + case page_content_annotations::ScreenshotOptions::ScreenshotImageFormat:: + kPng: + return glic::mojom::ScreenshotImageFormat::kPng; + case page_content_annotations::ScreenshotOptions::ScreenshotImageFormat:: + kWebp: + return glic::mojom::ScreenshotImageFormat::kWebp; + default: + return glic::mojom::ScreenshotImageFormat::kJpeg; + } + NOTREACHED(); + } + + static page_content_annotations::ScreenshotOptions::ScreenshotImageFormat + FromMojom(glic::mojom::ScreenshotImageFormat screenshot_image_format) { + switch (screenshot_image_format) { + case glic::mojom::ScreenshotImageFormat::kJpeg: + return page_content_annotations::ScreenshotOptions:: + ScreenshotImageFormat::kJpeg; + case glic::mojom::ScreenshotImageFormat::kPng: + return page_content_annotations::ScreenshotOptions:: + ScreenshotImageFormat::kPng; + case glic::mojom::ScreenshotImageFormat::kWebp: + return page_content_annotations::ScreenshotOptions:: + ScreenshotImageFormat::kWebp; + } + NOTREACHED(); + } +}; + +template <> +struct EnumTraits< + glic::mojom::ScreenshotCompressionQuality, + page_content_annotations::ScreenshotOptions::ScreenshotCompressionQuality> { + static glic::mojom::ScreenshotCompressionQuality ToMojom( + page_content_annotations::ScreenshotOptions::ScreenshotCompressionQuality + screenshot_compression_quality) { + switch (screenshot_compression_quality) { + case page_content_annotations::ScreenshotOptions:: + ScreenshotCompressionQuality::kLow: + return glic::mojom::ScreenshotCompressionQuality::kLow; + case page_content_annotations::ScreenshotOptions:: + ScreenshotCompressionQuality::kMedium: + return glic::mojom::ScreenshotCompressionQuality::kMedium; + case page_content_annotations::ScreenshotOptions:: + ScreenshotCompressionQuality::kHigh: + return glic::mojom::ScreenshotCompressionQuality::kHigh; + case page_content_annotations::ScreenshotOptions:: + ScreenshotCompressionQuality::kNone: + return glic::mojom::ScreenshotCompressionQuality::kNone; + } + NOTREACHED(); + } + + static page_content_annotations::ScreenshotOptions:: + ScreenshotCompressionQuality + FromMojom(glic::mojom::ScreenshotCompressionQuality + screenshot_compression_quality) { + switch (screenshot_compression_quality) { + case glic::mojom::ScreenshotCompressionQuality::kLow: + return page_content_annotations::ScreenshotOptions:: + ScreenshotCompressionQuality::kLow; + case glic::mojom::ScreenshotCompressionQuality::kMedium: + return page_content_annotations::ScreenshotOptions:: + ScreenshotCompressionQuality::kMedium; + case glic::mojom::ScreenshotCompressionQuality::kHigh: + return page_content_annotations::ScreenshotOptions:: + ScreenshotCompressionQuality::kHigh; + case glic::mojom::ScreenshotCompressionQuality::kNone: + return page_content_annotations::ScreenshotOptions:: + ScreenshotCompressionQuality::kNone; + } + NOTREACHED(); + } +}; + +} // namespace mojo + +#endif // CHROME_BROWSER_GLIC_HOST_GLIC_MOJOM_TRAITS_H_
diff --git a/chrome/browser/glic/host/glic_page_handler.cc b/chrome/browser/glic/host/glic_page_handler.cc index a12d225..eabca37b 100644 --- a/chrome/browser/glic/host/glic_page_handler.cc +++ b/chrome/browser/glic/host/glic_page_handler.cc
@@ -995,6 +995,10 @@ state->host_capabilities.push_back(mojom::HostCapability::kPdfZeroState); } + if (base::FeatureList::IsEnabled(features::kGlicInvoke)) { + state->host_capabilities.push_back(mojom::HostCapability::kInvoke); + } + const mojom::InvocationSource invocation_source = host().invocation_source().value_or( mojom::InvocationSource::kUnsupported);
diff --git a/chrome/browser/glic/public/features.cc b/chrome/browser/glic/public/features.cc index 2875034c..6ac4e70 100644 --- a/chrome/browser/glic/public/features.cc +++ b/chrome/browser/glic/public/features.cc
@@ -22,4 +22,6 @@ BASE_FEATURE(kAutoOpenGlicForPdf, base::FEATURE_DISABLED_BY_DEFAULT); +BASE_FEATURE(kGlicInvoke, base::FEATURE_ENABLED_BY_DEFAULT); + } // namespace features
diff --git a/chrome/browser/glic/public/features.h b/chrome/browser/glic/public/features.h index 54ae94c..93db8dc 100644 --- a/chrome/browser/glic/public/features.h +++ b/chrome/browser/glic/public/features.h
@@ -18,6 +18,8 @@ BASE_DECLARE_FEATURE(kAutoOpenGlicForPdf); +BASE_DECLARE_FEATURE(kGlicInvoke); + } // namespace features #endif // CHROME_BROWSER_GLIC_PUBLIC_FEATURES_H_
diff --git a/chrome/browser/glic/selection/selection_overlay_controller.cc b/chrome/browser/glic/selection/selection_overlay_controller.cc index bd69302e..04efcb8 100644 --- a/chrome/browser/glic/selection/selection_overlay_controller.cc +++ b/chrome/browser/glic/selection/selection_overlay_controller.cc
@@ -148,7 +148,8 @@ page_content_annotations::FetchPageContextOptions options; options.screenshot_options = page_content_annotations::ScreenshotOptions::ViewportOnly( - /*paint_preview_options=*/std::nullopt); + /*paint_preview_options=*/std::nullopt, + /*screenshot_collection_options=*/std::nullopt); options.annotated_page_content_options = optimization_guide::DefaultAIPageContentOptions( /*on_critical_path=*/true); @@ -337,9 +338,10 @@ paint.setColor(SK_ColorCYAN); canvas.drawRect(region, paint); } - - result = - page_content_annotations::EncodeScreenshot(deep_copy_bitmap); + // TODO(https://b/485548840): Pass in the screenshot collection + // options. + result = page_content_annotations::EncodeScreenshot( + deep_copy_bitmap, std::nullopt); return result; }, redacted_screenshot_, std::move(regions)),
diff --git a/chrome/browser/glic/service/glic_instance_coordinator_browsertest.cc b/chrome/browser/glic/service/glic_instance_coordinator_browsertest.cc index e1e6341..72a88e9 100644 --- a/chrome/browser/glic/service/glic_instance_coordinator_browsertest.cc +++ b/chrome/browser/glic/service/glic_instance_coordinator_browsertest.cc
@@ -836,4 +836,17 @@ EXPECT_EQ(error_future.Get(), GlicInvokeError::kInvalidTab); } +IN_PROC_BROWSER_TEST_F(GlicInstanceCoordinatorBrowserTest, + InvokeWithEmptyConversationId) { + base::test::TestFuture<GlicInvokeError> error_future; + GlicInvokeOptions options(mojom::InvocationSource::kOsButton); + options.on_error = error_future.GetCallback(); + options.conversation = ConversationId(""); + + coordinator().Invoke(GetTabListInterface()->GetActiveTab(), + std::move(options)); + + EXPECT_EQ(error_future.Get(), GlicInvokeError::kInvalidConversationId); +} + } // namespace glic
diff --git a/chrome/browser/google/google_update_settings_posix.cc b/chrome/browser/google/google_update_settings_posix.cc index 334f8333..1ef2d60 100644 --- a/chrome/browser/google/google_update_settings_posix.cc +++ b/chrome/browser/google/google_update_settings_posix.cc
@@ -48,19 +48,7 @@ #endif } -} // namespace - -// static -base::SequencedTaskRunner* -GoogleUpdateSettings::CollectStatsConsentTaskRunner() { - // TODO(fdoray): Use LazyThreadPoolSequencedTaskRunner::GetRaw() here instead - // of .Get().get() when it's added to the API, http://crbug.com/40524407. - return g_collect_stats_consent_task_runner.Get().get(); -} - -// static -bool GoogleUpdateSettings::GetCollectStatsConsentFromDir( - const base::FilePath& consent_dir) { +bool GetCollectStatsConsentFromDir(const base::FilePath& consent_dir) { if (!base::DirectoryExists(consent_dir)) { return false; } @@ -77,6 +65,16 @@ return consented; } +} // namespace + +// static +base::SequencedTaskRunner* +GoogleUpdateSettings::CollectStatsConsentTaskRunner() { + // TODO(fdoray): Use LazyThreadPoolSequencedTaskRunner::GetRaw() here instead + // of .Get().get() when it's added to the API, http://crbug.com/40524407. + return g_collect_stats_consent_task_runner.Get().get(); +} + // static bool GoogleUpdateSettings::GetCollectStatsConsent() { base::FilePath consent_dir; @@ -162,3 +160,10 @@ bool GoogleUpdateSettings::SetLastRunTime() { return false; } + +// static +bool GoogleUpdateSettings::GetCollectStatsConsentDefault( + bool* stats_consent_default) { + // We never know the default status of the consent button on POSIX platforms. + return false; +}
diff --git a/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthMenuDelegate.java b/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthMenuDelegate.java index f69676a..a0aa5f97 100644 --- a/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthMenuDelegate.java +++ b/chrome/browser/incognito/android/java/src/org/chromium/chrome/browser/incognito/reauth/IncognitoReauthMenuDelegate.java
@@ -9,6 +9,7 @@ import android.content.Context; import android.view.View; +import androidx.annotation.ColorRes; import androidx.annotation.DrawableRes; import androidx.annotation.IdRes; import androidx.annotation.IntDef; @@ -108,22 +109,22 @@ private MVCListAdapter.ListItem buildListItemByMenuItemType(@MenuItemType int type) { switch (type) { case MenuItemType.CLOSE_INCOGNITO_TABS: - return buildMenuListItemWithCustomApperance( + return buildMenuListItemWithCustomAppearance( /* titleId= */ R.string.menu_close_all_incognito_tabs, /* menuId= */ 0, /* startIconId= */ R.drawable.btn_close, /* enabled= */ true, - /* colorTint= */ R.color.default_icon_color_secondary_light_tint_list, + /* tintColorId= */ R.color.default_icon_color_secondary_light_tint_list, /* textAppearanceStyle= */ R.style .TextAppearance_TextLarge_Primary_Baseline_Light, /* textEllipsizedAtEnd= */ true); case MenuItemType.SETTINGS: - return buildMenuListItemWithCustomApperance( + return buildMenuListItemWithCustomAppearance( /* titleId= */ R.string.menu_settings, /* menuId= */ 0, /* startIconId= */ R.drawable.settings_cog, /* enabled= */ true, - /* colorTint= */ R.color.default_icon_color_secondary_light_tint_list, + /* tintColorId= */ R.color.default_icon_color_secondary_light_tint_list, /* textAppearanceStyle= */ R.style .TextAppearance_TextLarge_Primary_Baseline_Light, /* textEllipsizedAtEnd= */ true); @@ -149,18 +150,18 @@ * @param menuId Id of the menu item. * @param startIconId The icon on the start of the menu item. * @param enabled Whether or not this menu item should be enabled. - * @param colorTint The color tinr to apply on the menu item icons. + * @param tintColorId The color tint resource id to apply on the menu item icons. * @param textAppearanceStyle The style to apply on the text. * @param textEllipsizedAtEnd Whether to ellipsize the text at the end when it doesn't fit the * view width. * @return ListItem Representing an item with text or icon. */ - private static MVCListAdapter.ListItem buildMenuListItemWithCustomApperance( + private static MVCListAdapter.ListItem buildMenuListItemWithCustomAppearance( @StringRes int titleId, @IdRes int menuId, @DrawableRes int startIconId, boolean enabled, - int colorTint, + @ColorRes int tintColorId, int textAppearanceStyle, boolean textEllipsizedAtEnd) { return new MVCListAdapter.ListItem( @@ -170,7 +171,7 @@ .with(ListMenuItemProperties.MENU_ITEM_ID, menuId) .with(ListMenuItemProperties.START_ICON_ID, startIconId) .with(ListMenuItemProperties.ENABLED, enabled) - .with(ListMenuItemProperties.ICON_TINT_COLOR_STATE_LIST_ID, colorTint) + .with(ListMenuItemProperties.ICON_TINT_COLOR_STATE_LIST_ID, tintColorId) .with(ListMenuItemProperties.TEXT_APPEARANCE_ID, textAppearanceStyle) .with(ListMenuItemProperties.IS_TEXT_ELLIPSIZED_AT_END, textEllipsizedAtEnd) .build());
diff --git a/chrome/browser/lifetime/smart_restart_metrics_observer.cc b/chrome/browser/lifetime/smart_restart_metrics_observer.cc new file mode 100644 index 0000000..f3e7eabe --- /dev/null +++ b/chrome/browser/lifetime/smart_restart_metrics_observer.cc
@@ -0,0 +1,157 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/lifetime/smart_restart_metrics_observer.h" + +#include "base/metrics/histogram_functions.h" +#include "base/strings/strcat.h" +#include "build/build_config.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_window/public/browser_window_interface.h" +#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h" +#include "chrome/browser/upgrade_detector/upgrade_detector.h" +#include "ui/base/idle/idle.h" + +namespace smart_restart { +namespace { + +// Returns true if there are zero tabbed browser windows. +bool IsZeroTabbedBrowserCount() { + bool is_zero = true; + ForEachCurrentBrowserWindowInterfaceOrderedByActivation( + [&is_zero](BrowserWindowInterface* browser) { + if (browser->GetType() == BrowserWindowInterface::Type::TYPE_NORMAL) { + is_zero = false; + return false; + } + return true; + }); + return is_zero; +} + +void RecordDuration(std::string_view histogram_name, base::TimeDelta duration) { + base::UmaHistogramCustomTimes(histogram_name, duration, base::Seconds(1), + base::Days(1), 50); +} + +} // namespace + +SmartRestartMetricsObserver::SmartRestartMetricsObserver( + UpgradeDetector* upgrade_detector) + : SmartRestartMetricsObserver( + upgrade_detector, + base::BindRepeating(&IsZeroTabbedBrowserCount)) {} + +SmartRestartMetricsObserver::SmartRestartMetricsObserver( + UpgradeDetector* upgrade_detector, + IsZeroBrowserCallback is_zero_callback) + : upgrade_detector_(upgrade_detector), + is_zero_callback_(std::move(is_zero_callback)) { + upgrade_detector_->AddObserver(this); + BrowserList::AddObserver(this); + lock_state_subscription_ = ui::AddScreenLockCallback( + base::BindRepeating(&SmartRestartMetricsObserver::OnLockStateChanged, + base::Unretained(this))); + // Check initial state. + if (ui::CheckIdleStateIsLocked()) { + OnLockStateChanged(true); + } +} + +SmartRestartMetricsObserver::~SmartRestartMetricsObserver() { + upgrade_detector_->RemoveObserver(this); + BrowserList::RemoveObserver(this); + // If the browser process is shutting down, record any pending durations. +#if BUILDFLAG(IS_MAC) + RecordZeroWindowMetrics(); +#endif + RecordLockedDurationMetrics(); +} + +void SmartRestartMetricsObserver::OnUpgradeRecommended() { + if (locked_timer_.has_value() && !locked_update_timer_.has_value()) { + locked_update_timer_.emplace(); + } +#if BUILDFLAG(IS_MAC) + if (zero_window_timer_.has_value() && + !zero_window_update_timer_.has_value()) { + zero_window_update_timer_.emplace(); + } +#endif +} + +void SmartRestartMetricsObserver::SetLockedStateForTesting(bool is_locked) { + OnLockStateChanged(is_locked); +} + +void SmartRestartMetricsObserver::OnLockStateChanged(bool is_locked) { + if (is_locked && !was_locked_) { + // Transitioned to LOCKED state. Start timer. + locked_timer_.emplace(); + if (upgrade_detector_->is_upgrade_available()) { + locked_update_timer_.emplace(); + } + } else if (!is_locked && was_locked_) { + // Transitioned out of LOCKED state. Record duration. + RecordLockedDurationMetrics(); + } + was_locked_ = is_locked; +} + +void SmartRestartMetricsObserver::RecordLockedDurationMetrics() { + if (!locked_timer_.has_value()) { + return; + } + + // Record the baseline duration (how long users stay in lock state). + base::TimeDelta duration = locked_timer_->Elapsed(); + RecordDuration("Session.LockedDuration", duration); + + // Record the duration specifically when an update is pending. + if (locked_update_timer_.has_value()) { + base::TimeDelta update_duration = locked_update_timer_->Elapsed(); + RecordDuration("Session.LockedDuration.WithUpdate", update_duration); + } + locked_timer_.reset(); + locked_update_timer_.reset(); +} + +#if BUILDFLAG(IS_MAC) +void SmartRestartMetricsObserver::OnBrowserAdded(Browser* browser) { + // If we were tracking a zero-window duration, stop now because a window + // appeared. + RecordZeroWindowMetrics(); +} + +void SmartRestartMetricsObserver::OnBrowserRemoved(Browser* browser) { + // If the last browser window is closed, start tracking the duration. + // Note: On macOS, the application stays running even with zero windows. + if (is_zero_callback_.Run() && !zero_window_timer_.has_value()) { + zero_window_timer_.emplace(); + if (upgrade_detector_->is_upgrade_available()) { + zero_window_update_timer_.emplace(); + } + } +} + +void SmartRestartMetricsObserver::RecordZeroWindowMetrics() { + if (!zero_window_timer_.has_value()) { + return; + } + + // Record the baseline duration (how long users stay in Zero Window state). + base::TimeDelta duration = zero_window_timer_->Elapsed(); + RecordDuration("Session.ZeroWindowDuration", duration); + + // Record the duration specifically when an update is pending. + if (zero_window_update_timer_.has_value()) { + base::TimeDelta update_duration = zero_window_update_timer_->Elapsed(); + RecordDuration("Session.ZeroWindowDuration.WithUpdate", update_duration); + } + zero_window_timer_.reset(); + zero_window_update_timer_.reset(); +} +#endif + +} // namespace smart_restart
diff --git a/chrome/browser/lifetime/smart_restart_metrics_observer.h b/chrome/browser/lifetime/smart_restart_metrics_observer.h new file mode 100644 index 0000000..1c4f3c3 --- /dev/null +++ b/chrome/browser/lifetime/smart_restart_metrics_observer.h
@@ -0,0 +1,71 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_LIFETIME_SMART_RESTART_METRICS_OBSERVER_H_ +#define CHROME_BROWSER_LIFETIME_SMART_RESTART_METRICS_OBSERVER_H_ + +#include <optional> + +#include "base/callback_list.h" +#include "base/functional/callback.h" +#include "base/memory/raw_ptr.h" +#include "base/timer/elapsed_timer.h" +#include "chrome/browser/ui/browser_list_observer.h" +#include "chrome/browser/upgrade_detector/upgrade_observer.h" + +class UpgradeDetector; + +namespace smart_restart { + +// Observes to record metrics related to smart restart opportunities. +class SmartRestartMetricsObserver : public BrowserListObserver, + public UpgradeObserver { + public: + explicit SmartRestartMetricsObserver(UpgradeDetector* upgrade_detector); + + // A type of callback that is run to check if the browser is in a zero window + // state. + using IsZeroBrowserCallback = base::RepeatingCallback<bool()>; + + SmartRestartMetricsObserver(UpgradeDetector* upgrade_detector, + IsZeroBrowserCallback is_zero_callback); + SmartRestartMetricsObserver(const SmartRestartMetricsObserver&) = delete; + SmartRestartMetricsObserver& operator=(const SmartRestartMetricsObserver&) = + delete; + ~SmartRestartMetricsObserver() override; + +#if BUILDFLAG(IS_MAC) + // BrowserListObserver: + void OnBrowserAdded(Browser* browser) override; + void OnBrowserRemoved(Browser* browser) override; +#endif + + // For tests to simulate a change in the lock state. + void SetLockedStateForTesting(bool is_locked); + + // UpgradeObserver: + void OnUpgradeRecommended() override; + + private: +#if BUILDFLAG(IS_MAC) + void RecordZeroWindowMetrics(); +#endif + void RecordLockedDurationMetrics(); + void OnLockStateChanged(bool is_locked); + + const raw_ptr<UpgradeDetector> upgrade_detector_; + IsZeroBrowserCallback is_zero_callback_; +#if BUILDFLAG(IS_MAC) + std::optional<base::ElapsedTimer> zero_window_timer_; + std::optional<base::ElapsedTimer> zero_window_update_timer_; +#endif + base::CallbackListSubscription lock_state_subscription_; + std::optional<base::ElapsedTimer> locked_timer_; + std::optional<base::ElapsedTimer> locked_update_timer_; + bool was_locked_ = false; +}; + +} // namespace smart_restart + +#endif // CHROME_BROWSER_LIFETIME_SMART_RESTART_METRICS_OBSERVER_H_
diff --git a/chrome/browser/lifetime/smart_restart_metrics_observer_browsertest.cc b/chrome/browser/lifetime/smart_restart_metrics_observer_browsertest.cc new file mode 100644 index 0000000..cd731fb --- /dev/null +++ b/chrome/browser/lifetime/smart_restart_metrics_observer_browsertest.cc
@@ -0,0 +1,126 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/lifetime/smart_restart_metrics_observer.h" + +#include "base/test/metrics/histogram_tester.h" +#include "base/time/clock.h" +#include "base/time/default_clock.h" +#include "base/time/default_tick_clock.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_window/public/browser_window_interface.h" +#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h" +#include "chrome/browser/upgrade_detector/upgrade_detector.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "content/public/test/browser_test.h" +#include "content/public/test/browser_test_utils.h" + +namespace smart_restart { +namespace { + +// A fake UpgradeDetector to control update availability in tests without +// triggering actual restarts. +class FakeUpgradeDetector : public UpgradeDetector { + public: + FakeUpgradeDetector() + : UpgradeDetector(base::DefaultClock::GetInstance(), + base::DefaultTickClock::GetInstance()) { + set_upgrade_detected_time(this->clock()->Now()); + } + + // UpgradeDetector overrides: + base::Time GetAnnoyanceLevelDeadline( + UpgradeNotificationAnnoyanceLevel level) override { + return base::Time(); + } + + void SetUpgradeAvailableToRegular() { + set_upgrade_available(UPGRADE_AVAILABLE_REGULAR); + NotifyUpgrade(); + } +}; + +} // namespace + +class SmartRestartMetricsObserverBrowserTest + : public InProcessBrowserTest, + public testing::WithParamInterface<bool> { + protected: + void SetUpOnMainThread() override { + InProcessBrowserTest::SetUpOnMainThread(); + + // Create an observer for testing, injecting our fake detector. + observer_ = + std::make_unique<SmartRestartMetricsObserver>(&fake_upgrade_detector_); + } + + void TearDownOnMainThread() override { + observer_.reset(); + InProcessBrowserTest::TearDownOnMainThread(); + } + + bool TestUpdateAvailable() const { return GetParam(); } + + FakeUpgradeDetector fake_upgrade_detector_; + std::unique_ptr<SmartRestartMetricsObserver> observer_; +}; + +INSTANTIATE_TEST_SUITE_P(All, + SmartRestartMetricsObserverBrowserTest, + testing::Bool()); + +#if BUILDFLAG(IS_MAC) +IN_PROC_BROWSER_TEST_P(SmartRestartMetricsObserverBrowserTest, + ZeroWindowDuration) { + base::HistogramTester histogram_tester; + + histogram_tester.ExpectTotalCount("Session.ZeroWindowDuration", 0); + + // Simulate an upgrade available. + if (TestUpdateAvailable()) { + fake_upgrade_detector_.SetUpgradeAvailableToRegular(); + } + + // Close the default browser window the test starts with. + CloseBrowserSynchronously(browser()); + + // Open a new window. This ends the Zero Window state. + CreateBrowser(ProfileManager::GetLastUsedProfile()); + + // Verify the metric ran. + // Note: We expect two samples here because both the real production observer + // and the test observer are listening to the BrowserList. + histogram_tester.ExpectTotalCount("Session.ZeroWindowDuration", 2); + histogram_tester.ExpectTotalCount("Session.ZeroWindowDuration.WithUpdate", + TestUpdateAvailable() ? 1 : 0); +} +#endif + +IN_PROC_BROWSER_TEST_P(SmartRestartMetricsObserverBrowserTest, + RecordsLockedDuration) { + base::HistogramTester histogram_tester; + + histogram_tester.ExpectTotalCount("Session.LockedDuration", 0); + + // Simulate an upgrade. + if (TestUpdateAvailable()) { + fake_upgrade_detector_.SetUpgradeAvailableToRegular(); + } + + // Simulate Lock + observer_->SetLockedStateForTesting(true); + + // Simulate Unlock + observer_->SetLockedStateForTesting(false); + + // Verify histogram is recorded. + histogram_tester.ExpectTotalCount("Session.LockedDuration", 1); + histogram_tester.ExpectTotalCount("Session.LockedDuration.WithUpdate", + TestUpdateAvailable() ? 1 : 0); +} + +} // namespace smart_restart
diff --git a/chrome/browser/lifetime/smart_restart_metrics_observer_unittest.cc b/chrome/browser/lifetime/smart_restart_metrics_observer_unittest.cc new file mode 100644 index 0000000..dd01e8a --- /dev/null +++ b/chrome/browser/lifetime/smart_restart_metrics_observer_unittest.cc
@@ -0,0 +1,235 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/lifetime/smart_restart_metrics_observer.h" + +#include <memory> + +#include "base/functional/bind.h" +#include "base/test/metrics/histogram_tester.h" +#include "base/test/task_environment.h" +#include "base/time/clock.h" +#include "base/time/tick_clock.h" +#include "build/build_config.h" +#include "chrome/browser/upgrade_detector/upgrade_detector.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace smart_restart { +namespace { + +// A fake UpgradeDetector to control update availability in tests. +class FakeUpgradeDetector : public UpgradeDetector { + public: + explicit FakeUpgradeDetector(const base::Clock* clock, + const base::TickClock* tick_clock) + : UpgradeDetector(clock, tick_clock) { + set_upgrade_detected_time(this->clock()->Now()); + } + + FakeUpgradeDetector(const FakeUpgradeDetector&) = delete; + FakeUpgradeDetector& operator=(const FakeUpgradeDetector&) = delete; + + // UpgradeDetector overrides: + base::Time GetAnnoyanceLevelDeadline( + UpgradeNotificationAnnoyanceLevel level) override { + return base::Time(); + } + + void SetUpgradeAvailableToRegular() { + set_upgrade_available(UPGRADE_AVAILABLE_REGULAR); + } +}; + +} // namespace + +class SmartRestartMetricsObserverTest : public testing::Test { + public: + SmartRestartMetricsObserverTest() + : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME), + upgrade_detector_(task_environment_.GetMockClock(), + task_environment_.GetMockTickClock()) {} + + void SetUp() override { + observer_ = std::make_unique<SmartRestartMetricsObserver>( + &upgrade_detector_, + base::BindRepeating( + &SmartRestartMetricsObserverTest::IsZeroBrowserCallback, + base::Unretained(this))); + } + + void TearDown() override { observer_.reset(); } + + FakeUpgradeDetector* upgrade_detector() { return &upgrade_detector_; } + + bool IsZeroBrowserCallback() { return browser_count_ == 0; } + + void set_browser_count(size_t count) { browser_count_ = count; } + + protected: + base::test::TaskEnvironment task_environment_; + FakeUpgradeDetector upgrade_detector_; + std::unique_ptr<SmartRestartMetricsObserver> observer_; + size_t browser_count_ = 1; +}; + +#if BUILDFLAG(IS_MAC) +TEST_F(SmartRestartMetricsObserverTest, RecordZeroWindowDuration) { + base::HistogramTester histogram_tester; + + // 1. Initially we have a browser. + set_browser_count(1); + + // 2. Simulate entering "Zero Window" state. + set_browser_count(0); + observer_->OnBrowserRemoved(nullptr); + + // 3. Advance time to simulate a 1-minute background duration. + task_environment_.FastForwardBy(base::Minutes(1)); + + // 4. End the "Zero Window" state. + set_browser_count(1); + observer_->OnBrowserAdded(nullptr); + + // 5. Verify that the duration was recorded correctly. + histogram_tester.ExpectUniqueTimeSample("Session.ZeroWindowDuration", + base::Minutes(1), 1); + histogram_tester.ExpectTotalCount("Session.ZeroWindowDuration.WithUpdate", 0); +} + +TEST_F(SmartRestartMetricsObserverTest, RecordZeroWindowDurationWithUpdate) { + base::HistogramTester histogram_tester; + + // 1. Simulate a pending update. + upgrade_detector()->SetUpgradeAvailableToRegular(); + + // 2. Enter Zero Window state. + set_browser_count(0); + observer_->OnBrowserRemoved(nullptr); + + // 3. Advance time. + task_environment_.FastForwardBy(base::Minutes(5)); + + // 4. Exit Zero Window state. + set_browser_count(1); + observer_->OnBrowserAdded(nullptr); + + // 5. Verify both metrics. + histogram_tester.ExpectUniqueTimeSample("Session.ZeroWindowDuration", + base::Minutes(5), 1); + histogram_tester.ExpectUniqueTimeSample( + "Session.ZeroWindowDuration.WithUpdate", base::Minutes(5), 1); +} + +TEST_F(SmartRestartMetricsObserverTest, NoRecordIfNotEmpty) { + base::HistogramTester histogram_tester; + + // 1. Have 2 browsers. + set_browser_count(2); + + // 2. Remove one. Count is 1, not 0. + set_browser_count(1); + observer_->OnBrowserRemoved(nullptr); + + task_environment_.FastForwardBy(base::Minutes(1)); + + // 3. Remove the final browser. + set_browser_count(0); + observer_->OnBrowserRemoved(nullptr); + + // 4. Advance time for 2 minutes. + task_environment_.FastForwardBy(base::Minutes(2)); + + // 5. Re-add browser. + set_browser_count(1); + observer_->OnBrowserAdded(nullptr); + + // 6. Only the 2-minute duration should be recorded. + histogram_tester.ExpectUniqueTimeSample("Session.ZeroWindowDuration", + base::Minutes(2), 1); +} + +TEST_F(SmartRestartMetricsObserverTest, RecordOnDestruction) { + base::HistogramTester histogram_tester; + + // 1. Enter Zero Window state. + set_browser_count(0); + observer_->OnBrowserRemoved(nullptr); + + // 2. Advance time. + task_environment_.FastForwardBy(base::Minutes(10)); + + // 3. Destroy observer (simulates app quit). + observer_.reset(); + + // 4. Verify metric was recorded. + histogram_tester.ExpectUniqueTimeSample("Session.ZeroWindowDuration", + base::Minutes(10), 1); +} +#endif // BUILDFLAG(IS_MAC) + +TEST_F(SmartRestartMetricsObserverTest, RecordLockedDuration) { + base::HistogramTester histogram_tester; + + // 1. Transition to LOCKED. + observer_->SetLockedStateForTesting(true); + + // 2. Advance time for 10 minutes. + task_environment_.FastForwardBy(base::Minutes(10)); + + // 3. Transition to UNLOCKED. + observer_->SetLockedStateForTesting(false); + + // 4. Verify metric. + histogram_tester.ExpectUniqueTimeSample("Session.LockedDuration", + base::Minutes(10), 1); + histogram_tester.ExpectTotalCount("Session.LockedDuration.WithUpdate", 0); +} + +TEST_F(SmartRestartMetricsObserverTest, RecordLockedDurationWithUpdate) { + base::HistogramTester histogram_tester; + + // 1. Set update available. + upgrade_detector()->SetUpgradeAvailableToRegular(); + + // 2. Transition to LOCKED. + observer_->SetLockedStateForTesting(true); + + // 3. Advance time. + task_environment_.FastForwardBy(base::Minutes(30)); + + // 4. Transition to UNLOCKED. + observer_->SetLockedStateForTesting(false); + + // 5. Verify both metrics. + histogram_tester.ExpectUniqueTimeSample("Session.LockedDuration", + base::Minutes(30), 1); + histogram_tester.ExpectUniqueTimeSample("Session.LockedDuration.WithUpdate", + base::Minutes(30), 1); +} + +TEST_F(SmartRestartMetricsObserverTest, NoRecordIfNeverLocked) { + base::HistogramTester histogram_tester; + + task_environment_.FastForwardBy(base::Minutes(5)); + histogram_tester.ExpectTotalCount("Session.LockedDuration", 0); +} + +TEST_F(SmartRestartMetricsObserverTest, RecordLockOnDestruction) { + base::HistogramTester histogram_tester; + + // 1. Transition to LOCKED. + observer_->SetLockedStateForTesting(true); + + // 2. Advance time. + task_environment_.FastForwardBy(base::Minutes(60)); + + // 3. Destroy observer (simulates app quit while locked). + observer_.reset(); + + // 4. Verify metric was recorded. + histogram_tester.ExpectUniqueTimeSample("Session.LockedDuration", + base::Minutes(60), 1); +} + +} // namespace smart_restart
diff --git a/chrome/browser/long_screenshots/long_screenshots_tab_service.cc b/chrome/browser/long_screenshots/long_screenshots_tab_service.cc index 17de64f..1c930c8 100644 --- a/chrome/browser/long_screenshots/long_screenshots_tab_service.cc +++ b/chrome/browser/long_screenshots/long_screenshots_tab_service.cc
@@ -12,6 +12,7 @@ #include "base/android/jni_string.h" #include "base/functional/bind.h" #include "base/functional/callback.h" +#include "base/memory_coordinator/utils.h" #include "components/google/core/common/google_util.h" #include "components/paint_preview/browser/file_manager.h" #include "components/paint_preview/common/mojom/paint_preview_types.mojom.h" @@ -100,7 +101,7 @@ paint_preview::mojom::ClipCoordOverride clip_x_coord_override, paint_preview::mojom::ClipCoordOverride clip_y_coord_override) { // If the system is under memory pressure don't try to capture. - if (memory_pressure_level() >= base::MEMORY_PRESSURE_LEVEL_MODERATE) { + if (GetMemoryLimit() <= base::kModerateMemoryPressureThreshold) { JNIEnv* env = base::android::AttachCurrentThread(); Java_LongScreenshotsTabService_processCaptureTabStatus( env, java_ref_, Status::kLowMemoryDetected);
diff --git a/chrome/browser/media_galleries/fileapi/mtp_file_stream_reader.cc b/chrome/browser/media_galleries/fileapi/mtp_file_stream_reader.cc index ac9264d..fb63fd77c 100644 --- a/chrome/browser/media_galleries/fileapi/mtp_file_stream_reader.cc +++ b/chrome/browser/media_galleries/fileapi/mtp_file_stream_reader.cc
@@ -79,8 +79,7 @@ return net::ERR_IO_PENDING; } -int64_t MTPFileStreamReader::GetLength( - net::Int64CompletionOnceCallback callback) { +int64_t MTPFileStreamReader::GetLength(GetLengthCallback callback) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); MTPDeviceAsyncDelegate* delegate = @@ -147,7 +146,8 @@ DCHECK_CURRENTLY_ON(content::BrowserThread::IO); if (!VerifySnapshotTime(expected_modification_time_, file_info)) { - std::move(get_length_callback_).Run(net::ERR_UPLOAD_FILE_CHANGED); + std::move(get_length_callback_) + .Run(base::unexpected(net::ERR_UPLOAD_FILE_CHANGED)); return; } @@ -161,7 +161,8 @@ void MTPFileStreamReader::CallGetLengthCallbackWithPlatformFileError( base::File::Error file_error) { - std::move(get_length_callback_).Run(net::FileErrorToNetError(file_error)); + std::move(get_length_callback_) + .Run(base::unexpected(net::FileErrorToNetError(file_error))); } void MTPFileStreamReader::ReadBytes(
diff --git a/chrome/browser/media_galleries/fileapi/mtp_file_stream_reader.h b/chrome/browser/media_galleries/fileapi/mtp_file_stream_reader.h index 27da13ed..518536a 100644 --- a/chrome/browser/media_galleries/fileapi/mtp_file_stream_reader.h +++ b/chrome/browser/media_galleries/fileapi/mtp_file_stream_reader.h
@@ -38,7 +38,7 @@ int Read(net::IOBuffer* buf, int buf_len, net::CompletionOnceCallback callback) override; - int64_t GetLength(net::Int64CompletionOnceCallback callback) override; + int64_t GetLength(GetLengthCallback callback) override; private: void FinishValidateMediaHeader( @@ -67,7 +67,7 @@ int64_t current_offset_; const base::Time expected_modification_time_; net::CompletionOnceCallback read_callback_; - net::Int64CompletionOnceCallback get_length_callback_; + GetLengthCallback get_length_callback_; bool media_header_validated_;
diff --git a/chrome/browser/media_galleries/fileapi/readahead_file_stream_reader.cc b/chrome/browser/media_galleries/fileapi/readahead_file_stream_reader.cc index 2d207a0..b66ebf2 100644 --- a/chrome/browser/media_galleries/fileapi/readahead_file_stream_reader.cc +++ b/chrome/browser/media_galleries/fileapi/readahead_file_stream_reader.cc
@@ -53,8 +53,7 @@ return result; } -int64_t ReadaheadFileStreamReader::GetLength( - net::Int64CompletionOnceCallback callback) { +int64_t ReadaheadFileStreamReader::GetLength(GetLengthCallback callback) { return source_->GetLength(std::move(callback)); }
diff --git a/chrome/browser/media_galleries/fileapi/readahead_file_stream_reader.h b/chrome/browser/media_galleries/fileapi/readahead_file_stream_reader.h index e1c3481..421a4eab 100644 --- a/chrome/browser/media_galleries/fileapi/readahead_file_stream_reader.h +++ b/chrome/browser/media_galleries/fileapi/readahead_file_stream_reader.h
@@ -29,7 +29,7 @@ int Read(net::IOBuffer* buf, int buf_len, net::CompletionOnceCallback callback) override; - int64_t GetLength(net::Int64CompletionOnceCallback callback) override; + int64_t GetLength(GetLengthCallback callback) override; private: // Returns the number of bytes consumed from the internal cache into |sink|.
diff --git a/chrome/browser/metrics/system_memory_list_metrics_provider_win.cc b/chrome/browser/metrics/system_memory_list_metrics_provider_win.cc index 98f14f25..9c55d89 100644 --- a/chrome/browser/metrics/system_memory_list_metrics_provider_win.cc +++ b/chrome/browser/metrics/system_memory_list_metrics_provider_win.cc
@@ -94,9 +94,24 @@ // 30s, and then we reset these. bool last_zero_interval_was_exhausted = false; bool last_free_interval_was_exhausted = false; + bool last_standby_interval_was_exhausted = false; int zero_list_exhausted_interval_count = 0; int free_list_exhausted_interval_count = 0; - int both_lists_exhausted_interval_count = 0; + int standby_list_exhausted_interval_count = 0; + int all_lists_exhausted_interval_count = 0; + + // An interval qualifies as "low" if the size of the list is less than 2MiB, + // which is the size of one NT 'large page', the unit at which the memory + // manager works. + static constexpr int kLowMemoryPageThreshold = 512; + bool last_zero_interval_was_low = false; + bool last_free_interval_was_low = false; + bool last_standby_interval_was_low = false; + int zero_list_low_interval_count = 0; + int free_list_low_interval_count = 0; + int standby_list_low_interval_count = 0; + int all_lists_low_interval_count = 0; + int total_intervals_recorded = 0; base::TimeTicks last_pressured_interval_emission_time = base::TimeTicks::Now(); @@ -104,6 +119,9 @@ base::SampleMetadata zero_page_sample_metadata{ "WindowsZeroPageCount", base::SampleMetadataScope::kProcess}; + base::SampleMetadata available_page_sample_metadata{ + "WindowsAvailablePageCount", base::SampleMetadataScope::kProcess}; + while (!exit_signal_.TimedWait(sampling_interval_)) { SYSTEM_MEMORY_LIST_INFORMATION memory_list_information; @@ -111,30 +129,79 @@ outer_->GetSystemMemoryListInformation(memory_list_information); if (NT_SUCCESS(status)) { ++total_intervals_recorded; - if (memory_list_information.ZeroPageCount == 0 && - memory_list_information.FreePageCount == 0 && - last_free_interval_was_exhausted && - last_zero_interval_was_exhausted) { - ++both_lists_exhausted_interval_count; - } - if (memory_list_information.ZeroPageCount == 0) { - if (last_zero_interval_was_exhausted) { - ++zero_list_exhausted_interval_count; - } - last_zero_interval_was_exhausted = true; - } else { - last_zero_interval_was_exhausted = false; - } - if (memory_list_information.FreePageCount == 0) { - if (last_free_interval_was_exhausted) { - ++free_list_exhausted_interval_count; - } - last_free_interval_was_exhausted = true; - } else { - last_free_interval_was_exhausted = false; + + // Since the total number of standby pages isn't accessible in the data + // structure, sum the size of each list here. + uintptr_t total_standby_pages = 0; + for (uintptr_t standby_page_count : + memory_list_information.PageCountByPriority) { + total_standby_pages += standby_page_count; } + // Record the number of zero pages in SSM metadata. zero_page_sample_metadata.Set(memory_list_information.ZeroPageCount); + // Record the number of pages accessible in all "available" pages + // (according to the Windows Research Kernel) in SSM metadata. + available_page_sample_metadata.Set(memory_list_information.ZeroPageCount + + memory_list_information.FreePageCount + + total_standby_pages); + + // Exhausted Intervals: + // All lists: + if (last_free_interval_was_exhausted && + last_zero_interval_was_exhausted && + last_standby_interval_was_exhausted && + memory_list_information.ZeroPageCount == 0 && + memory_list_information.FreePageCount == 0 && + total_standby_pages == 0) { + ++all_lists_exhausted_interval_count; + } + // Zero: + if (memory_list_information.ZeroPageCount != 0) { + last_zero_interval_was_exhausted = false; + } else if (std::exchange(last_zero_interval_was_exhausted, true)) { + ++zero_list_exhausted_interval_count; + } + // Free: + if (memory_list_information.FreePageCount != 0) { + last_free_interval_was_exhausted = false; + } else if (std::exchange(last_free_interval_was_exhausted, true)) { + ++free_list_exhausted_interval_count; + } + // Standby: + if (total_standby_pages != 0) { + last_standby_interval_was_exhausted = false; + } else if (std::exchange(last_standby_interval_was_exhausted, true)) { + ++standby_list_exhausted_interval_count; + } + + // Low Intervals: + // All lists: + if (last_free_interval_was_low && last_zero_interval_was_low && + last_standby_interval_was_low && + memory_list_information.ZeroPageCount + + memory_list_information.FreePageCount + total_standby_pages <= + kLowMemoryPageThreshold) { + ++all_lists_low_interval_count; + } + // Zero: + if (memory_list_information.ZeroPageCount != 0) { + last_zero_interval_was_low = false; + } else if (std::exchange(last_zero_interval_was_low, true)) { + ++zero_list_low_interval_count; + } + // Free: + if (memory_list_information.FreePageCount != 0) { + last_free_interval_was_low = false; + } else if (std::exchange(last_free_interval_was_low, true)) { + ++free_list_low_interval_count; + } + // Standby: + if (total_standby_pages != 0) { + last_standby_interval_was_low = false; + } else if (std::exchange(last_standby_interval_was_low, true)) { + ++standby_list_low_interval_count; + } const base::TimeTicks now = base::TimeTicks::Now(); if (last_pressured_interval_emission_time <= @@ -149,8 +216,28 @@ free_list_exhausted_interval_count); base::UmaHistogramCounts1000( "Memory.SystemMemoryLists.ExhaustedIntervalsPerThirtySeconds." - "ZeroAndFreeList", - both_lists_exhausted_interval_count); + "StandbyList", + standby_list_exhausted_interval_count); + base::UmaHistogramCounts1000( + "Memory.SystemMemoryLists.ExhaustedIntervalsPerThirtySeconds." + "AllLists", + all_lists_exhausted_interval_count); + base::UmaHistogramCounts1000( + "Memory.SystemMemoryLists.LowIntervalsPerThirtySeconds." + "ZeroList", + zero_list_low_interval_count); + base::UmaHistogramCounts1000( + "Memory.SystemMemoryLists.LowIntervalsPerThirtySeconds." + "FreeList", + free_list_low_interval_count); + base::UmaHistogramCounts1000( + "Memory.SystemMemoryLists.LowIntervalsPerThirtySeconds." + "StandbyList", + standby_list_low_interval_count); + base::UmaHistogramCounts1000( + "Memory.SystemMemoryLists.LowIntervalsPerThirtySeconds." + "AllLists", + all_lists_low_interval_count); base::UmaHistogramCounts1000( "Memory.SystemMemoryLists.TotalIntervalsRecorded", total_intervals_recorded); @@ -170,10 +257,8 @@ 1, 500000000, 75); int priority_number = 1; - uintptr_t total_standby_pages = 0; for (uintptr_t standby_page_count : memory_list_information.PageCountByPriority) { - total_standby_pages += standby_page_count; base::UmaHistogramCustomCounts( base::StrCat( {"Memory.SystemMemoryLists.StandbyPageCountByPriority.", @@ -186,7 +271,12 @@ free_list_exhausted_interval_count = 0; zero_list_exhausted_interval_count = 0; - both_lists_exhausted_interval_count = 0; + standby_list_exhausted_interval_count = 0; + all_lists_exhausted_interval_count = 0; + free_list_low_interval_count = 0; + zero_list_low_interval_count = 0; + standby_list_low_interval_count = 0; + all_lists_low_interval_count = 0; total_intervals_recorded = 0; last_pressured_interval_emission_time = now; } @@ -204,4 +294,5 @@ } } zero_page_sample_metadata.Remove(); + available_page_sample_metadata.Remove(); }
diff --git a/chrome/browser/metrics/system_memory_list_metrics_provider_win_unittest.cc b/chrome/browser/metrics/system_memory_list_metrics_provider_win_unittest.cc index ac5e5b4..04f2c6f4 100644 --- a/chrome/browser/metrics/system_memory_list_metrics_provider_win_unittest.cc +++ b/chrome/browser/metrics/system_memory_list_metrics_provider_win_unittest.cc
@@ -23,9 +23,20 @@ "Memory.SystemMemoryLists.ExhaustedIntervalsPerThirtySeconds.ZeroList"; constexpr char kFreeMemoryListHistogramName[] = "Memory.SystemMemoryLists.ExhaustedIntervalsPerThirtySeconds.FreeList"; -constexpr char kZeroAndFreeMemoryListHistogramName[] = +constexpr char kStandbyMemoryListHistogramName[] = + "Memory.SystemMemoryLists.ExhaustedIntervalsPerThirtySeconds.StandbyList"; +constexpr char kAllMemoryListHistogramName[] = "Memory.SystemMemoryLists.ExhaustedIntervalsPerThirtySeconds." - "ZeroAndFreeList"; + "AllLists"; +constexpr char kLowZeroMemoryListHistogramName[] = + "Memory.SystemMemoryLists.LowIntervalsPerThirtySeconds.ZeroList"; +constexpr char kLowFreeMemoryListHistogramName[] = + "Memory.SystemMemoryLists.LowIntervalsPerThirtySeconds.FreeList"; +constexpr char kLowStandbyMemoryListHistogramName[] = + "Memory.SystemMemoryLists.LowIntervalsPerThirtySeconds.StandbyList"; +constexpr char kLowAllMemoryListHistogramName[] = + "Memory.SystemMemoryLists.LowIntervalsPerThirtySeconds." + "AllLists"; constexpr char kTotalIntervalsRecordedHistogramName[] = "Memory.SystemMemoryLists.TotalIntervalsRecorded"; constexpr char kFreeListCountHistogramName[] = @@ -112,9 +123,23 @@ histogram_tester_.ExpectTotalCount(kFreeMemoryListHistogramName, 2); histogram_tester_.ExpectBucketCount(kFreeMemoryListHistogramName, 1, 1); - histogram_tester_.ExpectTotalCount(kZeroAndFreeMemoryListHistogramName, 2); - histogram_tester_.ExpectBucketCount(kZeroAndFreeMemoryListHistogramName, 1, - 1); + histogram_tester_.ExpectTotalCount(kStandbyMemoryListHistogramName, 2); + histogram_tester_.ExpectBucketCount(kStandbyMemoryListHistogramName, 1, 1); + + histogram_tester_.ExpectTotalCount(kAllMemoryListHistogramName, 2); + histogram_tester_.ExpectBucketCount(kAllMemoryListHistogramName, 1, 1); + + histogram_tester_.ExpectTotalCount(kLowZeroMemoryListHistogramName, 2); + histogram_tester_.ExpectBucketCount(kLowZeroMemoryListHistogramName, 1, 1); + + histogram_tester_.ExpectTotalCount(kLowFreeMemoryListHistogramName, 2); + histogram_tester_.ExpectBucketCount(kLowFreeMemoryListHistogramName, 1, 1); + + histogram_tester_.ExpectTotalCount(kLowStandbyMemoryListHistogramName, 2); + histogram_tester_.ExpectBucketCount(kLowStandbyMemoryListHistogramName, 1, 1); + + histogram_tester_.ExpectTotalCount(kLowAllMemoryListHistogramName, 2); + histogram_tester_.ExpectBucketCount(kLowAllMemoryListHistogramName, 1, 1); histogram_tester_.ExpectTotalCount(kTotalIntervalsRecordedHistogramName, 2); histogram_tester_.ExpectBucketCount(kTotalIntervalsRecordedHistogramName, 1, @@ -163,9 +188,23 @@ histogram_tester_.ExpectTotalCount(kFreeMemoryListHistogramName, 1); histogram_tester_.ExpectBucketCount(kFreeMemoryListHistogramName, 0, 1); - histogram_tester_.ExpectTotalCount(kZeroAndFreeMemoryListHistogramName, 1); - histogram_tester_.ExpectBucketCount(kZeroAndFreeMemoryListHistogramName, 0, - 1); + histogram_tester_.ExpectTotalCount(kStandbyMemoryListHistogramName, 1); + histogram_tester_.ExpectBucketCount(kStandbyMemoryListHistogramName, 0, 1); + + histogram_tester_.ExpectTotalCount(kAllMemoryListHistogramName, 1); + histogram_tester_.ExpectBucketCount(kAllMemoryListHistogramName, 0, 1); + + histogram_tester_.ExpectTotalCount(kLowZeroMemoryListHistogramName, 1); + histogram_tester_.ExpectBucketCount(kLowZeroMemoryListHistogramName, 0, 1); + + histogram_tester_.ExpectTotalCount(kLowFreeMemoryListHistogramName, 1); + histogram_tester_.ExpectBucketCount(kLowFreeMemoryListHistogramName, 0, 1); + + histogram_tester_.ExpectTotalCount(kLowStandbyMemoryListHistogramName, 1); + histogram_tester_.ExpectBucketCount(kLowStandbyMemoryListHistogramName, 0, 1); + + histogram_tester_.ExpectTotalCount(kLowAllMemoryListHistogramName, 1); + histogram_tester_.ExpectBucketCount(kLowAllMemoryListHistogramName, 0, 1); histogram_tester_.ExpectTotalCount(kTotalIntervalsRecordedHistogramName, 1); histogram_tester_.ExpectBucketCount(kTotalIntervalsRecordedHistogramName, 1, @@ -211,8 +250,12 @@ histogram_tester_.ExpectBucketCount(kZeroMemoryListHistogramName, 1, 1); histogram_tester_.ExpectBucketCount(kFreeMemoryListHistogramName, 1, 1); - histogram_tester_.ExpectBucketCount(kZeroAndFreeMemoryListHistogramName, 1, - 1); + histogram_tester_.ExpectBucketCount(kStandbyMemoryListHistogramName, 1, 1); + histogram_tester_.ExpectBucketCount(kAllMemoryListHistogramName, 1, 1); + histogram_tester_.ExpectBucketCount(kLowZeroMemoryListHistogramName, 1, 1); + histogram_tester_.ExpectBucketCount(kLowFreeMemoryListHistogramName, 1, 1); + histogram_tester_.ExpectBucketCount(kLowStandbyMemoryListHistogramName, 1, 1); + histogram_tester_.ExpectBucketCount(kLowAllMemoryListHistogramName, 1, 1); histogram_tester_.ExpectUniqueSample(kTotalIntervalsRecordedHistogramName, 1, 2); histogram_tester_.ExpectUniqueSample(kFreeListCountHistogramName, 0, 2); @@ -244,7 +287,7 @@ .WillOnce(success_closure) .WillOnce( [&](SYSTEM_MEMORY_LIST_INFORMATION& memory_list_info) -> NTSTATUS { - EXPECT_EQ(initial_item_count + 1, + EXPECT_EQ(initial_item_count + 2, base::MetadataRecorder::MetadataProvider( base::GetSampleMetadataRecorder(), base::PlatformThread::CurrentId())
diff --git a/chrome/browser/page_content_annotations/multi_source_page_context_fetcher_browsertest.cc b/chrome/browser/page_content_annotations/multi_source_page_context_fetcher_browsertest.cc index 7b939a4..5dc3b27 100644 --- a/chrome/browser/page_content_annotations/multi_source_page_context_fetcher_browsertest.cc +++ b/chrome/browser/page_content_annotations/multi_source_page_context_fetcher_browsertest.cc
@@ -171,11 +171,14 @@ if (use_paint_preview_backend()) { options.screenshot_options = capture_full_page_screenshot() - ? ScreenshotOptions::FullPage(PaintPreviewOptions()) - : ScreenshotOptions::ViewportOnly(PaintPreviewOptions()); + ? ScreenshotOptions::FullPage(PaintPreviewOptions(), std::nullopt) + : ScreenshotOptions::ViewportOnly( + PaintPreviewOptions(), + /*screenshot_collection_options=*/std::nullopt); } else { - options.screenshot_options = - ScreenshotOptions::ViewportOnly(/*paint_preview_options=*/std::nullopt); + options.screenshot_options = ScreenshotOptions::ViewportOnly( + /*paint_preview_options=*/std::nullopt, + /*screenshot_collection_options=*/std::nullopt); } FetchPageContext(*web_contents(), options, nullptr, future.GetCallback()); @@ -241,8 +244,9 @@ base::test::TestFuture<FetchPageContextResultCallbackArg> future; FetchPageContextOptions options; - options.screenshot_options = - ScreenshotOptions::ViewportOnly(/*paint_preview_options=*/std::nullopt); + options.screenshot_options = ScreenshotOptions::ViewportOnly( + /*paint_preview_options=*/std::nullopt, + /*screenshot_collection_options=*/std::nullopt); FetchPageContext(*web_contents(), options, nullptr, future.GetCallback()); ASSERT_OK_AND_ASSIGN(std::unique_ptr<FetchPageContextResult> result, @@ -278,8 +282,8 @@ PaintPreviewOptions paint_preview_options; paint_preview_options.iframe_redaction_scope = page_content_annotations::ScreenshotIframeRedactionScope::kCrossSite; - ScreenshotOptions options = - ScreenshotOptions::ViewportOnly(paint_preview_options); + ScreenshotOptions options = ScreenshotOptions::ViewportOnly( + paint_preview_options, /*screenshot_collection_options=*/std::nullopt); return options; } @@ -440,8 +444,9 @@ FetchPageContextOptions options; - options.screenshot_options = - ScreenshotOptions::ViewportOnly(/*paint_preview_options=*/std::nullopt); + options.screenshot_options = ScreenshotOptions::ViewportOnly( + /*paint_preview_options=*/std::nullopt, + /*screenshot_collection_options=*/std::nullopt); FetchPageContext(*web_contents(), options, nullptr, future.GetCallback()); ASSERT_OK_AND_ASSIGN(std::unique_ptr<FetchPageContextResult> result, @@ -495,8 +500,9 @@ FetchPageContextOptions options; - options.screenshot_options = - ScreenshotOptions::ViewportOnly(/*paint_preview_options=*/std::nullopt); + options.screenshot_options = ScreenshotOptions::ViewportOnly( + /*paint_preview_options=*/std::nullopt, + /*screenshot_collection_options=*/std::nullopt); FetchPageContext(*web_contents(), options, nullptr, future.GetCallback()); ASSERT_OK_AND_ASSIGN(std::unique_ptr<FetchPageContextResult> result, @@ -555,7 +561,8 @@ options.annotated_page_content_options = optimization_guide::DefaultAIPageContentOptions(true); options.screenshot_options = ScreenshotOptions::ViewportOnly( - /*paint_preview_options=*/std::nullopt); + /*paint_preview_options=*/std::nullopt, + /*screenshot_collection_options=*/std::nullopt); options.screenshot_options->set_redaction_color_for_testing(SkColors::kRed); FetchPageContext(*web_contents(), options, nullptr, future.GetCallback()); @@ -609,7 +616,8 @@ options.annotated_page_content_options = optimization_guide::DefaultAIPageContentOptions(true); options.screenshot_options = ScreenshotOptions::ViewportOnly( - /*paint_preview_options=*/std::nullopt); + /*paint_preview_options=*/std::nullopt, + /*screenshot_collection_options=*/std::nullopt); options.screenshot_options->set_redaction_color_for_testing(SkColors::kRed); FetchPageContext(*web_contents(), options, nullptr, future.GetCallback()); @@ -648,7 +656,8 @@ options.annotated_page_content_options = optimization_guide::DefaultAIPageContentOptions(true); options.screenshot_options = ScreenshotOptions::ViewportOnly( - /*paint_preview_options=*/std::nullopt); + /*paint_preview_options=*/std::nullopt, + /*screenshot_collection_options=*/std::nullopt); options.screenshot_options->set_redaction_color_for_testing(SkColors::kRed); FetchPageContext(*web_contents(), options, nullptr, future.GetCallback()); @@ -725,8 +734,9 @@ FetchPageContextOptions options; options.annotated_page_content_options = optimization_guide::DefaultAIPageContentOptions(true); - options.screenshot_options = - ScreenshotOptions::ViewportOnly(/*paint_preview_options=*/std::nullopt); + options.screenshot_options = ScreenshotOptions::ViewportOnly( + /*paint_preview_options=*/std::nullopt, + /*screenshot_collection_options=*/std::nullopt); options.screenshot_options->set_redaction_color_for_testing(SkColors::kRed); FetchPageContext(*web_contents(), options, nullptr, future.GetCallback()); @@ -786,7 +796,8 @@ options.annotated_page_content_options = optimization_guide::DefaultAIPageContentOptions(true); options.screenshot_options = ScreenshotOptions::ViewportOnly( - /*paint_preview_options=*/std::nullopt); + /*paint_preview_options=*/std::nullopt, + /*screenshot_collection_options=*/std::nullopt); options.screenshot_options->set_redaction_color_for_testing(SkColors::kRed); FetchPageContext(*web_contents(), options, nullptr, future.GetCallback()); @@ -828,8 +839,9 @@ FetchPageContextOptions options; options.annotated_page_content_options = optimization_guide::DefaultAIPageContentOptions(true); - options.screenshot_options = - ScreenshotOptions::ViewportOnly(/*paint_preview_options=*/std::nullopt); + options.screenshot_options = ScreenshotOptions::ViewportOnly( + /*paint_preview_options=*/std::nullopt, + /*screenshot_collection_options=*/std::nullopt); options.screenshot_options->set_redaction_color_for_testing(SkColors::kRed); FetchPageContext(*web_contents(), options, nullptr, future.GetCallback()); @@ -865,4 +877,43 @@ IsColorWithinTolerance(SK_ColorRED, 0x20)); } +IN_PROC_BROWSER_TEST_F(MultiSourcePageContextFetcherBrowserTest, + TakesScreenshot_PngWithDimensions) { + GURL url = embedded_https_test_server().GetURL("/empty.html"); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + + SetBackground(web_contents()->GetPrimaryMainFrame(), "red"); + + base::test::TestFuture<FetchPageContextResultCallbackArg> future; + + FetchPageContextOptions options; + ScreenshotOptions::ScreenshotCollectionOptions collection_options; + collection_options.screenshot_image_format = + ScreenshotOptions::ScreenshotImageFormat::kPng; + collection_options.max_width = 500; + collection_options.max_height = 500; + + options.screenshot_options = ScreenshotOptions::ViewportOnly( + /*paint_preview_options=*/std::nullopt, collection_options); + FetchPageContext(*web_contents(), options, nullptr, future.GetCallback()); + + ASSERT_OK_AND_ASSIGN(std::unique_ptr<FetchPageContextResult> result, + future.Take()); + + ASSERT_TRUE(result); + ASSERT_TRUE(result->screenshot_result.has_value()); + + ScreenshotResult& screenshot = result->screenshot_result.value(); + + EXPECT_FALSE(screenshot.dimensions.IsZero()); + EXPECT_LE(screenshot.dimensions.width(), 500); + EXPECT_LE(screenshot.dimensions.height(), 500); + ASSERT_GT(screenshot.screenshot_data.size(), 0); + ASSERT_EQ(screenshot.mime_type, "image/png"); + + SkBitmap bitmap = gfx::PNGCodec::Decode(screenshot.screenshot_data); + EXPECT_FALSE(bitmap.isNull()); + EXPECT_FALSE(bitmap.empty()); +} + } // namespace page_content_annotations
diff --git a/chrome/browser/paint_preview/services/paint_preview_tab_service.cc b/chrome/browser/paint_preview/services/paint_preview_tab_service.cc index def4282..54fb42b 100644 --- a/chrome/browser/paint_preview/services/paint_preview_tab_service.cc +++ b/chrome/browser/paint_preview/services/paint_preview_tab_service.cc
@@ -8,6 +8,7 @@ #include <utility> #include "base/functional/callback.h" +#include "base/memory_coordinator/utils.h" #include "base/metrics/histogram_functions.h" #include "base/strings/string_number_conversions.h" #include "base/task/sequenced_task_runner.h" @@ -133,7 +134,7 @@ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // If the system is under memory pressure don't try to capture. - if (memory_pressure_level() >= base::MEMORY_PRESSURE_LEVEL_MODERATE) { + if (GetMemoryLimit() <= base::kModerateMemoryPressureThreshold) { return; }
diff --git a/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher.cc b/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher.cc index c0657b4..f81fd94 100644 --- a/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher.cc +++ b/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher.cc
@@ -71,14 +71,13 @@ } std::vector<Credential> result; for (const auto& account : *accounts) { - // TODO(crbug.com/479069886): Go over all displayed fields and check their - // use in credential picker. Credential credential; credential.type = CredentialType::kFederated; - // TODO(crbug.com/479069886): properly format the username - credential.username = - base::UTF8ToUTF16(account->display_name + " " + - account->identity_provider->idp_for_display); + // At this point, the only IdP(s) we support for actor login use email as an + // identifier. We'll fallback to the `display_identifier` in anticipation of + // generalizing to other IdPs. + credential.username = base::UTF8ToUTF16( + !account->email.empty() ? account->email : account->display_identifier); credential.source_site_or_app = base::UTF8ToUTF16(account->identity_provider->idp_for_display); credential.request_origin = request_origin_;
diff --git a/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher_unittest.cc b/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher_unittest.cc index 1e8451a..167a87c 100644 --- a/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher_unittest.cc +++ b/chrome/browser/password_manager/actor_login/internal/actor_login_federated_credentials_fetcher_unittest.cc
@@ -106,7 +106,7 @@ const auto& [credentials, status] = future.Get(); ASSERT_EQ(credentials.size(), 1u); EXPECT_EQ(credentials[0].type, CredentialType::kFederated); - EXPECT_EQ(credentials[0].username, u"Display Name idp"); + EXPECT_EQ(credentials[0].username, u"test@example.com"); EXPECT_EQ(credentials[0].source_site_or_app, u"idp"); EXPECT_EQ(credentials[0].request_origin, request_origin); EXPECT_EQ(credentials[0].display_origin, u"example.com");
diff --git a/chrome/browser/pdf/pdf_extension_test.cc b/chrome/browser/pdf/pdf_extension_test.cc index fe932fb..660ab1d 100644 --- a/chrome/browser/pdf/pdf_extension_test.cc +++ b/chrome/browser/pdf/pdf_extension_test.cc
@@ -122,6 +122,7 @@ #include "ui/base/clipboard/clipboard.h" #include "ui/base/clipboard/clipboard_monitor.h" #include "ui/base/clipboard/clipboard_observer.h" +#include "ui/base/clipboard/test/clipboard_test_util.h" #include "ui/base/clipboard/test/test_clipboard.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" @@ -2487,9 +2488,8 @@ ui::ClipboardMonitor::GetInstance()->RemoveObserver(this); auto* clipboard = ui::Clipboard::GetForCurrentThread(); - std::string clipboard_data; - clipboard->ReadAsciiText(clipboard_buffer, /* data_dst=*/nullptr, - &clipboard_data); + std::string clipboard_data = ui::clipboard_test_util::ReadAsciiText( + clipboard, clipboard_buffer, /* data_dst=*/nullptr); EXPECT_EQ(expected, clipboard_data); }
diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java index d71ced1..aea70fb 100644 --- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java +++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
@@ -473,23 +473,19 @@ /** * When the user is shown a badge that the current Android OS version is unsupported, and they * tap it to display the menu (which has additional information), we store the current version - * of Chrome to this preference to ensure we only show the badge once. The value is cleared - * if the Chrome version later changes. + * of Chrome to this preference to ensure we only show the badge once. The value is cleared if + * the Chrome version later changes. */ public static final String LATEST_UNSUPPORTED_VERSION = "android_os_unsupported_chrome_version"; - /** The previous browser process PID, updated when crash reporting is initialized. */ + /** The previous browser process exit reason, recorded when the process is created. */ + public static final String LAST_SESSION_BROWSER_EXIT_REASON = + "Chrome.CrashReporting.LastSessionBrowserExitReason"; + + /** The previous browser process PID, updated on deferred Startup for any ChromeActivity. */ public static final String LAST_SESSION_BROWSER_PID = "Chrome.CrashReporting.LastSessionBrowserPid"; - /** - * The application state last recorded by browser in previous session, updated when crash - * reporting is initialized and when current application state changes henceforth. If read after - * crash reporting is initialized, then the value would hold current session state. - */ - public static final String LAST_SESSION_APPLICATION_STATE = - "Chrome.CrashReporting.LastSessionApplicationState"; - public static final String LOCALE_MANAGER_AUTO_SWITCH = "LocaleManager_PREF_AUTO_SWITCH"; public static final String LOCALE_MANAGER_MISSING_TIMEZONES = "com.android.chrome.MISSING_TIMEZONES"; @@ -1279,8 +1275,8 @@ IS_LAST_VISITED_TAB_SRP, IS_DSE_GOOGLE, IS_MVT_VISIBLE, + LAST_SESSION_BROWSER_EXIT_REASON, LAST_SESSION_BROWSER_PID, - LAST_SESSION_APPLICATION_STATE, LOCALE_MANAGER_PROMO_V3_CHECKED, MULTI_WINDOW_START_TIME, MULTI_INSTANCE_CLOSE_WINDOW_SKIP_CONFIRM,
diff --git a/chrome/browser/privacy_sandbox/android/BUILD.gn b/chrome/browser/privacy_sandbox/android/BUILD.gn index 530b67ee..8917f2f8 100644 --- a/chrome/browser/privacy_sandbox/android/BUILD.gn +++ b/chrome/browser/privacy_sandbox/android/BUILD.gn
@@ -24,11 +24,7 @@ "java/src/org/chromium/chrome/browser/privacy_sandbox/PreferenceCategoryWithClickableSummary.java", "java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxBridge.java", "java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDebouncedOnClick.java", - "java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogConsentEEA.java", "java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogController.java", - "java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogNoticeEEA.java", - "java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogNoticeEeaV2.java", - "java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogNoticeROW.java", "java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogNoticeRestricted.java", "java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogUtils.java", "java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxReferrer.java", @@ -208,10 +204,8 @@ android_resources("java_resources") { sources = [ - "java/res/drawable-night/privacy_sandbox_interests_illustration.xml", "java/res/drawable-night/privacy_sandbox_notice_eea_illustration.xml", "java/res/drawable/cookie_24dp.xml", - "java/res/drawable/ic_arrow_back.xml", "java/res/drawable/ic_arrow_down.xml", "java/res/drawable/ic_auto_delete_24dp.xml", "java/res/drawable/ic_background_replace_24dp.xml", @@ -219,7 +213,6 @@ "java/res/drawable/ic_devices_24dp.xml", "java/res/drawable/ic_filter_list_24dp.xml", "java/res/drawable/ic_interests_24dp.xml", - "java/res/drawable/privacy_sandbox_interests_illustration.xml", "java/res/drawable/privacy_sandbox_notice_eea_illustration.xml", "java/res/drawable/topic_taxonomy_1_id_1.xml", "java/res/drawable/topic_taxonomy_1_id_100.xml", @@ -245,16 +238,7 @@ "java/res/drawable/topic_taxonomy_1_id_86.xml", "java/res/drawable/topic_taxonomy_placeholder.xml", "java/res/layout/category_with_clickable_summary_preference.xml", - "java/res/layout/privacy_sandbox_consent_eea.xml", - "java/res/layout/privacy_sandbox_consent_eea_dropdown.xml", - "java/res/layout/privacy_sandbox_notice_eea.xml", - "java/res/layout/privacy_sandbox_notice_eea_ad_measurement_dropdown.xml", - "java/res/layout/privacy_sandbox_notice_eea_dropdown.xml", - "java/res/layout/privacy_sandbox_notice_eea_site_suggested_ads_dropdown.xml", - "java/res/layout/privacy_sandbox_notice_eea_v2.xml", "java/res/layout/privacy_sandbox_notice_restricted.xml", - "java/res/layout/privacy_sandbox_notice_row.xml", - "java/res/layout/privacy_sandbox_notice_row_dropdown.xml", "java/res/layout/topics_explanation_summary.xml", "java/res/raw/keep_topic_taxonomy.xml", "java/res/values/dimens.xml",
diff --git a/chrome/browser/privacy_sandbox/android/java/res/drawable-night/privacy_sandbox_interests_illustration.xml b/chrome/browser/privacy_sandbox/android/java/res/drawable-night/privacy_sandbox_interests_illustration.xml deleted file mode 100644 index 96e47ea5..0000000 --- a/chrome/browser/privacy_sandbox/android/java/res/drawable-night/privacy_sandbox_interests_illustration.xml +++ /dev/null
@@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -Copyright 2022 The Chromium Authors -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. ---> -<vector android:height="73.68421dp" android:viewportHeight="140" - android:viewportWidth="380" android:width="200dp" xmlns:android="http://schemas.android.com/apk/res/android"> - <group> - <clip-path android:pathData="M16,0L364,0A16,16 0,0 1,380 16L380,124A16,16 0,0 1,364 140L16,140A16,16 0,0 1,0 124L0,16A16,16 0,0 1,16 0z"/> - <path android:fillColor="#3C4043" android:pathData="M16,0L364,0A16,16 0,0 1,380 16L380,124A16,16 0,0 1,364 140L16,140A16,16 0,0 1,0 124L0,16A16,16 0,0 1,16 0z"/> - <group> - <clip-path android:pathData="M77.68,-63L253.29,-93.97A25.84,25.84 125,0 1,283.22 -73.01L343.71,270.02A25.84,25.84 125,0 1,322.75 299.95L147.14,330.92A25.84,25.84 125,0 1,117.2 309.96L56.72,-33.07A25.84,25.84 125,0 1,77.68 -63z"/> - <path android:fillColor="#202124" android:pathData="M77.68,-63L253.29,-93.97A25.84,25.84 125,0 1,283.22 -73.01L343.71,270.02A25.84,25.84 125,0 1,322.75 299.95L147.14,330.92A25.84,25.84 125,0 1,117.2 309.96L56.72,-33.07A25.84,25.84 125,0 1,77.68 -63z"/> - <path android:fillColor="#174EA6" android:pathData="M95.18,18.07L264.57,-11.79A4,4 49.61,0 1,269.21 -8.55L274.76,22.96A4,4 75.52,0 1,271.52 27.6L102.13,57.47A4,4 49.53,0 1,97.5 54.22L91.94,22.71A4,4 128.17,0 1,95.18 18.07z"/> - <path android:fillColor="#669DF6" android:pathData="M111.49,47.18C110.77,47.31 110.11,47.16 109.5,46.74C108.9,46.32 108.54,45.75 108.41,45.03L104.86,24.9L107.32,26.63L109.02,24.17L111.48,25.89L113.2,23.43L115.63,25.16L117.32,22.7L119.76,24.43L121.45,21.98L123.91,23.7L125.64,21.24L128.07,22.97L129.79,20.51L133.34,40.63C133.46,41.35 133.32,42.01 132.89,42.62C132.47,43.22 131.9,43.58 131.18,43.71L111.49,47.18ZM111.03,44.57L119.61,43.05L118.26,35.36L109.67,36.87L111.03,44.57ZM122.14,42.61L130.72,41.1L130.27,38.51L121.68,40.02L122.14,42.61ZM121.24,37.5L129.82,35.99L129.37,33.4L120.78,34.92L121.24,37.5ZM109.22,34.35L128.92,30.88L128.31,27.4L108.61,30.87L109.22,34.35Z"/> - <path android:fillColor="#669DF6" android:pathData="M241.96,21.55L239.82,20.05L245.52,11.87L237.37,6.14L238.86,4L247.04,9.7L252.77,1.55L254.91,3.05L249.21,11.22L257.36,16.96L255.87,19.09L247.69,13.39L241.96,21.55Z"/> - <path android:fillColor="#0D652D" android:pathData="M102.82,61.4L272.21,31.54A4,4 125.6,0 1,276.85 34.78L282.4,66.3A4,4 128.22,0 1,279.16 70.93L109.77,100.8A4,4 101.52,0 1,105.14 97.55L99.58,66.04A4,4 121.01,0 1,102.82 61.4z"/> - <path android:fillColor="#5BB974" android:pathData="M126.63,87.51L126.72,85.69C126.73,85.41 126.82,85.16 126.97,84.94C127.14,84.72 127.37,84.58 127.65,84.51L131.44,83.52C131.7,83.43 131.96,83.44 132.21,83.54C132.48,83.64 132.68,83.81 132.8,84.04L133.5,85.13C134.22,84.51 134.84,83.82 135.36,83.05C135.9,82.26 136.31,81.4 136.59,80.49L136.19,80.37C135.93,80.24 135.73,80.08 135.59,79.87C135.45,79.64 135.38,79.39 135.4,79.11L135.59,75.17C135.6,74.9 135.69,74.66 135.84,74.46C136.02,74.26 136.23,74.13 136.47,74.07C136.28,73.57 136.06,73.09 135.81,72.63C135.58,72.16 135.3,71.71 134.97,71.29C134.8,71.42 134.6,71.51 134.38,71.55C134.17,71.56 133.97,71.52 133.79,71.43L130.15,70.04C129.9,69.96 129.7,69.8 129.56,69.57C129.43,69.32 129.36,69.06 129.35,68.79L129.45,67.69C128.76,67.51 128.05,67.42 127.33,67.4C126.63,67.37 125.93,67.42 125.23,67.54C124.92,67.6 124.62,67.66 124.31,67.74C124.03,67.81 123.74,67.9 123.44,68.02L124.77,69.94C124.91,70.17 124.98,70.43 124.97,70.73C124.97,71.01 124.88,71.25 124.68,71.45L122.24,74.51C122.04,74.72 121.81,74.87 121.53,74.96C121.26,75.02 121,75 120.75,74.87L117.62,73.68C117.29,74.56 117.07,75.47 116.96,76.4C116.86,77.32 116.9,78.26 117.07,79.2C117.13,79.55 117.27,80.08 117.47,80.78L120.14,80.06C120.42,79.96 120.69,79.97 120.97,80.07C121.24,80.15 121.44,80.31 121.59,80.56L123.67,83.88C123.82,84.1 123.89,84.36 123.9,84.63C123.9,84.88 123.8,85.11 123.61,85.32L122.65,86.53C123.25,86.83 123.88,87.07 124.55,87.24C125.23,87.4 125.92,87.49 126.63,87.51ZM127.93,81.76C127.67,81.85 127.41,81.84 127.16,81.74C126.91,81.63 126.71,81.47 126.57,81.24L124.17,77.7C124.02,77.45 123.96,77.19 124,76.93C124.03,76.65 124.14,76.41 124.32,76.21L127.01,72.94C127.18,72.72 127.4,72.59 127.67,72.54C127.93,72.47 128.19,72.49 128.44,72.6L132.27,74.01C132.52,74.12 132.73,74.29 132.88,74.54C133.04,74.77 133.12,75.02 133.1,75.3L132.86,79.5C132.85,79.77 132.75,80.02 132.58,80.24C132.42,80.44 132.22,80.58 131.96,80.67L127.93,81.76ZM129.19,90.01C127.45,90.32 125.76,90.28 124.13,89.89C122.49,89.5 121,88.84 119.65,87.92C118.32,86.97 117.19,85.79 116.27,84.39C115.35,82.99 114.73,81.41 114.42,79.67C114.11,77.92 114.16,76.24 114.54,74.6C114.93,72.97 115.59,71.49 116.52,70.16C117.47,68.81 118.64,67.67 120.04,66.75C121.45,65.82 123.02,65.21 124.76,64.9C126.49,64.59 128.17,64.64 129.8,65.03C131.46,65.41 132.95,66.08 134.28,67.03C135.63,67.95 136.76,69.12 137.69,70.52C138.61,71.92 139.23,73.5 139.54,75.24C139.84,76.98 139.8,78.67 139.41,80.31C139.02,81.94 138.36,83.43 137.41,84.79C136.48,86.11 135.3,87.24 133.88,88.17C132.48,89.09 130.92,89.71 129.19,90.01Z"/> - <path android:fillColor="#5BB974" android:pathData="M249.6,64.88L247.46,63.38L253.16,55.21L245.01,49.47L246.5,47.34L254.68,53.03L260.41,44.88L262.55,46.38L256.85,54.55L265,60.29L263.51,62.42L255.33,56.73L249.6,64.88Z"/> - <path android:fillColor="#B06000" android:pathData="M110.49,104.85L279.87,74.98A4,4 121.26,0 1,284.51 78.23L290.06,109.74A4,4 126.81,0 1,286.82 114.37L117.43,144.24A4,4 134.11,0 1,112.8 141L107.24,109.48A4,4 47.34,0 1,110.49 104.85z"/> - <path android:fillColor="#FA903E" android:pathData="M134.05,132.68C132.66,132.93 131.38,132.65 130.23,131.86C129.09,131.05 128.4,129.95 128.16,128.58C127.91,127.18 128.18,125.92 128.98,124.78C129.79,123.63 130.89,122.92 132.29,122.68C132.74,122.6 133.17,122.58 133.58,122.64C133.99,122.67 134.39,122.77 134.78,122.94L132.42,109.58L140.24,108.2L141.13,113.28L135.93,114.2L138.16,126.81C138.4,128.19 138.12,129.46 137.3,130.61C136.51,131.75 135.43,132.44 134.05,132.68Z"/> - <path android:fillColor="#FA903E" android:pathData="M257.26,108.32L255.12,106.83L260.82,98.65L252.67,92.92L254.16,90.78L262.34,96.48L268.07,88.33L270.21,89.82L264.51,98L272.66,103.73L271.17,105.87L262.99,100.17L257.26,108.32Z"/> - <path android:fillColor="#E37400" android:pathData="M118.13,148.18L287.51,118.31A4,4 131.27,0 1,292.15 121.56L297.7,153.07A4,4 124.99,0 1,294.46 157.71L125.07,187.57A4,4 76.33,0 1,120.44 184.33L114.88,152.81A4,4 56.6,0 1,118.13 148.18z"/> - <path android:fillColor="#FCC934" android:pathData="M264.9,151.65L262.76,150.16L268.46,141.98L260.31,136.25L261.8,134.11L269.98,139.81L275.71,131.66L277.85,133.15L272.15,141.33L280.3,147.06L278.81,149.2L270.63,143.5L264.9,151.65Z"/> - </group> - <path android:fillColor="#00000000" - android:pathData="M78.02,-61.04L253.64,-92A23.84,23.84 125,0 1,281.26 -72.66L341.74,270.37A23.84,23.84 125,0 1,322.4 297.98L146.79,328.95A23.84,23.84 125,0 1,119.17 309.61L58.69,-33.42A23.84,23.84 125,0 1,78.02 -61.04z" - android:strokeColor="#80868B" android:strokeWidth="4"/> - </group> -</vector> -
diff --git a/chrome/browser/privacy_sandbox/android/java/res/drawable/ic_arrow_back.xml b/chrome/browser/privacy_sandbox/android/java/res/drawable/ic_arrow_back.xml deleted file mode 100644 index b0191a7..0000000 --- a/chrome/browser/privacy_sandbox/android/java/res/drawable/ic_arrow_back.xml +++ /dev/null
@@ -1,15 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- -Copyright 2024 The Chromium Authors -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. ---> - -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="960" - android:viewportHeight="960"> - <path - android:fillColor="@macro/default_icon_color" - android:pathData="M313,520L537,744L480,800L160,480L480,160L537,216L313,440L800,440L800,520L313,520Z"/> -</vector>
diff --git a/chrome/browser/privacy_sandbox/android/java/res/drawable/privacy_sandbox_interests_illustration.xml b/chrome/browser/privacy_sandbox/android/java/res/drawable/privacy_sandbox_interests_illustration.xml deleted file mode 100644 index e448da5..0000000 --- a/chrome/browser/privacy_sandbox/android/java/res/drawable/privacy_sandbox_interests_illustration.xml +++ /dev/null
@@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -Copyright 2022 The Chromium Authors -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. ---> -<vector android:height="73.68421dp" android:viewportHeight="140" - android:viewportWidth="380" android:width="200dp" xmlns:android="http://schemas.android.com/apk/res/android"> - <group> - <clip-path android:pathData="M16,0L364,0A16,16 0,0 1,380 16L380,124A16,16 0,0 1,364 140L16,140A16,16 0,0 1,0 124L0,16A16,16 0,0 1,16 0z"/> - <path android:fillColor="#FEEFE3" android:pathData="M16,0L364,0A16,16 0,0 1,380 16L380,124A16,16 0,0 1,364 140L16,140A16,16 0,0 1,0 124L0,16A16,16 0,0 1,16 0z"/> - <group> - <clip-path android:pathData="M77.68,-63L253.29,-93.97A25.84,25.84 125,0 1,283.22 -73.01L343.71,270.02A25.84,25.84 125,0 1,322.75 299.95L147.14,330.92A25.84,25.84 125,0 1,117.2 309.96L56.72,-33.07A25.84,25.84 125,0 1,77.68 -63z"/> - <path android:fillColor="#ffffff" android:pathData="M77.68,-63L253.29,-93.97A25.84,25.84 125,0 1,283.22 -73.01L343.71,270.02A25.84,25.84 125,0 1,322.75 299.95L147.14,330.92A25.84,25.84 125,0 1,117.2 309.96L56.72,-33.07A25.84,25.84 125,0 1,77.68 -63z"/> - <path android:fillColor="#D2E3FC" android:pathData="M95.18,18.07L264.57,-11.79A4,4 123.86,0 1,269.21 -8.55L274.76,22.96A4,4 129.71,0 1,271.52 27.6L102.13,57.47A4,4 125.28,0 1,97.5 54.22L91.94,22.71A4,4 55.52,0 1,95.18 18.07z"/> - <path android:fillColor="#1A73E8" android:pathData="M111.49,47.18C110.77,47.31 110.11,47.16 109.5,46.74C108.9,46.32 108.54,45.75 108.41,45.03L104.86,24.9L107.32,26.63L109.02,24.17L111.48,25.89L113.2,23.43L115.63,25.16L117.32,22.7L119.76,24.43L121.45,21.98L123.91,23.7L125.63,21.24L128.07,22.97L129.79,20.51L133.34,40.63C133.46,41.35 133.32,42.01 132.89,42.62C132.47,43.22 131.9,43.58 131.18,43.71L111.49,47.18ZM111.03,44.57L119.61,43.05L118.26,35.36L109.67,36.87L111.03,44.57ZM122.14,42.61L130.72,41.1L130.27,38.51L121.68,40.02L122.14,42.61ZM121.24,37.5L129.82,35.99L129.37,33.4L120.78,34.92L121.24,37.5ZM109.22,34.35L128.92,30.88L128.31,27.4L108.61,30.87L109.22,34.35Z"/> - <path android:fillColor="#1A73E8" android:pathData="M241.96,21.55L239.82,20.05L245.52,11.87L237.37,6.14L238.86,4L247.04,9.7L252.77,1.55L254.91,3.05L249.21,11.22L257.36,16.96L255.87,19.09L247.69,13.39L241.96,21.55Z"/> - <path android:fillColor="#CEEAD6" android:pathData="M102.82,61.4L272.21,31.54A4,4 125.6,0 1,276.85 34.78L282.4,66.3A4,4 128.22,0 1,279.16 70.93L109.77,100.8A4,4 101.52,0 1,105.14 97.55L99.58,66.04A4,4 121.01,0 1,102.82 61.4z"/> - <path android:fillColor="#1E8E3E" android:pathData="M126.63,87.51L126.72,85.69C126.73,85.41 126.82,85.16 126.97,84.94C127.14,84.72 127.37,84.58 127.65,84.51L131.44,83.52C131.7,83.43 131.96,83.44 132.21,83.54C132.48,83.64 132.68,83.81 132.8,84.04L133.5,85.13C134.22,84.51 134.84,83.82 135.36,83.05C135.9,82.26 136.31,81.4 136.59,80.49L136.19,80.37C135.93,80.24 135.73,80.08 135.59,79.87C135.45,79.64 135.38,79.39 135.4,79.11L135.59,75.17C135.6,74.9 135.69,74.66 135.84,74.46C136.02,74.26 136.23,74.13 136.47,74.07C136.28,73.57 136.06,73.09 135.81,72.63C135.58,72.16 135.3,71.71 134.97,71.29C134.8,71.42 134.6,71.51 134.38,71.55C134.17,71.56 133.97,71.52 133.79,71.43L130.15,70.04C129.9,69.96 129.7,69.8 129.56,69.57C129.43,69.32 129.36,69.06 129.35,68.79L129.45,67.69C128.76,67.51 128.05,67.42 127.33,67.4C126.63,67.37 125.93,67.42 125.23,67.54C124.92,67.6 124.62,67.66 124.31,67.74C124.03,67.81 123.74,67.9 123.44,68.02L124.77,69.94C124.91,70.17 124.98,70.43 124.97,70.73C124.97,71.01 124.88,71.25 124.68,71.45L122.24,74.51C122.04,74.72 121.81,74.87 121.53,74.96C121.26,75.02 121,75 120.75,74.87L117.62,73.68C117.29,74.56 117.07,75.47 116.96,76.4C116.86,77.32 116.9,78.26 117.07,79.2C117.13,79.55 117.27,80.08 117.47,80.78L120.14,80.06C120.42,79.96 120.69,79.97 120.97,80.07C121.24,80.15 121.44,80.31 121.59,80.56L123.67,83.88C123.82,84.1 123.89,84.36 123.9,84.63C123.9,84.88 123.8,85.11 123.61,85.32L122.65,86.53C123.25,86.83 123.88,87.07 124.55,87.24C125.23,87.4 125.92,87.49 126.63,87.51ZM127.93,81.76C127.67,81.85 127.41,81.84 127.16,81.74C126.91,81.63 126.71,81.47 126.57,81.24L124.17,77.7C124.02,77.45 123.96,77.19 124,76.93C124.03,76.65 124.14,76.41 124.32,76.21L127.01,72.94C127.18,72.72 127.4,72.59 127.67,72.54C127.93,72.47 128.19,72.49 128.44,72.6L132.27,74.01C132.52,74.12 132.73,74.29 132.88,74.54C133.04,74.77 133.12,75.02 133.1,75.3L132.86,79.5C132.85,79.77 132.75,80.02 132.58,80.24C132.42,80.44 132.22,80.58 131.96,80.67L127.93,81.76ZM129.19,90.01C127.45,90.32 125.76,90.28 124.13,89.89C122.49,89.5 121,88.84 119.65,87.92C118.32,86.97 117.19,85.79 116.27,84.39C115.35,82.99 114.73,81.41 114.42,79.67C114.11,77.92 114.16,76.24 114.54,74.6C114.93,72.97 115.59,71.49 116.52,70.16C117.47,68.81 118.64,67.67 120.04,66.75C121.45,65.82 123.02,65.21 124.76,64.9C126.49,64.59 128.17,64.64 129.8,65.03C131.46,65.41 132.95,66.08 134.28,67.03C135.63,67.95 136.76,69.12 137.69,70.52C138.61,71.92 139.23,73.5 139.54,75.24C139.84,76.98 139.8,78.67 139.41,80.31C139.02,81.94 138.36,83.43 137.41,84.79C136.48,86.11 135.3,87.24 133.88,88.17C132.48,89.09 130.92,89.71 129.19,90.01Z"/> - <path android:fillColor="#1E8E3E" android:pathData="M249.6,64.88L247.46,63.38L253.16,55.21L245.01,49.47L246.5,47.34L254.68,53.03L260.41,44.88L262.55,46.38L256.85,54.55L265,60.29L263.51,62.42L255.33,56.73L249.6,64.88Z"/> - <path android:fillColor="#FEDFC8" android:pathData="M110.49,104.85L279.87,74.98A4,4 121.26,0 1,284.51 78.23L290.06,109.74A4,4 126.81,0 1,286.82 114.37L117.43,144.24A4,4 134.11,0 1,112.8 141L107.24,109.48A4,4 47.34,0 1,110.49 104.85z"/> - <path android:fillColor="#E8710A" android:pathData="M134.05,132.68C132.66,132.93 131.38,132.65 130.23,131.86C129.09,131.05 128.4,129.95 128.15,128.58C127.91,127.18 128.18,125.92 128.98,124.78C129.79,123.63 130.89,122.92 132.29,122.68C132.74,122.6 133.17,122.58 133.58,122.64C133.99,122.67 134.39,122.77 134.78,122.94L132.42,109.58L140.24,108.2L141.13,113.28L135.93,114.2L138.16,126.81C138.4,128.19 138.12,129.46 137.3,130.61C136.51,131.75 135.43,132.44 134.05,132.68Z"/> - <path android:fillColor="#E8710A" android:pathData="M257.26,108.32L255.12,106.83L260.82,98.65L252.67,92.92L254.16,90.78L262.34,96.48L268.07,88.33L270.21,89.82L264.51,98L272.66,103.73L271.17,105.87L262.99,100.17L257.26,108.32Z"/> - <path android:fillColor="#FEEFC3" android:pathData="M118.13,148.18L287.51,118.31A4,4 131.27,0 1,292.15 121.56L297.7,153.07A4,4 124.99,0 1,294.46 157.71L125.07,187.57A4,4 76.33,0 1,120.44 184.33L114.88,152.81A4,4 56.6,0 1,118.13 148.18z"/> - <path android:fillColor="#FBBC04" android:pathData="M264.9,151.65L262.76,150.16L268.46,141.98L260.31,136.25L261.8,134.11L269.98,139.81L275.71,131.66L277.85,133.15L272.15,141.33L280.3,147.06L278.81,149.2L270.63,143.5L264.9,151.65Z"/> - </group> - <path android:fillColor="#00000000" - android:pathData="M78.02,-61.04L253.64,-92A23.84,23.84 125,0 1,281.25 -72.66L341.74,270.37A23.84,23.84 125,0 1,322.4 297.98L146.79,328.95A23.84,23.84 125,0 1,119.17 309.61L58.69,-33.42A23.84,23.84 125,0 1,78.02 -61.04z" - android:strokeColor="#80868B" android:strokeWidth="4"/> - </group> -</vector> -
diff --git a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_consent_eea.xml b/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_consent_eea.xml deleted file mode 100644 index ec71abe..0000000 --- a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_consent_eea.xml +++ /dev/null
@@ -1,248 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -Copyright 2022 The Chromium Authors -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. ---> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/privacy_sandbox_dialog" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <LinearLayout - android:id="@+id/progress_bar_container" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:gravity="center" - android:layout_gravity="center" - android:visibility="gone"> - - <ProgressBar - android:id="@+id/progress_bar" - android:layout_width="@dimen/privacy_sandbox_spinner_dimension" - android:layout_height="@dimen/privacy_sandbox_spinner_dimension" /> - - <TextView - android:id="@+id/progress_bar_saving_label" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginHorizontal="@dimen/list_item_default_margin" - android:text="@string/privacy_sandbox_m1_consent_saving_label" - style="@style/TextAppearance.TextMedium.Secondary" /> - - </LinearLayout> - - <org.chromium.components.browser_ui.widget.BoundedLinearLayout - android:id="@+id/privacy_sandbox_consent_eea_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:layout_gravity="center" - app:maxWidthLandscape="@dimen/privacy_sandbox_dialog_max_width" - app:maxWidthPortrait="@dimen/privacy_sandbox_dialog_max_width"> - - <org.chromium.components.browser_ui.widget.FadingEdgeScrollView - android:id="@+id/privacy_sandbox_dialog_scroll_view" - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" - android:fillViewport="true"> - - <LinearLayout - android:id="@+id/privacy_sandbox_consent_eea_content" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="center_vertical" - android:layout_marginHorizontal="@dimen/list_item_default_margin" - android:orientation="vertical"> - - <ImageView - android:id="@+id/chrome_sync_logo" - android:layout_height="@dimen/privacy_sandbox_chrome_logo_height" - android:layout_width="@dimen/privacy_sandbox_chrome_logo_width" - android:layout_gravity="center" - app:srcCompat="@drawable/chrome_sync_logo" - android:layout_marginVertical="@dimen/privacy_sandbox_dialog_logo_margin" - android:importantForAccessibility="no" /> - - <TextView - android:id="@+id/privacy_sandbox_m1_consent_title" - android:layout_marginBottom="@dimen/privacy_sandbox_dialog_title_margin" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_consent_title" - android:layout_gravity="center" - android:gravity="center" - style="@style/TextAppearance.Headline.Primary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_consent_description_1" - android:layout_marginBottom="@dimen/list_item_default_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_consent_description_1" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_consent_description_2" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_consent_description_2" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <ImageView - android:id="@+id/privacy_sandbox_interests_illustration" - android:layout_marginVertical="@dimen/privacy_sandbox_dialog_illustration_margin_vertical" - android:layout_height="wrap_content" - android:layout_width="match_parent" - app:srcCompat="@drawable/privacy_sandbox_interests_illustration" - android:adjustViewBounds="true" - android:importantForAccessibility="no" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_consent_description_3" - android:layout_marginBottom="@dimen/list_item_default_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_consent_description_3" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <LinearLayout - android:id="@+id/dropdown_element" - android:layout_marginBottom="@dimen/list_item_default_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:orientation="horizontal"> - - <TextView - android:id="@+id/learn_more_expand_label" - android:layout_weight="1" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:minHeight="@dimen/privacy_sandbox_notice_dialog_dropdown_button_height" - android:gravity="center_vertical" - android:text="@string/privacy_sandbox_m1_consent_learn_more_expand_label" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.CheckableImageView - android:id="@+id/expand_arrow" - android:layout_width="24dp" - android:layout_height="@dimen/privacy_sandbox_notice_dialog_dropdown_button_height"/> - - </LinearLayout> - - <LinearLayout - android:id="@+id/dropdown_container" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:visibility="gone" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_consent_description_4" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_consent_description_4" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - </LinearLayout> - - </org.chromium.components.browser_ui.widget.FadingEdgeScrollView> - - <org.chromium.ui.widget.ButtonCompat - android:id="@+id/more_button" - android:focusable="true" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_dialog_more_button" - android:visibility="invisible" - android:layout_margin="@dimen/promo_dialog_padding" - android:layout_gravity="end" - android:gravity="center_vertical" - android:drawableEnd="@drawable/ic_arrow_down" - android:drawableTint="?attr/globalFilledButtonTextColor" - android:drawablePadding="@dimen/privacy_sandbox_more_button_drawable_padding" - style="@style/FilledButton" /> - - <LinearLayout - android:id="@+id/action_buttons" - android:orientation="horizontal" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:weightSum="2" - android:layout_margin="@dimen/promo_dialog_padding" - android:visibility="gone"> - - <org.chromium.ui.widget.ButtonCompat - android:id="@+id/no_button" - android:focusable="true" - android:layout_width="0dp" - android:layout_weight="1" - android:layout_height="wrap_content" - android:layout_marginEnd="@dimen/privacy_sandbox_consent_button_margin_between" - android:text="@string/privacy_sandbox_m1_consent_decline_button" - style="@style/OutlinedButton" /> - <org.chromium.ui.widget.ButtonCompat - android:id="@+id/ack_button" - android:focusable="true" - android:layout_width="0dp" - android:layout_weight="1" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/privacy_sandbox_consent_button_margin_between" - android:text="@string/privacy_sandbox_m1_consent_accept_button" - style="@style/OutlinedButton" /> - - </LinearLayout> - </org.chromium.components.browser_ui.widget.BoundedLinearLayout> - - <org.chromium.components.browser_ui.widget.BoundedLinearLayout - android:id="@+id/privacy_policy_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:visibility="gone"> - - <LinearLayout - android:id="@+id/privacy_policy_header" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:padding="16dp" - android:paddingStart="12dp" - android:gravity="center_vertical" - android:background="@macro/default_bg_color"> - - <org.chromium.ui.widget.ChromeImageButton - android:id="@+id/privacy_policy_back_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/ic_arrow_back" - android:background="?attr/selectableItemBackgroundBorderless" - android:contentDescription="@string/privacy_sandbox_privacy_policy_back_button" - android:padding="12dp"/> - - <TextView - android:id="@+id/privacy_policy_title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_privacy_policy_page_title" - android:layout_marginStart="12dp" - style="@style/TextAppearance.Headline.Primary"/> - </LinearLayout> - - <FrameLayout - android:id="@+id/privacy_policy_content" - android:layout_width="match_parent" - android:layout_height="match_parent"> - </FrameLayout> - </org.chromium.components.browser_ui.widget.BoundedLinearLayout> -</LinearLayout>
diff --git a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_consent_eea_dropdown.xml b/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_consent_eea_dropdown.xml deleted file mode 100644 index a9960a9..0000000 --- a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_consent_eea_dropdown.xml +++ /dev/null
@@ -1,60 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -Copyright 2022 The Chromium Authors -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. ---> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/privacy_sandbox_consent_eea_dropdown" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_consent_learn_more_bullet_one" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_consent_learn_more_bullet_two" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_consent_learn_more_bullet_2_description" - android:visibility="gone" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingStart="14dp" - android:text="@string/privacy_sandbox_m1_consent_learn_more_bullet_2_description_clank" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_consent_learn_more_bullet_three" - android:layout_marginBottom="@dimen/list_item_default_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_learn_more_text" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_learn_more_v2_clank" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - -</LinearLayout>
diff --git a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_eea.xml b/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_eea.xml deleted file mode 100644 index ed0918e..0000000 --- a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_eea.xml +++ /dev/null
@@ -1,190 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -Copyright 2022 The Chromium Authors -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. ---> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/privacy_sandbox_dialog" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <org.chromium.components.browser_ui.widget.BoundedLinearLayout - android:id="@+id/privacy_sandbox_notice_eea_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:layout_gravity="center" - app:maxWidthLandscape="@dimen/privacy_sandbox_dialog_max_width" - app:maxWidthPortrait="@dimen/privacy_sandbox_dialog_max_width"> - - <org.chromium.components.browser_ui.widget.FadingEdgeScrollView - android:id="@+id/privacy_sandbox_dialog_scroll_view" - android:layout_width="match_parent" - android:layout_height="0dp" - android:fillViewport="true" - android:layout_weight="1" - android:visibility="invisible"> - - <LinearLayout - android:id="@+id/privacy_sandbox_notice_eea_content" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_marginHorizontal="@dimen/list_item_default_margin" - android:gravity="center_vertical" - android:orientation="vertical"> - - <ImageView - android:id="@+id/privacy_sandbox_notice_eea_logo" - android:layout_height="@dimen/privacy_sandbox_chrome_logo_height" - android:layout_width="@dimen/privacy_sandbox_chrome_logo_width" - android:layout_gravity="center" - app:srcCompat="@drawable/chrome_sync_logo" - android:layout_marginVertical="@dimen/privacy_sandbox_dialog_logo_margin" - android:importantForAccessibility="no" /> - - <TextView - android:id="@+id/privacy_sandbox_notice_title" - android:layout_marginBottom="@dimen/list_item_default_margin" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:accessibilityHeading="true" - android:text="@string/privacy_sandbox_m1_notice_eea_title" - android:layout_gravity="center" - android:gravity="center" - style="@style/TextAppearance.Headline.Primary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_notice_eea_description_1" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_eea_description_1" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <ImageView - android:id="@+id/privacy_sandbox_notice_eea_illustration" - android:layout_marginVertical="@dimen/privacy_sandbox_dialog_illustration_margin_vertical" - android:layout_height="wrap_content" - android:layout_width="match_parent" - app:srcCompat="@drawable/privacy_sandbox_notice_eea_illustration" - android:adjustViewBounds="true" - android:importantForAccessibility="no" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_eea_bullet_one" - android:layout_marginBottom="@dimen/list_item_default_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_eea_bullet_two" - android:layout_marginBottom="@dimen/list_item_default_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <LinearLayout - android:layout_marginBottom="@dimen/list_item_default_margin" - android:id="@+id/dropdown_element" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:orientation="horizontal"> - - <TextView - android:id="@+id/privacy_sandbox_notice_eea_learn_more_expand_label" - android:layout_weight="1" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:gravity="center_vertical" - android:minHeight="@dimen/privacy_sandbox_notice_dialog_dropdown_button_height" - android:text="@string/privacy_sandbox_m1_notice_eea_learn_more_expand_label" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.CheckableImageView - android:id="@+id/expand_arrow" - android:layout_width="24dp" - android:layout_height="@dimen/privacy_sandbox_notice_dialog_dropdown_button_height"/> - - </LinearLayout> - - <LinearLayout - android:id="@+id/dropdown_container" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:visibility="gone" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_notice_eea_description_2" - android:layout_marginBottom="@dimen/privacy_sandbox_notice_margin_bottom" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:text="@string/privacy_sandbox_m1_notice_eea_description_2" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - </LinearLayout> - - </org.chromium.components.browser_ui.widget.FadingEdgeScrollView> - - <org.chromium.ui.widget.ButtonCompat - android:id="@+id/more_button" - android:focusable="true" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_dialog_more_button" - android:visibility="gone" - android:layout_marginHorizontal="24dp" - android:layout_marginVertical="@dimen/promo_dialog_padding" - android:layout_gravity="end" - android:gravity="center_vertical" - android:drawableEnd="@drawable/ic_arrow_down" - android:drawableTint="?attr/globalFilledButtonTextColor" - android:drawablePadding="@dimen/privacy_sandbox_more_button_drawable_padding" - style="@style/FilledButton" /> - - <LinearLayout - android:id="@+id/action_buttons" - android:orientation="horizontal" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:weightSum="2" - android:layout_marginTop="@dimen/promo_dialog_padding" - android:layout_marginBottom="@dimen/promo_dialog_padding" - android:layout_marginHorizontal="24dp" - android:visibility="invisible" > - - <org.chromium.ui.widget.ButtonCompat - android:id="@+id/settings_button" - android:focusable="true" - android:layout_width="0dp" - android:layout_weight="1" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_marginEnd="@dimen/privacy_sandbox_consent_button_margin_between" - android:text="@string/privacy_sandbox_m1_notice_eea_settings_button" - style="@style/OutlinedButton" /> - - <org.chromium.ui.widget.ButtonCompat - android:id="@+id/ack_button" - android:focusable="true" - android:layout_width="0dp" - android:layout_weight="1" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/privacy_sandbox_consent_button_margin_between" - android:text="@string/privacy_sandbox_m1_notice_eea_ack_button" - style="@style/OutlinedButton" /> - - </LinearLayout> - </org.chromium.components.browser_ui.widget.BoundedLinearLayout> -</LinearLayout>
diff --git a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_eea_ad_measurement_dropdown.xml b/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_eea_ad_measurement_dropdown.xml deleted file mode 100644 index 1097aa8..0000000 --- a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_eea_ad_measurement_dropdown.xml +++ /dev/null
@@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -Copyright 2024 The Chromium Authors -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. ---> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/privacy_sandbox_notice_eea_ad_measurement_dropdown" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_eea_ad_measurement_learn_more_bullet_one" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_eea_ad_measurement_learn_more_description_android" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingStart="14dp" - android:text="@string/privacy_sandbox_m1_notice_eea_learn_more_description_android" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - -</LinearLayout>
diff --git a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_eea_dropdown.xml b/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_eea_dropdown.xml deleted file mode 100644 index 7327629..0000000 --- a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_eea_dropdown.xml +++ /dev/null
@@ -1,76 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -Copyright 2022 The Chromium Authors -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. ---> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/privacy_sandbox_notice_eea_dropdown" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <TextView - android:id="@+id/privacy_sandbox_notice_eea_learn_more_heading_1" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:accessibilityHeading="true" - android:text="@string/privacy_sandbox_m1_notice_eea_learn_more_heading_1" - style="@style/TextAppearance.TextMediumThick.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_eea_learn_more_bullet_one" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_eea_learn_more_bullet_two" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_eea_learn_more_bullet_three" - android:layout_marginBottom="@dimen/list_item_default_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <TextView - android:id="@+id/privacy_sandbox_notice_eea_learn_more_heading_2" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:accessibilityHeading="true" - android:text="@string/privacy_sandbox_m1_notice_eea_learn_more_heading_2" - style="@style/TextAppearance.TextMediumThick.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_notice_eea_learn_more_description_1" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_eea_learn_more_description" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_notice_eea_learn_more_description_2" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_eea_learn_more_description_android" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - -</LinearLayout>
diff --git a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_eea_site_suggested_ads_dropdown.xml b/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_eea_site_suggested_ads_dropdown.xml deleted file mode 100644 index bbcaa87..0000000 --- a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_eea_site_suggested_ads_dropdown.xml +++ /dev/null
@@ -1,44 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -Copyright 2024 The Chromium Authors -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. ---> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/privacy_sandbox_notice_eea_site_suggested_ads_dropdown" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_eea_site_suggested_ads_learn_more_bullet_one" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_eea_site_suggested_ads_learn_more_bullet_one_description" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingStart="14dp" - android:text="@string/privacy_sandbox_m1_notice_eea_site_suggested_ads_learn_more_bullet_1_description_clank" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_eea_site_suggested_ads_learn_more_bullet_two" - android:layout_marginBottom="@dimen/list_item_default_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <!--TODO(crbug.com/380280378): Add learn more description android--> - -</LinearLayout>
diff --git a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_eea_v2.xml b/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_eea_v2.xml deleted file mode 100644 index 25d1cfee..0000000 --- a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_eea_v2.xml +++ /dev/null
@@ -1,284 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -Copyright 2024 The Chromium Authors -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. ---> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/privacy_sandbox_dialog" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <org.chromium.components.browser_ui.widget.BoundedLinearLayout - android:id="@+id/privacy_sandbox_notice_eea_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:layout_gravity="center" - app:maxWidthLandscape="@dimen/privacy_sandbox_dialog_max_width" - app:maxWidthPortrait="@dimen/privacy_sandbox_dialog_max_width"> - - <org.chromium.components.browser_ui.widget.FadingEdgeScrollView - android:id="@+id/privacy_sandbox_dialog_scroll_view" - android:layout_width="match_parent" - android:layout_height="0dp" - android:fillViewport="true" - android:layout_weight="1" - android:visibility="invisible"> - - <LinearLayout - android:id="@+id/privacy_sandbox_notice_eea_content" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_marginHorizontal="@dimen/list_item_default_margin" - android:gravity="center_vertical" - android:orientation="vertical"> - - <ImageView - android:id="@+id/privacy_sandbox_notice_eea_logo" - android:layout_height="@dimen/privacy_sandbox_chrome_logo_height" - android:layout_width="@dimen/privacy_sandbox_chrome_logo_width" - android:layout_gravity="center" - app:srcCompat="@drawable/chrome_sync_logo" - android:layout_marginVertical="@dimen/privacy_sandbox_dialog_logo_margin" - android:importantForAccessibility="no" /> - - <TextView - android:id="@+id/privacy_sandbox_notice_title" - android:layout_marginBottom="@dimen/list_item_default_margin" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:accessibilityHeading="true" - android:text="@string/privacy_sandbox_m1_notice_eea_title" - android:layout_gravity="center" - android:gravity="center" - style="@style/TextAppearance.Headline.Primary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_notice_eea_description_1" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_eea_description_1_v2" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <ImageView - android:id="@+id/privacy_sandbox_notice_eea_illustration" - android:layout_marginVertical="@dimen/privacy_sandbox_dialog_illustration_margin_vertical" - android:layout_height="wrap_content" - android:layout_width="match_parent" - app:srcCompat="@drawable/privacy_sandbox_notice_eea_illustration" - android:adjustViewBounds="true" - android:importantForAccessibility="no" /> - - <TextView - android:id="@+id/privacy_sandbox_notice_eea_site_suggested_ads_title" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:accessibilityHeading="true" - android:text="@string/privacy_sandbox_m1_notice_eea_site_suggested_ads_title" - style="@style/TextAppearance.TextMediumThick.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_notice_eea_site_suggested_ads_description" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_eea_site_suggested_ads_description" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <LinearLayout - android:layout_marginBottom="@dimen/list_item_default_margin" - android:id="@+id/site_suggested_ads_dropdown_element" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:orientation="horizontal"> - - <TextView - android:id="@+id/privacy_sandbox_notice_eea_site_suggested_ads_learn_more_label" - android:layout_weight="1" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:gravity="center_vertical" - android:minHeight="@dimen/privacy_sandbox_notice_dialog_dropdown_button_height" - android:text="@string/privacy_sandbox_m1_notice_eea_site_suggested_ads_learn_more_label" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.CheckableImageView - android:id="@+id/site_suggested_ads_expand_arrow" - android:layout_width="24dp" - android:layout_height="@dimen/privacy_sandbox_notice_dialog_dropdown_button_height"/> - - </LinearLayout> - - <LinearLayout - android:id="@+id/site_suggested_ads_dropdown_container" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:visibility="gone" /> - - <TextView - android:id="@+id/privacy_sandbox_notice_eea_ad_measurement_title" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:accessibilityHeading="true" - android:text="@string/privacy_sandbox_m1_notice_eea_ad_measurement_title" - style="@style/TextAppearance.TextMediumThick.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_notice_eea_ad_measurement_description" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_eea_ad_measurement_description" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <LinearLayout - android:layout_marginBottom="@dimen/list_item_default_margin" - android:id="@+id/ad_measurement_dropdown_element" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:orientation="horizontal"> - - <TextView - android:id="@+id/privacy_sandbox_notice_eea_ad_measurement_learn_more_label" - android:layout_weight="1" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:gravity="center_vertical" - android:minHeight="@dimen/privacy_sandbox_notice_dialog_dropdown_button_height" - android:text="@string/privacy_sandbox_m1_notice_eea_ad_measurement_learn_more_label" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.CheckableImageView - android:id="@+id/ad_measurement_expand_arrow" - android:layout_width="24dp" - android:layout_height="@dimen/privacy_sandbox_notice_dialog_dropdown_button_height"/> - - </LinearLayout> - - <LinearLayout - android:id="@+id/ad_measurement_dropdown_container" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:visibility="gone" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_notice_eea_last_text" - android:layout_marginBottom="@dimen/privacy_sandbox_notice_margin_bottom" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:text="@string/privacy_sandbox_m1_notice_eea_last_text" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - </LinearLayout> - - </org.chromium.components.browser_ui.widget.FadingEdgeScrollView> - - <org.chromium.ui.widget.ButtonCompat - android:id="@+id/more_button" - android:focusable="true" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_dialog_more_button" - android:visibility="gone" - android:layout_marginHorizontal="24dp" - android:layout_marginVertical="@dimen/promo_dialog_padding" - android:layout_gravity="end" - android:gravity="center_vertical" - android:drawableEnd="@drawable/ic_arrow_down" - android:drawableTint="?attr/globalFilledButtonTextColor" - android:drawablePadding="@dimen/privacy_sandbox_more_button_drawable_padding" - style="@style/FilledButton" /> - - <LinearLayout - android:id="@+id/action_buttons" - android:orientation="horizontal" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:weightSum="2" - android:layout_marginTop="@dimen/promo_dialog_padding" - android:layout_marginBottom="@dimen/promo_dialog_padding" - android:layout_marginHorizontal="24dp" - android:visibility="invisible" > - - <org.chromium.ui.widget.ButtonCompat - android:id="@+id/settings_button" - android:focusable="true" - android:layout_width="0dp" - android:layout_weight="1" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_marginEnd="@dimen/privacy_sandbox_consent_button_margin_between" - android:text="@string/privacy_sandbox_m1_notice_eea_settings_button" - style="@style/OutlinedButton" /> - - <org.chromium.ui.widget.ButtonCompat - android:id="@+id/ack_button" - android:focusable="true" - android:layout_width="0dp" - android:layout_weight="1" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/privacy_sandbox_consent_button_margin_between" - android:text="@string/privacy_sandbox_m1_notice_eea_ack_button" - style="@style/OutlinedButton" /> - - </LinearLayout> - </org.chromium.components.browser_ui.widget.BoundedLinearLayout> - - <org.chromium.components.browser_ui.widget.BoundedLinearLayout - android:id="@+id/privacy_policy_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:visibility="gone"> - - <LinearLayout - android:id="@+id/privacy_policy_header" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:padding="16dp" - android:paddingStart="12dp" - android:gravity="center_vertical" - android:background="@macro/default_bg_color"> - - <org.chromium.ui.widget.ChromeImageButton - android:id="@+id/privacy_policy_back_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/ic_arrow_back" - android:background="?attr/selectableItemBackgroundBorderless" - android:contentDescription="@string/privacy_sandbox_privacy_policy_back_button" - android:padding="12dp"/> - - <TextView - android:id="@+id/privacy_policy_title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_privacy_policy_page_title" - android:layout_marginStart="12dp" - style="@style/TextAppearance.Headline.Primary"/> - </LinearLayout> - - <FrameLayout - android:id="@+id/privacy_policy_content" - android:layout_width="match_parent" - android:layout_height="match_parent"> - </FrameLayout> - </org.chromium.components.browser_ui.widget.BoundedLinearLayout> -</LinearLayout>
diff --git a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_row.xml b/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_row.xml deleted file mode 100644 index d074111..0000000 --- a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_row.xml +++ /dev/null
@@ -1,242 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -Copyright 2022 The Chromium Authors -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. ---> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/privacy_sandbox_dialog" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <org.chromium.components.browser_ui.widget.BoundedLinearLayout - android:id="@+id/privacy_sandbox_notice_row_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:layout_gravity="center" - app:maxWidthLandscape="@dimen/privacy_sandbox_dialog_max_width" - app:maxWidthPortrait="@dimen/privacy_sandbox_dialog_max_width"> - - <org.chromium.components.browser_ui.widget.FadingEdgeScrollView - android:id="@+id/privacy_sandbox_dialog_scroll_view" - android:layout_width="match_parent" - android:layout_height="0dp" - android:fillViewport="true" - android:layout_weight="1"> - - <LinearLayout - android:id="@+id/privacy_sandbox_notice_row_content" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="center_vertical" - android:layout_marginHorizontal="@dimen/list_item_default_margin" - android:orientation="vertical"> - - <ImageView - android:id="@+id/privacy_sandbox_notice_row_logo" - android:layout_marginTop="@dimen/privacy_sandbox_dialog_illustration_margin_top" - android:layout_marginBottom="@dimen/privacy_sandbox_dialog_title_margin" - android:layout_height="@dimen/privacy_sandbox_chrome_logo_height" - android:layout_width="@dimen/privacy_sandbox_chrome_logo_width" - android:layout_gravity="center" - app:srcCompat="@drawable/chrome_sync_logo" - android:importantForAccessibility="no" /> - - <TextView - android:id="@+id/privacy_sandbox_notice_title" - android:layout_marginBottom="@dimen/privacy_sandbox_dialog_title_margin" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:accessibilityHeading="true" - android:text="@string/privacy_sandbox_m1_notice_row_title" - android:gravity="center" - style="@style/TextAppearance.Headline.Primary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_row_description_1" - android:layout_marginBottom="@dimen/list_item_default_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_row_description_1" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_row_description_2" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_row_description_2" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <ImageView - android:id="@+id/privacy_sandbox_notice_row_interests_illustration" - android:layout_marginVertical="@dimen/privacy_sandbox_dialog_illustration_margin_vertical" - android:layout_height="wrap_content" - android:layout_width="match_parent" - app:srcCompat="@drawable/privacy_sandbox_interests_illustration" - android:adjustViewBounds="true" - android:importantForAccessibility="no" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_row_description_2_v2" - android:layout_marginBottom="@dimen/list_item_default_margin" - android:visibility="gone" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_row_description_2" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_row_description_3" - android:layout_marginBottom="@dimen/list_item_default_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_row_description_3" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <LinearLayout - android:layout_marginBottom="@dimen/list_item_default_margin" - android:id="@+id/dropdown_element" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:orientation="horizontal"> - - <TextView - android:id="@+id/privacy_sandbox_m1_notice_row_learn_more_expand_label" - android:layout_weight="1" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:gravity="center_vertical" - android:minHeight="@dimen/privacy_sandbox_notice_dialog_dropdown_button_height" - android:accessibilityHeading="true" - android:text="@string/privacy_sandbox_m1_notice_row_learn_more_expand_label" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.CheckableImageView - android:id="@+id/expand_arrow" - android:layout_width="24dp" - android:layout_height="@dimen/privacy_sandbox_notice_dialog_dropdown_button_height"/> - - </LinearLayout> - - <LinearLayout - android:id="@+id/dropdown_container" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:visibility="gone" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_row_description_4" - android:layout_marginBottom="@dimen/privacy_sandbox_notice_margin_bottom" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_row_description_4" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - </LinearLayout> - - </org.chromium.components.browser_ui.widget.FadingEdgeScrollView> - - <org.chromium.ui.widget.ButtonCompat - android:id="@+id/more_button" - android:focusable="true" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_dialog_more_button" - android:visibility="gone" - android:layout_marginHorizontal="24dp" - android:layout_marginVertical="@dimen/promo_dialog_padding" - android:layout_gravity="end" - android:gravity="center_vertical" - android:drawableEnd="@drawable/ic_arrow_down" - android:drawableTint="?attr/globalFilledButtonTextColor" - android:drawablePadding="@dimen/privacy_sandbox_more_button_drawable_padding" - style="@style/FilledButton" /> - - <LinearLayout - android:id="@+id/action_buttons" - android:orientation="horizontal" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:weightSum="2" - android:layout_marginVertical="@dimen/promo_dialog_padding" - android:layout_marginHorizontal="24dp" - android:visibility="invisible"> - - <org.chromium.ui.widget.ButtonCompat - android:id="@+id/settings_button" - android:focusable="true" - android:layout_width="0dp" - android:layout_weight="1" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:layout_marginEnd="@dimen/privacy_sandbox_consent_button_margin_between" - android:text="@string/privacy_sandbox_m1_notice_row_settings_button" - style="@style/OutlinedButton" /> - <org.chromium.ui.widget.ButtonCompat - android:id="@+id/ack_button" - android:focusable="true" - android:layout_width="0dp" - android:layout_weight="1" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/privacy_sandbox_consent_button_margin_between" - android:text="@string/privacy_sandbox_m1_notice_row_ack_button" - style="@style/FilledButton" /> - - </LinearLayout> - </org.chromium.components.browser_ui.widget.BoundedLinearLayout> - - <org.chromium.components.browser_ui.widget.BoundedLinearLayout - android:id="@+id/privacy_policy_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:visibility="gone"> - - <LinearLayout - android:id="@+id/privacy_policy_header" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:padding="16dp" - android:paddingStart="12dp" - android:gravity="center_vertical" - android:background="@macro/default_bg_color"> - - <org.chromium.ui.widget.ChromeImageButton - android:id="@+id/privacy_policy_back_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/ic_arrow_back" - android:background="?attr/selectableItemBackgroundBorderless" - android:contentDescription="@string/privacy_sandbox_privacy_policy_back_button" - android:padding="12dp"/> - - <TextView - android:id="@+id/privacy_policy_title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_privacy_policy_page_title" - android:layout_marginStart="12dp" - style="@style/TextAppearance.Headline.Primary"/> - </LinearLayout> - - <FrameLayout - android:id="@+id/privacy_policy_content" - android:layout_width="match_parent" - android:layout_height="match_parent"> - </FrameLayout> - </org.chromium.components.browser_ui.widget.BoundedLinearLayout> -</LinearLayout>
diff --git a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_row_dropdown.xml b/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_row_dropdown.xml deleted file mode 100644 index 885ffc8a..0000000 --- a/chrome/browser/privacy_sandbox/android/java/res/layout/privacy_sandbox_notice_row_dropdown.xml +++ /dev/null
@@ -1,134 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -Copyright 2022 The Chromium Authors -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. ---> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/privacy_sandbox_notice_row_dropdown" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <TextView - android:id="@+id/privacy_sandbox_m1_notice_row_learn_more_heading_1" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:accessibilityHeading="true" - android:text="@string/privacy_sandbox_m1_notice_row_learn_more_heading_1" - style="@style/TextAppearance.TextMediumThick.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_row_learn_more_description_1" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_row_learn_more_description_1" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_row_learn_more_bullet_one" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_row_learn_more_bullet_two" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_row_learn_more_description_2" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_row_learn_more_description_2" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_row_learn_more_description_3" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_row_learn_more_description_3" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <TextView - android:id="@+id/privacy_sandbox_m1_notice_row_learn_more_heading_2" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:accessibilityHeading="true" - android:text="@string/privacy_sandbox_m1_notice_row_learn_more_heading_2" - style="@style/TextAppearance.TextMediumThick.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_row_learn_more_description_4" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_row_learn_more_description_4" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_row_learn_more_description_android" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_row_learn_more_description_android" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <TextView - android:id="@+id/privacy_sandbox_m1_notice_row_learn_more_heading_3" - android:visibility="gone" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:accessibilityHeading="true" - android:text="@string/privacy_sandbox_m1_notice_row_learn_more_heading_3" - style="@style/TextAppearance.TextMediumThick.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_row_learn_more_description_android_v2" - android:visibility="gone" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_row_learn_more_description_android" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_row_learn_more_description_5_v2" - android:visibility="gone" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_row_learn_more_description_5_v2_clank" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - - <org.chromium.ui.widget.TextViewWithLeading - android:id="@+id/privacy_sandbox_m1_notice_row_learn_more_description_5" - android:layout_marginBottom="@dimen/promo_between_text_margin" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/privacy_sandbox_m1_notice_row_learn_more_description_5" - app:leading="@dimen/text_size_medium_leading" - style="@style/TextAppearance.TextMedium.Secondary" /> - -</LinearLayout>
diff --git a/chrome/browser/privacy_sandbox/android/java/res/values/dimens.xml b/chrome/browser/privacy_sandbox/android/java/res/values/dimens.xml index 21a07d5..f789e8d 100644 --- a/chrome/browser/privacy_sandbox/android/java/res/values/dimens.xml +++ b/chrome/browser/privacy_sandbox/android/java/res/values/dimens.xml
@@ -7,16 +7,12 @@ <resources> <dimen name="privacy_sandbox_dialog_illustration_margin_top">32dp</dimen> - <dimen name="privacy_sandbox_dialog_logo_margin">8dp</dimen> <dimen name="privacy_sandbox_dialog_illustration_margin_vertical">16dp</dimen> <dimen name="privacy_sandbox_dialog_max_width">600dp</dimen> <dimen name="privacy_sandbox_consent_button_margin_between">8dp</dimen> - <dimen name="privacy_sandbox_notice_dialog_dropdown_button_height">48dp</dimen> - <dimen name="privacy_sandbox_notice_margin_bottom">32dp</dimen> <dimen name="privacy_sandbox_dialog_title_margin">20dp</dimen> <dimen name="privacy_sandbox_chrome_logo_width">40dp</dimen> <dimen name="privacy_sandbox_chrome_logo_height">40dp</dimen> - <dimen name="privacy_sandbox_spinner_dimension">20dp</dimen> <dimen name="custom_preference_category_paddingVertical">16dp</dimen> <dimen name="custom_preference_category_title_marginBottom">8dp</dimen> <dimen name="privacy_sandbox_more_button_drawable_padding">8dp</dimen>
diff --git a/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogConsentEEA.java b/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogConsentEEA.java deleted file mode 100644 index 175b0ee..0000000 --- a/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogConsentEEA.java +++ /dev/null
@@ -1,431 +0,0 @@ -// Copyright 2022 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package org.chromium.chrome.browser.privacy_sandbox; - -import static org.chromium.build.NullUtil.assumeNonNull; - -import android.app.Activity; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Handler; -import android.text.method.LinkMovementMethod; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ScrollView; - -import androidx.annotation.IdRes; -import androidx.annotation.StringRes; - -import org.chromium.base.metrics.RecordHistogram; -import org.chromium.base.metrics.RecordUserAction; -import org.chromium.build.annotations.NullMarked; -import org.chromium.build.annotations.Nullable; -import org.chromium.chrome.browser.content.WebContentsFactory; -import org.chromium.chrome.browser.flags.ChromeFeatureList; -import org.chromium.chrome.browser.profiles.Profile; -import org.chromium.chrome.browser.ui.edge_to_edge.EdgeToEdgeUtils; -import org.chromium.components.browser_ui.widget.ChromeDialog; -import org.chromium.components.thinwebview.ThinWebView; -import org.chromium.content_public.browser.LifecycleState; -import org.chromium.content_public.browser.WebContents; -import org.chromium.content_public.browser.WebContentsObserver; -import org.chromium.ui.base.ActivityWindowAndroid; -import org.chromium.ui.text.ChromeClickableSpan; -import org.chromium.ui.text.SpanApplier; -import org.chromium.ui.widget.ButtonCompat; -import org.chromium.ui.widget.CheckableImageView; -import org.chromium.ui.widget.ChromeImageButton; -import org.chromium.ui.widget.TextViewWithLeading; -import org.chromium.url.GURL; - -/** Dialog in the form of a consent shown for the Privacy Sandbox. */ -@NullMarked -public class PrivacySandboxDialogConsentEEA extends ChromeDialog - implements DialogInterface.OnShowListener { - private static final int SPINNER_DURATION_MS = 1500; - private static final int BACKGROUND_TRANSITION_DURATION_MS = 300; - - private final PrivacySandboxBridge mPrivacySandboxBridge; - - private final Activity mActivity; - private final View mContentView; - - private final CheckableImageView mExpandArrowView; - private final LinearLayout mDropdownContainer; - private final LinearLayout mDropdownElement; - private final LinearLayout mProgressBarContainer; - private final LinearLayout mConsentViewContainer; - private final ButtonCompat mMoreButton; - private final LinearLayout mActionButtons; - private final ScrollView mScrollView; - private @Nullable TextViewWithLeading mLearnMoreText; - private final int mSurfaceType; - private final LinearLayout mPrivacyPolicyView; - private final FrameLayout mPrivacyPolicyContent; - private final ChromeImageButton mPrivacyPolicyBackButton; - private final View.OnClickListener mOnClickListener; - - private @Nullable ThinWebView mThinWebView; - private @Nullable WebContents mWebContents; - private @Nullable WebContentsObserver mWebContentsObserver; - private long mPrivacyPolicyClickedTimestamp; - private final Profile mProfile; - private final ActivityWindowAndroid mActivityWindowAndroid; - private boolean mIsPrivacyPageLoaded; - - private final boolean mAreAnimationsDisabled; - - private @StringRes int mLearnMoreBullet1StringRes = - R.string.privacy_sandbox_m1_consent_learn_more_bullet_1; - private @StringRes int mLearnMoreBullet2StringRes = - R.string.privacy_sandbox_m1_consent_learn_more_bullet_2; - private @StringRes int mLearnMoreBullet3StringRes = - R.string.privacy_sandbox_m1_consent_learn_more_bullet_3; - private @IdRes int mLearnMoreTextIdRes = R.id.privacy_sandbox_learn_more_text; - private @StringRes int mLearnMoreLinkString = - R.string.privacy_sandbox_m1_notice_learn_more_v2_clank; - - public PrivacySandboxDialogConsentEEA( - Activity activity, - PrivacySandboxBridge privacySandboxBridge, - boolean disableAnimations, - @SurfaceType int surfaceType, - Profile profile, - ActivityWindowAndroid activityWindowAndroid) { - super( - activity, - R.style.ThemeOverlay_BrowserUI_Fullscreen, - EdgeToEdgeUtils.isEdgeToEdgeEverywhereEnabled()); - mActivity = activity; - mPrivacySandboxBridge = privacySandboxBridge; - mAreAnimationsDisabled = disableAnimations; - mSurfaceType = surfaceType; - mProfile = profile; - mActivityWindowAndroid = activityWindowAndroid; - mContentView = - LayoutInflater.from(mActivity).inflate(R.layout.privacy_sandbox_consent_eea, null); - setContentView(mContentView); - mOnClickListener = getOnClickListener(); - - ButtonCompat ackButton = mContentView.findViewById(R.id.ack_button); - ackButton.setOnClickListener(mOnClickListener); - ButtonCompat noButton = mContentView.findViewById(R.id.no_button); - noButton.setOnClickListener(mOnClickListener); - mMoreButton = mContentView.findViewById(R.id.more_button); - mActionButtons = mContentView.findViewById(R.id.action_buttons); - mScrollView = mContentView.findViewById(R.id.privacy_sandbox_dialog_scroll_view); - - mProgressBarContainer = mContentView.findViewById(R.id.progress_bar_container); - mConsentViewContainer = mContentView.findViewById(R.id.privacy_sandbox_consent_eea_view); - mPrivacyPolicyView = mContentView.findViewById(R.id.privacy_policy_view); - mPrivacyPolicyContent = mContentView.findViewById(R.id.privacy_policy_content); - mPrivacyPolicyBackButton = mContentView.findViewById(R.id.privacy_policy_back_button); - mPrivacyPolicyBackButton.setOnClickListener(mOnClickListener); - mIsPrivacyPageLoaded = false; - - // Controls for the expanding section. - mDropdownElement = mContentView.findViewById(R.id.dropdown_element); - mDropdownElement.setOnClickListener(mOnClickListener); - mDropdownContainer = mContentView.findViewById(R.id.dropdown_container); - mExpandArrowView = mContentView.findViewById(R.id.expand_arrow); - mExpandArrowView.setImageDrawable(PrivacySandboxDialogUtils.createExpandDrawable(activity)); - mExpandArrowView.setChecked(isDropdownExpanded()); - - mMoreButton.setOnClickListener(mOnClickListener); - setOnShowListener(this); - setCancelable(false); - - mScrollView - .getViewTreeObserver() - .addOnScrollChangedListener( - () -> { - if (!mScrollView.canScrollVertically(ScrollView.FOCUS_DOWN)) { - mMoreButton.setVisibility(View.GONE); - mActionButtons.setVisibility(View.VISIBLE); - mScrollView.post( - () -> { - mScrollView.pageScroll(ScrollView.FOCUS_DOWN); - }); - } - }); - handleAdsApiUxEnhancements(); - } - - private View.OnClickListener getOnClickListener() { - return new PrivacySandboxDebouncedOnClick( - "TopicsConsentModal" - + PrivacySandboxDialogUtils.getSurfaceTypeAsString(mSurfaceType)) { - @Override - public void processClick(View v) { - processClickImpl(v); - } - }; - } - - private void handleAdsApiUxEnhancements() { - if (!ChromeFeatureList.isEnabled( - ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS)) { - return; - } - Context context = mActivityWindowAndroid.getContext().get(); - assumeNonNull(context); - TextViewWithLeading description1 = - mContentView.findViewById(R.id.privacy_sandbox_m1_consent_description_1); - TextViewWithLeading description2 = - mContentView.findViewById(R.id.privacy_sandbox_m1_consent_description_2); - TextViewWithLeading description4 = - mContentView.findViewById(R.id.privacy_sandbox_m1_consent_description_4); - // Removing and modifying descriptions to be visible. - description1.setVisibility(View.GONE); - description2.setText( - context.getString(R.string.privacy_sandbox_m1_consent_description_2_v2)); - description4.setText( - context.getString(R.string.privacy_sandbox_m1_consent_description_4_v2)); - // Modifying the string used for the bullet points in the dropdown container. - mLearnMoreBullet1StringRes = R.string.privacy_sandbox_m1_consent_learn_more_bullet_1_v2; - mLearnMoreBullet2StringRes = R.string.privacy_sandbox_m1_consent_learn_more_bullet_2_v2; - mLearnMoreBullet3StringRes = R.string.privacy_sandbox_m1_consent_learn_more_bullet_3_v2; - // Modifying the id and string used for the privacy policy link - mLearnMoreTextIdRes = R.id.privacy_sandbox_m1_consent_learn_more_bullet_2_description; - mLearnMoreLinkString = - R.string.privacy_sandbox_m1_consent_learn_more_bullet_2_description_clank; - // Handling Ad Topics Content Parity feature - the changes are made on top of the changes to - // the Ads API UX Enhancements. - if (ChromeFeatureList.isEnabled( - ChromeFeatureList.PRIVACY_SANDBOX_AD_TOPICS_CONTENT_PARITY)) { - description2.setText( - context.getString( - R.string.privacy_sandbox_m1_consent_description_1_content_parity)); - mLearnMoreLinkString = - R.string - .privacy_sandbox_m1_consent_learn_more_bullet_2_description_content_parity_clank; - } - } - - @Override - public void show() { - mPrivacySandboxBridge.promptActionOccurred(PromptAction.CONSENT_SHOWN, mSurfaceType); - super.show(); - } - - public void processClickImpl(View view) { - int id = view.getId(); - if (id == R.id.ack_button) { - RecordUserAction.record("Settings.PrivacySandbox.ConsentDialog.AckClicked"); - mPrivacySandboxBridge.promptActionOccurred(PromptAction.CONSENT_ACCEPTED, mSurfaceType); - dismissAndMaybeShowNotice(); - } else if (id == R.id.no_button) { - RecordUserAction.record("Settings.PrivacySandbox.ConsentDialog.NoClicked"); - mPrivacySandboxBridge.promptActionOccurred(PromptAction.CONSENT_DECLINED, mSurfaceType); - dismissAndMaybeShowNotice(); - } else if (id == R.id.more_button) { - mPrivacySandboxBridge.promptActionOccurred( - PromptAction.CONSENT_MORE_BUTTON_CLICKED, mSurfaceType); - if (mScrollView.canScrollVertically(ScrollView.FOCUS_DOWN)) { - mScrollView.post( - () -> { - mScrollView.pageScroll(ScrollView.FOCUS_DOWN); - }); - } else { - mMoreButton.setVisibility(View.GONE); - mActionButtons.setVisibility(View.VISIBLE); - mScrollView.post( - () -> { - mScrollView.pageScroll(ScrollView.FOCUS_DOWN); - }); - } - } else if (id == R.id.dropdown_element) { - if (isDropdownExpanded()) { - mPrivacySandboxBridge.promptActionOccurred( - PromptAction.CONSENT_MORE_INFO_CLOSED, mSurfaceType); - mDropdownContainer.setVisibility(View.GONE); - mDropdownContainer.removeAllViews(); - mScrollView.post( - () -> { - mScrollView.fullScroll(ScrollView.FOCUS_DOWN); - }); - } else { - mDropdownContainer.setVisibility(View.VISIBLE); - mPrivacySandboxBridge.promptActionOccurred( - PromptAction.CONSENT_MORE_INFO_OPENED, mSurfaceType); - LayoutInflater.from(getContext()) - .inflate(R.layout.privacy_sandbox_consent_eea_dropdown, mDropdownContainer); - - PrivacySandboxDialogUtils.setBulletTextWithBoldContent( - getContext(), - mDropdownContainer, - R.id.privacy_sandbox_m1_consent_learn_more_bullet_one, - mLearnMoreBullet1StringRes); - PrivacySandboxDialogUtils.setBulletTextWithBoldContent( - getContext(), - mDropdownContainer, - R.id.privacy_sandbox_m1_consent_learn_more_bullet_two, - mLearnMoreBullet2StringRes); - PrivacySandboxDialogUtils.setBulletTextWithBoldContent( - getContext(), - mDropdownContainer, - R.id.privacy_sandbox_m1_consent_learn_more_bullet_three, - mLearnMoreBullet3StringRes); - // Removing the old learn more text and setting the new one to be visible. These - // changes aren't included in the handleAdsApiUxEnhancements function due to the - // mDropdownContainer not containing any views until this point. - if (ChromeFeatureList.isEnabled( - ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS)) { - mContentView.findViewById(mLearnMoreTextIdRes).setVisibility(View.VISIBLE); - mContentView - .findViewById(R.id.privacy_sandbox_learn_more_text) - .setVisibility(View.GONE); - } - - mScrollView.post( - () -> { - mScrollView.scrollTo(0, mDropdownElement.getTop()); - }); - handlePrivacyPolicyLink(); - } - - mExpandArrowView.setChecked(isDropdownExpanded()); - PrivacySandboxDialogUtils.updateDropdownControlContentDescription( - getContext(), - view, - isDropdownExpanded(), - R.string.privacy_sandbox_m1_consent_learn_more_expand_label); - view.announceForAccessibility( - getContext() - .getString( - isDropdownExpanded() - ? R.string.accessibility_expanded_group - : R.string.accessibility_collapsed_group)); - } else if (id == R.id.privacy_policy_back_button) { - handlePrivacyPolicyBackButtonClicked(); - } - } - - private void handlePrivacyPolicyBackButtonClicked() { - mPrivacyPolicyView.setVisibility(View.GONE); - mPrivacyPolicyContent.removeAllViews(); - mConsentViewContainer.setVisibility(View.VISIBLE); - } - - private void handlePrivacyPolicyLink() { - mLearnMoreText = mContentView.findViewById(mLearnMoreTextIdRes); - mLearnMoreText.setText( - SpanApplier.applySpans( - getContext().getString(mLearnMoreLinkString), - new SpanApplier.SpanInfo( - "<link>", - "</link>", - new ChromeClickableSpan( - getContext(), this::onPrivacyPolicyClicked)))); - mLearnMoreText.setMovementMethod(LinkMovementMethod.getInstance()); - if (mThinWebView == null || mWebContents == null || mWebContents.isDestroyed()) { - mWebContents = WebContentsFactory.createWebContents(mProfile, true, false); - mWebContentsObserver = - new WebContentsObserver(mWebContents) { - @Override - public void didFirstVisuallyNonEmptyPaint() { - if (!mIsPrivacyPageLoaded) { - RecordHistogram.recordTimesHistogram( - "PrivacySandbox.PrivacyPolicy.LoadingTime", - System.currentTimeMillis() - - mPrivacyPolicyClickedTimestamp); - mIsPrivacyPageLoaded = true; - } - } - - @Override - public void didFailLoad( - boolean isInPrimaryMainFrame, - int errorCode, - GURL failingUrl, - @LifecycleState int rfhLifecycleState) { - RecordHistogram.recordSparseHistogram( - "PrivacySandbox.PrivacyPolicy.FailedLoadErrorCode", errorCode); - } - }; - mThinWebView = - PrivacySandboxDialogController.createPrivacyPolicyThinWebView( - mWebContents, mProfile, mActivityWindowAndroid); - } - } - - @Override - public void onShow(DialogInterface dialogInterface) { - if (mScrollView.canScrollVertically(ScrollView.FOCUS_DOWN)) { - mMoreButton.setVisibility(View.VISIBLE); - mActionButtons.setVisibility(View.GONE); - } else { - mMoreButton.setVisibility(View.GONE); - mActionButtons.setVisibility(View.VISIBLE); - } - } - - /** - * Handles clicks on the Privacy Policy link. If a ThinWebView is available, loads and displays - * the privacy policy within it, replacing the consent view. - * - * @param unused_view The View that was clicked (typically the TextView containing the link). - */ - private void onPrivacyPolicyClicked(View unused_view) { - RecordUserAction.record("Settings.PrivacySandbox.Consent.PrivacyPolicyLinkClicked"); - mPrivacyPolicyClickedTimestamp = System.currentTimeMillis(); - mPrivacyPolicyContent.removeAllViews(); - if (mThinWebView != null && mThinWebView.getView() != null) { - mConsentViewContainer.setVisibility(View.GONE); - mPrivacyPolicyContent.addView(mThinWebView.getView()); - mPrivacyPolicyView.setVisibility(View.VISIBLE); - } - } - - @Override - protected void onStop() { - super.onStop(); - - // Clean up the WebContents, WebContentsObserver and when the dialog is stopped - if (mThinWebView != null) { - assumeNonNull(mWebContents); - assumeNonNull(mWebContentsObserver); - mWebContents.destroy(); - mWebContents = null; - mWebContentsObserver.observe(null); - mWebContentsObserver = null; - mThinWebView.destroy(); - mThinWebView = null; - } - } - - private void dismissAndMaybeShowNotice() { - mProgressBarContainer.setVisibility(View.VISIBLE); - mConsentViewContainer.setVisibility(View.GONE); - var consentHandler = new Handler(); - // Dismiss has a bigger timeout than spinner in order to guarantee a graceful transition - // between the spinner view and the notice one. - consentHandler.postDelayed(this::dismiss, getDismissTimeout()); - consentHandler.postDelayed(this::showNotice, getSpinnerTimeout()); - } - - private void showNotice() { - PrivacySandboxDialogController.showNoticeEEA( - mActivity, mPrivacySandboxBridge, mSurfaceType, mProfile, mActivityWindowAndroid); - } - - private boolean isDropdownExpanded() { - return mDropdownContainer != null && mDropdownContainer.getVisibility() == View.VISIBLE; - } - - private long getDismissTimeout() { - return mAreAnimationsDisabled ? 0 : SPINNER_DURATION_MS + BACKGROUND_TRANSITION_DURATION_MS; - } - - private long getSpinnerTimeout() { - return mAreAnimationsDisabled ? 0 : SPINNER_DURATION_MS; - } - - public int getSurfaceTypeForTesting() { - return mSurfaceType; - } -}
diff --git a/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogController.java b/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogController.java index d43ebec..86cfbe7 100644 --- a/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogController.java +++ b/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogController.java
@@ -16,7 +16,6 @@ import org.chromium.base.version_info.VersionInfo; import org.chromium.build.annotations.NullMarked; import org.chromium.build.annotations.Nullable; -import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.components.embedder_support.delegate.WebContentsDelegateAndroid; import org.chromium.components.embedder_support.view.ContentView; @@ -36,10 +35,7 @@ @NullMarked public class PrivacySandboxDialogController { private static @Nullable WeakReference<Dialog> sDialog; - private static boolean sDisableAnimations; - private static boolean sDisableEEANoticeForTesting; private static boolean sShowMoreButtonForTesting; - private static @Nullable Runnable sOnDialogDismissedRunnable; public static boolean shouldShowPrivacySandboxDialog(Profile profile, int surfaceType) { assert profile != null; @@ -48,10 +44,7 @@ } @PromptType int promptType = new PrivacySandboxBridge(profile).getRequiredPromptType(surfaceType); - if (promptType != PromptType.M1_CONSENT - && promptType != PromptType.M1_NOTICE_EEA - && promptType != PromptType.M1_NOTICE_ROW - && promptType != PromptType.M1_NOTICE_RESTRICTED) { + if (promptType != PromptType.M1_NOTICE_RESTRICTED) { return false; } return true; @@ -111,43 +104,6 @@ switch (promptType) { case PromptType.NONE: return false; - case PromptType.M1_CONSENT: - dialog = - new PrivacySandboxDialogConsentEEA( - activity, - privacySandboxBridge, - sDisableAnimations, - surfaceType, - profile, - activityWindowAndroid); - dialog.show(); - sDialog = new WeakReference<>(dialog); - return true; - case PromptType.M1_NOTICE_EEA: - showNoticeEEA( - activity, - privacySandboxBridge, - surfaceType, - profile, - activityWindowAndroid); - return true; - case PromptType.M1_NOTICE_ROW: - dialog = - new PrivacySandboxDialogNoticeROW( - activity, - privacySandboxBridge, - surfaceType, - profile, - activityWindowAndroid); - dialog.setOnDismissListener( - d -> { - if (sOnDialogDismissedRunnable != null) { - sOnDialogDismissedRunnable.run(); - } - }); - dialog.show(); - sDialog = new WeakReference<>(dialog); - return true; case PromptType.M1_NOTICE_RESTRICTED: dialog = new PrivacySandboxDialogNoticeRestricted( @@ -165,60 +121,12 @@ } } - /** Shows the NoticeEEA dialog. */ - public static void showNoticeEEA( - Activity activity, - PrivacySandboxBridge privacySandboxBridge, - @SurfaceType int surfaceType, - Profile profile, - ActivityWindowAndroid activityWindowAndroid) { - if (!sDisableEEANoticeForTesting) { - Dialog dialog; - if (ChromeFeatureList.isEnabled( - ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS)) { - dialog = - new PrivacySandboxDialogNoticeEeaV2( - activity, - privacySandboxBridge, - surfaceType, - profile, - activityWindowAndroid); - } else { - dialog = - new PrivacySandboxDialogNoticeEEA( - activity, privacySandboxBridge, surfaceType); - } - dialog.setOnDismissListener( - d -> { - if (sOnDialogDismissedRunnable != null) { - sOnDialogDismissedRunnable.run(); - } - }); - dialog.show(); - sDialog = new WeakReference<>(dialog); - } - } - - public static void setOnDialogDismissRunnable(Runnable runnable) { - sOnDialogDismissedRunnable = runnable; - } - @VisibleForTesting static @Nullable Dialog getDialog() { return sDialog != null ? sDialog.get() : null; } @VisibleForTesting - static void disableAnimations(boolean disable) { - sDisableAnimations = disable; - } - - @VisibleForTesting - static void disableEEANotice(boolean disable) { - sDisableEEANoticeForTesting = disable; - } - - @VisibleForTesting static void setShowMoreButton(boolean value) { sShowMoreButtonForTesting = value; }
diff --git a/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogNoticeEEA.java b/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogNoticeEEA.java deleted file mode 100644 index d909c45..0000000 --- a/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogNoticeEEA.java +++ /dev/null
@@ -1,217 +0,0 @@ -// Copyright 2022 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package org.chromium.chrome.browser.privacy_sandbox; - -import android.app.Activity; -import android.content.DialogInterface; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.ScrollView; - -import org.chromium.base.metrics.RecordUserAction; -import org.chromium.build.annotations.NullMarked; -import org.chromium.chrome.browser.ui.edge_to_edge.EdgeToEdgeUtils; -import org.chromium.components.browser_ui.widget.ChromeDialog; -import org.chromium.ui.widget.ButtonCompat; -import org.chromium.ui.widget.CheckableImageView; - -/** Dialog in the form of a notice shown for the Privacy Sandbox. */ -@NullMarked -public class PrivacySandboxDialogNoticeEEA extends ChromeDialog - implements DialogInterface.OnShowListener { - private final PrivacySandboxBridge mPrivacySandboxBridge; - private final View mContentView; - - private final ButtonCompat mMoreButton; - private final LinearLayout mActionButtons; - private final ScrollView mScrollView; - private final LinearLayout mDropdownElement; - - private final CheckableImageView mExpandArrowView; - private final LinearLayout mDropdownContainer; - private final @SurfaceType int mSurfaceType; - private final View.OnClickListener mOnClickListener; - - public PrivacySandboxDialogNoticeEEA( - Activity activity, - PrivacySandboxBridge privacySandboxBridge, - @SurfaceType int surfaceType) { - super( - activity, - R.style.ThemeOverlay_BrowserUI_Fullscreen, - EdgeToEdgeUtils.isEdgeToEdgeEverywhereEnabled()); - - mPrivacySandboxBridge = privacySandboxBridge; - mSurfaceType = surfaceType; - mContentView = - LayoutInflater.from(activity).inflate(R.layout.privacy_sandbox_notice_eea, null); - setContentView(mContentView); - mOnClickListener = getOnClickListener(); - - ButtonCompat ackButton = mContentView.findViewById(R.id.ack_button); - ackButton.setOnClickListener(mOnClickListener); - ButtonCompat settingsButton = mContentView.findViewById(R.id.settings_button); - settingsButton.setOnClickListener(mOnClickListener); - - mMoreButton = mContentView.findViewById(R.id.more_button); - mActionButtons = mContentView.findViewById(R.id.action_buttons); - mScrollView = mContentView.findViewById(R.id.privacy_sandbox_dialog_scroll_view); - - // Controls for the expanding section. - mDropdownElement = mContentView.findViewById(R.id.dropdown_element); - mDropdownElement.setOnClickListener(mOnClickListener); - mDropdownContainer = mContentView.findViewById(R.id.dropdown_container); - mExpandArrowView = mContentView.findViewById(R.id.expand_arrow); - mExpandArrowView.setImageDrawable(PrivacySandboxDialogUtils.createExpandDrawable(activity)); - mExpandArrowView.setChecked(isDropdownExpanded()); - - setBulletsDescription(); - - mMoreButton.setOnClickListener(mOnClickListener); - setOnShowListener(this); - setCancelable(false); - - mScrollView - .getViewTreeObserver() - .addOnScrollChangedListener( - () -> { - if (!mScrollView.canScrollVertically(ScrollView.FOCUS_DOWN)) { - mMoreButton.setVisibility(View.GONE); - mActionButtons.setVisibility(View.VISIBLE); - mScrollView.post( - () -> { - mScrollView.pageScroll(ScrollView.FOCUS_DOWN); - }); - } - }); - } - - private View.OnClickListener getOnClickListener() { - return new PrivacySandboxDebouncedOnClick( - "ProtectedAudienceMeasurementNoticeModal" - + PrivacySandboxDialogUtils.getSurfaceTypeAsString(mSurfaceType)) { - @Override - public void processClick(View v) { - processClickImpl(v); - } - }; - } - - @Override - public void show() { - mPrivacySandboxBridge.promptActionOccurred(PromptAction.NOTICE_SHOWN, mSurfaceType); - super.show(); - } - - public void processClickImpl(View view) { - int id = view.getId(); - if (id == R.id.ack_button) { - RecordUserAction.record("Settings.PrivacySandbox.NoticeEeaDialog.AckClicked"); - mPrivacySandboxBridge.promptActionOccurred( - PromptAction.NOTICE_ACKNOWLEDGE, mSurfaceType); - dismiss(); - } else if (id == R.id.settings_button) { - RecordUserAction.record("Settings.PrivacySandbox.NoticeEeaDialog.OpenSettingsClicked"); - mPrivacySandboxBridge.promptActionOccurred( - PromptAction.NOTICE_OPEN_SETTINGS, mSurfaceType); - dismiss(); - PrivacySandboxSettingsBaseFragment.launchPrivacySandboxSettings( - getContext(), PrivacySandboxReferrer.PRIVACY_SANDBOX_NOTICE); - } else if (id == R.id.more_button) { - mPrivacySandboxBridge.promptActionOccurred( - PromptAction.NOTICE_MORE_BUTTON_CLICKED, mSurfaceType); - if (mScrollView.canScrollVertically(ScrollView.FOCUS_DOWN)) { - mScrollView.post( - () -> { - mScrollView.pageScroll(ScrollView.FOCUS_DOWN); - }); - } else { - mMoreButton.setVisibility(View.GONE); - mActionButtons.setVisibility(View.VISIBLE); - mScrollView.post( - () -> { - mScrollView.pageScroll(ScrollView.FOCUS_DOWN); - }); - } - } else if (id == R.id.dropdown_element) { - if (isDropdownExpanded()) { - mPrivacySandboxBridge.promptActionOccurred( - PromptAction.NOTICE_MORE_INFO_CLOSED, mSurfaceType); - mDropdownContainer.setVisibility(View.GONE); - mDropdownContainer.removeAllViews(); - } else { - mDropdownContainer.setVisibility(View.VISIBLE); - mPrivacySandboxBridge.promptActionOccurred( - PromptAction.NOTICE_MORE_INFO_OPENED, mSurfaceType); - LayoutInflater.from(getContext()) - .inflate(R.layout.privacy_sandbox_notice_eea_dropdown, mDropdownContainer); - - PrivacySandboxDialogUtils.setBulletTextWithBoldContent( - getContext(), - mDropdownContainer, - R.id.privacy_sandbox_m1_notice_eea_learn_more_bullet_one, - R.string.privacy_sandbox_m1_notice_eea_learn_more_bullet_1); - PrivacySandboxDialogUtils.setBulletTextWithBoldContent( - getContext(), - mDropdownContainer, - R.id.privacy_sandbox_m1_notice_eea_learn_more_bullet_two, - R.string.privacy_sandbox_m1_notice_eea_learn_more_bullet_2); - PrivacySandboxDialogUtils.setBulletTextWithBoldContent( - getContext(), - mDropdownContainer, - R.id.privacy_sandbox_m1_notice_eea_learn_more_bullet_three, - R.string.privacy_sandbox_m1_notice_eea_learn_more_bullet_3); - - mScrollView.post( - () -> { - mScrollView.scrollTo(0, mDropdownElement.getTop()); - }); - } - - mExpandArrowView.setChecked(isDropdownExpanded()); - PrivacySandboxDialogUtils.updateDropdownControlContentDescription( - getContext(), - view, - isDropdownExpanded(), - R.string.privacy_sandbox_m1_notice_eea_learn_more_expand_label); - view.announceForAccessibility( - getContext() - .getString( - isDropdownExpanded() - ? R.string.accessibility_expanded_group - : R.string.accessibility_collapsed_group)); - } - } - - @Override - public void onShow(DialogInterface dialogInterface) { - if (mScrollView.canScrollVertically(ScrollView.FOCUS_DOWN)) { - mMoreButton.setVisibility(View.VISIBLE); - mActionButtons.setVisibility(View.GONE); - } else { - mMoreButton.setVisibility(View.GONE); - mActionButtons.setVisibility(View.VISIBLE); - } - mScrollView.setVisibility(View.VISIBLE); - } - - private void setBulletsDescription() { - PrivacySandboxDialogUtils.setBulletText( - getContext(), - mContentView, - R.id.privacy_sandbox_m1_notice_eea_bullet_one, - R.string.privacy_sandbox_m1_notice_eea_bullet_1); - PrivacySandboxDialogUtils.setBulletText( - getContext(), - mContentView, - R.id.privacy_sandbox_m1_notice_eea_bullet_two, - R.string.privacy_sandbox_m1_notice_eea_bullet_2); - } - - private boolean isDropdownExpanded() { - return mDropdownContainer != null && mDropdownContainer.getVisibility() == View.VISIBLE; - } -}
diff --git a/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogNoticeEeaV2.java b/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogNoticeEeaV2.java deleted file mode 100644 index eac7339..0000000 --- a/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogNoticeEeaV2.java +++ /dev/null
@@ -1,394 +0,0 @@ -// Copyright 2024 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package org.chromium.chrome.browser.privacy_sandbox; - -import static org.chromium.build.NullUtil.assumeNonNull; - -import android.app.Activity; -import android.content.DialogInterface; -import android.text.method.LinkMovementMethod; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ScrollView; - -import org.chromium.base.metrics.RecordHistogram; -import org.chromium.base.metrics.RecordUserAction; -import org.chromium.build.annotations.NullMarked; -import org.chromium.build.annotations.Nullable; -import org.chromium.chrome.browser.content.WebContentsFactory; -import org.chromium.chrome.browser.profiles.Profile; -import org.chromium.chrome.browser.ui.edge_to_edge.EdgeToEdgeUtils; -import org.chromium.components.browser_ui.widget.ChromeDialog; -import org.chromium.components.thinwebview.ThinWebView; -import org.chromium.content_public.browser.LifecycleState; -import org.chromium.content_public.browser.WebContents; -import org.chromium.content_public.browser.WebContentsObserver; -import org.chromium.ui.base.ActivityWindowAndroid; -import org.chromium.ui.text.ChromeClickableSpan; -import org.chromium.ui.text.SpanApplier; -import org.chromium.ui.widget.ButtonCompat; -import org.chromium.ui.widget.CheckableImageView; -import org.chromium.ui.widget.ChromeImageButton; -import org.chromium.ui.widget.TextViewWithLeading; -import org.chromium.url.GURL; - -/** - * Dialog in the form of a notice shown for the Privacy Sandbox - V2. This new version is part of - * the Ads API UX Enhancement. - */ -@NullMarked -public class PrivacySandboxDialogNoticeEeaV2 extends ChromeDialog - implements DialogInterface.OnShowListener { - private final PrivacySandboxBridge mPrivacySandboxBridge; - private final View mContentView; - - private final LinearLayout mNoticeViewContainer; - private final ButtonCompat mMoreButton; - private final LinearLayout mActionButtons; - private final ScrollView mScrollView; - private final LinearLayout mSiteSuggestedAdsDropdownElement; - private final LinearLayout mAdMeasurementDropdownElement; - - private final CheckableImageView mSiteSuggestedAdsExpandArrowView; - private final CheckableImageView mAdMeasurementExpandArrowView; - private final LinearLayout mSiteSuggestedAdsDropdownContainer; - private final LinearLayout mAdMeasurementDropdownContainer; - private final @SurfaceType int mSurfaceType; - - private final LinearLayout mPrivacyPolicyView; - private final FrameLayout mPrivacyPolicyContent; - private final ChromeImageButton mPrivacyPolicyBackButton; - private @Nullable TextViewWithLeading mLearnMoreBullet1Description; - private @Nullable ThinWebView mThinWebView; - private @Nullable WebContents mWebContents; - private @Nullable WebContentsObserver mWebContentsObserver; - private final Profile mProfile; - private long mPrivacyPolicyClickedTimestamp; - private final ActivityWindowAndroid mActivityWindowAndroid; - private boolean mIsPrivacyPageLoaded; - private final View.OnClickListener mOnClickListener; - - public PrivacySandboxDialogNoticeEeaV2( - Activity activity, - PrivacySandboxBridge privacySandboxBridge, - @SurfaceType int surfaceType, - Profile profile, - ActivityWindowAndroid activityWindowAndroid) { - super( - activity, - R.style.ThemeOverlay_BrowserUI_Fullscreen, - EdgeToEdgeUtils.isEdgeToEdgeEverywhereEnabled()); - - mPrivacySandboxBridge = privacySandboxBridge; - mSurfaceType = surfaceType; - mProfile = profile; - mActivityWindowAndroid = activityWindowAndroid; - mContentView = - LayoutInflater.from(activity).inflate(R.layout.privacy_sandbox_notice_eea_v2, null); - setContentView(mContentView); - mOnClickListener = getOnClickListener(); - - ButtonCompat ackButton = mContentView.findViewById(R.id.ack_button); - ackButton.setOnClickListener(mOnClickListener); - ButtonCompat settingsButton = mContentView.findViewById(R.id.settings_button); - settingsButton.setOnClickListener(mOnClickListener); - - mMoreButton = mContentView.findViewById(R.id.more_button); - mActionButtons = mContentView.findViewById(R.id.action_buttons); - mScrollView = mContentView.findViewById(R.id.privacy_sandbox_dialog_scroll_view); - - // Controls for the Site Suggested Ads expanding section. - mSiteSuggestedAdsDropdownElement = - mContentView.findViewById(R.id.site_suggested_ads_dropdown_element); - mSiteSuggestedAdsDropdownElement.setOnClickListener(mOnClickListener); - mSiteSuggestedAdsDropdownContainer = - mContentView.findViewById(R.id.site_suggested_ads_dropdown_container); - mSiteSuggestedAdsExpandArrowView = - mContentView.findViewById(R.id.site_suggested_ads_expand_arrow); - mSiteSuggestedAdsExpandArrowView.setImageDrawable( - PrivacySandboxDialogUtils.createExpandDrawable(activity)); - mSiteSuggestedAdsExpandArrowView.setChecked(isSiteSuggestedAdsDropdownExpanded()); - - // Controls for the Ad Measurement expanding section. - mAdMeasurementDropdownElement = - mContentView.findViewById(R.id.ad_measurement_dropdown_element); - mAdMeasurementDropdownElement.setOnClickListener(mOnClickListener); - mAdMeasurementDropdownContainer = - mContentView.findViewById(R.id.ad_measurement_dropdown_container); - mAdMeasurementExpandArrowView = mContentView.findViewById(R.id.ad_measurement_expand_arrow); - mAdMeasurementExpandArrowView.setImageDrawable( - PrivacySandboxDialogUtils.createExpandDrawable(activity)); - mAdMeasurementExpandArrowView.setChecked(isMeasurementDropdownExpanded()); - - mNoticeViewContainer = mContentView.findViewById(R.id.privacy_sandbox_notice_eea_view); - mPrivacyPolicyView = mContentView.findViewById(R.id.privacy_policy_view); - mPrivacyPolicyContent = mContentView.findViewById(R.id.privacy_policy_content); - mPrivacyPolicyBackButton = mContentView.findViewById(R.id.privacy_policy_back_button); - mPrivacyPolicyBackButton.setOnClickListener(mOnClickListener); - mIsPrivacyPageLoaded = false; - - mMoreButton.setOnClickListener(mOnClickListener); - setOnShowListener(this); - setCancelable(false); - - mScrollView - .getViewTreeObserver() - .addOnScrollChangedListener( - () -> { - if (!mScrollView.canScrollVertically(ScrollView.FOCUS_DOWN)) { - mMoreButton.setVisibility(View.GONE); - mActionButtons.setVisibility(View.VISIBLE); - mScrollView.post( - () -> { - mScrollView.pageScroll(ScrollView.FOCUS_DOWN); - }); - } - }); - } - - private View.OnClickListener getOnClickListener() { - return new PrivacySandboxDebouncedOnClick( - "ProtectedAudienceMeasurementNoticeModal" - + PrivacySandboxDialogUtils.getSurfaceTypeAsString(mSurfaceType)) { - @Override - public void processClick(View v) { - processClickImpl(v); - } - }; - } - - @Override - public void show() { - mPrivacySandboxBridge.promptActionOccurred(PromptAction.NOTICE_SHOWN, mSurfaceType); - super.show(); - } - - private void handleAckButtonClick() { - mPrivacySandboxBridge.promptActionOccurred(PromptAction.NOTICE_ACKNOWLEDGE, mSurfaceType); - dismiss(); - } - - private void handleSettingsButtonClick() { - mPrivacySandboxBridge.promptActionOccurred(PromptAction.NOTICE_OPEN_SETTINGS, mSurfaceType); - dismiss(); - PrivacySandboxSettingsBaseFragment.launchPrivacySandboxSettings( - getContext(), PrivacySandboxReferrer.PRIVACY_SANDBOX_NOTICE); - } - - private void handleMoreButtonClick() { - mPrivacySandboxBridge.promptActionOccurred( - PromptAction.NOTICE_MORE_BUTTON_CLICKED, mSurfaceType); - mScrollView.post(() -> mScrollView.pageScroll(ScrollView.FOCUS_DOWN)); - } - - private void handleSiteSuggestedAdsDropdownClick(View view) { - if (isSiteSuggestedAdsDropdownExpanded()) { - mPrivacySandboxBridge.promptActionOccurred( - PromptAction.NOTICE_SITE_SUGGESTED_ADS_MORE_INFO_CLOSED, mSurfaceType); - mSiteSuggestedAdsDropdownContainer.setVisibility(View.GONE); - mSiteSuggestedAdsDropdownContainer.removeAllViews(); - } else { - mPrivacySandboxBridge.promptActionOccurred( - PromptAction.NOTICE_SITE_SUGGESTED_ADS_MORE_INFO_OPENED, mSurfaceType); - mSiteSuggestedAdsDropdownContainer.setVisibility(View.VISIBLE); - LayoutInflater.from(getContext()) - .inflate( - R.layout.privacy_sandbox_notice_eea_site_suggested_ads_dropdown, - mSiteSuggestedAdsDropdownContainer); - - PrivacySandboxDialogUtils.setBulletTextWithBoldContent( - getContext(), - mSiteSuggestedAdsDropdownContainer, - R.id.privacy_sandbox_m1_notice_eea_site_suggested_ads_learn_more_bullet_one, - R.string.privacy_sandbox_m1_notice_eea_site_suggested_ads_learn_more_bullet_1); - PrivacySandboxDialogUtils.setBulletTextWithBoldContent( - getContext(), - mSiteSuggestedAdsDropdownContainer, - R.id.privacy_sandbox_m1_notice_eea_site_suggested_ads_learn_more_bullet_two, - R.string.privacy_sandbox_m1_notice_eea_site_suggested_ads_learn_more_bullet_2); - - mScrollView.post( - () -> mScrollView.scrollTo(0, mSiteSuggestedAdsDropdownElement.getTop())); - handlePrivacyPolicyLink(); - } - - mSiteSuggestedAdsExpandArrowView.setChecked(isSiteSuggestedAdsDropdownExpanded()); - PrivacySandboxDialogUtils.updateDropdownControlContentDescription( - getContext(), - view, - isSiteSuggestedAdsDropdownExpanded(), - R.string.privacy_sandbox_m1_notice_eea_site_suggested_ads_learn_more_label); - view.announceForAccessibility( - getContext() - .getString( - isSiteSuggestedAdsDropdownExpanded() - ? R.string.accessibility_expanded_group - : R.string.accessibility_collapsed_group)); - } - - private void handleAdMeasurementDropdownClick(View view) { - if (isMeasurementDropdownExpanded()) { - mPrivacySandboxBridge.promptActionOccurred( - PromptAction.NOTICE_ADS_MEASUREMENT_MORE_INFO_CLOSED, mSurfaceType); - mAdMeasurementDropdownContainer.setVisibility(View.GONE); - mAdMeasurementDropdownContainer.removeAllViews(); - } else { - mPrivacySandboxBridge.promptActionOccurred( - PromptAction.NOTICE_ADS_MEASUREMENT_MORE_INFO_OPENED, mSurfaceType); - mAdMeasurementDropdownContainer.setVisibility(View.VISIBLE); - LayoutInflater.from(getContext()) - .inflate( - R.layout.privacy_sandbox_notice_eea_ad_measurement_dropdown, - mAdMeasurementDropdownContainer); - - PrivacySandboxDialogUtils.setBulletTextWithBoldContent( - getContext(), - mAdMeasurementDropdownContainer, - R.id.privacy_sandbox_m1_notice_eea_ad_measurement_learn_more_bullet_one, - R.string.privacy_sandbox_m1_notice_eea_ad_measurement_learn_more_bullet_1); - - mScrollView.post(() -> mScrollView.scrollTo(0, mAdMeasurementDropdownElement.getTop())); - } - - mAdMeasurementExpandArrowView.setChecked(isMeasurementDropdownExpanded()); - PrivacySandboxDialogUtils.updateDropdownControlContentDescription( - getContext(), - view, - isMeasurementDropdownExpanded(), - R.string.privacy_sandbox_m1_notice_eea_ad_measurement_learn_more_label); - view.announceForAccessibility( - getContext() - .getString( - isMeasurementDropdownExpanded() - ? R.string.accessibility_expanded_group - : R.string.accessibility_collapsed_group)); - } - - private void handlePrivacyPolicyBackButtonClicked() { - mPrivacyPolicyView.setVisibility(View.GONE); - mPrivacyPolicyContent.removeAllViews(); - mNoticeViewContainer.setVisibility(View.VISIBLE); - } - - public void processClickImpl(View view) { - int id = view.getId(); - if (id == R.id.ack_button) { - handleAckButtonClick(); - } else if (id == R.id.settings_button) { - handleSettingsButtonClick(); - } else if (id == R.id.more_button) { - handleMoreButtonClick(); - } else if (id == R.id.site_suggested_ads_dropdown_element) { - handleSiteSuggestedAdsDropdownClick(view); - } else if (id == R.id.ad_measurement_dropdown_element) { - handleAdMeasurementDropdownClick(view); - } else if (id == R.id.privacy_policy_back_button) { - handlePrivacyPolicyBackButtonClicked(); - } - } - - @Override - public void onShow(DialogInterface dialogInterface) { - if (mScrollView.canScrollVertically(ScrollView.FOCUS_DOWN)) { - mMoreButton.setVisibility(View.VISIBLE); - mActionButtons.setVisibility(View.GONE); - } else { - mMoreButton.setVisibility(View.GONE); - mActionButtons.setVisibility(View.VISIBLE); - } - mScrollView.setVisibility(View.VISIBLE); - } - - @Override - protected void onStop() { - super.onStop(); - - // Clean up the WebContents, WebContentsObserver and when the dialog is stopped - if (mThinWebView != null) { - assumeNonNull(mWebContents); - assumeNonNull(mWebContentsObserver); - mWebContents.destroy(); - mWebContents = null; - mWebContentsObserver.observe(null); - mWebContentsObserver = null; - mThinWebView.destroy(); - mThinWebView = null; - } - } - - private void handlePrivacyPolicyLink() { - mLearnMoreBullet1Description = - mContentView.findViewById( - R.id - .privacy_sandbox_m1_notice_eea_site_suggested_ads_learn_more_bullet_one_description); - mLearnMoreBullet1Description.setText( - SpanApplier.applySpans( - mLearnMoreBullet1Description.getText().toString(), - new SpanApplier.SpanInfo( - "<link>", - "</link>", - new ChromeClickableSpan( - getContext(), this::onPrivacyPolicyClicked)))); - mLearnMoreBullet1Description.setMovementMethod(LinkMovementMethod.getInstance()); - if (mThinWebView == null || mWebContents == null || mWebContents.isDestroyed()) { - mWebContents = WebContentsFactory.createWebContents(mProfile, true, false); - mWebContentsObserver = - new WebContentsObserver(mWebContents) { - @Override - public void didFirstVisuallyNonEmptyPaint() { - if (!mIsPrivacyPageLoaded) { - RecordHistogram.recordTimesHistogram( - "PrivacySandbox.PrivacyPolicy.LoadingTime", - System.currentTimeMillis() - - mPrivacyPolicyClickedTimestamp); - mIsPrivacyPageLoaded = true; - } - } - - @Override - public void didFailLoad( - boolean isInPrimaryMainFrame, - int errorCode, - GURL failingUrl, - @LifecycleState int rfhLifecycleState) { - RecordHistogram.recordSparseHistogram( - "PrivacySandbox.PrivacyPolicy.FailedLoadErrorCode", errorCode); - } - }; - mThinWebView = - PrivacySandboxDialogController.createPrivacyPolicyThinWebView( - mWebContents, mProfile, mActivityWindowAndroid); - } - } - - /** - * Handles clicks on the Privacy Policy link. If a ThinWebView is available, loads and displays - * the privacy policy within it, replacing the consent view. - * - * @param unused_view The View that was clicked (typically the TextView containing the link). - */ - private void onPrivacyPolicyClicked(View unused_view) { - RecordUserAction.record("Settings.PrivacySandbox.Notice.PrivacyPolicyLinkClicked"); - mPrivacyPolicyClickedTimestamp = System.currentTimeMillis(); - mPrivacyPolicyContent.removeAllViews(); - if (mThinWebView != null && mThinWebView.getView() != null) { - mNoticeViewContainer.setVisibility(View.GONE); - mPrivacyPolicyContent.addView(mThinWebView.getView()); - mPrivacyPolicyView.setVisibility(View.VISIBLE); - } - } - - private boolean isSiteSuggestedAdsDropdownExpanded() { - return mSiteSuggestedAdsDropdownContainer != null - && mSiteSuggestedAdsDropdownContainer.getVisibility() == View.VISIBLE; - } - - private boolean isMeasurementDropdownExpanded() { - return mAdMeasurementDropdownContainer != null - && mAdMeasurementDropdownContainer.getVisibility() == View.VISIBLE; - } -}
diff --git a/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogNoticeROW.java b/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogNoticeROW.java deleted file mode 100644 index 96a060c..0000000 --- a/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogNoticeROW.java +++ /dev/null
@@ -1,381 +0,0 @@ -// Copyright 2022 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package org.chromium.chrome.browser.privacy_sandbox; - -import static org.chromium.build.NullUtil.assumeNonNull; - -import android.app.Activity; -import android.content.DialogInterface; -import android.text.method.LinkMovementMethod; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ScrollView; - -import androidx.annotation.StringRes; - -import org.chromium.base.metrics.RecordHistogram; -import org.chromium.base.metrics.RecordUserAction; -import org.chromium.build.annotations.NullMarked; -import org.chromium.build.annotations.Nullable; -import org.chromium.chrome.browser.content.WebContentsFactory; -import org.chromium.chrome.browser.flags.ChromeFeatureList; -import org.chromium.chrome.browser.profiles.Profile; -import org.chromium.chrome.browser.ui.edge_to_edge.EdgeToEdgeUtils; -import org.chromium.components.browser_ui.widget.ChromeDialog; -import org.chromium.components.thinwebview.ThinWebView; -import org.chromium.content_public.browser.LifecycleState; -import org.chromium.content_public.browser.WebContents; -import org.chromium.content_public.browser.WebContentsObserver; -import org.chromium.ui.base.ActivityWindowAndroid; -import org.chromium.ui.text.ChromeClickableSpan; -import org.chromium.ui.text.SpanApplier; -import org.chromium.ui.widget.ButtonCompat; -import org.chromium.ui.widget.CheckableImageView; -import org.chromium.ui.widget.ChromeImageButton; -import org.chromium.ui.widget.TextViewWithLeading; -import org.chromium.url.GURL; - -/** Dialog in the form of a notice shown for the Privacy Sandbox. */ -@NullMarked -public class PrivacySandboxDialogNoticeROW extends ChromeDialog - implements DialogInterface.OnShowListener { - private final PrivacySandboxBridge mPrivacySandboxBridge; - private final View mContentView; - - private final LinearLayout mNoticeViewContainer; - private final CheckableImageView mExpandArrowView; - private final LinearLayout mDropdownContainer; - private final LinearLayout mDropdownElement; - private final ButtonCompat mMoreButton; - private final LinearLayout mActionButtons; - private final ScrollView mScrollView; - private final @SurfaceType int mSurfaceType; - private @StringRes int mLearnMoreBullet2StringRes = - R.string.privacy_sandbox_m1_notice_row_learn_more_bullet_2; - - private final LinearLayout mPrivacyPolicyView; - private final FrameLayout mPrivacyPolicyContent; - private final ChromeImageButton mPrivacyPolicyBackButton; - private @Nullable TextViewWithLeading mLearnMoreDescription5V2; - private @Nullable ThinWebView mThinWebView; - private @Nullable WebContents mWebContents; - private @Nullable WebContentsObserver mWebContentsObserver; - private final Profile mProfile; - private long mPrivacyPolicyClickedTimestamp; - private final ActivityWindowAndroid mActivityWindowAndroid; - private boolean mIsPrivacyPageLoaded; - private final View.OnClickListener mOnClickListener; - - public PrivacySandboxDialogNoticeROW( - Activity activity, - PrivacySandboxBridge privacySandboxBridge, - @SurfaceType int surfaceType, - Profile profile, - ActivityWindowAndroid activityWindowAndroid) { - super( - activity, - R.style.ThemeOverlay_BrowserUI_Fullscreen, - EdgeToEdgeUtils.isEdgeToEdgeEverywhereEnabled()); - mPrivacySandboxBridge = privacySandboxBridge; - mSurfaceType = surfaceType; - mProfile = profile; - mActivityWindowAndroid = activityWindowAndroid; - mContentView = - LayoutInflater.from(activity).inflate(R.layout.privacy_sandbox_notice_row, null); - setContentView(mContentView); - mOnClickListener = getOnClickListener(); - - ButtonCompat ackButton = mContentView.findViewById(R.id.ack_button); - ackButton.setOnClickListener(mOnClickListener); - ButtonCompat settingsButton = mContentView.findViewById(R.id.settings_button); - settingsButton.setOnClickListener(mOnClickListener); - - mMoreButton = mContentView.findViewById(R.id.more_button); - mActionButtons = mContentView.findViewById(R.id.action_buttons); - mScrollView = mContentView.findViewById(R.id.privacy_sandbox_dialog_scroll_view); - - // Controls for the expanding section. - mDropdownElement = mContentView.findViewById(R.id.dropdown_element); - mDropdownElement.setOnClickListener(mOnClickListener); - mDropdownContainer = mContentView.findViewById(R.id.dropdown_container); - mExpandArrowView = mContentView.findViewById(R.id.expand_arrow); - mExpandArrowView.setImageDrawable(PrivacySandboxDialogUtils.createExpandDrawable(activity)); - mExpandArrowView.setChecked(isDropdownExpanded()); - - mNoticeViewContainer = mContentView.findViewById(R.id.privacy_sandbox_notice_row_view); - mPrivacyPolicyView = mContentView.findViewById(R.id.privacy_policy_view); - mPrivacyPolicyContent = mContentView.findViewById(R.id.privacy_policy_content); - mPrivacyPolicyBackButton = mContentView.findViewById(R.id.privacy_policy_back_button); - mPrivacyPolicyBackButton.setOnClickListener(mOnClickListener); - mIsPrivacyPageLoaded = false; - - mMoreButton.setOnClickListener(mOnClickListener); - setOnShowListener(this); - setCancelable(false); - - mScrollView - .getViewTreeObserver() - .addOnScrollChangedListener( - () -> { - if (!mScrollView.canScrollVertically(ScrollView.FOCUS_DOWN)) { - mMoreButton.setVisibility(View.GONE); - mActionButtons.setVisibility(View.VISIBLE); - mScrollView.post( - () -> { - mScrollView.pageScroll(ScrollView.FOCUS_DOWN); - }); - } - }); - handleAdsApiUxEnhancements(); - } - - private View.OnClickListener getOnClickListener() { - return new PrivacySandboxDebouncedOnClick( - "ThreeAdsAPIsNoticeModal" - + PrivacySandboxDialogUtils.getSurfaceTypeAsString(mSurfaceType)) { - @Override - public void processClick(View v) { - processClickImpl(v); - } - }; - } - - private void handleAdsApiUxEnhancements() { - if (!ChromeFeatureList.isEnabled( - ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS)) { - return; - } - mContentView - .findViewById(R.id.privacy_sandbox_m1_notice_row_description_2) - .setVisibility(View.GONE); - mContentView - .findViewById(R.id.privacy_sandbox_m1_notice_row_description_2_v2) - .setVisibility(View.VISIBLE); - ((TextViewWithLeading) - mContentView.findViewById(R.id.privacy_sandbox_m1_notice_row_description_4)) - .setText(getContext().getString(R.string.privacy_sandbox_m1_notice_row_last_text)); - mLearnMoreBullet2StringRes = R.string.privacy_sandbox_m1_notice_row_learn_more_bullet_2_v2; - } - - private void handleAdsApiUxEnhancementsDropdown() { - if (!ChromeFeatureList.isEnabled( - ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS)) { - return; - } - ((TextViewWithLeading) - mContentView.findViewById( - R.id.privacy_sandbox_m1_notice_row_learn_more_description_2)) - .setText( - getContext() - .getString( - R.string - .privacy_sandbox_m1_notice_row_learn_more_description_2_v2)); - mContentView - .findViewById(R.id.privacy_sandbox_m1_notice_row_learn_more_description_5) - .setVisibility(View.GONE); - mContentView - .findViewById(R.id.privacy_sandbox_m1_notice_row_learn_more_heading_3) - .setVisibility(View.VISIBLE); - mContentView - .findViewById(R.id.privacy_sandbox_m1_notice_row_learn_more_description_5_v2) - .setVisibility(View.VISIBLE); - mContentView - .findViewById(R.id.privacy_sandbox_m1_notice_row_learn_more_heading_1) - .setVisibility(View.GONE); - mContentView - .findViewById(R.id.privacy_sandbox_m1_notice_row_learn_more_description_android) - .setVisibility(View.GONE); - mContentView - .findViewById(R.id.privacy_sandbox_m1_notice_row_learn_more_description_android_v2) - .setVisibility(View.VISIBLE); - } - - private void handlePrivacyPolicyBackButtonClicked() { - mPrivacyPolicyView.setVisibility(View.GONE); - mPrivacyPolicyContent.removeAllViews(); - mNoticeViewContainer.setVisibility(View.VISIBLE); - } - - @Override - public void show() { - mPrivacySandboxBridge.promptActionOccurred(PromptAction.NOTICE_SHOWN, mSurfaceType); - super.show(); - } - - public void processClickImpl(View view) { - int id = view.getId(); - if (id == R.id.ack_button) { - RecordUserAction.record("Settings.PrivacySandbox.NoticeRowDialog.AckClicked"); - mPrivacySandboxBridge.promptActionOccurred( - PromptAction.NOTICE_ACKNOWLEDGE, mSurfaceType); - dismiss(); - } else if (id == R.id.settings_button) { - RecordUserAction.record("Settings.PrivacySandbox.NoticeRowDialog.OpenSettingsClicked"); - mPrivacySandboxBridge.promptActionOccurred( - PromptAction.NOTICE_OPEN_SETTINGS, mSurfaceType); - dismiss(); - PrivacySandboxSettingsBaseFragment.launchPrivacySandboxSettings( - getContext(), PrivacySandboxReferrer.PRIVACY_SANDBOX_NOTICE); - } else if (id == R.id.more_button) { - mPrivacySandboxBridge.promptActionOccurred( - PromptAction.NOTICE_MORE_BUTTON_CLICKED, mSurfaceType); - if (mScrollView.canScrollVertically(ScrollView.FOCUS_DOWN)) { - mScrollView.post( - () -> { - mScrollView.pageScroll(ScrollView.FOCUS_DOWN); - }); - } else { - mMoreButton.setVisibility(View.GONE); - mActionButtons.setVisibility(View.VISIBLE); - mScrollView.post( - () -> { - mScrollView.pageScroll(ScrollView.FOCUS_DOWN); - }); - } - } else if (id == R.id.dropdown_element) { - if (isDropdownExpanded()) { - mPrivacySandboxBridge.promptActionOccurred( - PromptAction.NOTICE_MORE_INFO_CLOSED, mSurfaceType); - mDropdownContainer.setVisibility(View.GONE); - mDropdownContainer.removeAllViews(); - } else { - mDropdownContainer.setVisibility(View.VISIBLE); - mPrivacySandboxBridge.promptActionOccurred( - PromptAction.NOTICE_MORE_INFO_OPENED, mSurfaceType); - LayoutInflater.from(getContext()) - .inflate(R.layout.privacy_sandbox_notice_row_dropdown, mDropdownContainer); - - PrivacySandboxDialogUtils.setBulletText( - getContext(), - mContentView, - R.id.privacy_sandbox_m1_notice_row_learn_more_bullet_one, - R.string.privacy_sandbox_m1_notice_row_learn_more_bullet_1); - PrivacySandboxDialogUtils.setBulletText( - getContext(), - mContentView, - R.id.privacy_sandbox_m1_notice_row_learn_more_bullet_two, - mLearnMoreBullet2StringRes); - - mScrollView.post( - () -> { - mScrollView.scrollTo(0, mDropdownElement.getTop()); - }); - handleAdsApiUxEnhancementsDropdown(); - handlePrivacyPolicyLink(); - } - - mExpandArrowView.setChecked(isDropdownExpanded()); - PrivacySandboxDialogUtils.updateDropdownControlContentDescription( - getContext(), - view, - isDropdownExpanded(), - R.string.privacy_sandbox_m1_notice_row_learn_more_expand_label); - view.announceForAccessibility( - getContext() - .getString( - isDropdownExpanded() - ? R.string.accessibility_expanded_group - : R.string.accessibility_collapsed_group)); - } else if (id == R.id.privacy_policy_back_button) { - handlePrivacyPolicyBackButtonClicked(); - } - } - - @Override - public void onShow(DialogInterface dialogInterface) { - if (mScrollView.canScrollVertically(ScrollView.FOCUS_DOWN)) { - mMoreButton.setVisibility(View.VISIBLE); - mActionButtons.setVisibility(View.GONE); - } else { - mMoreButton.setVisibility(View.GONE); - mActionButtons.setVisibility(View.VISIBLE); - } - mScrollView.setVisibility(View.VISIBLE); - } - - @Override - protected void onStop() { - super.onStop(); - - // Clean up the WebContents, WebContentsObserver and when the dialog is stopped - if (mThinWebView != null) { - assumeNonNull(mWebContents); - assumeNonNull(mWebContentsObserver); - mWebContents.destroy(); - mWebContents = null; - mWebContentsObserver.observe(null); - mWebContentsObserver = null; - mThinWebView.destroy(); - mThinWebView = null; - } - } - - private void handlePrivacyPolicyLink() { - mLearnMoreDescription5V2 = - mContentView.findViewById( - R.id.privacy_sandbox_m1_notice_row_learn_more_description_5_v2); - mLearnMoreDescription5V2.setText( - SpanApplier.applySpans( - mLearnMoreDescription5V2.getText().toString(), - new SpanApplier.SpanInfo( - "<link>", - "</link>", - new ChromeClickableSpan( - getContext(), this::onPrivacyPolicyClicked)))); - mLearnMoreDescription5V2.setMovementMethod(LinkMovementMethod.getInstance()); - if (mThinWebView == null || mWebContents == null || mWebContents.isDestroyed()) { - mWebContents = WebContentsFactory.createWebContents(mProfile, true, false); - mWebContentsObserver = - new WebContentsObserver(mWebContents) { - @Override - public void didFirstVisuallyNonEmptyPaint() { - if (!mIsPrivacyPageLoaded) { - RecordHistogram.recordTimesHistogram( - "PrivacySandbox.PrivacyPolicy.LoadingTime", - System.currentTimeMillis() - - mPrivacyPolicyClickedTimestamp); - mIsPrivacyPageLoaded = true; - } - } - - @Override - public void didFailLoad( - boolean isInPrimaryMainFrame, - int errorCode, - GURL failingUrl, - @LifecycleState int rfhLifecycleState) { - RecordHistogram.recordSparseHistogram( - "PrivacySandbox.PrivacyPolicy.FailedLoadErrorCode", errorCode); - } - }; - mThinWebView = - PrivacySandboxDialogController.createPrivacyPolicyThinWebView( - mWebContents, mProfile, mActivityWindowAndroid); - } - } - - /** - * Handles clicks on the Privacy Policy link. If a ThinWebView is available, loads and displays - * the privacy policy within it, replacing the consent view. - * - * @param unused_view The View that was clicked (typically the TextView containing the link). - */ - private void onPrivacyPolicyClicked(View unused_view) { - RecordUserAction.record("Settings.PrivacySandbox.Notice.PrivacyPolicyLinkClicked"); - mPrivacyPolicyClickedTimestamp = System.currentTimeMillis(); - mPrivacyPolicyContent.removeAllViews(); - if (mThinWebView != null && mThinWebView.getView() != null) { - mNoticeViewContainer.setVisibility(View.GONE); - mPrivacyPolicyContent.addView(mThinWebView.getView()); - mPrivacyPolicyView.setVisibility(View.VISIBLE); - } - } - - private boolean isDropdownExpanded() { - return mDropdownContainer != null && mDropdownContainer.getVisibility() == View.VISIBLE; - } -}
diff --git a/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDebouncedOnClickTest.java b/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDebouncedOnClickTest.java index ccab4ac..a471ec4 100644 --- a/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDebouncedOnClickTest.java +++ b/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDebouncedOnClickTest.java
@@ -38,12 +38,7 @@ Arrays.asList( new ParameterSet().value(R.id.ack_button).name("Ack"), new ParameterSet().value(R.id.settings_button).name("Settings"), - new ParameterSet().value(R.id.more_button).name("More"), - new ParameterSet().value(R.id.dropdown_element).name("Dropdown"), - new ParameterSet().value(R.id.no_button).name("No"), - new ParameterSet() - .value(R.id.privacy_policy_back_button) - .name("PrivacyPolicyBack")); + new ParameterSet().value(R.id.more_button).name("More")); private final View mMockView; private int mNumClicks; @@ -55,7 +50,7 @@ mButtonRid = rid; mFakeDialog = new PrivacySandboxDebouncedOnClickImpl( - "ProtectedAudienceMeasurementNoticeModal" + "MeasurementNoticeModal" + PrivacySandboxDialogUtils.getSurfaceTypeAsString( SurfaceType.BR_APP)); }
diff --git a/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogTest.java b/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogTest.java index aa7c724..cc08aaf 100644 --- a/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogTest.java +++ b/chrome/browser/privacy_sandbox/android/javatests/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxDialogTest.java
@@ -6,26 +6,20 @@ import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.action.ViewActions.scrollTo; import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; -import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.RootMatchers.isDialog; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.chromium.base.test.transit.ViewFinder.waitForView; -import static org.chromium.ui.test.util.ViewUtils.clickOnClickableSpan; import static org.chromium.ui.test.util.ViewUtils.onViewWaiting; import android.app.Dialog; import android.content.Context; -import android.os.Bundle; import android.view.View; import androidx.test.espresso.PerformException; @@ -44,15 +38,9 @@ import org.chromium.base.ThreadUtils; import org.chromium.base.test.util.CommandLineFlags; -import org.chromium.base.test.util.DisableIf; -import org.chromium.base.test.util.DisabledTest; import org.chromium.base.test.util.DoNotBatch; import org.chromium.base.test.util.Feature; -import org.chromium.base.test.util.Features.DisableFeatures; -import org.chromium.base.test.util.Features.EnableFeatures; -import org.chromium.base.test.util.HistogramWatcher; import org.chromium.base.test.util.UserActionTester; -import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.settings.SettingsNavigationFactory; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; @@ -100,7 +88,6 @@ mFakePrivacySandboxBridge = new FakePrivacySandboxBridge(); PrivacySandboxBridgeJni.setInstanceForTesting(mFakePrivacySandboxBridge); - PrivacySandboxDialogController.disableAnimations(true); SettingsNavigationFactory.setInstanceForTesting(mSettingsNavigation); mUserActionTester = new UserActionTester(); } @@ -166,18 +153,7 @@ moreClicked = true; var promptType = mFakePrivacySandboxBridge.getRequiredPromptType(SurfaceType.BR_APP); - if (promptType == PromptType.M1_CONSENT) { - assertEquals( - "Last dialog action", - PromptAction.CONSENT_MORE_BUTTON_CLICKED, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - } else if (promptType == PromptType.M1_NOTICE_EEA - || promptType == PromptType.M1_NOTICE_ROW) { - assertEquals( - "Last dialog action", - PromptAction.NOTICE_MORE_BUTTON_CLICKED, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - } else if (promptType == PromptType.M1_NOTICE_RESTRICTED) { + if (promptType == PromptType.M1_NOTICE_RESTRICTED) { assertEquals( "Last dialog action", PromptAction.RESTRICTED_NOTICE_MORE_BUTTON_CLICKED, @@ -192,435 +168,6 @@ @Test @SmallTest @Feature({"RenderTest"}) - @DisableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS) - public void renderEEAConsent() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - ThreadUtils.runOnUiThreadBlocking( - () -> { - mDialog = - new PrivacySandboxDialogConsentEEA( - mActivityTestRule.getActivity(), - new PrivacySandboxBridge(mActivityTestRule.getProfile(false)), - false, - SurfaceType.BR_APP, - mActivityTestRule.getProfile(false), - mActivityTestRule.getActivity().getWindowAndroid()); - mDialog.show(); - }); - renderViewWithId(R.id.privacy_sandbox_dialog, "privacy_sandbox_eea_consent_dialog"); - } - - @Test - @SmallTest - @Feature({"RenderTest"}) - @EnableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS) - @DisableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_AD_TOPICS_CONTENT_PARITY) - @DisabledTest(message = "https://crbug.com/414613581") - public void renderEeaConsentV2() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_CONSENT); - launchDialog(); - waitForView(withId(R.id.privacy_sandbox_dialog)); - renderViewWithId(R.id.privacy_sandbox_dialog, "privacy_sandbox_eea_consent_dialog_v2"); - } - - @Test - @SmallTest - @Feature({"RenderTest"}) - @EnableFeatures({ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS}) - @DisableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_AD_TOPICS_CONTENT_PARITY) - // TODO(crbug.com/381241999): fix and re-enable on ARM devices. - @DisableIf.Build(supported_abis_includes = "armeabi-v7a") - @DisableIf.Build(supported_abis_includes = "arm64-v8a") - public void renderEeaConsentV2PrivacyPolicyEnabled() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_CONSENT); - launchDialog(); - waitForView(withId(R.id.privacy_sandbox_dialog)); - onView(withId(R.id.dropdown_element)).inRoot(isDialog()).perform(scrollTo(), click()); - renderViewWithId( - R.id.privacy_sandbox_dialog, - "privacy_sandbox_eea_consent_dialog_v2_privacy_policy_link_shown"); - } - - @Test - @SmallTest - @Feature({"RenderTest"}) - @EnableFeatures({ - ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS, - ChromeFeatureList.PRIVACY_SANDBOX_AD_TOPICS_CONTENT_PARITY - }) - @DisabledTest(message = "https://crbug.com/425457237") - public void renderEeaConsentV2ContentParity() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_CONSENT); - launchDialog(); - waitForView(withId(R.id.privacy_sandbox_dialog)); - renderViewWithId( - R.id.privacy_sandbox_dialog, - "privacy_sandbox_eea_consent_dialog_v2_content_parity"); - } - - @Test - @SmallTest - @Feature({"RenderTest"}) - @EnableFeatures({ - ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS, - ChromeFeatureList.PRIVACY_SANDBOX_AD_TOPICS_CONTENT_PARITY - }) - @DisabledTest(message = "https://crbug.com/399734809") - public void renderEeaConsentV2ContentParityPrivacyPolicyEnabled() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_CONSENT); - launchDialog(); - waitForView(withId(R.id.privacy_sandbox_dialog)); - onView(withId(R.id.dropdown_element)).inRoot(isDialog()).perform(scrollTo(), click()); - renderViewWithId( - R.id.privacy_sandbox_dialog, - "privacy_sandbox_eea_consent_dialog_v2_content_parity_privacy_policy_link_shown"); - } - - @Test - @SmallTest - @DisableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS) - // TODO(crbug.com/369540483): fix and re-enable on ARM devices. - @DisableIf.Build(supported_abis_includes = "armeabi-v7a") - @DisableIf.Build(supported_abis_includes = "arm64-v8a") - public void eeaConsentPrivacyPolicyLink() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_CONSENT); - launchDialog(); - waitForView(withId(R.id.privacy_sandbox_dialog)); - onView(withId(R.id.dropdown_element)).inRoot(isDialog()).perform(scrollTo(), click()); - onView(withId(R.id.privacy_sandbox_learn_more_text)) - .inRoot(isDialog()) - .check(matches(isDisplayed())); - // Click "Privacy Policy" link - onView(withId(R.id.privacy_sandbox_learn_more_text)) - .inRoot(isDialog()) - .perform(clickOnClickableSpan(0)); - // Validate EEA Consent is not shown - onView(withId(R.id.privacy_sandbox_consent_eea_view)) - .inRoot(isDialog()) - .check(matches(not(isDisplayed()))); - // Validate Privacy Policy View is shown - onView(withId(R.id.privacy_policy_view)).inRoot(isDialog()).check(matches(isDisplayed())); - onView(withId(R.id.privacy_policy_title)).inRoot(isDialog()).check(matches(isDisplayed())); - onView(withId(R.id.privacy_policy_back_button)) - .inRoot(isDialog()) - .check(matches(isDisplayed())); - assertEquals( - 1, - mUserActionTester.getActionCount( - "Settings.PrivacySandbox.Consent.PrivacyPolicyLinkClicked")); - HistogramWatcher watcher = - HistogramWatcher.newSingleRecordWatcher("PrivacySandbox.PrivacyPolicy.LoadingTime"); - watcher.pollInstrumentationThreadUntilSatisfied(); - // Click back button - onView(withId(R.id.privacy_policy_back_button)).inRoot(isDialog()).perform(click()); - // Validate EEA Consent is shown - onView(withId(R.id.privacy_sandbox_consent_eea_view)) - .inRoot(isDialog()) - .check(matches(isDisplayed())); - // Validate Privacy Policy View is not shown - onView(withId(R.id.privacy_policy_view)) - .inRoot(isDialog()) - .check(matches(not(isDisplayed()))); - } - - @Test - @SmallTest - @Feature({"RenderTest"}) - @DisableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS) - // TODO(crbug.com/381241999): fix and re-enable on ARM devices. - @DisableIf.Build(supported_abis_includes = "armeabi-v7a") - @DisableIf.Build(supported_abis_includes = "arm64-v8a") - public void renderEEAConsentPrivacyPolicyLink() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - ThreadUtils.runOnUiThreadBlocking( - () -> { - mDialog = - new PrivacySandboxDialogConsentEEA( - mActivityTestRule.getActivity(), - new PrivacySandboxBridge(mActivityTestRule.getProfile(false)), - false, - SurfaceType.BR_APP, - mActivityTestRule.getProfile(false), - mActivityTestRule.getActivity().getWindowAndroid()); - mDialog.show(); - }); - waitForView(withId(R.id.privacy_sandbox_dialog)); - onView(withId(R.id.dropdown_element)).inRoot(isDialog()).perform(scrollTo(), click()); - renderViewWithId( - R.id.privacy_sandbox_dialog, "privacy_sandbox_eea_consent_privacy_policy_link"); - } - - @Test - @SmallTest - @Feature({"RenderTest"}) - @EnableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS) - public void renderEeaNoticeV2() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - ThreadUtils.runOnUiThreadBlocking( - () -> { - mDialog = - new PrivacySandboxDialogNoticeEeaV2( - mActivityTestRule.getActivity(), - new PrivacySandboxBridge(mActivityTestRule.getProfile(false)), - SurfaceType.BR_APP, - mActivityTestRule.getProfile(false), - mActivityTestRule.getActivity().getWindowAndroid()); - mDialog.show(); - }); - waitForView(withId(R.id.privacy_sandbox_dialog)); - renderViewWithId(R.id.privacy_sandbox_dialog, "privacy_sandbox_eea_notice_dialog_v2"); - } - - @Test - @SmallTest - @Feature({"RenderTest"}) - @EnableFeatures({ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS}) - public void renderEeaNoticeV2PrivacyPolicyEnabled() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_NOTICE_EEA); - launchDialog(); - waitForView(withId(R.id.privacy_sandbox_dialog)); - onView(withId(R.id.site_suggested_ads_dropdown_element)) - .inRoot(isDialog()) - .perform(scrollTo(), click()); - renderViewWithId( - R.id.privacy_sandbox_dialog, - "privacy_sandbox_eea_notice_dialog_v2_privacy_policy_link_shown"); - } - - @Test - @SmallTest - @Feature({"RenderTest"}) - @EnableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS) - public void renderEeaNoticeV2AdMeasurementDropdown() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - ThreadUtils.runOnUiThreadBlocking( - () -> { - mDialog = - new PrivacySandboxDialogNoticeEeaV2( - mActivityTestRule.getActivity(), - new PrivacySandboxBridge(mActivityTestRule.getProfile(false)), - SurfaceType.BR_APP, - mActivityTestRule.getProfile(false), - mActivityTestRule.getActivity().getWindowAndroid()); - mDialog.show(); - }); - waitForView(withId(R.id.privacy_sandbox_dialog)); - tryClickOn(withId(R.id.ad_measurement_dropdown_element)); - renderViewWithId( - R.id.privacy_sandbox_dialog, - "privacy_sandbox_eea_notice_dialog_v2_ad_measurement_dropdown"); - } - - @Test - @SmallTest - @EnableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS) - public void eeaNoticeV2AckButton() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_NOTICE_EEA); - launchDialog(); - // Verify that the EEA notice is shown. - waitForView(withId(R.id.privacy_sandbox_notice_title)); - assertEquals( - "Last dialog action", - PromptAction.NOTICE_SHOWN, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - // Ack the notice and verify it worked correctly. - tryClickOn(withId(R.id.ack_button)); - assertEquals( - "Last dialog action", - PromptAction.NOTICE_ACKNOWLEDGE, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - onView(withId(R.id.privacy_sandbox_notice_title)).check(doesNotExist()); - } - - @Test - @SmallTest - @EnableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS) - public void eeaNoticeV2SiteSuggestedAdsDropdown() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_NOTICE_EEA); - launchDialog(); - // Verify the EEA Notice is shown. - waitForView(withId(R.id.privacy_sandbox_notice_title)); - // Click on the site suggested ads expanding section and verify it worked correctly. - tryClickOn(withId(R.id.site_suggested_ads_dropdown_element)); - assertEquals( - "Last dialog action", - PromptAction.NOTICE_SITE_SUGGESTED_ADS_MORE_INFO_OPENED, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - - onView(withId(R.id.privacy_sandbox_notice_eea_site_suggested_ads_dropdown)) - .inRoot(isDialog()) - .perform(scrollTo()); - onView(withId(R.id.privacy_sandbox_notice_eea_site_suggested_ads_dropdown)) - .inRoot(isDialog()) - .check(matches(isDisplayed())); - tryClickOn(withId(R.id.site_suggested_ads_dropdown_element)); - assertEquals( - "Last dialog action", - PromptAction.NOTICE_SITE_SUGGESTED_ADS_MORE_INFO_CLOSED, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - onView(withId(R.id.privacy_sandbox_notice_eea_site_suggested_ads_dropdown)) - .check(doesNotExist()); - } - - @Test - @SmallTest - @EnableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS) - public void eeaNoticeV2AdMeasurementDropdown() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_NOTICE_EEA); - launchDialog(); - // Verify the EEA Notice is shown. - waitForView(withId(R.id.privacy_sandbox_notice_title)); - // Click on the Ad Measurement expanding section and verify it worked correctly. - tryClickOn(withId(R.id.ad_measurement_dropdown_element)); - assertEquals( - "Last dialog action", - PromptAction.NOTICE_ADS_MEASUREMENT_MORE_INFO_OPENED, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - - onView(withId(R.id.privacy_sandbox_notice_eea_ad_measurement_dropdown)) - .inRoot(isDialog()) - .perform(scrollTo()); - onView(withId(R.id.privacy_sandbox_notice_eea_ad_measurement_dropdown)) - .inRoot(isDialog()) - .check(matches(isDisplayed())); - tryClickOn(withId(R.id.ad_measurement_dropdown_element)); - assertEquals( - "Last dialog action", - PromptAction.NOTICE_ADS_MEASUREMENT_MORE_INFO_CLOSED, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - onView(withId(R.id.privacy_sandbox_notice_eea_ad_measurement_dropdown)) - .check(doesNotExist()); - } - - @Test - @SmallTest - @EnableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS) - public void eeaNoticeV2SettingsButton() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_NOTICE_EEA); - launchDialog(); - // Verify the EEA Notice is shown. - waitForView(withId(R.id.privacy_sandbox_notice_title)); - // Click on the settings button and verify it worked correctly. - tryClickOn(withId(R.id.settings_button)); - onView(withId(R.id.privacy_sandbox_notice_title)).check(doesNotExist()); - assertEquals( - "Last dialog action", - PromptAction.NOTICE_OPEN_SETTINGS, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - Mockito.verify(mSettingsNavigation) - .startSettings( - any(Context.class), - eq(PrivacySandboxSettingsFragment.class), - any(Bundle.class), - eq(false)); - } - - @Test - @SmallTest - @Feature({"RenderTest"}) - @DisableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS) - public void renderEEANotice() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - ThreadUtils.runOnUiThreadBlocking( - () -> { - mDialog = - new PrivacySandboxDialogNoticeEEA( - mActivityTestRule.getActivity(), - new PrivacySandboxBridge(mActivityTestRule.getProfile(false)), - SurfaceType.BR_APP); - mDialog.show(); - }); - renderViewWithId(R.id.privacy_sandbox_dialog, "privacy_sandbox_eea_notice_dialog"); - } - - @Test - @SmallTest - @Feature({"RenderTest"}) - @DisableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS) - public void renderEeaNoticeAdMeasurementDropdown() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - ThreadUtils.runOnUiThreadBlocking( - () -> { - mDialog = - new PrivacySandboxDialogNoticeEEA( - mActivityTestRule.getActivity(), - new PrivacySandboxBridge(mActivityTestRule.getProfile(false)), - SurfaceType.BR_APP); - mDialog.show(); - }); - waitForView(withId(R.id.privacy_sandbox_dialog)); - tryClickOn(withId(R.id.dropdown_element)); - renderViewWithId( - R.id.privacy_sandbox_dialog, - "privacy_sandbox_eea_notice_dialog_measurement_dropdown"); - } - - @Test - @SmallTest - @Feature({"RenderTest"}) - @DisableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS) - public void renderROWNotice() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - ThreadUtils.runOnUiThreadBlocking( - () -> { - mDialog = - new PrivacySandboxDialogNoticeROW( - mActivityTestRule.getActivity(), - new PrivacySandboxBridge(mActivityTestRule.getProfile(false)), - SurfaceType.BR_APP, - mActivityTestRule.getProfile(false), - mActivityTestRule.getActivity().getWindowAndroid()); - mDialog.show(); - }); - renderViewWithId(R.id.privacy_sandbox_dialog, "privacy_sandbox_row_notice_dialog"); - } - - @Test - @SmallTest - @Feature({"RenderTest"}) - @EnableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS) - @DisabledTest(message = "https://crbug.com/383531831 - the test is flaky") - public void renderRowNoticeV2() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_NOTICE_ROW); - launchDialog(); - waitForView(withId(R.id.privacy_sandbox_dialog)); - renderViewWithId(R.id.privacy_sandbox_dialog, "privacy_sandbox_row_notice_dialog_v2"); - } - - @Test - @SmallTest - @Feature({"RenderTest"}) - @EnableFeatures({ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS}) - @DisabledTest(message = "https://crbug.com/383473428 - the test is flaky") - public void renderRowNoticeV2PrivacyPolicyEnabled() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_NOTICE_ROW); - launchDialog(); - waitForView(withId(R.id.privacy_sandbox_dialog)); - onView(withId(R.id.dropdown_element)).inRoot(isDialog()).perform(scrollTo(), click()); - onView(withId(R.id.dropdown_container)).inRoot(isDialog()).check(matches(isDisplayed())); - onView(withId(R.id.privacy_sandbox_m1_notice_row_learn_more_description_5_v2)) - .inRoot(isDialog()) - .perform(scrollTo()); - renderViewWithId( - R.id.privacy_sandbox_dialog, - "privacy_sandbox_row_notice_dialog_v2_privacy_policy_link_shown"); - } - - @Test - @SmallTest - @Feature({"RenderTest"}) public void renderRestrictedNotice() throws IOException { mPage = mActivityTestRule.startOnBlankPage(); ThreadUtils.runOnUiThreadBlocking( @@ -664,248 +211,6 @@ @Test @SmallTest - public void controllerShowsEEAConsent() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - PrivacySandboxDialogController.disableEEANotice(true); - - mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_CONSENT); - launchDialog(); - - // Verify that the EEA consent is shown - waitForView(withId(R.id.privacy_sandbox_m1_consent_title)); - assertEquals( - "Last dialog action", - PromptAction.CONSENT_SHOWN, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - // Accept the consent and verify it worked correctly. - tryClickOn(withId(R.id.ack_button)); - assertEquals( - 1, - mUserActionTester.getActionCount( - "Settings.PrivacySandbox.ConsentDialog.AckClicked")); - assertEquals( - "Last dialog action", - PromptAction.CONSENT_ACCEPTED, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - onView(withId(R.id.privacy_sandbox_consent_eea_dropdown)).check(doesNotExist()); - } - - @Test - @SmallTest - public void controllerShowsEEAConsentDropdown() { - mPage = mActivityTestRule.startOnBlankPage(); - PrivacySandboxDialogController.disableEEANotice(true); - - mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_CONSENT); - launchDialog(); - - // Click on the expanding section and verify it worked correctly. - waitForView(withId(R.id.privacy_sandbox_m1_consent_title)); - onView(withId(R.id.dropdown_element)).inRoot(isDialog()).perform(scrollTo(), click()); - assertEquals( - "Last dialog action", - PromptAction.CONSENT_MORE_INFO_OPENED, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - - onView(withId(R.id.privacy_sandbox_consent_eea_dropdown)).inRoot(isDialog()).perform(scrollTo()); - onView(withId(R.id.privacy_sandbox_consent_eea_dropdown)).inRoot(isDialog()).check(matches(isDisplayed())); - onView(withId(R.id.dropdown_element)).inRoot(isDialog()).perform(scrollTo(), click()); - assertEquals( - "Last dialog action", - PromptAction.CONSENT_MORE_INFO_CLOSED, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - onView(withId(R.id.privacy_sandbox_consent_eea_dropdown)).inRoot(isDialog()).check(doesNotExist()); - - // Decline the consent and verify it worked correctly. - tryClickOn(withId(R.id.no_button)); - assertEquals( - "Last dialog action", - PromptAction.CONSENT_DECLINED, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - onView(withId(R.id.privacy_sandbox_consent_eea_dropdown)).check(doesNotExist()); - assertEquals( - 1, - mUserActionTester.getActionCount( - "Settings.PrivacySandbox.ConsentDialog.NoClicked")); - } - - @Test - @SmallTest - public void afterEEAConsentSpinnerAndNoticeAreShown() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - PrivacySandboxDialogController.disableAnimations(false); - - // Launch the consent - mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_CONSENT); - launchDialog(); - - // Accept the consent and verify the spinner it's shown. - tryClickOn(withId(R.id.ack_button)); - onView(withId(R.id.privacy_sandbox_m1_consent_title)) - .inRoot(isDialog()) - .check(matches(not(isDisplayed()))); - onView(withId(R.id.progress_bar_container)) - .inRoot(isDialog()) - .check(matches(isDisplayed())); - - // Wait for the spinner to disappear and check the notice is shown - waitForView(withId(R.id.privacy_sandbox_notice_title)); - - onView(withId(R.id.privacy_sandbox_m1_consent_title)) - .inRoot(isDialog()) - .check(doesNotExist()); - onView(withId(R.id.progress_bar_container)).inRoot(isDialog()).check(doesNotExist()); - - // Launch the consent - launchDialog(); - - // Decline the consent and verify the spinner it's shown. - tryClickOn(withId(R.id.no_button)); - onView(withId(R.id.privacy_sandbox_m1_consent_title)) - .inRoot(isDialog()) - .check(matches(not(isDisplayed()))); - - onView(withId(R.id.progress_bar_container)) - .inRoot(isDialog()) - .check(matches(isDisplayed())); - - // Wait for the spinner to disappear and check the notice is shown - waitForView(withId(R.id.privacy_sandbox_notice_title)); - onView(withId(R.id.privacy_sandbox_m1_consent_title)) - .inRoot(isDialog()) - .check(doesNotExist()); - onView(withId(R.id.progress_bar_container)).inRoot(isDialog()).check(doesNotExist()); - } - - @Test - @SmallTest - @DisableFeatures({ChromeFeatureList.PRIVACY_SANDBOX_ADS_API_UX_ENHANCEMENTS}) - public void controllerShowsEEANotice() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_NOTICE_EEA); - launchDialog(); - // Verify that the EEA notice is shown - waitForView(withId(R.id.privacy_sandbox_notice_title)); - assertEquals( - "Last dialog action", - PromptAction.NOTICE_SHOWN, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - // Ack the notice and verify it worked correctly. - tryClickOn(withId(R.id.ack_button)); - assertEquals( - "Last dialog action", - PromptAction.NOTICE_ACKNOWLEDGE, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - onView(withId(R.id.privacy_sandbox_notice_title)).check(doesNotExist()); - // check for notice ack here - assertEquals( - 1, - mUserActionTester.getActionCount( - "Settings.PrivacySandbox.NoticeEeaDialog.AckClicked")); - - launchDialog(); - // Click on the expanding section and verify it worked correctly. - waitForView(withId(R.id.privacy_sandbox_notice_title)); - onView(withId(R.id.dropdown_element)).inRoot(isDialog()).perform(scrollTo(), click()); - assertEquals( - "Last dialog action", - PromptAction.NOTICE_MORE_INFO_OPENED, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - - onView(withId(R.id.privacy_sandbox_notice_eea_dropdown)) - .inRoot(isDialog()) - .perform(scrollTo()); - onView(withId(R.id.privacy_sandbox_notice_eea_dropdown)) - .inRoot(isDialog()) - .check(matches(isDisplayed())); - onView(withId(R.id.dropdown_element)).inRoot(isDialog()).perform(scrollTo(), click()); - assertEquals( - "Last dialog action", - PromptAction.NOTICE_MORE_INFO_CLOSED, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - onView(withId(R.id.privacy_sandbox_notice_eea_dropdown)).check(doesNotExist()); - - // Click on the settings button and verify it worked correctly. - tryClickOn(withId(R.id.settings_button)); - onView(withId(R.id.privacy_sandbox_notice_title)).check(doesNotExist()); - assertEquals( - "Last dialog action", - PromptAction.NOTICE_OPEN_SETTINGS, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - assertEquals( - 1, - mUserActionTester.getActionCount( - "Settings.PrivacySandbox.NoticeEeaDialog.OpenSettingsClicked")); - Mockito.verify(mSettingsNavigation) - .startSettings( - any(Context.class), - eq(PrivacySandboxSettingsFragment.class), - any(Bundle.class), - eq(false)); - } - - @Test - @SmallTest - public void controllerShowsROWNotice() throws IOException { - mPage = mActivityTestRule.startOnBlankPage(); - mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_NOTICE_ROW); - launchDialog(); - // Verify that the ROW notice is shown - waitForView(withId(R.id.privacy_sandbox_notice_title)); - assertEquals( - "Last dialog action", - PromptAction.NOTICE_SHOWN, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - // Ack the notice and verify it worked correctly. - tryClickOn(withId(R.id.ack_button)); - assertEquals( - "Last dialog action", - PromptAction.NOTICE_ACKNOWLEDGE, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - onView(withId(R.id.privacy_sandbox_notice_title)).check(doesNotExist()); - assertEquals( - 1, - mUserActionTester.getActionCount( - "Settings.PrivacySandbox.NoticeRowDialog.AckClicked")); - - launchDialog(); - // Click on the expanding section and verify it worked correctly. - waitForView(withId(R.id.privacy_sandbox_notice_title)); - onView(withId(R.id.dropdown_element)).inRoot(isDialog()).perform(scrollTo(), click()); - assertEquals( - "Last dialog action", - PromptAction.NOTICE_MORE_INFO_OPENED, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - - onView(withId(R.id.privacy_sandbox_notice_row_dropdown)).inRoot(isDialog()).check(matches(isDisplayed())); - onView(withId(R.id.dropdown_element)).inRoot(isDialog()).perform(scrollTo(), click()); - assertEquals( - "Last dialog action", - PromptAction.NOTICE_MORE_INFO_CLOSED, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - onView(withId(R.id.privacy_sandbox_notice_row_dropdown)).inRoot(isDialog()).check(doesNotExist()); - - // Click on the settings button and verify it worked correctly. - tryClickOn(withId(R.id.settings_button)); - assertEquals( - "Last dialog action", - PromptAction.NOTICE_OPEN_SETTINGS, - (int) mFakePrivacySandboxBridge.getLastPromptAction()); - assertEquals( - 1, - mUserActionTester.getActionCount( - "Settings.PrivacySandbox.NoticeRowDialog.OpenSettingsClicked")); - onView(withId(R.id.privacy_sandbox_notice_title)).check(doesNotExist()); - Mockito.verify(mSettingsNavigation) - .startSettings( - any(Context.class), - eq(PrivacySandboxSettingsFragment.class), - any(Bundle.class), - eq(false)); - } - - @Test - @SmallTest public void controllerShowsRestrictedNotice() throws IOException { mPage = mActivityTestRule.startOnBlankPage(); mFakePrivacySandboxBridge.setRequiredPromptType(PromptType.M1_NOTICE_RESTRICTED);
diff --git a/chrome/browser/profiles/profile_manager.cc b/chrome/browser/profiles/profile_manager.cc index 93ad431..c0ae461 100644 --- a/chrome/browser/profiles/profile_manager.cc +++ b/chrome/browser/profiles/profile_manager.cc
@@ -133,11 +133,11 @@ #include "chrome/browser/lifetime/application_lifetime_desktop.h" #include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h" #include "chrome/browser/profiles/nuke_profile_directory_utils.h" -#include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_window/public/browser_window_interface.h" // nogncheck crbug.com/40147906 #include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h" +#include "chrome/browser/ui/browser_window/public/global_browser_collection.h" #include "components/live_caption/live_caption_controller.h" #include "components/optimization_guide/core/model_execution/model_execution_features.h" #else @@ -2185,15 +2185,16 @@ } #if !BUILDFLAG(IS_ANDROID) -void ProfileManager::OnBrowserOpened(Browser* browser) { +void ProfileManager::OnBrowserOpened(BrowserWindowInterface* browser) { DCHECK(browser); - Profile* profile = browser->profile(); + Profile* profile = browser->GetProfile(); DCHECK(profile); if (!profile->IsOffTheRecord() && !IsRegisteredAsEphemeral(&GetProfileAttributesStorage(), profile->GetPath()) && - !browser->is_type_app() && ++browser_counts_[profile] == 1) { + browser->GetType() != BrowserWindowInterface::Type::TYPE_APP && + ++browser_counts_[profile] == 1) { active_profiles_.push_back(profile); SaveActiveProfiles(); } @@ -2204,10 +2205,11 @@ closing_all_browsers_ = false; } -void ProfileManager::OnBrowserClosed(Browser* browser) { - Profile* profile = browser->profile(); +void ProfileManager::OnBrowserClosed(BrowserWindowInterface* browser) { + Profile* profile = browser->GetProfile(); DCHECK(profile); - if (!profile->IsOffTheRecord() && !browser->is_type_app() && + if (!profile->IsOffTheRecord() && + browser->GetType() != BrowserWindowInterface::Type::TYPE_APP && --browser_counts_[profile] == 0) { active_profiles_.erase(std::ranges::find(active_profiles_, profile)); if (!closing_all_browsers_) @@ -2258,26 +2260,27 @@ } } -ProfileManager::BrowserListObserver::BrowserListObserver( +ProfileManager::BrowserCollectionObserver::BrowserCollectionObserver( ProfileManager* manager) : profile_manager_(manager) { - BrowserList::AddObserver(this); + browser_collection_observer_.Observe(GlobalBrowserCollection::GetInstance()); } -ProfileManager::BrowserListObserver::~BrowserListObserver() { - BrowserList::RemoveObserver(this); -} +ProfileManager::BrowserCollectionObserver::~BrowserCollectionObserver() = + default; -void ProfileManager::BrowserListObserver::OnBrowserAdded(Browser* browser) { +void ProfileManager::BrowserCollectionObserver::OnBrowserCreated( + BrowserWindowInterface* browser) { profile_manager_->OnBrowserOpened(browser); } -void ProfileManager::BrowserListObserver::OnBrowserRemoved(Browser* browser) { +void ProfileManager::BrowserCollectionObserver::OnBrowserClosed( + BrowserWindowInterface* browser) { profile_manager_->OnBrowserClosed(browser); } -void ProfileManager::BrowserListObserver::OnBrowserSetLastActive( - Browser* browser) { +void ProfileManager::BrowserCollectionObserver::OnBrowserActivated( + BrowserWindowInterface* browser) { // If all browsers are being closed (e.g. the user is in the process of // shutting down), this event will be fired after each browser is // closed. This does not represent a user intention to change the active @@ -2286,7 +2289,7 @@ return; } - profile_manager_->SetProfileAsLastUsed(browser->profile()); + profile_manager_->SetProfileAsLastUsed(browser->GetProfile()); } void ProfileManager::OnClosingAllBrowsersChanged(bool closing) {
diff --git a/chrome/browser/profiles/profile_manager.h b/chrome/browser/profiles/profile_manager.h index 32d363f..c5cc24f 100644 --- a/chrome/browser/profiles/profile_manager.h +++ b/chrome/browser/profiles/profile_manager.h
@@ -32,8 +32,11 @@ #include "chrome/common/buildflags.h" #if !BUILDFLAG(IS_ANDROID) +#include "base/scoped_observation.h" #include "chrome/browser/profiles/delete_profile_helper.h" -#include "chrome/browser/ui/browser_list_observer.h" +#include "chrome/browser/ui/browser_window/public/browser_collection_observer.h" + +class GlobalBrowserCollection; #endif // !BUILDFLAG(IS_ANDROID) #if BUILDFLAG(IS_CHROMEOS) @@ -546,23 +549,27 @@ void SaveActiveProfiles(); #if !BUILDFLAG(IS_ANDROID) - void OnBrowserOpened(Browser* browser); - void OnBrowserClosed(Browser* browser); + void OnBrowserOpened(BrowserWindowInterface* browser); + void OnBrowserClosed(BrowserWindowInterface* browser); - class BrowserListObserver : public ::BrowserListObserver { + class BrowserCollectionObserver : public ::BrowserCollectionObserver { public: - explicit BrowserListObserver(ProfileManager* manager); - BrowserListObserver(const BrowserListObserver&) = delete; - BrowserListObserver& operator=(const BrowserListObserver&) = delete; - ~BrowserListObserver() override; + explicit BrowserCollectionObserver(ProfileManager* manager); + BrowserCollectionObserver(const BrowserCollectionObserver&) = delete; + BrowserCollectionObserver& operator=(const BrowserCollectionObserver&) = + delete; + ~BrowserCollectionObserver() override; - // ::BrowserListObserver implementation. - void OnBrowserAdded(Browser* browser) override; - void OnBrowserRemoved(Browser* browser) override; - void OnBrowserSetLastActive(Browser* browser) override; + // ::BrowserCollectionObserver implementation. + void OnBrowserCreated(BrowserWindowInterface* browser) override; + void OnBrowserClosed(BrowserWindowInterface* browser) override; + void OnBrowserActivated(BrowserWindowInterface* browser) override; private: raw_ptr<ProfileManager> profile_manager_; + base::ScopedObservation<GlobalBrowserCollection, + ::BrowserCollectionObserver> + browser_collection_observer_{this}; }; void OnClosingAllBrowsersChanged(bool closing); @@ -601,7 +608,7 @@ bool logged_in_ = false; #if !BUILDFLAG(IS_ANDROID) - BrowserListObserver browser_list_observer_{this}; + BrowserCollectionObserver browser_collection_observer_{this}; std::unique_ptr<DeleteProfileHelper> delete_profile_helper_; #endif // !BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/resources/glic/glic_api/glic_api.ts b/chrome/browser/resources/glic/glic_api/glic_api.ts index fd4c0b7..d7aa7e1 100644 --- a/chrome/browser/resources/glic/glic_api/glic_api.ts +++ b/chrome/browser/resources/glic/glic_api/glic_api.ts
@@ -1416,6 +1416,34 @@ /** The default value of TabContextOptions.innerTextBytesLimit. */ export const DEFAULT_INNER_TEXT_BYTES_LIMIT = 20000; +/** Options for screenshot collection. */ +export declare interface ScreenshotCollectionOptions { + /** + * Screenshot will be scaled to fit the max width and height while + * maintaining the aspect ratio. + * If not set or set to 0, the screenshot will be captured without limiting + * the width (so long as the height is not limited). + */ + maxWidth?: number; + /** + * Screenshot will be scaled to fit the max width and height while maintaining + * the aspect ratio. + * If not set or set to 0, the screenshot will be captured without limiting + * the height (so long as the width is not limited). + */ + maxHeight?: number; + /** + * The format of the screenshot. If not set, the screenshot will be returned + * as a jpeg image. + */ + screenshotImageFormat?: ScreenshotImageFormat; + /** + * The compression quality of the screenshot. If not set, the screenshot will + * be returned with medium compression quality. + */ + screenshotCompressionQuality?: ScreenshotCompressionQuality; +} + /** Options for getting context from a tab. */ export declare interface TabContextOptions { /** @@ -1432,8 +1460,11 @@ */ innerTextBytesLimit?: number; /** + * @deprecated Use `screenshotCollectionOptions` instead. + * * If true, a screenshot of the user visible viewport will be included in the - * response. + * response. If `screenshotCollectionOptions` is set, the screenshot will be + * captured with the specified options regardless of this field. */ viewportScreenshot?: boolean; /** If true, returns the serialized annotatedPageContent proto. */ @@ -1460,6 +1491,13 @@ * maps directly to the AnnotatedPageContentMode enum in the proto. */ annotatedPageContentMode?: number; + + /** + * If set, the screenshot collection options will be used to capture the + * screenshot. Otherwise, the screenshot will be captured with the default + * options. + */ + screenshotCollectionOptions?: ScreenshotCollectionOptions; } /** @@ -2142,13 +2180,19 @@ id: number; // The username of the credential. Unique for a given sourceSiteOrApp. It can // be empty if, for example, the credential is stored as a password only. + // For federated credentials, this is the user's email, if used by the + // identity provider, otherwise the account display identifier. username: string; - // The original website or application for which this credential was saved - // for. + // The original website or application for which this credential was saved. + // For federated credentials, this is the site of the identity provider + // formatted for display. sourceSiteOrApp: string; // The origin for which this credential was requested. requestOrigin?: string; // The optional icon for the credential, encoded as a PNG image. + // Not provided for federated credentials. + // TODO(https://crbug.com/488443317): Consider providing icons for federated + // credentials. getIcon?(): Promise<Blob>; // The login method for this credential. type?: CredentialType; @@ -2480,6 +2524,34 @@ /////////////////////////////////////////////// // WARNING - GENERATED FROM MOJOM, DO NOT EDIT. +// Enum to specify the image format of the screenshot. +export enum ScreenshotImageFormat { + // JPEG screenshot format. This is the default format. + JPEG = 0, + // PNG screenshot format. + PNG = 1, + // WEBP screenshot format. + WEBP = 2, +} + +/////////////////////////////////////////////// +// WARNING - GENERATED FROM MOJOM, DO NOT EDIT. +// Enum to specify the compression quality of the screenshot. Depending on +// screenshot format, the compression quality may not be respected or may mean +// something different. +export enum ScreenshotCompressionQuality { + // No compression. + NONE = 0, + // Low compression quality. + LOW = 1, + // Medium compression quality. + MEDIUM = 2, + // High compression quality. + HIGH = 3, +} + +/////////////////////////////////////////////// +// WARNING - GENERATED FROM MOJOM, DO NOT EDIT. // Reason why scrollTo() failed. export enum ScrollToErrorReason { // Invalid params were provided to scrollTo(), or the browser doesn't support @@ -2776,6 +2848,8 @@ SHARE_ADDITIONAL_IMAGE_CONTEXT = 6, // Enables the PDF Zero State Web UI. PDF_ZERO_STATE = 7, + // Indicates that the host supports the invoke mechanism. + INVOKE = 8, } ///////////////////////////////////////////////
diff --git a/chrome/browser/resources/glic/glic_api_impl/host/conversions.ts b/chrome/browser/resources/glic/glic_api_impl/host/conversions.ts index fafce88..281af7e9 100644 --- a/chrome/browser/resources/glic/glic_api_impl/host/conversions.ts +++ b/chrome/browser/resources/glic/glic_api_impl/host/conversions.ts
@@ -18,9 +18,9 @@ import type {Url} from '//resources/mojo/url/mojom/url.mojom-webui.js'; import type {PageMetadata as PageMetadataMojo} from '../../ai_page_content_metadata.mojom-webui.js'; -import type {AdditionalContext as AdditionalContextMojo, AdditionalContextPart as AdditionalContextPartMojo, AnnotatedPageData as AnnotatedPageDataMojo, CaptureRegionResult as CaptureRegionResultMojo, ContextData as ContextDataMojo, ConversationInfo as ConversationInfoMojo, FocusedTabData as FocusedTabDataMojo, FormFactor as FormFactorMojo, GetPinCandidatesOptions as GetPinCandidatesOptionsMojo, GetTabContextOptions as TabContextOptionsMojo, HostCapability as HostCapabilityMojo, InvokeOptions as InvokeOptionsMojo, PanelOpeningData as PanelOpeningDataMojo, PanelState as PanelStateMojo, PdfDocumentData as PdfDocumentDataMojo, PinTabsOptions as PinTabsOptionsMojo, Platform as PlatformMojo, Screenshot as ScreenshotMojo, TabContext as TabContextMojo, TabData as TabDataMojo, UnpinTabsOptions as UnpinTabsOptionsMojo, WebPageData as WebPageDataMojo, ZeroStateSuggestionsV2 as ZeroStateSuggestionsV2Mojo} from '../../glic.mojom-webui.js'; -import {MicrophoneStatus as MicrophoneStatusMojo, PinTrigger as PinTriggerMojo, UnpinTrigger as UnpinTriggerMojo, WebClientMode as WebClientModeMojo} from '../../glic.mojom-webui.js'; -import type {AdditionalContextSource, CaptureRegionResult, ConversationInfo, CredentialType, FeatureMode, FormFactor, GetPinCandidatesOptions, HostCapability, InvocationSource, PageMetadata, PanelOpeningData, PanelState, PinTabsOptions, PinTrigger, Platform, Screenshot, TabContextOptions, TaskOptions, UnpinTabsOptions, UnpinTrigger, WebPageData, ZeroStateSuggestionsV2} from '../../glic_api/glic_api.js'; +import type {AdditionalContext as AdditionalContextMojo, AdditionalContextPart as AdditionalContextPartMojo, AnnotatedPageData as AnnotatedPageDataMojo, CaptureRegionResult as CaptureRegionResultMojo, ContextData as ContextDataMojo, ConversationInfo as ConversationInfoMojo, FocusedTabData as FocusedTabDataMojo, FormFactor as FormFactorMojo, GetPinCandidatesOptions as GetPinCandidatesOptionsMojo, GetTabContextOptions as TabContextOptionsMojo, HostCapability as HostCapabilityMojo, InvokeOptions as InvokeOptionsMojo, PanelOpeningData as PanelOpeningDataMojo, PanelState as PanelStateMojo, PdfDocumentData as PdfDocumentDataMojo, PinTabsOptions as PinTabsOptionsMojo, Platform as PlatformMojo, Screenshot as ScreenshotMojo, ScreenshotCollectionOptions as ScreenshotCollectionOptionsMojo, TabContext as TabContextMojo, TabData as TabDataMojo, UnpinTabsOptions as UnpinTabsOptionsMojo, WebPageData as WebPageDataMojo, ZeroStateSuggestionsV2 as ZeroStateSuggestionsV2Mojo} from '../../glic.mojom-webui.js'; +import {MicrophoneStatus as MicrophoneStatusMojo, PinTrigger as PinTriggerMojo, ScreenshotCompressionQuality as ScreenshotCompressionQualityMojo, ScreenshotImageFormat as ScreenshotImageFormatMojo, UnpinTrigger as UnpinTriggerMojo, WebClientMode as WebClientModeMojo} from '../../glic.mojom-webui.js'; +import type {AdditionalContextSource, CaptureRegionResult, ConversationInfo, CredentialType, FeatureMode, FormFactor, GetPinCandidatesOptions, HostCapability, InvocationSource, PageMetadata, PanelOpeningData, PanelState, PinTabsOptions, PinTrigger, Platform, Screenshot, ScreenshotCollectionOptions, ScreenshotCompressionQuality, ScreenshotImageFormat, TabContextOptions, TaskOptions, UnpinTabsOptions, UnpinTrigger, WebPageData, ZeroStateSuggestionsV2} from '../../glic_api/glic_api.js'; import {DEFAULT_INNER_TEXT_BYTES_LIMIT, DEFAULT_PDF_SIZE_LIMIT, MicrophoneStatus, WebClientMode} from '../../glic_api/glic_api.js'; import type {ConfirmationRequestErrorReason as ConfirmationRequestErrorReasonMojo, CredentialType as CredentialTypeMojo, NavigationConfirmationRequest as NavigationConfirmationRequestMojo, NavigationConfirmationResponse as NavigationConfirmationResponseMojo, SelectAutofillSuggestionsDialogErrorReason as SelectAutofillSuggestionsDialogErrorReasonMojo, SelectAutofillSuggestionsDialogRequest as SelectAutofillSuggestionsDialogRequestMojo, SelectAutofillSuggestionsDialogResponse as SelectAutofillSuggestionsDialogResponseMojo, SelectCredentialDialogErrorReason as SelectCredentialDialogErrorReasonMojo, SelectCredentialDialogRequest as SelectCredentialDialogRequestMojo, SelectCredentialDialogResponse as SelectCredentialDialogResponseMojo, TaskOptions as TaskOptionsMojo, UserConfirmationDialogRequest as UserConfirmationDialogRequestMojo, UserConfirmationDialogResponse as UserConfirmationDialogResponseMojo, UserGrantedPermissionDuration as UserGrantedPermissionDurationMojo} from './../../actor_webui.mojom-webui.js'; @@ -397,7 +397,8 @@ includeInnerText: options.innerText ?? false, innerTextBytesLimit: options.innerTextBytesLimit ?? DEFAULT_INNER_TEXT_BYTES_LIMIT, - includeViewportScreenshot: options.viewportScreenshot ?? false, + includeViewportScreenshot: options.viewportScreenshot === true || + options.screenshotCollectionOptions !== undefined, includePdf: options.pdfData ?? false, includeAnnotatedPageContent: options.annotatedPageContent ?? false, maxMetaTags: options.maxMetaTags ?? 0, @@ -407,6 +408,8 @@ annotatedPageContentMode: options.annotatedPageContentMode === undefined ? 0 : options.annotatedPageContentMode, + screenshotCollectionOptions: screenshotCollectionOptionsFromClient( + options.screenshotCollectionOptions), }; } @@ -739,3 +742,29 @@ } return result; } + +export function screenshotCollectionOptionsFromClient( + options: ScreenshotCollectionOptions| + undefined): ScreenshotCollectionOptionsMojo { + return { + maxWidth: options?.maxWidth ?? 0, + maxHeight: options?.maxHeight ?? 0, + screenshotImageFormat: options?.screenshotImageFormat ? + screenshotImageFormatFromClient(options.screenshotImageFormat) : + ScreenshotImageFormatMojo.kJpeg, + screenshotCompressionQuality: options?.screenshotCompressionQuality ? + screenshotCompressionQualityFromClient( + options.screenshotCompressionQuality) : + ScreenshotCompressionQualityMojo.kMedium, + }; +} + +export function screenshotImageFormatFromClient(format: ScreenshotImageFormat): + ScreenshotImageFormatMojo { + return format as number as ScreenshotImageFormatMojo; +} + +export function screenshotCompressionQualityFromClient( + quality: ScreenshotCompressionQuality): ScreenshotCompressionQualityMojo { + return quality as number as ScreenshotCompressionQualityMojo; +}
diff --git a/chrome/browser/resources/settings/autofill_page/autofill_ai_add_or_edit_dialog.ts b/chrome/browser/resources/settings/autofill_page/autofill_ai_add_or_edit_dialog.ts index 33d063e..b5de23b 100644 --- a/chrome/browser/resources/settings/autofill_page/autofill_ai_add_or_edit_dialog.ts +++ b/chrome/browser/resources/settings/autofill_page/autofill_ai_add_or_edit_dialog.ts
@@ -456,6 +456,15 @@ return ''; } + if (loadTimeData.getBoolean('enableAutofillAiWalletPrivatePasses')) { + // Show footer only when it is a new entity and type supports Wallet + // storage. This is sufficient because the entities stored in Wallet are + // not editable from the settings. + return this.i18n( + 'saveInfoToWalletSettingsAccountNotice', + this.i18n('googleWalletTitle'), this.userEmail_); + } + // Show footer only when it is a new entity and type supports Wallet // storage. This is sufficient because the entities stored in Wallet are not // editable from the settings.
diff --git a/chrome/browser/resources/side_panel/read_anything/app/read_anything_toolbar.html.ts b/chrome/browser/resources/side_panel/read_anything/app/read_anything_toolbar.html.ts index 5f7314b..d8e45ba 100644 --- a/chrome/browser/resources/side_panel/read_anything/app/read_anything_toolbar.html.ts +++ b/chrome/browser/resources/side_panel/read_anything/app/read_anything_toolbar.html.ts
@@ -65,20 +65,16 @@ </cr-button> ` : ''} </span> - ${!this.isImmersiveEnabled_ ? html` - <cr-button class="toolbar-button" id="rate" + ${!this.isImmersiveEnabled_ ? html` + <cr-button class="toolbar-button" id="rate" tabindex="${this.getRateTabIndex_()}" aria-label="${this.getVoiceSpeedLabel_()}" title="${this.i18n('voiceSpeedLabel')}" aria-haspopup="menu" @click="${this.onShowRateMenuClick_}"> ${this.getFormattedSpeechRate_()} - </cr-button> - ` : ''} - <rate-menu id="rateMenu" .settingsPrefs="${this.settingsPrefs}" - @rate-change="${this.onRateChange_}"> - </rate-menu> - ${!this.isImmersiveEnabled_ ? html` + </cr-button> + <cr-icon-button class="toolbar-button" id="voice-selection" tabindex="-1" aria-label="$i18n{voiceSelectionLabel}" title="$i18n{voiceSelectionLabel}" @@ -206,6 +202,9 @@ </cr-action-menu> `}'> </cr-lazy-render-lit> + <rate-menu id="rateMenu" .settingsPrefs="${this.settingsPrefs}" + @rate-change="${this.onRateChange_}"> + </rate-menu> <highlight-menu id="highlightMenu" class="${this.isImmersiveEnabled_ ? 'settings-submenu' : ''}"
diff --git a/chrome/browser/safe_browsing/client_side_detection_host_browsertest.cc b/chrome/browser/safe_browsing/client_side_detection_host_browsertest.cc index 8e51211..ae36fd2 100644 --- a/chrome/browser/safe_browsing/client_side_detection_host_browsertest.cc +++ b/chrome/browser/safe_browsing/client_side_detection_host_browsertest.cc
@@ -173,6 +173,40 @@ ~MockSafeBrowsingUIManager() override = default; }; +// This class waits for page load state that ClientSideDetectionHost observes as +// a WebContentsObserver. ClientSideDetectionHost observes the two functions +// listed before starting the preclassification check. +class PaintObserverWaiter : public content::WebContentsObserver { + public: + explicit PaintObserverWaiter(content::WebContents* web_contents) + : WebContentsObserver(web_contents) {} + + void DidFirstVisuallyNonEmptyPaint() override { + did_paint_ = true; + if (did_fcp_) { + run_loop_.Quit(); + } + } + + void OnFirstContentfulPaintInPrimaryMainFrame() override { + did_fcp_ = true; + if (did_paint_) { + run_loop_.Quit(); + } + } + + void Wait() { + if (!did_paint_ || !did_fcp_) { + run_loop_.Run(); + } + } + + private: + bool did_paint_ = false; + bool did_fcp_ = false; + base::RunLoop run_loop_; +}; + std::string set_up_client_side_model() { flatbuffers::FlatBufferBuilder builder(1024); std::vector<flatbuffers::Offset<flat::Hash>> hashes; @@ -345,7 +379,17 @@ fake_csd_service.SendModelToRenderers(); GURL page_url(embedded_test_server()->GetURL("/safe_browsing/malware.html")); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url)); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + // With new observers, we wait beyond the prerender state to start + // pre-classification. + PaintObserverWaiter paint_waiter(GetWebContents()); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url)); + prerender_helper().AddPrerender(page_url); + prerender_helper().NavigatePrimaryPage(page_url); + paint_waiter.Wait(); + } else { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url)); + } base::RunLoop run_loop; fake_csd_service.SetRequestCallback(run_loop.QuitClosure()); @@ -393,7 +437,15 @@ fake_csd_service.SendModelToRenderers(); GURL page_url(embedded_test_server()->GetURL("/safe_browsing/malware.html")); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url)); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(web_contents); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url)); + prerender_helper().AddPrerender(page_url); + prerender_helper().NavigatePrimaryPage(page_url); + paint_waiter.Wait(); + } else { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url)); + } base::RunLoop run_loop; fake_csd_service.SetRequestCallback(run_loop.QuitClosure()); @@ -457,13 +509,25 @@ fake_csd_service.SetRequestCallback(run_loop.QuitClosure()); const GURL initial_url(embedded_test_server()->GetURL("/title1.html")); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + paint_waiter.Wait(); + } else { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + } // Prerender then activate a phishing page. const GURL prerender_url = embedded_test_server()->GetURL("/safe_browsing/malware.html"); prerender_helper().AddPrerender(prerender_url); - prerender_helper().NavigatePrimaryPage(prerender_url); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + prerender_helper().NavigatePrimaryPage(prerender_url); + paint_waiter.Wait(); + } else { + prerender_helper().NavigatePrimaryPage(prerender_url); + } // Bypass the pre-classification checks. csd_host->OnPhishingPreClassificationDone( @@ -597,13 +661,25 @@ fake_csd_service.SetRequestCallback(run_loop.QuitClosure()); const GURL initial_url(embedded_test_server()->GetURL("/title1.html")); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + paint_waiter.Wait(); + } else { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + } // Prerender then activate a phishing page. const GURL prerender_url = embedded_test_server()->GetURL("/safe_browsing/malware.html"); prerender_helper().AddPrerender(prerender_url); - prerender_helper().NavigatePrimaryPage(prerender_url); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + prerender_helper().NavigatePrimaryPage(prerender_url); + paint_waiter.Wait(); + } else { + prerender_helper().NavigatePrimaryPage(prerender_url); + } // Bypass the pre-classification checks. csd_host->OnPhishingPreClassificationDone( @@ -664,13 +740,25 @@ fake_csd_service.SetRequestCallback(run_loop.QuitClosure()); const GURL initial_url(embedded_test_server()->GetURL("/title1.html")); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + paint_waiter.Wait(); + } else { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + } // Prerender then activate a phishing page. const GURL prerender_url = embedded_test_server()->GetURL("/safe_browsing/malware.html"); prerender_helper().AddPrerender(prerender_url); - prerender_helper().NavigatePrimaryPage(prerender_url); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + prerender_helper().NavigatePrimaryPage(prerender_url); + paint_waiter.Wait(); + } else { + prerender_helper().NavigatePrimaryPage(prerender_url); + } feature_cache_map->Clear(); @@ -727,12 +815,15 @@ fake_csd_service.SendModelToRenderers(); const GURL initial_url(embedded_test_server()->GetURL("/title1.html")); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); - - // TODO(andysjlim): Navigating to initial page alongside the first page logs - // the histogram twice. Figure out why. - histogram_tester.ExpectTotalCount( - "SBClientPhishing.PreClassificationCheckResult", 2); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + prerender_helper().AddPrerender(initial_url); + prerender_helper().NavigatePrimaryPage(initial_url); + paint_waiter.Wait(); + } else { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + } EnterActiveTabFullscreen(); ASSERT_TRUE(RequestKeyboardLock(/*esc_key_locked=*/true)); @@ -744,7 +835,7 @@ // times with the keyboard lock notify, but this is added twice. Investigate // why. histogram_tester.ExpectTotalCount( - "SBClientPhishing.PreClassificationCheckResult", 4); + "SBClientPhishing.PreClassificationCheckResult.KeyboardLockRequested", 2); } IN_PROC_BROWSER_TEST_F( @@ -773,11 +864,15 @@ fake_csd_service.SendModelToRenderers(); const GURL initial_url(embedded_test_server()->GetURL("/title1.html")); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); - - // Navigating to initial page logs the histogram twice. - histogram_tester.ExpectTotalCount( - "SBClientPhishing.PreClassificationCheckResult", 2); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + prerender_helper().AddPrerender(initial_url); + prerender_helper().NavigatePrimaryPage(initial_url); + paint_waiter.Wait(); + } else { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + } // The function automatically approves the lock request, but for tests, // functionally, nothing changes. @@ -790,7 +885,7 @@ // response to web_contents observer that PointerLockRequest has been sent. csd_host->PointerLockRequested(); histogram_tester.ExpectTotalCount( - "SBClientPhishing.PreClassificationCheckResult", 3); + "SBClientPhishing.PreClassificationCheckResult.PointerLockRequested", 1); } IN_PROC_BROWSER_TEST_F( @@ -822,10 +917,22 @@ fake_csd_service.SetRequestCallback(run_loop.QuitClosure()); const GURL initial_url(embedded_test_server()->GetURL("/title1.html")); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + paint_waiter.Wait(); + } else { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + } prerender_helper().AddPrerender(initial_url); - prerender_helper().NavigatePrimaryPage(initial_url); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + prerender_helper().NavigatePrimaryPage(initial_url); + paint_waiter.Wait(); + } else { + prerender_helper().NavigatePrimaryPage(initial_url); + } EnterActiveTabFullscreen(); ASSERT_TRUE(RequestKeyboardLock(/*esc_key_locked=*/true)); @@ -892,10 +999,22 @@ fake_csd_service.SetRequestCallback(run_loop.QuitClosure()); const GURL initial_url(embedded_test_server()->GetURL("/title1.html")); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + paint_waiter.Wait(); + } else { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + } prerender_helper().AddPrerender(initial_url); - prerender_helper().NavigatePrimaryPage(initial_url); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + prerender_helper().NavigatePrimaryPage(initial_url); + paint_waiter.Wait(); + } else { + prerender_helper().NavigatePrimaryPage(initial_url); + } RequestToLockPointer(true, false); ASSERT_TRUE(GetExclusiveAccessManager() @@ -934,7 +1053,11 @@ class ClientSideDetectionHostVibrateTest : public InProcessBrowserTest { public: - ClientSideDetectionHostVibrateTest() = default; + ClientSideDetectionHostVibrateTest() { + prerender_helper_ = std::make_unique<content::test::PrerenderTestHelper>( + base::BindRepeating(&ClientSideDetectionHostVibrateTest::GetWebContents, + base::Unretained(this))); + } ClientSideDetectionHostVibrateTest( const ClientSideDetectionHostVibrateTest&) = delete; @@ -950,6 +1073,10 @@ std::string client_side_model() { return flatbuffer_model_str_; } + content::test::PrerenderTestHelper& prerender_helper() { + return *prerender_helper_.get(); + } + content::WebContents* GetWebContents() { return browser()->tab_strip_model()->GetActiveWebContents(); } @@ -964,6 +1091,7 @@ } private: + std::unique_ptr<content::test::PrerenderTestHelper> prerender_helper_; std::string flatbuffer_model_str_; }; @@ -1014,12 +1142,15 @@ fake_csd_service.SendModelToRenderers(); const GURL initial_url(embedded_test_server()->GetURL("/title1.html")); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); - - // TODO(andysjlim): Navigating to initial page alongside the first page logs - // the histogram twice. Figure out why. - histogram_tester.ExpectTotalCount( - "SBClientPhishing.PreClassificationCheckResult.TriggerModel", 2); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + prerender_helper().AddPrerender(initial_url); + prerender_helper().NavigatePrimaryPage(initial_url); + paint_waiter.Wait(); + } else { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + } VibrationObserverWaiter waiter(GetWebContents()); EXPECT_FALSE(waiter.DidVibrate()); @@ -1076,7 +1207,13 @@ fake_csd_service.SetRequestCallback(run_loop.QuitClosure()); const GURL initial_url(embedded_test_server()->GetURL("/title1.html")); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + paint_waiter.Wait(); + } else { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + } // Bypass the pre-classification check because it would otherwise return // "NO_CLASSIFY_PRIVATE_IP". @@ -1232,7 +1369,13 @@ fake_csd_service.SendModelToRenderers(); const GURL initial_url(embedded_test_server()->GetURL("/title1.html")); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + paint_waiter.Wait(); + } else { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + } histogram_tester.ExpectTotalCount( "SBClientPhishing.PreClassificationCheckResult.ClipboardCopyApi", 0); @@ -1282,7 +1425,13 @@ fake_csd_service.SetRequestCallback(csd_request_run_loop.QuitClosure()); const GURL initial_url(embedded_test_server()->GetURL("/title1.html")); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + paint_waiter.Wait(); + } else { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + } histogram_tester.ExpectTotalCount( "SBClientPhishing.PhishingDetectorResult.ClipboardCopyApi", 0); @@ -1386,7 +1535,13 @@ base::HistogramTester histogram_tester; const GURL initial_url(embedded_test_server()->GetURL("/title1.html")); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + paint_waiter.Wait(); + } else { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + } histogram_tester.ExpectTotalCount( "SBClientPhishing.PreClassificationCheckResult.ClipboardCopyApi", 0); @@ -1420,7 +1575,13 @@ base::HistogramTester histogram_tester; const GURL initial_url(embedded_test_server()->GetURL("/title1.html")); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + paint_waiter.Wait(); + } else { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + } histogram_tester.ExpectTotalCount( "SBClientPhishing.PreClassificationCheckResult.ClipboardCopyApi", 0); @@ -1487,7 +1648,13 @@ GURL NavigateToCreditCardForm() { const GURL url(embedded_test_server()->GetURL( "/autofill/autofill_creditcard_form.html")); - EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + paint_waiter.Wait(); + } else { + EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + } return url; } @@ -1689,9 +1856,22 @@ fake_csd_service.SetRequestCallback(run_loop.QuitClosure()); const GURL initial_url(embedded_test_server()->GetURL("/title1.html")); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + paint_waiter.Wait(); + } else { + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url)); + } + prerender_helper().AddPrerender(initial_url); - prerender_helper().NavigatePrimaryPage(initial_url); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + PaintObserverWaiter paint_waiter(GetWebContents()); + prerender_helper().NavigatePrimaryPage(initial_url); + paint_waiter.Wait(); + } else { + prerender_helper().NavigatePrimaryPage(initial_url); + } csd_host->OnPhishingPreClassificationDone( ClientSideDetectionType::FORCE_REQUEST, /*should_classify=*/true,
diff --git a/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc b/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc index 67d9757..68f6ba93 100644 --- a/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc +++ b/chrome/browser/safe_browsing/client_side_detection_host_unittest.cc
@@ -79,6 +79,7 @@ #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.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/web_contents.h" #include "content/public/test/browser_test_utils.h" @@ -125,16 +126,6 @@ const bool kFalse = false; const bool kTrue = true; -std::unique_ptr<content::NavigationSimulator> NavigateAndKeepLoading( - content::WebContents* web_contents, - const GURL& url) { - auto navigation = - content::NavigationSimulator::CreateBrowserInitiated(url, web_contents); - navigation->SetKeepLoading(true); - navigation->Commit(); - return navigation; -} - void WaitUntilHighConfidenceAllowlistCheckDone() { base::StatisticsRecorder::HistogramWaiter( "SBClientPhishing.MatchHighConfidenceAllowlist") @@ -585,11 +576,30 @@ } } - void NavigateAndCommit(const GURL& safe_url) { + void NotifyClientSideDetectionObservers() { + content::WebContentsTester::For(web_contents()) + ->TestDidFirstVisuallyNonEmptyPaint(); + if (csd_host_) { + csd_host_->OnFirstContentfulPaintInPrimaryMainFrame(); + } + } + + void NavigateAndCommit(const GURL& safe_url, + bool reverse_callback_order = false) { controller().LoadURL(safe_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string()); - content::WebContentsTester::For(web_contents())->CommitPendingNavigation(); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + if (!reverse_callback_order) { + NotifyClientSideDetectionObservers(); + } else { + if (csd_host_) { + csd_host_->OnFirstContentfulPaintInPrimaryMainFrame(); + } + content::WebContentsTester::For(web_contents()) + ->TestDidFirstVisuallyNonEmptyPaint(); + } + } } void AdvanceTimeTickClock(base::TimeDelta delta) { clock_.Advance(delta); } @@ -1132,7 +1142,41 @@ database_manager_->SetAllowlistLookupDetailsForUrl(url, false); ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, &kFalse); - NavigateAndKeepLoading(web_contents(), url); + NavigateAndCommit(url); + WaitAndCheckPreClassificationChecks(); + + fake_phishing_detector_.CheckMessage(&url); + + histogram_tester.ExpectBucketCount( + "SBClientPhishing.PreClassificationCheckResult", + PreClassificationCheckResult::CLASSIFY, 1); + histogram_tester.ExpectBucketCount( + "SBClientPhishing.PreClassificationCheckResult.TriggerModel", + PreClassificationCheckResult::CLASSIFY, 1); + histogram_tester.ExpectBucketCount( + "SBClientPhishing.OnDeviceModelSessionAliveOnNewPreclassification", false, + 1); + histogram_tester.ExpectBucketCount( + "SBClientPhishing.IntelligentScanOngoingOnNewPreclassification", false, + 1); +} + +TEST_F(ClientSideDetectionHostTest, + TestPreClassificationCheckPassAlternateObserverOrder) { + if (base::FeatureList::IsEnabled(kClientSideDetectionKillswitch)) { + GTEST_SKIP(); + } + + base::HistogramTester histogram_tester; + + // Navigate the tab to a page. We should see a StartPhishingDetection IPC. + GURL url("http://host.com/"); + database_manager_->SetAllowlistLookupDetailsForUrl(url, false); + ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, + &kFalse); + + NavigateAndCommit(url, /*reverse_callback_order=*/true); + WaitAndCheckPreClassificationChecks(); fake_phishing_detector_.CheckMessage(&url); @@ -1161,7 +1205,7 @@ database_manager_->SetAllowlistLookupDetailsForUrl(url, false); ExpectPreClassificationChecks(url, &kFalse, &kTrue, nullptr, nullptr, &kFalse); - NavigateAndKeepLoading(web_contents(), url); + NavigateAndCommit(url); WaitAndCheckPreClassificationChecks(); } @@ -1178,7 +1222,7 @@ database_manager_->SetAllowlistLookupDetailsForUrl(url, /*match=*/true); ExpectPreClassificationChecks(url, &kFalse, &kFalse, nullptr, nullptr, nullptr); - NavigateAndKeepLoading(web_contents(), url); + NavigateAndCommit(url); WaitAndCheckPreClassificationChecks(); histogram_tester.ExpectTotalCount( @@ -1201,7 +1245,7 @@ database_manager_->SetAllowlistLookupDetailsForUrl(url, /*match=*/true); ExpectPreClassificationChecks(url, &kFalse, &kFalse, nullptr, nullptr, nullptr); - NavigateAndKeepLoading(web_contents(), url); + NavigateAndCommit(url); WaitAndCheckPreClassificationChecks(); histogram_tester.ExpectTotalCount( @@ -1228,6 +1272,9 @@ ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, &kFalse); navigation->Commit(); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + NotifyClientSideDetectionObservers(); + } WaitAndCheckPreClassificationChecks(); fake_phishing_detector_.CheckMessage(&url); @@ -1243,7 +1290,7 @@ database_manager_->SetAllowlistLookupDetailsForUrl(url1, false); ExpectPreClassificationChecks(url1, &kFalse, &kFalse, &kFalse, &kFalse, &kFalse); - NavigateAndKeepLoading(web_contents(), url1); + NavigateAndCommit(url1); WaitAndCheckPreClassificationChecks(); fake_phishing_detector_.CheckMessage(&url1); @@ -1252,7 +1299,7 @@ database_manager_->SetAllowlistLookupDetailsForUrl(url2, false); ExpectPreClassificationChecks(url2, &kFalse, &kFalse, &kFalse, &kFalse, &kFalse); - NavigateAndKeepLoading(web_contents(), url2); + NavigateAndCommit(url2); WaitAndCheckPreClassificationChecks(); fake_phishing_detector_.CheckMessage(&url2); @@ -1268,11 +1315,11 @@ // preclassification check and continue loading, so that url2 can cancel it. GURL url1("http://host1.com/"); database_manager_->SetAllowlistLookupDetailsForUrl(url1, false); - NavigateAndKeepLoading(web_contents(), url1); + NavigateAndCommit(url1); GURL url2("http://host2.com/"); database_manager_->SetAllowlistLookupDetailsForUrl(url2, false); - NavigateAndKeepLoading(web_contents(), url2); + NavigateAndCommit(url2); // Navigating to a second page will cancel the preclassification check of the // first page. @@ -1353,6 +1400,9 @@ content::NavigationSimulator::CreateBrowserInitiated(url, web_contents()); navigation->Fail(net::ERR_FAILED); navigation->CommitErrorPage(); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + NotifyClientSideDetectionObservers(); + } WaitAndCheckPreClassificationChecks(); histogram_tester.ExpectUniqueSample( @@ -1378,7 +1428,7 @@ ExpectPreClassificationChecks(url, &kFalse, nullptr, nullptr, nullptr, &kFalse); - content::WebContentsTester::For(web_contents())->NavigateAndCommit(url); + NavigateAndCommit(url); WaitAndCheckPreClassificationChecks(); fake_phishing_detector_.CheckMessage(nullptr); @@ -1396,7 +1446,7 @@ database_manager_->SetAllowlistLookupDetailsForUrl(url, false); ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kTrue, &kFalse); - NavigateAndKeepLoading(web_contents(), url); + NavigateAndCommit(url); WaitAndCheckPreClassificationChecks(); fake_phishing_detector_.CheckMessage(nullptr); @@ -1411,7 +1461,7 @@ database_manager_->SetAllowlistLookupDetailsForUrl(url, false); ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, &kFalse); - NavigateAndKeepLoading(web_contents(), url); + NavigateAndCommit(url); WaitAndCheckPreClassificationChecks(); fake_phishing_detector_.CheckMessage(&url); @@ -1426,7 +1476,7 @@ GURL url("file://host.com/"); ExpectPreClassificationChecks(url, &kFalse, nullptr, nullptr, nullptr, &kFalse); - NavigateAndKeepLoading(web_contents(), url); + NavigateAndCommit(url); WaitAndCheckPreClassificationChecks(); fake_phishing_detector_.CheckMessage(nullptr); @@ -1448,7 +1498,7 @@ EXPECT_CALL(*ui_manager_.get(), DisplayBlockingPage(_)) .WillOnce(SaveArg<0>(&resource)); - NavigateAndKeepLoading(web_contents(), url); + NavigateAndCommit(url); WaitAndCheckPreClassificationChecks(); EXPECT_EQ(url, resource.url); EXPECT_EQ(url, resource.original_url); @@ -1470,7 +1520,7 @@ ExpectPreClassificationChecks(url, &kFalse, nullptr, nullptr, nullptr, &kFalse); - NavigateAndKeepLoading(web_contents(), url); + NavigateAndCommit(url); WaitAndCheckPreClassificationChecks(); fake_phishing_detector_.CheckMessage(nullptr); @@ -2038,7 +2088,7 @@ database_manager_->SetAllowlistLookupDetailsForUrl(url, /*match=*/true); ExpectPreClassificationChecks(url, &kFalse, &kFalse, nullptr, nullptr, nullptr); - NavigateAndKeepLoading(web_contents(), url); + NavigateAndCommit(url); WaitAndCheckPreClassificationChecks(); // Check that the clipboard histograms haven't been recorded yet. @@ -2082,7 +2132,7 @@ database_manager_->SetAllowlistLookupDetailsForUrl(url, /*match=*/true); ExpectPreClassificationChecks(url, &kFalse, &kFalse, nullptr, nullptr, nullptr); - NavigateAndKeepLoading(web_contents(), url); + NavigateAndCommit(url); WaitAndCheckPreClassificationChecks(); // Check that the clipboard histograms haven't been recorded yet. @@ -2128,7 +2178,7 @@ database_manager_->SetAllowlistLookupDetailsForUrl(url, /*match=*/true); ExpectPreClassificationChecks(url, &kFalse, &kFalse, nullptr, nullptr, nullptr); - NavigateAndKeepLoading(web_contents(), url); + NavigateAndCommit(url); WaitAndCheckPreClassificationChecks(); // Check that the clipboard histograms haven't been recorded yet. @@ -2169,7 +2219,7 @@ database_manager_->SetAllowlistLookupDetailsForUrl(url, /*match=*/true); ExpectPreClassificationChecks(url, &kFalse, &kFalse, nullptr, nullptr, nullptr); - NavigateAndKeepLoading(web_contents(), url); + NavigateAndCommit(url); WaitAndCheckPreClassificationChecks(); // Check that the clipboard histograms haven't been recorded yet. @@ -3763,7 +3813,7 @@ ExpectPreClassificationChecks(url, &kFalse, nullptr, nullptr, nullptr, &kFalse); EXPECT_CALL(*database_manager_.get(), CheckCsdAllowlistUrl(url, _)).Times(0); - NavigateAndKeepLoading(web_contents(), url); + NavigateAndCommit(url); WaitAndCheckPreClassificationChecks(); fake_phishing_detector_.CheckMessage(&url); } @@ -3779,7 +3829,7 @@ ExpectPreClassificationChecks(url, &kFalse, nullptr, nullptr, nullptr, &kFalse); EXPECT_CALL(*csd_service_, GetValidCachedResult(url, NotNull())).Times(0); - NavigateAndKeepLoading(web_contents(), url); + NavigateAndCommit(url); WaitAndCheckPreClassificationChecks(); fake_phishing_detector_.CheckMessage(&url); } @@ -3795,7 +3845,7 @@ ExpectPreClassificationChecks(url, &kFalse, nullptr, nullptr, nullptr, &kFalse); EXPECT_CALL(*csd_service_, AtPhishingReportLimit()).Times(0); - NavigateAndKeepLoading(web_contents(), url); + NavigateAndCommit(url); WaitAndCheckPreClassificationChecks(); fake_phishing_detector_.CheckMessage(&url); }
diff --git a/chrome/browser/search_engines/android/BUILD.gn b/chrome/browser/search_engines/android/BUILD.gn index cd9974a..65dda323a 100644 --- a/chrome/browser/search_engines/android/BUILD.gn +++ b/chrome/browser/search_engines/android/BUILD.gn
@@ -38,6 +38,9 @@ "java/src/org/chromium/chrome/browser/search_engines/settings/extensions/ExtensionSearchEngineAdapter.java", "java/src/org/chromium/chrome/browser/search_engines/settings/extensions/ExtensionSearchEngineCoordinator.java", "java/src/org/chromium/chrome/browser/search_engines/settings/extensions/ExtensionSearchEngineMediator.java", + "java/src/org/chromium/chrome/browser/search_engines/settings/inactive_shortcut/InactiveShortcutAdapter.java", + "java/src/org/chromium/chrome/browser/search_engines/settings/inactive_shortcut/InactiveShortcutCoordinator.java", + "java/src/org/chromium/chrome/browser/search_engines/settings/inactive_shortcut/InactiveShortcutMediator.java", ] srcjar_deps = [ ":jni_headers" ] @@ -140,6 +143,7 @@ "junit/src/org/chromium/chrome/browser/search_engines/settings/custom_site_search/AddSearchEngineDialogCoordinatorUnitTest.java", "junit/src/org/chromium/chrome/browser/search_engines/settings/custom_site_search/CustomSiteSearchMediatorUnitTest.java", "junit/src/org/chromium/chrome/browser/search_engines/settings/extensions/ExtensionSearchEngineMediatorUnitTest.java", + "junit/src/org/chromium/chrome/browser/search_engines/settings/inactive_shortcut/InactiveShortcutMediatorUnitTest.java", ] deps = [ ":java",
diff --git a/chrome/browser/search_engines/android/java/res/layout/site_search_add_item.xml b/chrome/browser/search_engines/android/java/res/layout/site_search_add_item.xml index a806ea02..842460a5 100644 --- a/chrome/browser/search_engines/android/java/res/layout/site_search_add_item.xml +++ b/chrome/browser/search_engines/android/java/res/layout/site_search_add_item.xml
@@ -5,9 +5,7 @@ found in the LICENSE file. --> -<!-- TODO: most of the style is copy-paste from search_engine_with_logo.xml. - Check if needed to fine tune for the Preference view. --> -<org.chromium.components.browser_ui.widget.containment.ContainedLinearLayout +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" @@ -34,4 +32,4 @@ android:text="@string/site_search_add_site_search_button_label" android:textAppearance="@style/TextAppearance.TextLarge.Primary" /> -</org.chromium.components.browser_ui.widget.containment.ContainedLinearLayout> +</LinearLayout>
diff --git a/chrome/browser/search_engines/android/java/res/layout/site_search_engine_item.xml b/chrome/browser/search_engines/android/java/res/layout/site_search_engine_item.xml index 099490b..3373730 100644 --- a/chrome/browser/search_engines/android/java/res/layout/site_search_engine_item.xml +++ b/chrome/browser/search_engines/android/java/res/layout/site_search_engine_item.xml
@@ -4,9 +4,8 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<!-- TODO: most of the style is copy-paste from search_engine_with_logo.xml. - Check if needed to fine tune for the Preference view. --> -<org.chromium.components.browser_ui.widget.containment.ContainedLinearLayout + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/container" @@ -48,15 +47,14 @@ <org.chromium.ui.listmenu.ListMenuButton android:id="@+id/overflow_menu_button" - android:layout_width="48dp" - android:layout_height="48dp" + android:layout_width="wrap_content" + android:layout_height="match_parent" android:background="?attr/selectableItemBackgroundBorderless" android:scaleType="center" - android:padding="12dp" android:layout_marginStart="16dp" android:layout_marginEnd="10dp" android:contentDescription="@string/more" app:tint="@color/default_icon_color_tint_list" app:srcCompat="@drawable/ic_more_vert_24dp"/> -</org.chromium.components.browser_ui.widget.containment.ContainedLinearLayout> +</LinearLayout>
diff --git a/chrome/browser/search_engines/android/java/res/layout/site_search_more_item.xml b/chrome/browser/search_engines/android/java/res/layout/site_search_more_item.xml index 8b3f752..cd41e51 100644 --- a/chrome/browser/search_engines/android/java/res/layout/site_search_more_item.xml +++ b/chrome/browser/search_engines/android/java/res/layout/site_search_more_item.xml
@@ -5,7 +5,7 @@ found in the LICENSE file. --> -<org.chromium.components.browser_ui.widget.containment.ContainedLinearLayout +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" @@ -33,4 +33,4 @@ android:text="@string/site_search_additional_sites_button_label" android:textAppearance="@style/TextAppearance.TextLarge.Primary" /> -</org.chromium.components.browser_ui.widget.containment.ContainedLinearLayout> \ No newline at end of file +</LinearLayout> \ No newline at end of file
diff --git a/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/settings/SiteSearchSettings.java b/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/settings/SiteSearchSettings.java index a514a237..8673bef 100644 --- a/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/settings/SiteSearchSettings.java +++ b/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/settings/SiteSearchSettings.java
@@ -16,6 +16,7 @@ import org.chromium.chrome.browser.search_engines.settings.custom_search_engine.CustomSearchEngineListCoordinator; import org.chromium.chrome.browser.search_engines.settings.custom_site_search.CustomSiteSearchCoordinator; import org.chromium.chrome.browser.search_engines.settings.extensions.ExtensionSearchEngineCoordinator; +import org.chromium.chrome.browser.search_engines.settings.inactive_shortcut.InactiveShortcutCoordinator; import org.chromium.chrome.browser.settings.ChromeBaseSettingsFragment; import org.chromium.components.browser_ui.settings.SettingsFragment; import org.chromium.components.browser_ui.settings.SettingsUtils; @@ -32,12 +33,14 @@ "keyboard_shortcut_radio_group"; private static final String CUSTOM_SEARCH_ENGINE_LIST_PREF = "custom_search_engine_item_list"; private static final String CUSTOM_SITE_SEARCH_LIST_PREF = "custom_site_search_item_list"; + private static final String INACTIVE_SHORTCUT_LIST_PREF = "inactive_shortcut_list"; private static final String EXTENSIONS_PREF_KEY = "extension_item_list"; private final SettableMonotonicObservableSupplier<String> mPageTitle = ObservableSuppliers.createMonotonic(); private @Nullable CustomSearchEngineListCoordinator mSearchEngineCoordinator; private @Nullable CustomSiteSearchCoordinator mSiteSearchCoordinator; + private @Nullable InactiveShortcutCoordinator mInactiveShortcutCoordinator; private @Nullable ExtensionSearchEngineCoordinator mExtensionSearchEngineCoordinator; @Override @@ -80,6 +83,13 @@ // Inactive Shortcuts SettingsUtils.addPreferencesFromResource(this, R.xml.inactive_shortcut_preferences); + SearchEngineListPreference inactiveShortcutPref = + findPreference(INACTIVE_SHORTCUT_LIST_PREF); + if (mInactiveShortcutCoordinator == null) { + mInactiveShortcutCoordinator = + new InactiveShortcutCoordinator( + getContext(), getProfile(), inactiveShortcutPref); + } // Extensions SettingsUtils.addPreferencesFromResource(this, R.xml.extensions_preferences); @@ -111,6 +121,10 @@ mSiteSearchCoordinator.destroy(); mSiteSearchCoordinator = null; } + if (mInactiveShortcutCoordinator != null) { + mInactiveShortcutCoordinator.destroy(); + mInactiveShortcutCoordinator = null; + } super.onDestroy(); } }
diff --git a/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/settings/inactive_shortcut/InactiveShortcutAdapter.java b/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/settings/inactive_shortcut/InactiveShortcutAdapter.java new file mode 100644 index 0000000..a37cbe0 --- /dev/null +++ b/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/settings/inactive_shortcut/InactiveShortcutAdapter.java
@@ -0,0 +1,45 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.search_engines.settings.inactive_shortcut; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; + +import org.chromium.build.annotations.NullMarked; +import org.chromium.chrome.browser.search_engines.R; +import org.chromium.chrome.browser.search_engines.settings.common.SiteSearchProperties.ViewType; +import org.chromium.chrome.browser.search_engines.settings.common.SiteSearchViewBinder; +import org.chromium.ui.modelutil.MVCListAdapter.ModelList; +import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter; + +@NullMarked +class InactiveShortcutAdapter extends SimpleRecyclerViewAdapter { + InactiveShortcutAdapter(Context context, ModelList modelList) { + super(modelList); + + registerType( + ViewType.SEARCH_ENGINE, + parent -> { + View view = + LayoutInflater.from(context) + .inflate(R.layout.site_search_engine_item, parent, false); + view.setTag(new SiteSearchViewBinder.ViewHolder(view)); + return view; + }, + SiteSearchViewBinder::bind); + + registerType( + ViewType.MORE, + parent -> { + View view = + LayoutInflater.from(context) + .inflate(R.layout.site_search_more_item, parent, false); + view.setTag(new SiteSearchViewBinder.ViewHolder(view)); + return view; + }, + SiteSearchViewBinder::bind); + } +}
diff --git a/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/settings/inactive_shortcut/InactiveShortcutCoordinator.java b/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/settings/inactive_shortcut/InactiveShortcutCoordinator.java new file mode 100644 index 0000000..2ec9b9fa --- /dev/null +++ b/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/settings/inactive_shortcut/InactiveShortcutCoordinator.java
@@ -0,0 +1,49 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.search_engines.settings.inactive_shortcut; + +import android.content.Context; + +import org.chromium.build.annotations.NullMarked; +import org.chromium.chrome.browser.profiles.Profile; +import org.chromium.chrome.browser.search_engines.settings.common.SearchEngineListPreference; +import org.chromium.chrome.browser.search_engines.settings.common.SiteSearchProperties; +import org.chromium.chrome.browser.search_engines.settings.common.SiteSearchViewBinder; +import org.chromium.ui.modelutil.MVCListAdapter.ModelList; +import org.chromium.ui.modelutil.PropertyModel; +import org.chromium.ui.modelutil.PropertyModelChangeProcessor; +import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter; + +@NullMarked +public class InactiveShortcutCoordinator { + private final ModelList mModelList; + private final SimpleRecyclerViewAdapter mAdapter; + private final InactiveShortcutMediator mMediator; + private final PropertyModel mPropertyModel; + private final PropertyModelChangeProcessor mPropertyModelChangeProcessor; + + public InactiveShortcutCoordinator( + Context context, Profile profile, SearchEngineListPreference pref) { + mModelList = new ModelList(); + mAdapter = new InactiveShortcutAdapter(context, mModelList); + mMediator = new InactiveShortcutMediator(context, mModelList, profile); + + mPropertyModel = + new PropertyModel.Builder(SiteSearchProperties.ALL_KEYS) + .with(SiteSearchProperties.ADAPTER, mAdapter) + .build(); + + mPropertyModelChangeProcessor = + PropertyModelChangeProcessor.create( + mPropertyModel, pref, SiteSearchViewBinder::bindPreference); + } + + public void destroy() { + mPropertyModel.set(SiteSearchProperties.ADAPTER, null); + mPropertyModelChangeProcessor.destroy(); + mAdapter.destroy(); + mMediator.destroy(); + } +}
diff --git a/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/settings/inactive_shortcut/InactiveShortcutMediator.java b/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/settings/inactive_shortcut/InactiveShortcutMediator.java new file mode 100644 index 0000000..e9b40f9 --- /dev/null +++ b/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/settings/inactive_shortcut/InactiveShortcutMediator.java
@@ -0,0 +1,211 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.search_engines.settings.inactive_shortcut; + +import android.content.Context; +import android.graphics.Bitmap; + +import androidx.annotation.VisibleForTesting; + +import org.chromium.build.annotations.NullMarked; +import org.chromium.chrome.browser.profiles.Profile; +import org.chromium.chrome.browser.search_engines.R; +import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory; +import org.chromium.chrome.browser.search_engines.settings.SearchEngineIconUtils; +import org.chromium.chrome.browser.search_engines.settings.common.SiteSearchProperties; +import org.chromium.chrome.browser.ui.favicon.FaviconUtils; +import org.chromium.components.browser_ui.widget.BrowserUiListMenuUtils; +import org.chromium.components.browser_ui.widget.ListItemBuilder; +import org.chromium.components.favicon.LargeIconBridge; +import org.chromium.components.search_engines.StarterPackId; +import org.chromium.components.search_engines.TemplateUrl; +import org.chromium.components.search_engines.TemplateUrlCategory; +import org.chromium.components.search_engines.TemplateUrlService; +import org.chromium.ui.listmenu.ListMenuDelegate; +import org.chromium.ui.listmenu.ListMenuItemProperties; +import org.chromium.ui.modelutil.MVCListAdapter.ListItem; +import org.chromium.ui.modelutil.MVCListAdapter.ModelList; +import org.chromium.ui.modelutil.PropertyModel; +import org.chromium.url.GURL; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@NullMarked +public class InactiveShortcutMediator implements TemplateUrlService.TemplateUrlServiceObserver { + private static final int DEFAULT_MAX_ROWS = 5; + + private final Context mContext; + private final ModelList mModelList; + private final TemplateUrlService mTemplateUrlService; + private final LargeIconBridge mLargeIconBridge; + private final int mFaviconSize; + private final Map<GURL, Bitmap> mIconCache = new HashMap<GURL, Bitmap>(); + private final List<TemplateUrl> mHiddenUrls = new ArrayList<>(); + private final List<ListItem> mHiddenItems = new ArrayList<>(); + private boolean mIsExpanded; + + boolean isExpandedForTesting() { + return mIsExpanded; + } + + public InactiveShortcutMediator(Context context, ModelList modelList, Profile profile) { + mContext = context; + mModelList = modelList; + mTemplateUrlService = TemplateUrlServiceFactory.getForProfile(profile); + mLargeIconBridge = new LargeIconBridge(profile); + mFaviconSize = context.getResources().getDimensionPixelSize(R.dimen.default_favicon_size); + + mTemplateUrlService.addObserver(this); + mTemplateUrlService.runWhenLoaded(this::refreshList); + } + + public void destroy() { + mTemplateUrlService.removeObserver(this); + mLargeIconBridge.destroy(); + } + + @Override + public void onTemplateURLServiceChanged() { + mIsExpanded = false; + refreshList(); + } + + private void refreshList() { + mModelList.clear(); + mHiddenUrls.clear(); + mHiddenItems.clear(); + + List<TemplateUrl> urls = + mTemplateUrlService.getTemplateUrlsByCategory( + TemplateUrlCategory.INACTIVE_SITE_SEARCH); + + setUpSiteSearchList(urls); + setUpMoreButtonIfNeeded(urls.size()); + } + + private void setUpSiteSearchList(List<TemplateUrl> urls) { + for (int i = 0; i < urls.size(); i++) { + TemplateUrl url = urls.get(i); + if (i < DEFAULT_MAX_ROWS) { + mModelList.add(createListItem(url)); + } else { + mHiddenUrls.add(url); + } + } + } + + private ListItem createListItem(TemplateUrl url) { + PropertyModel model = + new PropertyModel.Builder(SiteSearchProperties.ALL_KEYS) + .with(SiteSearchProperties.SITE_NAME, url.getShortName()) + .with(SiteSearchProperties.SITE_SHORTCUT, url.getKeyword()) + .with(SiteSearchProperties.MENU_DELEGATE, createMenuDelegate(url)) + .with( + SiteSearchProperties.ICON, + FaviconUtils.createGenericFaviconBitmap( + mContext, mFaviconSize, null)) + .build(); + + fetchFavicon(url, model); + return new ListItem(SiteSearchProperties.ViewType.SEARCH_ENGINE, model); + } + + private void setUpMoreButtonIfNeeded(int numUrls) { + if (numUrls <= DEFAULT_MAX_ROWS) { + return; + } + PropertyModel moreButtonModel = + new PropertyModel.Builder(SiteSearchProperties.ALL_KEYS) + .with(SiteSearchProperties.IS_EXPANDED, mIsExpanded) + .build(); + moreButtonModel.set( + SiteSearchProperties.ON_CLICK, v -> onMoreButtonClicked(moreButtonModel)); + mModelList.add(new ListItem(SiteSearchProperties.ViewType.MORE, moreButtonModel)); + } + + // TODO: We're using a single RecyclerView + ModelList to display the list + More button + + // the hidden list items. Consider splitting into one RecyclerView for the list + More button + // and another RecyclerView for the hidden list items. + private void onMoreButtonClicked(PropertyModel moreButtonModel) { + mIsExpanded = !mIsExpanded; + moreButtonModel.set(SiteSearchProperties.IS_EXPANDED, mIsExpanded); + if (mIsExpanded) { + // Lazy load models and fetch favicons on the first expansion. + if (mHiddenItems.isEmpty() && !mHiddenUrls.isEmpty()) { + for (TemplateUrl url : mHiddenUrls) { + mHiddenItems.add(createListItem(url)); + } + } + mModelList.addAll(mHiddenItems); + } else { + // Remove items starting from index DEFAULT_MAX_ROWS + 1. + // Index layout: + // [0 to DEFAULT_MAX_ROWS - 1] : Site search list + // [DEFAULT_MAX_ROWS] : 'More' button + // [DEFAULT_MAX_ROWS + 1 to ... + 1 + mHiddenItems.size()] : Hidden items + mModelList.removeRange(DEFAULT_MAX_ROWS + 1, mHiddenItems.size()); + } + } + + private void fetchFavicon(TemplateUrl url, PropertyModel model) { + GURL faviconUrl = url.getFaviconURL(); + if (faviconUrl == null) { + return; + } + SearchEngineIconUtils.updateIcon( + mContext, + model, + SiteSearchProperties.ICON, + url, + faviconUrl, + mLargeIconBridge, + mIconCache); + } + + private ListMenuDelegate createMenuDelegate(TemplateUrl url) { + return () -> { + ModelList menuItems = new ModelList(); + // If url is from a starter pack, we can only activate it; otherwise, we can make + // activate, make default and delete it. + // TODO: Handle this in the native layer. + menuItems.add( + new ListItemBuilder() + .withTitleRes(R.string.site_search_list_menu_activate) + .build()); + if (url.getStarterPackId() == StarterPackId.NONE) { + menuItems.add( + new ListItemBuilder() + .withTitleRes(R.string.site_search_list_menu_make_default) + .build()); + menuItems.add( + new ListItemBuilder() + .withTitleRes(R.string.site_search_list_menu_delete) + .build()); + } + + return BrowserUiListMenuUtils.getBasicListMenu( + mContext, + menuItems, + (model, view) -> { + int textId = model.get(ListMenuItemProperties.TITLE_ID); + onMenuItemClicked(textId, url); + }); + }; + } + + @VisibleForTesting + void onMenuItemClicked(int textId, TemplateUrl url) { + if (textId == R.string.site_search_list_menu_activate) { + // TODO: Activate search engine + } else if (textId == R.string.site_search_list_menu_make_default) { + mTemplateUrlService.setSearchEngine(url.getKeyword()); + } else if (textId == R.string.site_search_list_menu_delete) { + mTemplateUrlService.removeSearchEngine(url.getKeyword()); + } + } +}
diff --git a/chrome/browser/search_engines/android/junit/src/org/chromium/chrome/browser/search_engines/settings/inactive_shortcut/InactiveShortcutMediatorUnitTest.java b/chrome/browser/search_engines/android/junit/src/org/chromium/chrome/browser/search_engines/settings/inactive_shortcut/InactiveShortcutMediatorUnitTest.java new file mode 100644 index 0000000..20454bd --- /dev/null +++ b/chrome/browser/search_engines/android/junit/src/org/chromium/chrome/browser/search_engines/settings/inactive_shortcut/InactiveShortcutMediatorUnitTest.java
@@ -0,0 +1,265 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.search_engines.settings.inactive_shortcut; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import org.chromium.base.test.BaseRobolectricTestRunner; +import org.chromium.chrome.browser.profiles.Profile; +import org.chromium.chrome.browser.search_engines.R; +import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory; +import org.chromium.chrome.browser.search_engines.settings.common.SiteSearchProperties; +import org.chromium.components.favicon.LargeIconBridgeJni; +import org.chromium.components.search_engines.StarterPackId; +import org.chromium.components.search_engines.TemplateUrl; +import org.chromium.components.search_engines.TemplateUrlCategory; +import org.chromium.components.search_engines.TemplateUrlService; +import org.chromium.ui.listmenu.BasicListMenu; +import org.chromium.ui.listmenu.ListMenuDelegate; +import org.chromium.ui.listmenu.ListMenuItemProperties; +import org.chromium.ui.modelutil.MVCListAdapter.ListItem; +import org.chromium.ui.modelutil.MVCListAdapter.ModelList; +import org.chromium.ui.modelutil.ModelListAdapter; +import org.chromium.ui.modelutil.PropertyModel; +import org.chromium.url.GURL; + +import java.util.ArrayList; +import java.util.List; + +/** Unit tests for {@link InactiveShortcutMediator}. */ +@RunWith(BaseRobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class InactiveShortcutMediatorUnitTest { + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock private Profile mProfile; + @Mock private TemplateUrlService mTemplateUrlService; + @Mock private LargeIconBridgeJni mLargeIconBridgeJni; + + private Context mContext; + private InactiveShortcutMediator mMediator; + private ModelList mModelList; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + TemplateUrlServiceFactory.setInstanceForTesting(mTemplateUrlService); + LargeIconBridgeJni.setInstanceForTesting(mLargeIconBridgeJni); + + mModelList = new ModelList(); + + doAnswer( + invocation -> { + Runnable runnable = invocation.getArgument(0); + runnable.run(); + return null; + }) + .when(mTemplateUrlService) + .runWhenLoaded(any()); + } + + private TemplateUrl createMockTemplateUrl(String keyword, String shortName) { + TemplateUrl templateUrl = org.mockito.Mockito.mock(TemplateUrl.class); + when(templateUrl.getKeyword()).thenReturn(keyword); + when(templateUrl.getShortName()).thenReturn(shortName); + when(templateUrl.getFaviconURL()).thenReturn(new GURL("http://example.com/favicon.ico")); + return templateUrl; + } + + private void setUpTemplateUrlService(int searchEngineCount) { + List<TemplateUrl> urls = new ArrayList<>(); + for (int i = 0; i < searchEngineCount; i++) { + urls.add(createMockTemplateUrl("keyword" + i, "test site " + i)); + } + when(mTemplateUrlService.getTemplateUrlsByCategory( + TemplateUrlCategory.INACTIVE_SITE_SEARCH)) + .thenReturn(urls); + } + + @Test + public void testSiteSearchList_underMaxRows() { + setUpTemplateUrlService(/* searchEngineCount= */ 3); + mMediator = new InactiveShortcutMediator(mContext, mModelList, mProfile); + + verifyCollapsedModelListView(/* searchEngineCount= */ 3); + } + + @Test + public void testSiteSearchList_exactMaxRows() { + setUpTemplateUrlService(/* searchEngineCount= */ 5); + mMediator = new InactiveShortcutMediator(mContext, mModelList, mProfile); + + verifyCollapsedModelListView(/* searchEngineCount= */ 5); + } + + @Test + public void testSiteSearchList_overMaxRows() { + setUpTemplateUrlService(/* searchEngineCount= */ 7); + mMediator = new InactiveShortcutMediator(mContext, mModelList, mProfile); + + verifyCollapsedModelListView(/* searchEngineCount= */ 7); + + // Click "More" to expand + PropertyModel moreButtonModel = mModelList.get(5).model; + assertFalse(moreButtonModel.get(SiteSearchProperties.IS_EXPANDED)); + moreButtonModel.get(SiteSearchProperties.ON_CLICK).onClick(null); + + assertTrue(moreButtonModel.get(SiteSearchProperties.IS_EXPANDED)); + // 5 site search items + 1 more button + 2 expanded site search items + assertEquals(8, mModelList.size()); + + for (int i = 6; i < 8; i++) { + ListItem item = mModelList.get(i); + assertEquals(SiteSearchProperties.ViewType.SEARCH_ENGINE, item.type); + assertEquals("test site " + (i - 1), item.model.get(SiteSearchProperties.SITE_NAME)); + assertEquals("keyword" + (i - 1), item.model.get(SiteSearchProperties.SITE_SHORTCUT)); + } + + // Click "More" to collapse + moreButtonModel.get(SiteSearchProperties.ON_CLICK).onClick(null); + + assertFalse(moreButtonModel.get(SiteSearchProperties.IS_EXPANDED)); + verifyCollapsedModelListView(/* searchEngineCount= */ 7); + } + + @Test + public void testSiteSearchList_templateUrlServiceChanged() { + setUpTemplateUrlService(7); + mMediator = new InactiveShortcutMediator(mContext, mModelList, mProfile); + + verifyCollapsedModelListView(/* searchEngineCount= */ 7); + + // Expand the list + PropertyModel moreButtonModel = mModelList.get(5).model; + moreButtonModel.get(SiteSearchProperties.ON_CLICK).onClick(null); + assertTrue(mMediator.isExpandedForTesting()); + + mMediator.onTemplateURLServiceChanged(); + + assertFalse(mMediator.isExpandedForTesting()); + verifyCollapsedModelListView(/* searchEngineCount= */ 7); + } + + @Test + public void testMenuDelegate_NormalUrl() { + TemplateUrl templateUrl = createMockTemplateUrl("keyword", "shortName"); + when(mTemplateUrlService.getTemplateUrlsByCategory( + TemplateUrlCategory.INACTIVE_SITE_SEARCH)) + .thenReturn(List.of(templateUrl)); + when(templateUrl.getStarterPackId()).thenReturn(StarterPackId.NONE); + + mMediator = new InactiveShortcutMediator(mContext, mModelList, mProfile); + + PropertyModel model = mModelList.get(0).model; + ListMenuDelegate delegate = model.get(SiteSearchProperties.MENU_DELEGATE); + assertNotNull(delegate); + + BasicListMenu listMenu = (BasicListMenu) delegate.getListMenu(); + ModelListAdapter adapter = listMenu.getContentAdapter(); + assertEquals(3, adapter.getCount()); + + ListItem activateItem = (ListItem) adapter.getItem(0); + assertEquals( + R.string.site_search_list_menu_activate, + activateItem.model.get(ListMenuItemProperties.TITLE_ID)); + + ListItem makeDefaultItem = (ListItem) adapter.getItem(1); + assertEquals( + R.string.site_search_list_menu_make_default, + makeDefaultItem.model.get(ListMenuItemProperties.TITLE_ID)); + + ListItem deleteItem = (ListItem) adapter.getItem(2); + assertEquals( + R.string.site_search_list_menu_delete, + deleteItem.model.get(ListMenuItemProperties.TITLE_ID)); + } + + @Test + public void testMenuDelegate_StarterPackUrl() { + TemplateUrl templateUrl = createMockTemplateUrl("keyword", "shortName"); + when(mTemplateUrlService.getTemplateUrlsByCategory( + TemplateUrlCategory.INACTIVE_SITE_SEARCH)) + .thenReturn(List.of(templateUrl)); + when(templateUrl.getStarterPackId()).thenReturn(StarterPackId.GEMINI); + + mMediator = new InactiveShortcutMediator(mContext, mModelList, mProfile); + + PropertyModel model = mModelList.get(0).model; + ListMenuDelegate delegate = model.get(SiteSearchProperties.MENU_DELEGATE); + assertNotNull(delegate); + + BasicListMenu listMenu = (BasicListMenu) delegate.getListMenu(); + ModelListAdapter adapter = listMenu.getContentAdapter(); + assertEquals(1, adapter.getCount()); + + ListItem activateItem = (ListItem) adapter.getItem(0); + assertEquals( + R.string.site_search_list_menu_activate, + activateItem.model.get(ListMenuItemProperties.TITLE_ID)); + } + + @Test + public void testMakeDefaultClicked() { + mMediator = new InactiveShortcutMediator(mContext, mModelList, mProfile); + TemplateUrl templateUrl = createMockTemplateUrl("keyword", "shortName"); + + mMediator.onMenuItemClicked(R.string.site_search_list_menu_make_default, templateUrl); + + verify(mTemplateUrlService).setSearchEngine("keyword"); + } + + @Test + public void testDeleteClicked() { + mMediator = new InactiveShortcutMediator(mContext, mModelList, mProfile); + TemplateUrl templateUrl = createMockTemplateUrl("keyword", "shortName"); + + mMediator.onMenuItemClicked(R.string.site_search_list_menu_delete, templateUrl); + + verify(mTemplateUrlService).removeSearchEngine("keyword"); + } + + private void verifyCollapsedModelListView(int searchEngineCount) { + int defaultViewCount = 5; + boolean expectMoreButton = searchEngineCount > defaultViewCount; + int visibleSearchEngines = Math.min(searchEngineCount, defaultViewCount); + + int expectedTotalCount = visibleSearchEngines; + if (expectMoreButton) { + expectedTotalCount += 1; + } + assertEquals(expectedTotalCount, mModelList.size()); + + for (int i = 0; i < visibleSearchEngines; i++) { + ListItem item = mModelList.get(i); + assertEquals(SiteSearchProperties.ViewType.SEARCH_ENGINE, item.type); + assertEquals("test site " + i, item.model.get(SiteSearchProperties.SITE_NAME)); + assertEquals("keyword" + i, item.model.get(SiteSearchProperties.SITE_SHORTCUT)); + } + + if (expectMoreButton) { + assertEquals( + SiteSearchProperties.ViewType.MORE, mModelList.get(visibleSearchEngines).type); + } + } +}
diff --git a/chrome/browser/search_engines/prepopulated_engines_migration_browsertest.cc b/chrome/browser/search_engines/prepopulated_engines_migration_browsertest.cc index 034a20d..fd1edde 100644 --- a/chrome/browser/search_engines/prepopulated_engines_migration_browsertest.cc +++ b/chrome/browser/search_engines/prepopulated_engines_migration_browsertest.cc
@@ -2,10 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <memory> +#include <optional> +#include <ostream> +#include <string> +#include <utility> +#include <vector> + #include "base/check.h" #include "base/check_deref.h" #include "base/run_loop.h" #include "base/test/with_feature_override.h" +#include "base/values.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/search_engines/template_url_service_factory.h" #include "chrome/test/base/in_process_browser_test.h" @@ -19,6 +27,8 @@ #include "components/search_engines/template_url_service.h" #include "components/search_engines/template_url_starter_pack_data.h" #include "content/public/test/browser_test.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" #include "third_party/search_engines_data/resources/definitions/prepopulated_engines.h" namespace { @@ -26,14 +36,15 @@ using ::TemplateURLPrepopulateData::bing; using ::TemplateURLPrepopulateData::google; using ::TemplateURLPrepopulateData::PrepopulatedEngine; +using ::testing::ElementsAreArray; using TemplateURLVector = ::TemplateURL::TemplateURLVector; // This test suite simulates that Android codesearch is being split from the // other codesearch services, to get its own dedicated ID. const char16_t android_keyword[] = u"cs.android.com"; -const int generic_id = 2424; -const int new_id = 1313; +const int generic_id = 924; +const int new_id = 913; // Variants of a "codesearch" collection of services @@ -114,17 +125,54 @@ RegionalAndNonRegionalEngines chrome_migrated_region_engines = { {&google, &bing, &chrome_codesearch}, - {&android_codesearch, &android_codesearch_migrating, + {&chromium_codesearch, &android_codesearch_migrating, &android_codesearch_next}, }; -struct SearchProviderExpectation { +// Describes various forms of search providers (`TemplateURL`, +// `TemplateURLData`, `PrepopulatedEngine`) for checking expectations. +struct SearchProviderSummary { int id; std::u16string keyword; - bool is_default; + + // Optional fields are checked for object equality only if both summaries + // specify it. So always make sure that the "actual" summary populates this + // field. + std::optional<bool> is_default; std::optional<std::string> search_url; + + bool operator==(const SearchProviderSummary& other) const { + if (id != other.id || keyword != other.keyword) { + return false; + } + + if (is_default.has_value() && other.is_default.has_value() && + is_default != other.is_default) { + return false; + } + + if (search_url.has_value() && other.search_url.has_value() && + *search_url != *other.search_url) { + return false; + } + + return true; + } }; +// For GTEST error messages. +[[maybe_unused]] void PrintTo(const SearchProviderSummary& summary, + std::ostream* os) { + *os << "{ id: " << summary.id << ", keyword: " << summary.keyword; + if (summary.is_default.has_value()) { + *os << ", is_default: " << (summary.is_default ? "true" : "false"); + }; + if (summary.search_url.has_value()) { + *os << ", search_url: " << *summary.search_url; + } + *os << " }"; +} + // Tests related to Prepopulated engine migration. // // Assuming we have regional lists like @@ -137,7 +185,7 @@ class PrepopulatedEnginesMigrationBrowserTestBase : public InProcessBrowserTest, public base::test::WithFeatureOverride { - public: + protected: explicit PrepopulatedEnginesMigrationBrowserTestBase( std::vector<RegionalAndNonRegionalEngines> engine_set_load_order) : base::test::WithFeatureOverride( @@ -150,7 +198,7 @@ engine_set_load_order.size() - 1 - GetTestPreCount(); CHECK_GE(set_to_load_index, 0); CHECK_LT(set_to_load_index, engine_set_load_order.size()); - auto engines_to_load = engine_set_load_order[set_to_load_index]; + active_engines_config_ = engine_set_load_order[set_to_load_index]; // Note: Since this gets recorded as a search provider list override, // rebuilding the keywords DB is always triggered, without having to change @@ -158,7 +206,7 @@ scoped_engines_override_ = std::make_unique< regional_capabilities::ScopedPrepopulatedEnginesOverride>( regional_capabilities::SetPrepopulatedEnginesOverrideForTesting( - engines_to_load.first, engines_to_load.second)); + active_engines_config_.first, active_engines_config_.second)); } void SetUpOnMainThread() override { @@ -175,21 +223,6 @@ return CHECK_DEREF(browser()->profile()->GetPrefs()); } - TemplateURLVector GetTemplateURLsWithoutStarterPack() { - auto urls = template_url_service().GetTemplateURLs(); - urls.erase( - std::remove_if( - urls.begin(), urls.end(), - [](const auto& template_url_unique_ptr) { - return template_url_unique_ptr->starter_pack_id() != - template_url_starter_pack_data::StarterPackId::kNone; - }), - urls.end()); - return urls; - } - - int CountEngines() { return GetTemplateURLsWithoutStarterPack().size(); } - void WaitForTemplateURLServiceLoad() { base::RunLoop run_loop; auto subscription = @@ -200,34 +233,44 @@ ASSERT_TRUE(template_url_service().loaded()); } - void CheckSearchProviderExpectations( - base::span<SearchProviderExpectation> expectations) { - TemplateURLVector template_urls = GetTemplateURLsWithoutStarterPack(); - auto* dse = template_url_service().GetDefaultSearchProvider(); + std::vector<SearchProviderSummary> GetServiceSearchProviders() { + std::vector<SearchProviderSummary> actuals; + TemplateURL::TemplateURLVector template_urls = + template_url_service().GetTemplateURLs(); + const TemplateURL* dse = template_url_service().GetDefaultSearchProvider(); - ASSERT_EQ(expectations.size(), template_urls.size()); - for (int i = 0; i < expectations.size(); ++i) { - const SearchProviderExpectation& expectation = expectations[i]; - const TemplateURL* actual = template_urls[i].get(); - - EXPECT_EQ(expectation.id, actual->prepopulate_id()); - EXPECT_EQ(expectation.keyword, actual->keyword()); - EXPECT_EQ(expectation.is_default, actual == dse); - - if (expectation.search_url.has_value()) { - EXPECT_EQ(*expectation.search_url, actual->url()); + for (const auto& turl : template_urls) { + if (turl->starter_pack_id() != + template_url_starter_pack_data::StarterPackId::kNone) { + // Ignore starter pack entries (tabs, bookmarks, etc), they just add + // noise and are irrelevant to the current logic. + continue; } + + actuals.push_back({ + .id = turl->prepopulate_id(), + .keyword = turl->keyword(), + .is_default = turl.get() == dse, + .search_url = turl->url(), + }); } + return actuals; } - void CheckSearchProviderInPrefs(std::string_view pref_name, - SearchProviderExpectation expectations) { - const base::DictValue& url_dict = profile_prefs().GetDict(pref_name); - auto turl_data = TemplateURLDataFromDictionary(url_dict); - EXPECT_EQ(turl_data->keyword(), expectations.keyword); - EXPECT_EQ(turl_data->prepopulate_id, expectations.id); + SearchProviderSummary GetSearchProviderFromPrefs(std::string_view pref_name) { + std::unique_ptr<TemplateURLData> turl_data = + TemplateURLDataFromDictionary(profile_prefs().GetDict(pref_name)); + CHECK(turl_data); + return { + .id = turl_data->prepopulate_id, + .keyword = turl_data->keyword(), + .search_url = turl_data->url(), + }; } + protected: + RegionalAndNonRegionalEngines active_engines_config_; + private: std::unique_ptr<regional_capabilities::ScopedPrepopulatedEnginesOverride> scoped_engines_override_; @@ -240,7 +283,7 @@ class PrepopulatedEnginesMigrationBrowserTest : public PrepopulatedEnginesMigrationBrowserTestBase { - public: + protected: PrepopulatedEnginesMigrationBrowserTest() : PrepopulatedEnginesMigrationBrowserTestBase( {android_region_engines, android_migrated_region_engines}) {} @@ -248,65 +291,77 @@ INSTANTIATE_TEST_SUITE_P(, PrepopulatedEnginesMigrationBrowserTest, - testing::Bool(), + testing::Bool(), // Feature states &ParamToTestSuffix); // -- Non DSE ----------------------------------------------------------------- IN_PROC_BROWSER_TEST_P(PrepopulatedEnginesMigrationBrowserTest, PRE_NonDse) { - std::vector<SearchProviderExpectation> expectations = { + ASSERT_EQ(active_engines_config_, android_region_engines); + + std::vector<SearchProviderSummary> expectations = { {google.id, google.keyword, true}, {bing.id, bing.keyword, false}, {generic_id, android_keyword, false}}; - CheckSearchProviderExpectations(expectations); + EXPECT_THAT(GetServiceSearchProviders(), ElementsAreArray(expectations)); } IN_PROC_BROWSER_TEST_P(PrepopulatedEnginesMigrationBrowserTest, NonDse) { - std::vector<SearchProviderExpectation> expectations{ + ASSERT_EQ(active_engines_config_, android_migrated_region_engines); + + std::vector<SearchProviderSummary> expectations{ {google.id, google.keyword, true}, {bing.id, bing.keyword, false}, {IsParamFeatureEnabled() ? new_id : generic_id, android_keyword, false}}; - CheckSearchProviderExpectations(expectations); + EXPECT_THAT(GetServiceSearchProviders(), ElementsAreArray(expectations)); } // -- DSE -------------------------------------------------------------------- IN_PROC_BROWSER_TEST_P(PrepopulatedEnginesMigrationBrowserTest, PRE_Dse) { + ASSERT_EQ(active_engines_config_, android_region_engines); + template_url_service().SetUserSelectedDefaultSearchProvider( template_url_service().GetTemplateURLForKeyword(android_keyword)); - std::vector<SearchProviderExpectation> expectations = { + std::vector<SearchProviderSummary> expectations = { {google.id, google.keyword, false}, {bing.id, bing.keyword, false}, {generic_id, android_keyword, true}}; - CheckSearchProviderExpectations(expectations); + EXPECT_THAT(GetServiceSearchProviders(), ElementsAreArray(expectations)); - CheckSearchProviderInPrefs( - DefaultSearchManager::kDefaultSearchProviderDataPrefName, - {generic_id, android_codesearch.keyword}); - CheckSearchProviderInPrefs( - DefaultSearchManager::kMirroredDefaultSearchProviderDataPrefName, - {generic_id, android_codesearch.keyword}); + EXPECT_EQ(GetSearchProviderFromPrefs( + DefaultSearchManager::kDefaultSearchProviderDataPrefName), + (SearchProviderSummary{generic_id, android_codesearch.keyword})); + EXPECT_EQ( + GetSearchProviderFromPrefs( + DefaultSearchManager::kMirroredDefaultSearchProviderDataPrefName), + (SearchProviderSummary{generic_id, android_codesearch.keyword})); } IN_PROC_BROWSER_TEST_P(PrepopulatedEnginesMigrationBrowserTest, Dse) { - std::vector<SearchProviderExpectation> expectations{ + ASSERT_EQ(active_engines_config_, android_migrated_region_engines); + + std::vector<SearchProviderSummary> expectations{ {google.id, google.keyword, false}, {bing.id, bing.keyword, false}, {IsParamFeatureEnabled() ? new_id : generic_id, android_keyword, true}}; - CheckSearchProviderExpectations(expectations); + EXPECT_THAT(GetServiceSearchProviders(), ElementsAreArray(expectations)); // Prefs are not affected by the feature state. - CheckSearchProviderInPrefs( - DefaultSearchManager::kDefaultSearchProviderDataPrefName, - {generic_id, android_codesearch.keyword}); - CheckSearchProviderInPrefs( - DefaultSearchManager::kMirroredDefaultSearchProviderDataPrefName, - {generic_id, android_codesearch.keyword}); + EXPECT_EQ(GetSearchProviderFromPrefs( + DefaultSearchManager::kDefaultSearchProviderDataPrefName), + (SearchProviderSummary{generic_id, android_codesearch.keyword})); + EXPECT_EQ( + GetSearchProviderFromPrefs( + DefaultSearchManager::kMirroredDefaultSearchProviderDataPrefName), + (SearchProviderSummary{generic_id, android_codesearch.keyword})); } // -- UserModified ------------------------------------------------------------ IN_PROC_BROWSER_TEST_P(PrepopulatedEnginesMigrationBrowserTest, PRE_UserModified) { + ASSERT_EQ(active_engines_config_, android_region_engines); + auto* migrating_t_url = template_url_service().GetTemplateURLForKeyword(android_keyword); ASSERT_EQ(generic_id, migrating_t_url->prepopulate_id()); @@ -314,18 +369,20 @@ template_url_service().ResetTemplateURL(migrating_t_url, u"Searchy Search", u"cs", android_codesearch.search_url); - std::vector<SearchProviderExpectation> expectations = { + std::vector<SearchProviderSummary> expectations = { {google.id, google.keyword, true}, {bing.id, bing.keyword, false}, {generic_id, u"cs", false}}; - CheckSearchProviderExpectations(expectations); + EXPECT_THAT(GetServiceSearchProviders(), ElementsAreArray(expectations)); } IN_PROC_BROWSER_TEST_P(PrepopulatedEnginesMigrationBrowserTest, UserModified) { - std::vector<SearchProviderExpectation> expectations{ + ASSERT_EQ(active_engines_config_, android_migrated_region_engines); + + std::vector<SearchProviderSummary> expectations{ {google.id, google.keyword, true}, {bing.id, bing.keyword, false}, {IsParamFeatureEnabled() ? new_id : generic_id, u"cs", false}}; - CheckSearchProviderExpectations(expectations); + EXPECT_THAT(GetServiceSearchProviders(), ElementsAreArray(expectations)); auto* modified_url = template_url_service().GetTemplateURLForKeyword(u"cs"); EXPECT_FALSE(modified_url->safe_for_autoreplace()); @@ -340,7 +397,7 @@ // the original region + migration class PrepopulatedEnginesXRegionMigrationBrowserTest : public PrepopulatedEnginesMigrationBrowserTestBase { - public: + protected: PrepopulatedEnginesXRegionMigrationBrowserTest() : PrepopulatedEnginesMigrationBrowserTestBase( {android_region_engines, chrome_region_engines, @@ -349,85 +406,100 @@ INSTANTIATE_TEST_SUITE_P(, PrepopulatedEnginesXRegionMigrationBrowserTest, - testing::Bool(), + testing::Bool(), // Feature states &ParamToTestSuffix); IN_PROC_BROWSER_TEST_P(PrepopulatedEnginesXRegionMigrationBrowserTest, PRE_PRE_NonDse) { - std::vector<SearchProviderExpectation> expectations = { + ASSERT_EQ(active_engines_config_, android_region_engines); + + std::vector<SearchProviderSummary> expectations = { {google.id, google.keyword, true}, {bing.id, bing.keyword, false}, {generic_id, android_codesearch.keyword, false}}; - CheckSearchProviderExpectations(expectations); + EXPECT_THAT(GetServiceSearchProviders(), ElementsAreArray(expectations)); } IN_PROC_BROWSER_TEST_P(PrepopulatedEnginesXRegionMigrationBrowserTest, PRE_NonDse) { - std::vector<SearchProviderExpectation> expectations = { + ASSERT_EQ(active_engines_config_, chrome_region_engines); + + std::vector<SearchProviderSummary> expectations = { {google.id, google.keyword, true}, {bing.id, bing.keyword, false}, {generic_id, chrome_codesearch.keyword, false}}; - CheckSearchProviderExpectations(expectations); + EXPECT_THAT(GetServiceSearchProviders(), ElementsAreArray(expectations)); } IN_PROC_BROWSER_TEST_P(PrepopulatedEnginesXRegionMigrationBrowserTest, NonDse) { - std::vector<SearchProviderExpectation> expectations{ + ASSERT_EQ(active_engines_config_, android_migrated_region_engines); + + std::vector<SearchProviderSummary> expectations{ {google.id, google.keyword, true}, {bing.id, bing.keyword, false}, {IsParamFeatureEnabled() ? new_id : generic_id, android_keyword, false}}; - CheckSearchProviderExpectations(expectations); + EXPECT_THAT(GetServiceSearchProviders(), ElementsAreArray(expectations)); } // -- CrossRegionDSE ---------------------------------------------------------- IN_PROC_BROWSER_TEST_P(PrepopulatedEnginesXRegionMigrationBrowserTest, PRE_PRE_Dse) { + ASSERT_EQ(active_engines_config_, android_region_engines); + template_url_service().SetUserSelectedDefaultSearchProvider( template_url_service().GetTemplateURLForKeyword(android_keyword)); - std::vector<SearchProviderExpectation> expectations = { + std::vector<SearchProviderSummary> expectations = { {google.id, google.keyword, false}, {bing.id, bing.keyword, false}, {generic_id, android_codesearch.keyword, true}}; - CheckSearchProviderExpectations(expectations); - CheckSearchProviderInPrefs( - DefaultSearchManager::kDefaultSearchProviderDataPrefName, - expectations[2]); - CheckSearchProviderInPrefs( - DefaultSearchManager::kMirroredDefaultSearchProviderDataPrefName, + EXPECT_THAT(GetServiceSearchProviders(), ElementsAreArray(expectations)); + EXPECT_EQ(GetSearchProviderFromPrefs( + DefaultSearchManager::kDefaultSearchProviderDataPrefName), + expectations[2]); + EXPECT_EQ( + GetSearchProviderFromPrefs( + DefaultSearchManager::kMirroredDefaultSearchProviderDataPrefName), expectations[2]); } IN_PROC_BROWSER_TEST_P(PrepopulatedEnginesXRegionMigrationBrowserTest, PRE_Dse) { - std::vector<SearchProviderExpectation> expectations = { + ASSERT_EQ(active_engines_config_, chrome_region_engines); + + std::vector<SearchProviderSummary> expectations = { {google.id, google.keyword, false}, {bing.id, bing.keyword, false}, {generic_id, chrome_codesearch.keyword, true}}; - CheckSearchProviderExpectations(expectations); + EXPECT_THAT(GetServiceSearchProviders(), ElementsAreArray(expectations)); // TODO(crbug.com/480071119): Prefs are still the ones associated with // `android_codesearch`. - CheckSearchProviderInPrefs( - DefaultSearchManager::kDefaultSearchProviderDataPrefName, - {generic_id, android_codesearch.keyword}); - CheckSearchProviderInPrefs( - DefaultSearchManager::kMirroredDefaultSearchProviderDataPrefName, - {generic_id, android_codesearch.keyword}); + EXPECT_EQ(GetSearchProviderFromPrefs( + DefaultSearchManager::kDefaultSearchProviderDataPrefName), + (SearchProviderSummary{generic_id, android_codesearch.keyword})); + EXPECT_EQ( + GetSearchProviderFromPrefs( + DefaultSearchManager::kMirroredDefaultSearchProviderDataPrefName), + (SearchProviderSummary{generic_id, android_codesearch.keyword})); } IN_PROC_BROWSER_TEST_P(PrepopulatedEnginesXRegionMigrationBrowserTest, Dse) { - std::vector<SearchProviderExpectation> expectations{ + ASSERT_EQ(active_engines_config_, android_migrated_region_engines); + + std::vector<SearchProviderSummary> expectations{ {google.id, google.keyword, false}, {bing.id, bing.keyword, false}, {IsParamFeatureEnabled() ? new_id : generic_id, android_keyword, true}}; - CheckSearchProviderExpectations(expectations); + EXPECT_THAT(GetServiceSearchProviders(), ElementsAreArray(expectations)); // TODO(crbug.com/480071119): Prefs did not change since the PRE_PRE_ // run, we migrate from them although the previous state was "non-migrating". - CheckSearchProviderInPrefs( - DefaultSearchManager::kDefaultSearchProviderDataPrefName, - {generic_id, android_codesearch.keyword}); - CheckSearchProviderInPrefs( - DefaultSearchManager::kMirroredDefaultSearchProviderDataPrefName, - {generic_id, android_codesearch.keyword}); + EXPECT_EQ(GetSearchProviderFromPrefs( + DefaultSearchManager::kDefaultSearchProviderDataPrefName), + (SearchProviderSummary{generic_id, android_codesearch.keyword})); + EXPECT_EQ( + GetSearchProviderFromPrefs( + DefaultSearchManager::kMirroredDefaultSearchProviderDataPrefName), + (SearchProviderSummary{generic_id, android_codesearch.keyword})); } // -- CrossRegion No Migration ------------------------------------------------ @@ -439,7 +511,7 @@ class PrepopulatedEnginesXRegionNoMigrationBrowserTest : public PrepopulatedEnginesMigrationBrowserTestBase { - public: + protected: PrepopulatedEnginesXRegionNoMigrationBrowserTest() : PrepopulatedEnginesMigrationBrowserTestBase( {chrome_region_engines, android_region_engines, @@ -448,47 +520,55 @@ INSTANTIATE_TEST_SUITE_P(, PrepopulatedEnginesXRegionNoMigrationBrowserTest, - testing::Bool(), + testing::Bool(), // Feature states &ParamToTestSuffix); IN_PROC_BROWSER_TEST_P(PrepopulatedEnginesXRegionNoMigrationBrowserTest, PRE_PRE_Dse) { + ASSERT_EQ(active_engines_config_, chrome_region_engines); + template_url_service().SetUserSelectedDefaultSearchProvider( template_url_service().GetTemplateURLForKeyword( chrome_codesearch.keyword)); - std::vector<SearchProviderExpectation> expectations = { + std::vector<SearchProviderSummary> expectations = { {google.id, google.keyword, false}, {bing.id, bing.keyword, false}, {generic_id, chrome_codesearch.keyword, true}}; - CheckSearchProviderExpectations(expectations); - CheckSearchProviderInPrefs( - DefaultSearchManager::kDefaultSearchProviderDataPrefName, - expectations[2]); - CheckSearchProviderInPrefs( - DefaultSearchManager::kMirroredDefaultSearchProviderDataPrefName, + EXPECT_THAT(GetServiceSearchProviders(), ElementsAreArray(expectations)); + EXPECT_EQ(GetSearchProviderFromPrefs( + DefaultSearchManager::kDefaultSearchProviderDataPrefName), + expectations[2]); + EXPECT_EQ( + GetSearchProviderFromPrefs( + DefaultSearchManager::kMirroredDefaultSearchProviderDataPrefName), expectations[2]); } IN_PROC_BROWSER_TEST_P(PrepopulatedEnginesXRegionNoMigrationBrowserTest, PRE_Dse) { - std::vector<SearchProviderExpectation> expectations = { + ASSERT_EQ(active_engines_config_, android_region_engines); + + std::vector<SearchProviderSummary> expectations = { {google.id, google.keyword, false}, {bing.id, bing.keyword, false}, {generic_id, android_codesearch.keyword, true}}; - CheckSearchProviderExpectations(expectations); + EXPECT_THAT(GetServiceSearchProviders(), ElementsAreArray(expectations)); // TODO(crbug.com/480071119): Prefs are still the ones associated with // `chrome_codesearch`. - CheckSearchProviderInPrefs( - DefaultSearchManager::kDefaultSearchProviderDataPrefName, - {generic_id, chrome_codesearch.keyword}); - CheckSearchProviderInPrefs( - DefaultSearchManager::kMirroredDefaultSearchProviderDataPrefName, - {generic_id, chrome_codesearch.keyword}); + EXPECT_EQ(GetSearchProviderFromPrefs( + DefaultSearchManager::kDefaultSearchProviderDataPrefName), + (SearchProviderSummary{generic_id, chrome_codesearch.keyword})); + EXPECT_EQ( + GetSearchProviderFromPrefs( + DefaultSearchManager::kMirroredDefaultSearchProviderDataPrefName), + (SearchProviderSummary{generic_id, chrome_codesearch.keyword})); } IN_PROC_BROWSER_TEST_P(PrepopulatedEnginesXRegionNoMigrationBrowserTest, Dse) { - std::vector<SearchProviderExpectation> expectations; + ASSERT_EQ(active_engines_config_, android_migrated_region_engines); + + std::vector<SearchProviderSummary> expectations; expectations.push_back({google.id, google.keyword, false}); expectations.push_back({bing.id, bing.keyword, false}); if (IsParamFeatureEnabled()) { @@ -509,18 +589,21 @@ {generic_id, android_keyword, true, android_codesearch.search_url}); } - CheckSearchProviderExpectations(expectations); + EXPECT_THAT(GetServiceSearchProviders(), ElementsAreArray(expectations)); // TODO(crbug.com/480071119): Prefs did not change since the PRE_PRE_ // run, we migrate from them although the previous state was "non-migrating". // Whether the feature is enabled or not, we're not using the URL selected and // present in prefs. - CheckSearchProviderInPrefs( - DefaultSearchManager::kDefaultSearchProviderDataPrefName, - {chrome_codesearch.id, chrome_codesearch.keyword}); - CheckSearchProviderInPrefs( - DefaultSearchManager::kMirroredDefaultSearchProviderDataPrefName, - {chrome_codesearch.id, chrome_codesearch.keyword}); + EXPECT_EQ( + GetSearchProviderFromPrefs( + DefaultSearchManager::kDefaultSearchProviderDataPrefName), + (SearchProviderSummary{chrome_codesearch.id, chrome_codesearch.keyword})); + + EXPECT_EQ( + GetSearchProviderFromPrefs( + DefaultSearchManager::kMirroredDefaultSearchProviderDataPrefName), + (SearchProviderSummary{chrome_codesearch.id, chrome_codesearch.keyword})); } } // namespace
diff --git a/chrome/browser/search_integrity/search_integrity_unittest.cc b/chrome/browser/search_integrity/search_integrity_unittest.cc index 1c61923..71bd096 100644 --- a/chrome/browser/search_integrity/search_integrity_unittest.cc +++ b/chrome/browser/search_integrity/search_integrity_unittest.cc
@@ -205,4 +205,27 @@ "Search.Integrity.IsDefaultCustomWithMatchingPolicyEngine", true, 1); } +TEST_F(SearchIntegrityTest, CheckDefaultSearchEngine_DefaultIsStarterPack) { + TemplateURL* turl = AddSearchEngine(u"Starter Pack", "http://starter.com", + /*created_by_policy=*/false, + /*prepopulate_id=*/0, + /*starter_pack_id=*/1); + SetDefaultSearchProvider(turl); + + SearchIntegrityReport report = CheckSearchEnginesReport(); + + EXPECT_FALSE(report.is_default_custom); +} + +TEST_F(SearchIntegrityTest, CheckDefaultSearchEngine_DefaultIsPrepopulated) { + TemplateURL* turl = + AddSearchEngine(u"Prepopulated", "http://prepopulated.com", + /*created_by_policy=*/false, /*prepopulate_id=*/1); + SetDefaultSearchProvider(turl); + + SearchIntegrityReport report = CheckSearchEnginesReport(); + + EXPECT_FALSE(report.is_default_custom); +} + } // namespace search_integrity
diff --git a/chrome/browser/segmentation_platform/android/home_modules_ranking_helper.cc b/chrome/browser/segmentation_platform/android/home_modules_ranking_helper.cc index dd5452a6..da83120 100644 --- a/chrome/browser/segmentation_platform/android/home_modules_ranking_helper.cc +++ b/chrome/browser/segmentation_platform/android/home_modules_ranking_helper.cc
@@ -58,7 +58,7 @@ segmentation_platform::PredictionOptions native_prediction_options = segmentation_platform::PredictionOptionsAndroid:: ToNativePredictionOptions(env, prediction_options); - registry->get_rank_fecther_helper()->GetHomeModulesRank( + registry->get_rank_fetcher_helper()->GetHomeModulesRank( service, native_prediction_options, native_input_context, base::BindOnce(&RunGetClassificationResultCallback, ScopedJavaGlobalRef<jobject>(callback)));
diff --git a/chrome/browser/segmentation_platform/segmentation_platform_config.cc b/chrome/browser/segmentation_platform/segmentation_platform_config.cc index cd1c2d1..744fe5fe 100644 --- a/chrome/browser/segmentation_platform/segmentation_platform_config.cc +++ b/chrome/browser/segmentation_platform/segmentation_platform_config.cc
@@ -8,7 +8,6 @@ #include <string_view> #include <vector> -#include "base/check_is_test.h" #include "base/feature_list.h" #include "base/logging.h" #include "base/metrics/field_trial_params.h" @@ -183,8 +182,6 @@ if (home_modules_card_registry) { configs.emplace_back(home_modules::EphemeralHomeModuleBackend::GetConfig( home_modules_card_registry)); - } else { - CHECK_IS_TEST(); } #if BUILDFLAG(ENABLE_COMPOSE)
diff --git a/chrome/browser/segmentation_platform/segmentation_platform_service_factory.cc b/chrome/browser/segmentation_platform/segmentation_platform_service_factory.cc index 18cf396..304a28e 100644 --- a/chrome/browser/segmentation_platform/segmentation_platform_service_factory.cc +++ b/chrome/browser/segmentation_platform/segmentation_platform_service_factory.cc
@@ -162,7 +162,7 @@ auto tab_fetcher = std::make_unique<processing::LocalTabHandler>( session_sync_service, profile); auto home_modules_card_registry = - std::make_unique<home_modules::HomeModulesCardRegistry>( + home_modules::HomeModulesCardRegistry::Create( profile->GetPrefs(), g_browser_process->local_state()); InitializeUkmDatabaseIfNeeded(profile);
diff --git a/chrome/browser/segmentation_platform/segmentation_platform_service_factory_unittest.cc b/chrome/browser/segmentation_platform/segmentation_platform_service_factory_unittest.cc index db5f702..2439bbd 100644 --- a/chrome/browser/segmentation_platform/segmentation_platform_service_factory_unittest.cc +++ b/chrome/browser/segmentation_platform/segmentation_platform_service_factory_unittest.cc
@@ -631,21 +631,18 @@ home_modules::HomeModulesCardRegistry* registry = SegmentationPlatformServiceFactory::GetHomeModulesCardRegistry( profile_->profile.get()); + +#if BUILDFLAG(IS_ANDROID) ASSERT_TRUE(registry); // Update this test when adding new cards with inputs. // Each card's feature flag should be enabled by test framework for this // integration test. -#if BUILDFLAG(IS_ANDROID) ASSERT_EQ(7u, registry->get_all_cards_by_priority().size()); -#else - EXPECT_TRUE(registry->get_all_cards_by_priority().empty()); -#endif // BUILDFLAG(IS_ANDROID) PredictionOptions prediction_options; prediction_options.on_demand_execution = true; auto input_context = base::MakeRefCounted<InputContext>(); -#if BUILDFLAG(IS_ANDROID) input_context->metadata_args.emplace( "auxiliary_search_available", processing::ProcessedValue::FromFloat(0)); input_context->metadata_args.emplace( @@ -667,12 +664,15 @@ processing::ProcessedValue::FromFloat(0)); input_context->metadata_args.emplace( "is_eligible_to_tips_opt_in", processing::ProcessedValue::FromFloat(0)); -#endif // BUILDFLAG(IS_ANDROID) // No cards are added, the model fetches no results and fails. ExpectGetAnnotatedNumericResult( kEphemeralHomeModuleBackendKey, prediction_options, input_context, /*expected_status=*/segmentation_platform::PredictionStatus::kSucceeded); +#else + // Desktop platforms do not support HomeModulesCardRegistry. + EXPECT_FALSE(registry); +#endif // BUILDFLAG(IS_ANDROID) } TEST_F(SegmentationPlatformServiceFactoryTest, TestFedCmUserModel) {
diff --git a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetControllerUnitTest.java b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetControllerUnitTest.java index 95876e3..9edf070 100644 --- a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetControllerUnitTest.java +++ b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetControllerUnitTest.java
@@ -163,9 +163,9 @@ mActivityScenario.getScenario().onActivity((activity) -> mActivity = activity); mActivityScenario.getScenario().moveToState(State.RESUMED); mWindow = - new ActivityWindowAndroid( + ActivityWindowAndroid.create( mActivity, - false, + /* listenToActivityState= */ false, IntentRequestTracker.createFromActivity(mActivity), mInsetObserver, /* trackOcclusion= */ true);
diff --git a/chrome/browser/signin/dice_signed_in_profile_creator_unittest.cc b/chrome/browser/signin/dice_signed_in_profile_creator_unittest.cc index 747dd62..f532b5f 100644 --- a/chrome/browser/signin/dice_signed_in_profile_creator_unittest.cc +++ b/chrome/browser/signin/dice_signed_in_profile_creator_unittest.cc
@@ -34,6 +34,7 @@ #include "content/public/browser/storage_partition.h" #include "content/public/test/browser_task_environment.h" #include "services/network/public/mojom/cookie_manager.mojom.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace { @@ -252,18 +253,13 @@ return; } - // I don't know why this test looks like this, but I am only removing - // !BUILDFLAG(IS_FUCHSIA). - return; - EXPECT_EQ(3u, cookies_source_profile.size()); EXPECT_EQ(3u, cookies_new_profile.size()); for (const auto& cookie : cookies_new_profile) { EXPECT_TRUE(cookie.IsDomainMatch(url.GetHost())); - EXPECT_TRUE(cookie.Name() == "oldgoogle0" || - cookie.Name() == "validgoogle1" || - cookie.Name() == "newgoogle1"); + EXPECT_THAT(cookie.Name(), + testing::AnyOf("oldgoogle0", "validgoogle1", "newgoogle2")); } }
diff --git a/chrome/browser/signin/services/android/BUILD.gn b/chrome/browser/signin/services/android/BUILD.gn index 6b15302..027f3f7 100644 --- a/chrome/browser/signin/services/android/BUILD.gn +++ b/chrome/browser/signin/services/android/BUILD.gn
@@ -16,6 +16,7 @@ android_library("java") { sources = [ + "java/src/org/chromium/chrome/browser/signin/services/BadgeConfig.java", "java/src/org/chromium/chrome/browser/signin/services/DisplayableProfileData.java", "java/src/org/chromium/chrome/browser/signin/services/IdentityServicesProvider.java", "java/src/org/chromium/chrome/browser/signin/services/ProfileDataCache.java", @@ -98,6 +99,7 @@ resources_package = "org.chromium.chrome.browser.signin.services" sources = [ "java/src/org/chromium/chrome/browser/signin/services/SigninFlowTimestampsLoggerUnitTest.java", + "junit/src/org/chromium/chrome/browser/signin/services/BadgeConfigBuilderUnitTest.java", "junit/src/org/chromium/chrome/browser/signin/services/ProfileDataCacheUnitTest.java", "junit/src/org/chromium/chrome/browser/signin/services/WebSigninBridgeTest.java", ]
diff --git a/chrome/browser/signin/services/android/java/src/org/chromium/chrome/browser/signin/services/BadgeConfig.java b/chrome/browser/signin/services/android/java/src/org/chromium/chrome/browser/signin/services/BadgeConfig.java new file mode 100644 index 0000000..4a8d5f2f --- /dev/null +++ b/chrome/browser/signin/services/android/java/src/org/chromium/chrome/browser/signin/services/BadgeConfig.java
@@ -0,0 +1,163 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.signin.services; + +import android.content.Context; +import android.graphics.Point; +import android.graphics.drawable.Drawable; + +import androidx.annotation.DimenRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.Px; +import androidx.appcompat.content.res.AppCompatResources; + +import org.chromium.build.annotations.NullMarked; + +import java.util.Objects; + +/** + * Encapsulates info necessary to overlay a circular badge (e.g., child account icon) on top of a + * user avatar. + */ +@NullMarked +public final class BadgeConfig { + private final int mBadgeResId; + private final Drawable mBadge; + private final @Px int mBadgeSize; + private final @Px int mBorderSize; + private final Point mPosition; + + private BadgeConfig( + @DrawableRes int badgeResId, + Drawable badge, + @Px int badgeSize, + @Px int borderSize, + Point position) { + assert badgeResId != 0; + + mBadgeResId = badgeResId; + mBadge = badge; + mBadgeSize = badgeSize; + mBorderSize = borderSize; + mPosition = position; + } + + /** Returns the badge drawable. */ + Drawable getBadge() { + return mBadge; + } + + /** Returns the badge size in pixels. */ + @Px + int getBadgeSize() { + return mBadgeSize; + } + + /** Returns the border size in pixels. */ + @Px + int getBorderSize() { + return mBorderSize; + } + + /** Returns the position of the badge on the avatar in pixels. */ + Point getPosition() { + return mPosition; + } + + @Override + public boolean equals(Object o) { + return o instanceof BadgeConfig bc + && mBadgeResId == bc.mBadgeResId + && mBadgeSize == bc.mBadgeSize + && mBorderSize == bc.mBorderSize + && mPosition.equals(bc.mPosition); + } + + @Override + public int hashCode() { + return Objects.hash(mBadgeResId, mBadgeSize, mBorderSize, mPosition); + } + + /** Returns a new {@link Builder} for the given badge drawable. */ + public static Builder create(@DrawableRes int badgeResId) { + return new Builder(badgeResId); + } + + /** Builder for {@link BadgeConfig}. */ + public static final class Builder { + private final @DrawableRes int mBadgeResId; + private @DimenRes int mBadgeSizeResId; + private @DimenRes int mBorderSizeResId; + private @DimenRes int mPositionXResId; + private @DimenRes int mPositionYResId; + + private Builder(@DrawableRes int badgeResId) { + assert badgeResId != 0; + mBadgeResId = badgeResId; + } + + /** Sets the full config for a child account badge. */ + public Builder withDefaultSizeChildAccountConfig() { + return withBadgeSize(R.dimen.badge_size) + .withBorderSize(R.dimen.badge_border_size) + .withXPosition(R.dimen.badge_position_x) + .withYPosition(R.dimen.badge_position_y); + } + + /* Set full config for toolbar identity disc. */ + public Builder withToolbarIdentityDiscConfig() { + return withBadgeSize(R.dimen.toolbar_identity_disc_badge_size) + .withBorderSize(R.dimen.toolbar_identity_disc_badge_border_size) + .withXPosition(R.dimen.toolbar_identity_disc_badge_position_x) + .withYPosition(R.dimen.toolbar_identity_disc_badge_position_y); + } + + /** Sets the badge size to the given dimension resource ID. */ + Builder withBadgeSize(@DimenRes int badgeSizeDimResId) { + assert badgeSizeDimResId != 0; + mBadgeSizeResId = badgeSizeDimResId; + return this; + } + + /** Sets the border size to the given dimension resource ID. */ + Builder withBorderSize(@DimenRes int borderSizeDimResId) { + assert borderSizeDimResId != 0; + mBorderSizeResId = borderSizeDimResId; + return this; + } + + /** Sets the X position to the given dimension resource ID. */ + Builder withXPosition(@DimenRes int positionXResId) { + assert positionXResId != 0; + mPositionXResId = positionXResId; + return this; + } + + /** Sets the Y position to the given dimension resource ID. */ + Builder withYPosition(@DimenRes int positionYResId) { + assert positionYResId != 0; + mPositionYResId = positionYResId; + return this; + } + + /** Builds a {@link BadgeConfig} with the given parameters. */ + public BadgeConfig build(Context context) { + assert mBadgeSizeResId != 0 : "Badge size dimension resource ID must be set."; + assert mBorderSizeResId != 0 : "Border size dimension resource ID must be set."; + assert mPositionXResId != 0 : "Position X dimension resource ID must be set."; + assert mPositionYResId != 0 : "Position Y dimension resource ID must be set."; + + var resources = context.getResources(); + return new BadgeConfig( + mBadgeResId, + AppCompatResources.getDrawable(context, mBadgeResId), + resources.getDimensionPixelSize(mBadgeSizeResId), + resources.getDimensionPixelSize(mBorderSizeResId), + new Point( + resources.getDimensionPixelOffset(mPositionXResId), + resources.getDimensionPixelOffset(mPositionYResId))); + } + } +}
diff --git a/chrome/browser/signin/services/android/java/src/org/chromium/chrome/browser/signin/services/ProfileDataCache.java b/chrome/browser/signin/services/android/java/src/org/chromium/chrome/browser/signin/services/ProfileDataCache.java index 8f570e7..5521a1d 100644 --- a/chrome/browser/signin/services/android/java/src/org/chromium/chrome/browser/signin/services/ProfileDataCache.java +++ b/chrome/browser/signin/services/android/java/src/org/chromium/chrome/browser/signin/services/ProfileDataCache.java
@@ -7,13 +7,11 @@ import static org.chromium.build.NullUtil.assumeNonNull; import android.content.Context; -import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; -import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.drawable.BitmapDrawable; @@ -75,65 +73,6 @@ void onProfileDataUpdated(DisplayableProfileData profileData); } - /** - * Encapsulates info necessary to overlay a circular badge (e.g., child account icon) on top of - * a user avatar. - */ - private static class BadgeConfig { - private final int mBadgeResId; - private final Drawable mBadge; - private final @Px int mBadgeSize; - private final @Px int mBorderSize; - private final Point mPosition; - - private BadgeConfig( - Context context, - @DrawableRes int badgeResId, - @Px int badgeSize, - @Px int borderSize, - Point position) { - assert badgeResId != 0; - - mBadgeResId = badgeResId; - mBadge = AppCompatResources.getDrawable(context, badgeResId); - mBadgeSize = badgeSize; - mBorderSize = borderSize; - mPosition = position; - } - - Drawable getBadge() { - return mBadge; - } - - @Px - int getBadgeSize() { - return mBadgeSize; - } - - @Px - int getBorderSize() { - return mBorderSize; - } - - Point getPosition() { - return mPosition; - } - - @Override - public boolean equals(Object o) { - return o instanceof BadgeConfig bc - && mBadgeResId == bc.mBadgeResId - && mBadgeSize == bc.mBadgeSize - && mBorderSize == bc.mBorderSize - && mPosition.equals(bc.mPosition); - } - - @Override - public int hashCode() { - return Objects.hash(mBadgeResId, mBadgeSize, mBorderSize, mPosition); - } - } - private final Context mContext; private final AccountManagerFacade mAccountManagerFacade; private final IdentityManager mIdentityManager; @@ -195,7 +134,7 @@ AccountManagerFacadeProvider.getInstance(), identityManager, context.getResources().getDimensionPixelSize(R.dimen.user_picture_size), - createDefaultSizeChildAccountBadgeConfig(context, badgeResId)); + BadgeConfig.create(badgeResId).withDefaultSizeChildAccountConfig().build(context)); } /** @@ -214,54 +153,6 @@ } /** - * Creates a {@link BadgeConfig} with default badge size. - * - * @param context Context of the application to extract resources from. - * @param badgeResId Resource id of the badge to be attached. - * @return A {@link BadgeConfig} with default badge size(R.dimen.badge_size) of given badgeResId - * provided. - */ - public static BadgeConfig createDefaultSizeChildAccountBadgeConfig( - Context context, @DrawableRes int badgeResId) { - assert badgeResId != 0; - - Resources resources = context.getResources(); - return new BadgeConfig( - context, - badgeResId, - resources.getDimensionPixelSize(R.dimen.badge_size), - resources.getDimensionPixelSize(R.dimen.badge_border_size), - new Point( - resources.getDimensionPixelOffset(R.dimen.badge_position_x), - resources.getDimensionPixelOffset(R.dimen.badge_position_y))); - } - - /** - * Creates a {@link BadgeConfig} with toolbar identity disc badge size. - * - * @param context Context of the application to extract resources from. - * @param badgeResId Resource id of the badge to be attached. - * @return A {@link BadgeConfig} with toolbar identity disc badge size badge - * size(R.dimen.toolbar_identity_disc_badge_size) of given badgeResId provided. - */ - public static BadgeConfig createToolbarIdentityDiscBadgeConfig( - Context context, @DrawableRes int badgeResId) { - assert badgeResId != 0; - - Resources resources = context.getResources(); - return new BadgeConfig( - context, - badgeResId, - resources.getDimensionPixelSize(R.dimen.toolbar_identity_disc_badge_size), - resources.getDimensionPixelSize(R.dimen.toolbar_identity_disc_badge_border_size), - new Point( - resources.getDimensionPixelOffset( - R.dimen.toolbar_identity_disc_badge_position_x), - resources.getDimensionPixelOffset( - R.dimen.toolbar_identity_disc_badge_position_y))); - } - - /** * Gets the list of cached accounts that are synchronized with the device accounts. * * <p>Accounts data are populated from {@link IdentityManager}. To observe changes to accounts,
diff --git a/chrome/browser/signin/services/android/java/src/org/chromium/chrome/browser/signin/services/ProfileDataCacheWithBadgeRenderTest.java b/chrome/browser/signin/services/android/java/src/org/chromium/chrome/browser/signin/services/ProfileDataCacheWithBadgeRenderTest.java index b821eac..706fc9f 100644 --- a/chrome/browser/signin/services/android/java/src/org/chromium/chrome/browser/signin/services/ProfileDataCacheWithBadgeRenderTest.java +++ b/chrome/browser/signin/services/android/java/src/org/chromium/chrome/browser/signin/services/ProfileDataCacheWithBadgeRenderTest.java
@@ -173,8 +173,9 @@ TestAccounts.ACCOUNT1.getId(), badgeResId == 0 ? null - : ProfileDataCache.createDefaultSizeChildAccountBadgeConfig( - sActivity, badgeResId)); + : BadgeConfig.create(badgeResId) + .withDefaultSizeChildAccountConfig() + .build(sActivity)); }); CriteriaHelper.pollUiThread( () -> {
diff --git a/chrome/browser/signin/services/android/junit/src/org/chromium/chrome/browser/signin/services/BadgeConfigBuilderUnitTest.java b/chrome/browser/signin/services/android/junit/src/org/chromium/chrome/browser/signin/services/BadgeConfigBuilderUnitTest.java new file mode 100644 index 0000000..95f5c0d --- /dev/null +++ b/chrome/browser/signin/services/android/junit/src/org/chromium/chrome/browser/signin/services/BadgeConfigBuilderUnitTest.java
@@ -0,0 +1,82 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.signin.services; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.LooperMode; + +import org.chromium.base.test.BaseRobolectricTestRunner; + +/** Unit tests for {@link BadgeConfig} and {@link BadgeConfig.Builder} */ +@RunWith(BaseRobolectricTestRunner.class) +@LooperMode(LooperMode.Mode.LEGACY) +public class BadgeConfigBuilderUnitTest { + + @Test + public void testDefaultSizeChildAccountBadgeConfig() { + BadgeConfig childAccountConfig = + BadgeConfig.create(R.drawable.ic_sync_badge_error_20dp) + .withDefaultSizeChildAccountConfig() + .build(RuntimeEnvironment.application.getApplicationContext()); + + BadgeConfig expectedConfig = + BadgeConfig.create(R.drawable.ic_sync_badge_error_20dp) + .withBadgeSize(R.dimen.badge_size) + .withBorderSize(R.dimen.badge_border_size) + .withXPosition(R.dimen.badge_position_x) + .withYPosition(R.dimen.badge_position_y) + .build(RuntimeEnvironment.application.getApplicationContext()); + + Assert.assertEquals(expectedConfig, childAccountConfig); + } + + @Test + public void testToolbarIdentityDiscBadgeConfig() { + var context = RuntimeEnvironment.application.getApplicationContext(); + var resources = context.getResources(); + BadgeConfig identityDiscBadgeConfig = + BadgeConfig.create(R.drawable.ic_error_badge_16dp) + .withToolbarIdentityDiscConfig() + .build(context); + + BadgeConfig expectedConfig = + BadgeConfig.create(R.drawable.ic_error_badge_16dp) + .withBadgeSize(R.dimen.toolbar_identity_disc_badge_size) + .withBorderSize(R.dimen.toolbar_identity_disc_badge_border_size) + .withXPosition(R.dimen.toolbar_identity_disc_badge_position_x) + .withYPosition(R.dimen.toolbar_identity_disc_badge_position_y) + .build(RuntimeEnvironment.application.getApplicationContext()); + + Assert.assertEquals(expectedConfig, identityDiscBadgeConfig); + } + + @Test + public void testBadgeConfigBuilder() { + var context = RuntimeEnvironment.application.getApplicationContext(); + var resources = context.getResources(); + BadgeConfig badgeConfig = + BadgeConfig.create(R.drawable.ic_error_badge_16dp) + .withBadgeSize(R.dimen.badge_size) + .withBorderSize(R.dimen.badge_border_size) + .withXPosition(R.dimen.badge_position_x) + .withYPosition(R.dimen.badge_position_y) + .build(context); + + Assert.assertEquals( + resources.getDimensionPixelSize(R.dimen.badge_size), badgeConfig.getBadgeSize()); + Assert.assertEquals( + resources.getDimensionPixelSize(R.dimen.badge_border_size), + badgeConfig.getBorderSize()); + Assert.assertEquals( + resources.getDimensionPixelOffset(R.dimen.badge_position_x), + badgeConfig.getPosition().x); + Assert.assertEquals( + resources.getDimensionPixelOffset(R.dimen.badge_position_y), + badgeConfig.getPosition().y); + } +}
diff --git a/chrome/browser/signin/services/android/junit/src/org/chromium/chrome/browser/signin/services/ProfileDataCacheUnitTest.java b/chrome/browser/signin/services/android/junit/src/org/chromium/chrome/browser/signin/services/ProfileDataCacheUnitTest.java index ff5ccde1..05da945 100644 --- a/chrome/browser/signin/services/android/junit/src/org/chromium/chrome/browser/signin/services/ProfileDataCacheUnitTest.java +++ b/chrome/browser/signin/services/android/junit/src/org/chromium/chrome/browser/signin/services/ProfileDataCacheUnitTest.java
@@ -104,9 +104,9 @@ public void accountInfoIsUpdatedWithOnlyBadgeConfig() { mProfileDataCache.setBadge( TestAccounts.TEST_ACCOUNT_NO_NAME.getId(), - ProfileDataCache.createDefaultSizeChildAccountBadgeConfig( - RuntimeEnvironment.application.getApplicationContext(), - R.drawable.ic_sync_badge_error_20dp)); + BadgeConfig.create(R.drawable.ic_sync_badge_error_20dp) + .withDefaultSizeChildAccountConfig() + .build(RuntimeEnvironment.application.getApplicationContext())); mProfileDataCache.addObserver(mObserverMock); Assert.assertFalse( mProfileDataCache.hasProfileDataForTesting( @@ -206,9 +206,9 @@ mAccountManagerTestRule.blockExtendedAccountInfoUpdate(); mProfileDataCache.setBadge( TestAccounts.TEST_ACCOUNT_NO_NAME.getId(), - ProfileDataCache.createDefaultSizeChildAccountBadgeConfig( - RuntimeEnvironment.application.getApplicationContext(), - R.drawable.ic_sync_badge_error_20dp)); + BadgeConfig.create(R.drawable.ic_sync_badge_error_20dp) + .withDefaultSizeChildAccountConfig() + .build(RuntimeEnvironment.application.getApplicationContext())); mProfileDataCache.addObserver(mObserverMock); Assert.assertTrue(mProfileDataCache.getAccounts().getResult().isEmpty()); mAccountManagerTestRule.addAccount(TestAccounts.TEST_ACCOUNT_NO_NAME); @@ -222,9 +222,9 @@ mAccountManagerTestRule.blockGetAccountsUpdate(); mProfileDataCache.setBadge( TestAccounts.TEST_ACCOUNT_NO_NAME.getId(), - ProfileDataCache.createDefaultSizeChildAccountBadgeConfig( - RuntimeEnvironment.application.getApplicationContext(), - R.drawable.ic_sync_badge_error_20dp)); + BadgeConfig.create(R.drawable.ic_sync_badge_error_20dp) + .withDefaultSizeChildAccountConfig() + .build(RuntimeEnvironment.application.getApplicationContext())); mProfileDataCache.addObserver(mObserverMock); Assert.assertTrue(mProfileDataCache.getAccounts().getResult().isEmpty()); mAccountManagerTestRule.addAccount(TestAccounts.TEST_ACCOUNT_NO_NAME);
diff --git a/chrome/browser/sync/synced_set_up/OWNERS b/chrome/browser/sync/synced_set_up/OWNERS new file mode 100644 index 0000000..1f9c979 --- /dev/null +++ b/chrome/browser/sync/synced_set_up/OWNERS
@@ -0,0 +1 @@ +file://components/sync_preferences/synced_set_up/OWNERS \ No newline at end of file
diff --git a/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtilsTest.java b/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtilsTest.java index 513864fe..9172bac 100644 --- a/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtilsTest.java +++ b/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtilsTest.java
@@ -126,9 +126,9 @@ public void setUp() { mActivity = Robolectric.buildActivity(Activity.class).get(); mWindowAndroid = - new ActivityWindowAndroid( + ActivityWindowAndroid.create( mActivity, - false, + /* listenToActivityState= */ false, IntentRequestTracker.createFromActivity(mActivity), mInsetObserver, /* trackOcclusion= */ true);
diff --git a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientBridgeUnitTest.java b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientBridgeUnitTest.java index 201f22c7..ff086ff 100644 --- a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientBridgeUnitTest.java +++ b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyClientBridgeUnitTest.java
@@ -66,9 +66,9 @@ doReturn(mDelegateSurveyClient).when(mFactory).createClient(any(), any(), any(), any()); mWindow = - new ActivityWindowAndroid( + ActivityWindowAndroid.create( mActivity, - false, + /* listenToActivityState= */ false, IntentRequestTracker.createFromActivity(mActivity), /* insetObserver= */ null, /* trackOcclusion= */ true);
diff --git a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyUiDelegateBridgeUnitTest.java b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyUiDelegateBridgeUnitTest.java index 5de2ecb..5e43f1c2 100644 --- a/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyUiDelegateBridgeUnitTest.java +++ b/chrome/browser/ui/android/hats/internal/java/src/org/chromium/chrome/browser/ui/hats/SurveyUiDelegateBridgeUnitTest.java
@@ -61,9 +61,9 @@ mActivity = Robolectric.buildActivity(Activity.class).get(); mWindow = - new ActivityWindowAndroid( + ActivityWindowAndroid.create( mActivity, - false, + /* listenToActivityState= */ false, IntentRequestTracker.createFromActivity(mActivity), mInsetObserver, /* trackOcclusion= */ true);
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java index f0dcf7d..42b865a 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
@@ -565,7 +565,7 @@ * changes to/from specialized (e.g. aim)/conventional (e.g. plain old search). It is not * assumed that this will be called when the session ends. */ - public void onSpecializedFuseboxModeActivatedC(boolean isSpecializedRequestType) {} + public void onSpecializedFuseboxModeActivated(boolean isSpecializedRequestType) {} /** * Signal that the list of suggestions shown in the associated omnibox suggestions list has
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java index 50fa86e..16d7cdaaa 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
@@ -1830,7 +1830,7 @@ } updateButtonVisibility(); onSearchBoxHintTextChanged(); - mLocationBarLayout.onSpecializedFuseboxModeActivatedC(isSpecializedRequestType); + mLocationBarLayout.onSpecializedFuseboxModeActivated(isSpecializedRequestType); } private boolean isLensOnOmniboxEnabled() {
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarTablet.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarTablet.java index de76f38..5566503 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarTablet.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarTablet.java
@@ -396,7 +396,7 @@ } @Override - public void onSpecializedFuseboxModeActivatedC(boolean isSpecializedRequestType) { + public void onSpecializedFuseboxModeActivated(boolean isSpecializedRequestType) { if (isSpecializedRequestType) { mFocusedPopupDrawable.setDrawableByLayerId(R.id.glif_border_layer, mGlifBorderDrawable); mGlifBorderDrawable.start(); @@ -528,6 +528,7 @@ layoutParams.bottomMargin = -inset; outerRect.setCornerRadius(cornerRadius); mFocusedPopupDrawable.setLayerInsetRelative(1, inset, inset, inset, inset); + setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), inset); } else { // Remove the extra padding and un-round the corners of the outer rect since we're now // bleeding into the suggestions dropdown. @@ -537,6 +538,7 @@ cornerRadius, cornerRadius, cornerRadius, cornerRadius, 0, 0, 0, 0 }); mFocusedPopupDrawable.setLayerInsetRelative(1, inset, inset, inset, 0); + setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), 0); } setLayoutParams(layoutParams); }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarTabletUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarTabletUnitTest.java index e59f065..40cb7c14 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarTabletUnitTest.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarTabletUnitTest.java
@@ -231,6 +231,7 @@ .getDimensionPixelSize(R.dimen.location_bar_tablet_fusebox_popup_inset); assertEquals(cornerRadius, outerRect.getCornerRadius(), MathUtils.EPSILON); assertEquals(inset, background.getLayerInsetBottom(1)); + assertEquals(inset, mLocationBarTablet.getPaddingBottom()); mLocationBarTablet.onSuggestionsChanged(true); assertEquals(0, layoutParams.bottomMargin); @@ -239,6 +240,7 @@ outerRect.getCornerRadii(), MathUtils.EPSILON); assertEquals(0, background.getLayerInsetBottom(1)); + assertEquals(0, mLocationBarTablet.getPaddingBottom()); } @Test
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderUnitTest.java index 132492b..117bfc8 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderUnitTest.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/geo/GeolocationHeaderUnitTest.java
@@ -112,10 +112,6 @@ GeolocationHeader.setAppPermissionsForTesting(/* hasCoarse= */ true, /* hasFine= */ true); // This is to reset `sCurrentLocationRequested`. GeolocationHeader.stopListeningForLocationUpdates(); - when(mWebsitePreferenceBridgeJniMock.getPermissionSettingForOrigin( - any(BrowserContextHandle.class), eq(ContentSettingsType.GEOLOCATION), - anyString(), anyString())) - .thenReturn(ContentSetting.ALLOW); setSiteGeolocationPermissions( /* approximate= */ ContentSetting.ALLOW, /* precise= */ ContentSetting.ALLOW); when(mWebsitePreferenceBridgeJniMock.isDSEOrigin( @@ -182,10 +178,6 @@ @Test public void testPrimeLocationForGeoHeaderDseAutograntOff() { - when(mWebsitePreferenceBridgeJniMock.getPermissionSettingForOrigin( - any(BrowserContextHandle.class), eq(ContentSettingsType.GEOLOCATION), - anyString(), anyString())) - .thenReturn(ContentSetting.ASK); setSiteGeolocationPermissions( /* approximate= */ ContentSetting.ASK, /* precise= */ ContentSetting.ASK); GeolocationHeader.primeLocationForGeoHeaderIfEnabled(mProfileMock, mTemplateUrlServiceMock);
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewRenderTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewRenderTest.java index dc0af76..0d46e56 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewRenderTest.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewRenderTest.java
@@ -31,6 +31,11 @@ import org.chromium.base.supplier.ObservableSuppliers; import org.chromium.base.test.BaseActivityTestRule; +import org.chromium.base.test.params.ParameterAnnotations; +import org.chromium.base.test.params.ParameterAnnotations.UseMethodParameter; +import org.chromium.base.test.params.ParameterProvider; +import org.chromium.base.test.params.ParameterSet; +import org.chromium.base.test.params.ParameterizedRunner; import org.chromium.base.test.util.Batch; import org.chromium.base.test.util.Feature; import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider.ControlsPosition; @@ -42,7 +47,7 @@ import org.chromium.chrome.browser.omnibox.status.StatusProperties.StatusIconResource; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.toolbar.LocationBarModel; -import org.chromium.chrome.test.ChromeJUnit4ClassRunner; +import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate; import org.chromium.chrome.test.util.ChromeRenderTestRule; import org.chromium.chrome.test.util.ToolbarUnitTestUtils; import org.chromium.components.browser_ui.site_settings.ContentSettingsResources; @@ -55,9 +60,12 @@ import org.chromium.ui.test.util.BlankUiTestActivity; import java.io.IOException; +import java.util.Arrays; +import java.util.List; /** Render tests for {@link StatusView}. */ -@RunWith(ChromeJUnit4ClassRunner.class) +@RunWith(ParameterizedRunner.class) +@ParameterAnnotations.UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class) @Batch(Batch.PER_CLASS) public class StatusViewRenderTest { @ClassRule @@ -196,16 +204,34 @@ mRenderTestRule.render(mStatusView, "status_view_with_icon"); } + public static class GeolocationContentSettingsParams implements ParameterProvider { + private static final List<ParameterSet> sGeolocationContentSettingsParams = + Arrays.asList( + new ParameterSet() + .value(ContentSettingsType.GEOLOCATION) + .name("LegacyGeolocation"), + new ParameterSet() + .value(ContentSettingsType.GEOLOCATION_WITH_OPTIONS) + .name("GeolocationWithOptions")); + + @Override + public List<ParameterSet> getParameters() { + return sGeolocationContentSettingsParams; + } + } + + @UseMethodParameter(GeolocationContentSettingsParams.class) @Test @MediumTest @Feature({"RenderTest"}) - public void testStatusViewWithLocationPermissionIcon() throws IOException { + public void testStatusViewWithLocationPermissionIcon( + @ContentSettingsType.EnumType int geolocation) throws IOException { runOnUiThreadBlocking( () -> { Drawable locationIcon = ContentSettingsResources.getIconForOmnibox( mStatusView.getContext(), - ContentSettingsType.GEOLOCATION, + geolocation, ContentSetting.ALLOW, false); PermissionIconResource statusIcon =
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SigninUtils.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SigninUtils.java index 09a292d..74eec6de 100644 --- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SigninUtils.java +++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/SigninUtils.java
@@ -24,18 +24,8 @@ private SigninUtils() {} /** - * Opens a Settings page to configure settings for a single account. - * - * @param activity Activity to use when starting the Activity. - * @param accountEmail The account email for which the Settings page should be opened. - * @return Whether or not Android accepted the Intent. - */ - public static boolean openSettingsForAccount(Activity activity, String accountEmail) { - return openSettingsForAllAccounts(activity); - } - - /** * Opens a Settings page with all accounts on the device. + * * @param activity Activity to use when starting the Activity. * @return Whether or not Android accepted the Intent. */ @@ -67,7 +57,7 @@ return context.getString(R.string.sync_promo_continue_as, profileData.getAccountEmail()); } - /** Returns the accessibility label for the the account picker. */ + /** Returns the accessibility label for the account picker. */ public static String getChooseAccountLabel( final Context context, DisplayableProfileData profileData,
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/fullscreen_signin/FullscreenSigninMediator.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/fullscreen_signin/FullscreenSigninMediator.java index e1499f2..5b2532d6 100644 --- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/fullscreen_signin/FullscreenSigninMediator.java +++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/fullscreen_signin/FullscreenSigninMediator.java
@@ -29,6 +29,7 @@ import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManager; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.ProfileProvider; +import org.chromium.chrome.browser.signin.services.BadgeConfig; import org.chromium.chrome.browser.signin.services.DisplayableProfileData; import org.chromium.chrome.browser.signin.services.IdentityServicesProvider; import org.chromium.chrome.browser.signin.services.ProfileDataCache; @@ -742,8 +743,9 @@ // Selected account data will be updated in {@link #onProfileDataUpdated} mProfileDataCache.setBadge( isChild - ? ProfileDataCache.createDefaultSizeChildAccountBadgeConfig( - mContext, R.drawable.ic_account_child_20dp) + ? BadgeConfig.create(R.drawable.ic_account_child_20dp) + .withDefaultSizeChildAccountConfig() + .build(mContext) : null); }
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd index 34120e6..9e21e68 100644 --- a/chrome/browser/ui/android/strings/android_chrome_strings.grd +++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -515,6 +515,9 @@ <message name="IDS_SITE_SEARCH_LIST_MENU_MAKE_DEFAULT" desc="Option in site search list menu. User can click the 'Make default' option to make the selected option the default search engine."> Make default </message> + <message name="IDS_SITE_SEARCH_LIST_MENU_ACTIVATE" desc="Option in site search list menu. User can click the 'Activate' option to activate the selected option."> + Activate + </message> <message name="IDS_SITE_SEARCH_LIST_MENU_DELETE" desc="Option in site search list menu. User can click the 'Delete' option to remove the search engine."> Delete </message> @@ -1001,24 +1004,12 @@ </message> <!-- Privacy Sandbox v4 - Consent & Notice Flow --> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_BULLET_2" desc="* This is 2 of 2 bullets that site beneath the sentence: 'We’re launching new ways to limit what sites can learn about you when they show you personalized ads, for example:' * 'ad measurement' is the name of a new setting we're launching and that appears on the Ad privacy page of Chrome settings. * 'limited types of data': This setting helps an advertiser associate a user's actions on one site with their actions on another. For example, a user sees an ad on one site and then later buys that product from the company that sells the product. The ad measurement setting allows Chrome to help a company make the association between the two sites so that the first site can be fairly compensated for showing an ad. Compared with third-party cookies, very little info is shared between sites to support this functionality. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA NOTICE **** 1) This screen provides notice to Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is the second of 2 screens. This second screen describes 2 settings: “Site-suggested ads” and “Ad measurement”. The first screen describes the “Ad topics” setting. "> - With ad measurement, limited types of data are shared between sites to measure the performance of their ads, such as whether you made a purchase after visiting a site. - </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_DESCRIPTION_3" desc="A paragraph on the 'Enhanced ad privacy in Chrome' page that provides notice to Chrome users outside of the EEA. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen. "> - To measure the performance of an ad, limited types of data are shared between sites, such as whether you made a purchase after visiting a site. - </message> <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_RESTRICTED_DESCRIPTION_1_ANDROID" desc="First description in the restricted notice which applies to Android only"> We’re launching a new ad privacy feature called ad measurement. Chrome shares only very limited information among sites and apps, such as when an ad was shown to you, to help measure the performance of ads. </message> <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_RESTRICTED_DESCRIPTION_ANDROID" desc="Special description in the restricted notice which applies to Android only"> Your Android device may include a similar setting. If this setting is turned on in Chrome and on your Android device, a company may be able to measure the effectiveness of an ad across websites you visit and apps you use. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LEARN_MORE_DESCRIPTION_ANDROID" desc="As part of Privacy Sandbox (see details below), both Chrome and Android devices will have a new setting called 'Ad measurement'. Some details are different, but these settings essentially do the same thing. The Chrome setting allows sites you visit to ask Chrome for information that helps them measure the performance of their ads, linking 1) an ad for site B the user sees on site A, with 2) a purchase or other action the user might take on site B. On Android, it's essentially the same story only it's between apps and not sites. The challenge is that on Android, Chrome is a browser but it's also an App. This paragraph helps the user understand that, if both settings are on, information about the user's actions on a site might be connected with actions the user takes in an app. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com."> - Your Android device may include a similar setting. If ad measurement is turned on in Chrome and on your Android device, a company may be able to measure the effectiveness of an ad across web sites you visit and apps you use. - </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_DESCRIPTION_ANDROID" desc="As part of Privacy Sandbox (see details below), both Chrome and Android devices will have a new setting called 'Ad measurement'. Some details are different, but these settings essentially do the same thing. The Chrome setting allows sites you visit to ask Chrome for information that helps them measure the performance of their ads, linking 1) an ad for site B the user sees on site A, with 2) a purchase or other action the user might take on site B. On Android, it's essentially the same story only it's between apps and not sites. The challenge is that on Android, Chrome is a browser but it's also an App. This paragraph helps the user understand that, if both settings are on, information about the user's actions on a site might be connected with actions the user takes in an app. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com."> - Your Android device may include a similar setting. If ad measurement is turned on in Chrome and on your Android device, a company may be able to measure the effectiveness of an ad across web sites you visit and apps you use. - </message> <!-- Privacy Sandbox v4 - Ad Privacy Page --> <message name="IDS_AD_PRIVACY_PAGE_TITLE" desc="A new page title for a page that includes 3 settings. This same text appears as a label on chrome://settings/privacy between 'Third-party cookies' and 'Security'. This same pair of words is used in the Consent and Notice flow and must be consistently translated in both contexts. We mean privacy associated with ads, and not privacy from ads. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** NEW SETTINGS SECTION IN CHROME **** 3 new ad-specific settings appear on an “Ad privacy” page in Chrome settings. For an equivalent structure, see “Security” on chrome://settings/privacy that opens chrome://settings/security. Likewise, “Ad privacy” on chrome://settings/privacy will open chrome://settings/AdPrivacy.">
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_BULLET_2.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_BULLET_2.png.sha1 deleted file mode 100644 index 02b8ff7..0000000 --- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_BULLET_2.png.sha1 +++ /dev/null
@@ -1 +0,0 @@ -655a77f6d35b09bc6b1a54b63e5b8e2b990a042b \ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LEARN_MORE_DESCRIPTION_ANDROID.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LEARN_MORE_DESCRIPTION_ANDROID.png.sha1 deleted file mode 100644 index d39ad53..0000000 --- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LEARN_MORE_DESCRIPTION_ANDROID.png.sha1 +++ /dev/null
@@ -1 +0,0 @@ -59f22697905a6cc3a452410183baead395a1a439 \ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_DESCRIPTION_3.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_DESCRIPTION_3.png.sha1 deleted file mode 100644 index dba29fd..0000000 --- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_DESCRIPTION_3.png.sha1 +++ /dev/null
@@ -1 +0,0 @@ -62af92233cd63b1186223ee072e246905699ab99 \ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_DESCRIPTION_ANDROID.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_DESCRIPTION_ANDROID.png.sha1 deleted file mode 100644 index 53f076e..0000000 --- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_DESCRIPTION_ANDROID.png.sha1 +++ /dev/null
@@ -1 +0,0 @@ -41d4792f8589172082929024cd453954a8d175c2 \ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SITE_SEARCH_LIST_MENU_ACTIVATE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SITE_SEARCH_LIST_MENU_ACTIVATE.png.sha1 new file mode 100644 index 0000000..2fdee528 --- /dev/null +++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SITE_SEARCH_LIST_MENU_ACTIVATE.png.sha1
@@ -0,0 +1 @@ +6b87b46be2e452d477b837808338c66f8d90a0f4 \ No newline at end of file
diff --git a/chrome/browser/ui/android/toolbar/BUILD.gn b/chrome/browser/ui/android/toolbar/BUILD.gn index 881a7a5..19d19cb2 100644 --- a/chrome/browser/ui/android/toolbar/BUILD.gn +++ b/chrome/browser/ui/android/toolbar/BUILD.gn
@@ -324,6 +324,7 @@ "java/res/layout/optional_button_layout.xml", "java/res/layout/radio_button_group_adaptive_toolbar_preference.xml", "java/res/layout/sheet_tab_toolbar.xml", + "java/res/layout/signin_button_view.xml", "java/res/layout/toolbar_phone.xml", "java/res/layout/toolbar_progress_bar.xml", "java/res/layout/toolbar_tablet.xml",
diff --git a/chrome/browser/ui/android/toolbar/java/res/layout/signin_button_view.xml b/chrome/browser/ui/android/toolbar/java/res/layout/signin_button_view.xml new file mode 100644 index 0000000..9d304d5 --- /dev/null +++ b/chrome/browser/ui/android/toolbar/java/res/layout/signin_button_view.xml
@@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright 2026 The Chromium Authors +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +--> + +<org.chromium.chrome.browser.toolbar.signin_button.SigninButtonView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/signin_button"> + + <org.chromium.ui.widget.ChromeImageButton + android:id="@+id/avatar_button" + style="@style/ToolbarHoverableButton" + app:tint="@color/default_icon_color_tint_list" + android:visibility="gone"/> +</org.chromium.chrome.browser.toolbar.signin_button.SigninButtonView>
diff --git a/chrome/browser/ui/android/toolbar/java/res/layout/toolbar_phone.xml b/chrome/browser/ui/android/toolbar/java/res/layout/toolbar_phone.xml index 0b97dd69..38d6620 100644 --- a/chrome/browser/ui/android/toolbar/java/res/layout/toolbar_phone.xml +++ b/chrome/browser/ui/android/toolbar/java/res/layout/toolbar_phone.xml
@@ -58,6 +58,13 @@ android:layout_width="52dp" style="@style/ToolbarHoverableButton"/> + <ViewStub + android:id="@+id/signin_button_stub" + android:layout="@layout/signin_button_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone"/> + <org.chromium.chrome.browser.toolbar.top.ToggleTabStackButton android:id="@+id/tab_switcher_button" style="@style/ToolbarHoverableButton"
diff --git a/chrome/browser/ui/android/toolbar/java/res/layout/toolbar_tablet.xml b/chrome/browser/ui/android/toolbar/java/res/layout/toolbar_tablet.xml index 7643bc14..7ffcd08 100644 --- a/chrome/browser/ui/android/toolbar/java/res/layout/toolbar_tablet.xml +++ b/chrome/browser/ui/android/toolbar/java/res/layout/toolbar_tablet.xml
@@ -90,6 +90,13 @@ android:visibility="gone" /> <ViewStub + android:id="@+id/signin_button_stub" + android:layout="@layout/signin_button_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone"/> + + <ViewStub android:id="@+id/incognito_indicator_stub" android:inflatedId="@+id/incognito_indicator" android:layout="@layout/incognito_indicator"
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionController.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionController.java index 8519c09c..2b01bad 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionController.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarPositionController.java
@@ -869,27 +869,30 @@ Log.i(TAG, "Current %s showing a NTP", isNtpShowing ? "is" : "isn't"); } - boolean isEmptyUrl = false; - if (!isNtpShowing) { - Tab tab = mActiveTabSupplier.get(); - if (tab != null) { - GURL url = tab.getUrl(); - isEmptyUrl = url.isEmpty(); - Log.i( - TAG, - "URL of the current tab: [isEmpty: %b] [isValid: %b]", - isEmptyUrl, - url.isValid()); - } - } - - if (isNtpShowing || isEmptyUrl) { + if (isNtpShowing) { // On certain devices, the toolbar position could switch from top to bottom, and then // back to the top when creating a NTP. Force calling onToEdgeChange() will reset the // correct top padding which has been set on the toolbar. Since the toolbar is always // shown at the top on NTPs, skips the temporary bottom position on NTPs. See // https://crbug.com/485266759. return; + } else { + Tab tab = mActiveTabSupplier.get(); + if (tab != null) { + GURL url = tab.getUrl(); + boolean isEmptyUrl = url.isEmpty(); + if (mEnableLogs) { + Log.i( + TAG, + "URL of the current tab: [isEmpty: %b] [isValid: %b]", + isEmptyUrl, + url.isValid()); + } + if (isEmptyUrl) { + // Also skips calling onToEdgeChange() if the URL is empty. + return; + } + } } // When the toolbar is at bottom, it shouldn't add any top inset. Calling
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsMediator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsMediator.java index e7ea11f..5146b68 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsMediator.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsMediator.java
@@ -296,7 +296,7 @@ @Override public int getType() { - return LayerType.TABSTRIP_TOOLBAR_BELOW_READALOUD; + return LayerType.TABSTRIP_TOOLBAR; } @Override
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonCoordinator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonCoordinator.java index 42222f8..baa4d511b 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonCoordinator.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonCoordinator.java
@@ -5,6 +5,7 @@ package org.chromium.chrome.browser.toolbar.signin_button; import android.content.Context; +import android.view.ViewStub; import org.chromium.base.supplier.MonotonicObservableSupplier; import org.chromium.build.annotations.NullMarked; @@ -13,29 +14,40 @@ import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModelChangeProcessor; -/** - * The coordinator for a signin button on the NTP toolbar. Owns the the SigninButton view. - * TODO(crbug.com/475816843): Ensure implementation is complete and the View is inflated. - */ +/** The coordinator for a signin button on the NTP toolbar. Owns the the SigninButton view. */ @NullMarked public final class SigninButtonCoordinator { private final SigninButtonMediator mMediator; - private final SigninButtonView mView; + private final PropertyModel mModel; + private final @Nullable ViewStub mViewStub; + private @Nullable SigninButtonView mView; private @Nullable PropertyModelChangeProcessor mPropertyModelChangeProcessor; public SigninButtonCoordinator( - Context context, MonotonicObservableSupplier<Profile> profileSupplier) { - PropertyModel model = new PropertyModel.Builder(SigninButtonProperties.ALL_KEYS).build(); - mMediator = new SigninButtonMediator(profileSupplier); - mView = new SigninButtonView(context); - mPropertyModelChangeProcessor = - PropertyModelChangeProcessor.create(model, mView, SigninButtonViewBinder::bind); + Context context, + ViewStub viewStub, + MonotonicObservableSupplier<Profile> profileSupplier) { + mModel = new PropertyModel.Builder(SigninButtonProperties.ALL_KEYS).build(); + mMediator = new SigninButtonMediator(context, mModel, profileSupplier); + + // Defers setting the view and binding the model until the button needs to be shown. + mViewStub = viewStub; } - public @Nullable SigninButtonView getView() { - return mView; + /** Updates the button visibility and inflates SigninButton once it should be visible. */ + public void updateButtonVisibility(Boolean showButton) { + mMediator.updateButtonVisibility(showButton); + if (mModel.get(SigninButtonProperties.SHOW_BUTTON) && mView == null && mViewStub != null) { + + // Once the view initially is set to be visible, SigninButtonView should be inflated. + mView = (SigninButtonView) mViewStub.inflate(); + mPropertyModelChangeProcessor = + PropertyModelChangeProcessor.create( + mModel, mView, SigninButtonViewBinder::bind); + } } + /** Call to tear down dependencies. */ public void destroy() { if (mPropertyModelChangeProcessor != null) { mPropertyModelChangeProcessor.destroy();
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonMediator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonMediator.java index d109077..f9308f5 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonMediator.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonMediator.java
@@ -1,16 +1,24 @@ // Copyright 2026 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. + package org.chromium.chrome.browser.toolbar.signin_button; +import android.content.Context; +import android.graphics.drawable.Drawable; + +import androidx.appcompat.content.res.AppCompatResources; + import org.chromium.base.Callback; import org.chromium.base.supplier.MonotonicObservableSupplier; import org.chromium.build.annotations.NullMarked; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.signin.services.DisplayableProfileData; import org.chromium.chrome.browser.signin.services.ProfileDataCache; +import org.chromium.chrome.browser.toolbar.R; import org.chromium.components.signin.identitymanager.IdentityManager; import org.chromium.components.sync.SyncService; +import org.chromium.ui.modelutil.PropertyModel; /** * The mediator for a signin button on the NTP toolbar. Listens for sign in state changes and drives @@ -22,12 +30,20 @@ implements ProfileDataCache.Observer, IdentityManager.Observer, SyncService.SyncStateChangedListener { + private final Context mContext; + private final PropertyModel mModel; private final MonotonicObservableSupplier<Profile> mProfileSupplier; private final Callback<Profile> mProfileSupplierObserver = this::setProfile; - public SigninButtonMediator(MonotonicObservableSupplier<Profile> profileSupplier) { + public SigninButtonMediator( + Context context, + PropertyModel model, + MonotonicObservableSupplier<Profile> profileSupplier) { + mContext = context; + mModel = model; mProfileSupplier = profileSupplier; mProfileSupplier.addSyncObserverAndPostIfNonNull(mProfileSupplierObserver); + setButtonState(); } /** @@ -48,6 +64,16 @@ // TODO(crbug.com/475816843): Add implementation for necessary override. } + void updateButtonVisibility(Boolean showButton) { + mModel.set(SigninButtonProperties.SHOW_BUTTON, showButton); + } + + private void setButtonState() { + Drawable buttonAvatar = AppCompatResources.getDrawable(mContext, R.drawable.account_circle); + mModel.set(SigninButtonProperties.BUTTON_AVATAR, buttonAvatar); + mModel.set(SigninButtonProperties.SHOW_AVATAR, true); + } + /** * Triggered by mProfileSupplierObserver when profile is changed in mProfileSupplier. * mIdentityManager is updated with the profile, or set to null if profile is off-the-record.
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonMediatorTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonMediatorTest.java index 239362d..44f95e9 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonMediatorTest.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonMediatorTest.java
@@ -1,9 +1,13 @@ // Copyright 2026 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. + package org.chromium.chrome.browser.toolbar.signin_button; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.content.Context; import org.junit.Before; import org.junit.Rule; @@ -13,22 +17,28 @@ import org.mockito.junit.MockitoRule; import org.mockito.quality.Strictness; +import org.chromium.base.ContextUtils; import org.chromium.base.supplier.MonotonicObservableSupplier; import org.chromium.base.supplier.ObservableSuppliers; import org.chromium.base.test.BaseRobolectricTestRunner; import org.chromium.chrome.browser.profiles.Profile; +import org.chromium.ui.modelutil.PropertyModel; /** Unit tests for {@link SigninButtonMediator}. */ @RunWith(BaseRobolectricTestRunner.class) public final class SigninButtonMediatorTest { @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); + private Context mContext; private final MonotonicObservableSupplier<Profile> mProfileSupplier = ObservableSuppliers.alwaysNull(); private SigninButtonMediator mMediator; + private PropertyModel mModel; @Before public void setUp() { - mMediator = new SigninButtonMediator(mProfileSupplier); + mContext = ContextUtils.getApplicationContext(); + mModel = new PropertyModel.Builder(SigninButtonProperties.ALL_KEYS).build(); + mMediator = new SigninButtonMediator(mContext, mModel, mProfileSupplier); } @Test @@ -36,4 +46,13 @@ mMediator.destroy(); assertFalse(mProfileSupplier.hasObservers()); } + + @Test + public void testUpdateButtonVisibility() { + mMediator.updateButtonVisibility(true); + assertTrue(mModel.get(SigninButtonProperties.SHOW_BUTTON)); + + mMediator.updateButtonVisibility(false); + assertFalse(mModel.get(SigninButtonProperties.SHOW_BUTTON)); + } }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonProperties.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonProperties.java index 253819a..9ffcd3f 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonProperties.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonProperties.java
@@ -4,14 +4,27 @@ package org.chromium.chrome.browser.toolbar.signin_button; +import android.graphics.drawable.Drawable; + import org.chromium.build.annotations.NullMarked; import org.chromium.ui.modelutil.PropertyKey; +import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey; +import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey; -/** - * TODO(crbug.com/475816843): Implement a SigninButton Model to allow for interactions between View - * and Controller. - */ +/** The SigninButton model allows for interactions between the view and the controller. */ @NullMarked final class SigninButtonProperties { - public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {}; + + // Indicates whether the signin button view should be displayed. + public static final WritableBooleanPropertyKey SHOW_BUTTON = new WritableBooleanPropertyKey(); + + // Indicates whether the inner signin avatar button should be displayed. + public static final WritableBooleanPropertyKey SHOW_AVATAR = new WritableBooleanPropertyKey(); + + // The image displayed within the avatar button, i.e. profile picture or generic account circle. + public static final WritableObjectPropertyKey<Drawable> BUTTON_AVATAR = + new WritableObjectPropertyKey<>(); + + public static final PropertyKey[] ALL_KEYS = + new PropertyKey[] {SHOW_BUTTON, SHOW_AVATAR, BUTTON_AVATAR}; }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonView.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonView.java index 40db97b..c1f4f10 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonView.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonView.java
@@ -5,18 +5,32 @@ package org.chromium.chrome.browser.toolbar.signin_button; import android.content.Context; +import android.util.AttributeSet; import android.widget.FrameLayout; import org.chromium.build.annotations.NullMarked; +import org.chromium.build.annotations.Nullable; +import org.chromium.chrome.browser.toolbar.R; +import org.chromium.ui.widget.ChromeImageButton; /** - * TODO(crbug.com/475816843): Implement View which has displays for various situations (user sign-in - * state, whether NTP is shown, etc.) + * A View which has displays for various situations (user sign-in state, whether NTP is shown, etc.) */ @NullMarked final class SigninButtonView extends FrameLayout { + private ChromeImageButton mAvatarButton; - public SigninButtonView(Context context) { - super(context); + public SigninButtonView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mAvatarButton = findViewById(R.id.avatar_button); + } + + ChromeImageButton getAvatarButton() { + return mAvatarButton; } }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonViewBinder.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonViewBinder.java index 733eaff..755e751 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonViewBinder.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/signin_button/SigninButtonViewBinder.java
@@ -4,16 +4,27 @@ package org.chromium.chrome.browser.toolbar.signin_button; +import android.view.View; + import org.chromium.build.annotations.NullMarked; import org.chromium.ui.modelutil.PropertyKey; import org.chromium.ui.modelutil.PropertyModel; +import org.chromium.ui.widget.ChromeImageButton; -/** - * TODO(crbug.com/475816843): Implement ViewBinder connecting SigninButtonProperties and - * SigninButtonView. - */ +/** The ViewBinder connecting SigninButtonProperties and SigninButtonView. */ @NullMarked final class SigninButtonViewBinder { - - public static void bind(PropertyModel model, SigninButtonView view, PropertyKey propertyKey) {} + public static void bind(PropertyModel model, SigninButtonView view, PropertyKey propertyKey) { + if (SigninButtonProperties.BUTTON_AVATAR.equals(propertyKey)) { + ChromeImageButton avatarButton = view.getAvatarButton(); + avatarButton.setImageDrawable(model.get(SigninButtonProperties.BUTTON_AVATAR)); + } else if (SigninButtonProperties.SHOW_BUTTON.equals(propertyKey)) { + view.setVisibility( + model.get(SigninButtonProperties.SHOW_BUTTON) ? View.VISIBLE : View.GONE); + } else if (SigninButtonProperties.SHOW_AVATAR.equals(propertyKey)) { + ChromeImageButton avatarButton = view.getAvatarButton(); + avatarButton.setVisibility( + model.get(SigninButtonProperties.SHOW_AVATAR) ? View.VISIBLE : View.GONE); + } + } }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarUtils.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarUtils.java index c054a4c..4f354ef9 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarUtils.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarUtils.java
@@ -39,6 +39,7 @@ ToolbarComponentId.BACK, ToolbarComponentId.INCOGNITO_INDICATOR, ToolbarComponentId.ADAPTIVE_BUTTON, + ToolbarComponentId.SIGNIN_BUTTON, ToolbarComponentId.RELOAD, ToolbarComponentId.FORWARD, ToolbarComponentId.HOME, @@ -82,6 +83,7 @@ ToolbarComponentId.TAB_SWITCHER, ToolbarComponentId.MENU, ToolbarComponentId.PADDING, + ToolbarComponentId.SIGNIN_BUTTON, ToolbarComponentId.COUNT }) @Retention(RetentionPolicy.SOURCE) @@ -105,7 +107,8 @@ int TAB_SWITCHER = 16; int MENU = 17; int PADDING = 18; - int COUNT = 19; + int SIGNIN_BUTTON = 19; + int COUNT = 20; } // LINT.ThenChange(//chrome/browser/ui/android/toolbar/java/res/layout/toolbar_tablet.xml:toolbar_tablet_components|//chrome/browser/ui/android/omnibox/java/res/layout/url_action_container.xml:toolbar_tablet_components)
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java index 9791e62..58d7208 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarCoordinator.java
@@ -11,6 +11,7 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; +import android.view.ViewStub; import android.widget.ImageButton; import androidx.annotation.ColorInt; @@ -73,6 +74,7 @@ import org.chromium.chrome.browser.user_education.UserEducationHelper; import org.chromium.components.browser_ui.desktop_windowing.DesktopWindowStateManager; import org.chromium.components.browser_ui.widget.ClipDrawableProgressBar.DrawingInfo; +import org.chromium.components.embedder_support.util.UrlUtilities; import org.chromium.components.feature_engagement.Tracker; import org.chromium.components.signin.SigninFeatureMap; import org.chromium.ui.resources.ResourceManager; @@ -108,6 +110,7 @@ private OptionalBrowsingModeButtonController mOptionalButtonController; private @Nullable SigninButtonCoordinator mSigninButtonCoordinator; + private @Nullable NullableObservableSupplier<Tab> mTabSupplier; private final MenuButtonCoordinator mMenuButtonCoordinator; private @Nullable ReloadButtonCoordinator mReloadButtonCoordinator; @@ -232,8 +235,13 @@ () -> toolbarDataProvider.getTab()); if (SigninFeatureMap.sSigninLevelUpButton.isEnabled()) { - mSigninButtonCoordinator = - new SigninButtonCoordinator(toolbarLayout.getContext(), profileSupplier); + mTabSupplier = tabSupplier; + ViewStub signinButtonStub = mToolbarLayout.findViewById(R.id.signin_button_stub); + if (signinButtonStub != null) { + mSigninButtonCoordinator = + new SigninButtonCoordinator( + toolbarLayout.getContext(), signinButtonStub, profileSupplier); + } } mResourceManagerSupplier = resourceManagerSupplier; mTabCountSupplier = tabCountSupplier; @@ -610,6 +618,16 @@ if (mOptionalButtonController != null) { mOptionalButtonController.updateButtonVisibility(); } + if (mSigninButtonCoordinator != null && mTabSupplier != null) { + @Nullable Tab tab = mTabSupplier.get(); + + // Should only show the signin button when on the NTP and not incognito. + if (tab != null && UrlUtilities.isNtpUrl(tab.getUrl()) && !tab.isOffTheRecord()) { + mSigninButtonCoordinator.updateButtonVisibility(true); + } else { + mSigninButtonCoordinator.updateButtonVisibility(false); + } + } } /**
diff --git a/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/Snackbar.java b/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/Snackbar.java index ac6dd08..ccd94f6 100644 --- a/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/Snackbar.java +++ b/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/Snackbar.java
@@ -146,7 +146,7 @@ private @Nullable String mActionText; private @Nullable Object mActionData; private int mBackgroundColor; - private int mTextApperanceResId; + private int mTextAppearanceResId; private boolean mDefaultLines = true; private int mDurationMs; private @Nullable Drawable mProfileImage; @@ -256,7 +256,7 @@ * appearance. */ public Snackbar setTextAppearance(int resId) { - mTextApperanceResId = resId; + mTextAppearanceResId = resId; return this; } @@ -313,7 +313,7 @@ /** If method returns zero, then default text appearance for snackbar will be used. */ int getTextAppearance() { - return mTextApperanceResId; + return mTextAppearanceResId; } /**
diff --git a/chrome/browser/ui/omnibox/omnibox_context_menu_controller.cc b/chrome/browser/ui/omnibox/omnibox_context_menu_controller.cc index ef3ff8a..45e5997 100644 --- a/chrome/browser/ui/omnibox/omnibox_context_menu_controller.cc +++ b/chrome/browser/ui/omnibox/omnibox_context_menu_controller.cc
@@ -1007,8 +1007,10 @@ return false; } - const omnibox::ToolMode aim_tool_mode = - omnibox_popup_ui->composebox_handler()->GetInputState().active_tool; + const omnibox::ToolMode aim_tool_mode = omnibox_popup_ui->composebox_handler() + ->input_state_model() + ->GetInputState() + .active_tool; auto* session_handle = omnibox_popup_ui->GetOrCreateContextualSessionHandle(); std::vector<contextual_search::FileInfo> file_infos;
diff --git a/chrome/browser/ui/sad_tab_controller.cc b/chrome/browser/ui/sad_tab_controller.cc index a0a9e64..26243548 100644 --- a/chrome/browser/ui/sad_tab_controller.cc +++ b/chrome/browser/ui/sad_tab_controller.cc
@@ -4,36 +4,124 @@ #include "chrome/browser/ui/sad_tab_controller.h" +#include "chrome/browser/ui/browser_window/public/browser_window_interface.h" +#include "chrome/browser/ui/views/frame/browser_view.h" +#include "chrome/browser/ui/views/frame/contents_web_view.h" #include "chrome/browser/ui/views/sad_tab_view.h" +#include "components/tabs/public/tab_interface.h" #include "content/public/browser/web_contents.h" #include "ui/views/view.h" #include "ui/views/view_utils.h" -SadTabController::SadTabController(content::WebContents* sad_tab_web_contents, - SadTabKind sad_tab_kind) - : SadTab(sad_tab_web_contents, sad_tab_kind) { - view_ = std::make_unique<SadTabView>( - this, web_contents(), kind(), GetTitle(), GetInfoMessage(), - GetSubMessages(), GetErrorCodeFormatString(), GetCrashedErrorCode(), - GetButtonTitle(), GetHelpLinkTitle()); +namespace { + +ContentsWebView* FindContentsWebView(content::WebContents* web_contents) { + tabs::TabInterface* tab = + tabs::TabInterface::MaybeGetFromContents(web_contents); + if (!tab) { + return nullptr; + } + + // In unit tests, browser->GetWindow() might not be a real BrowserView. + BrowserWindowInterface* browser = tab->GetBrowserWindowInterface(); + if (!browser || !browser->GetWindow() || + !browser->GetWindow()->GetNativeWindow()) { + return nullptr; + } + BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser); + DCHECK(browser_view); + + for (auto* contents_view : browser_view->GetAllVisibleContentsWebViews()) { + if (contents_view->web_contents() == web_contents) { + return contents_view; + } + } + + return nullptr; } -SadTabController::~SadTabController() = default; +} // namespace + +SadTabController::SadTabController(content::WebContents* web_contents, + SadTabKind kind) + : SadTab(web_contents, kind) { + ReinstallInWebView(); +} + +SadTabController::~SadTabController() { + ContentsWebView* contents_view = FindContentsWebView(web_contents()); + if (!contents_view) { + return; + } + + if (view_tracker_ && !owned_sad_tab_view_) { + auto crashed_overlay_view = contents_view->DetachCrashedOverlayView(); + CHECK_EQ(view_tracker_.view(), crashed_overlay_view.get()); + } +} void SadTabController::ReinstallInWebView() { - view_->ReinstallInWebView(); + std::unique_ptr<SadTabView> sad_tab_view; + if (contents_view_tracker_) { + ContentsWebView* contents_view = + views::AsViewClass<ContentsWebView>(contents_view_tracker_.view()); + if (view_tracker_) { + sad_tab_view = + owned_sad_tab_view_ + ? std::move(owned_sad_tab_view_) + : contents_view->DetachCrashedOverlayView<SadTabView>(); + } + contents_view_tracker_.SetView(nullptr); + } + + ContentsWebView* contents_view = FindContentsWebView(web_contents()); + if (!contents_view) { + return; + } + + if (!sad_tab_view) { + sad_tab_view = std::make_unique<SadTabView>( + this, kind(), GetTitle(), GetInfoMessage(), GetSubMessages(), + GetErrorCodeFormatString(), GetCrashedErrorCode(), GetButtonTitle(), + GetHelpLinkTitle()); + view_tracker_.SetView(sad_tab_view.get()); + } + CHECK_EQ(view_tracker_.view(), sad_tab_view.get()); + sad_tab_view->SetBackgroundRadii(contents_view->GetBackgroundRadii()); + contents_view->TakeCrashedOverlayView( + std::move(sad_tab_view), + base::BindOnce( + [](base::WeakPtr<SadTabController> controller, + std::unique_ptr<views::View> crashed_overlay_view) { + CHECK(!controller->owned_sad_tab_view_); + std::unique_ptr<SadTabView> sad_tab_view = + views::AsViewClass<SadTabView>(std::move(crashed_overlay_view)); + CHECK(sad_tab_view); + controller->owned_sad_tab_view_.swap(sad_tab_view); + }, + weak_factory_.GetWeakPtr())); + contents_view_tracker_.SetView(contents_view); } void SadTabController::SetBackgroundRadii(const gfx::RoundedCornersF& radii) { - view_->SetBackgroundRadii(radii); + if (view_tracker_.view()) { + views::AsViewClass<SadTabView>(view_tracker_.view()) + ->SetBackgroundRadii(radii); + } } gfx::RoundedCornersF SadTabController::GetBackgroundRadii() const { - return view_->GetBackgroundRadii(); + if (view_tracker_) { + return views::AsViewClass<SadTabView>(view_tracker_.view()) + ->GetBackgroundRadii(); + } + return gfx::RoundedCornersF(); } void SadTabController::RequestFocus() { - view_->RequestFocus(); + if (view_tracker_) { + view_tracker_.view()->RequestFocus(); + } } // SadTab::Create implementation moved here.
diff --git a/chrome/browser/ui/sad_tab_controller.h b/chrome/browser/ui/sad_tab_controller.h index b7dc69f..e1f96895 100644 --- a/chrome/browser/ui/sad_tab_controller.h +++ b/chrome/browser/ui/sad_tab_controller.h
@@ -6,10 +6,11 @@ #define CHROME_BROWSER_UI_SAD_TAB_CONTROLLER_H_ #include <memory> -#include <vector> +#include "base/memory/weak_ptr.h" #include "chrome/browser/ui/sad_tab.h" #include "ui/gfx/geometry/rounded_corners_f.h" +#include "ui/views/view_tracker.h" namespace content { class WebContents; @@ -19,8 +20,7 @@ class SadTabController : public SadTab { public: - SadTabController(content::WebContents* sad_tab_web_contents, - SadTabKind sad_tab_kind); + SadTabController(content::WebContents* web_contents, SadTabKind kind); SadTabController(const SadTabController&) = delete; SadTabController& operator=(const SadTabController&) = delete; ~SadTabController() override; @@ -33,7 +33,10 @@ void RequestFocus(); private: - std::unique_ptr<SadTabView> view_; + views::ViewTracker view_tracker_; + views::ViewTracker contents_view_tracker_; + std::unique_ptr<SadTabView> owned_sad_tab_view_; + base::WeakPtrFactory<SadTabController> weak_factory_{this}; }; #endif // CHROME_BROWSER_UI_SAD_TAB_CONTROLLER_H_
diff --git a/chrome/browser/ui/startup/infobar_utils.cc b/chrome/browser/ui/startup/infobar_utils.cc index 35979cf..57d7394 100644 --- a/chrome/browser/ui/startup/infobar_utils.cc +++ b/chrome/browser/ui/startup/infobar_utils.cc
@@ -239,7 +239,7 @@ if (pin_feature_enabled && base::FeatureList::IsEnabled(features::kSeparateDefaultAndPinPrompt)) { const int seed = features::kSeparateDefaultAndPinPromptRandSeed.Get(); - const int choice = (seed > 0) ? (seed % 2) : base::RandInt(0, 1); + const int choice = (seed > 0) ? (seed % 2) : base::RandIntInclusive(0, 1); base::OnceCallback<void(bool)> pdf_callback = base::DoNothing(); if (base::FeatureList::IsEnabled(features::kPdfInfoBar)) {
diff --git a/chrome/browser/ui/tabs/organization/BUILD.gn b/chrome/browser/ui/tabs/organization/BUILD.gn index e576651..be1e4bc 100644 --- a/chrome/browser/ui/tabs/organization/BUILD.gn +++ b/chrome/browser/ui/tabs/organization/BUILD.gn
@@ -30,6 +30,7 @@ "//chrome/browser/metrics/desktop_session_duration", "//chrome/browser/profiles:profile", "//chrome/browser/ui:browser_list", + "//chrome/browser/ui/browser_window", "//chrome/browser/ui/tabs:tab_model", "//chrome/browser/ui/tabs:tab_strip_model_observer", "//chrome/browser/ui/webui/tab_search:mojo_bindings", @@ -103,6 +104,7 @@ deps += [ "//chrome/browser/ash/ownership", "//chrome/browser/ash/settings", + "//chrome/browser/ui/browser_window", ] } public_deps = [ "//chrome/browser:browser_public_dependencies" ]
diff --git a/chrome/browser/ui/tabs/organization/trigger_observer.cc b/chrome/browser/ui/tabs/organization/trigger_observer.cc index 899cbb5..54d6f6f 100644 --- a/chrome/browser/ui/tabs/organization/trigger_observer.cc +++ b/chrome/browser/ui/tabs/organization/trigger_observer.cc
@@ -8,8 +8,9 @@ #include "base/location.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" -#include "chrome/browser/ui/browser_list.h" -#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h" +#include "chrome/browser/ui/browser_window/public/browser_collection.h" +#include "chrome/browser/ui/browser_window/public/browser_window_interface.h" +#include "chrome/browser/ui/browser_window/public/profile_browser_collection.h" #include "chrome/browser/ui/tabs/organization/trigger.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" @@ -21,35 +22,32 @@ : trigger_logic_(std::move(trigger_logic)), on_trigger_(on_trigger), browser_context_(browser_context) { - ForEachCurrentBrowserWindowInterfaceOrderedByActivation( - [this](BrowserWindowInterface* browser) { - OnBrowserAdded(browser->GetBrowserForMigrationOnly()); - return true; - }); - BrowserList::GetInstance()->AddObserver(this); + Profile* profile = Profile::FromBrowserContext(browser_context_); + ProfileBrowserCollection* collection = + ProfileBrowserCollection::GetForProfile(profile); + collection->ForEach([this](BrowserWindowInterface* browser) { + OnBrowserCreated(browser); + return true; + }); + browser_collection_observation_.Observe(collection); } -TabOrganizationTriggerObserver::~TabOrganizationTriggerObserver() { - BrowserList::GetInstance()->RemoveObserver(this); - // TabStripModelObserver destructor will stop observing the TabStripModels. +TabOrganizationTriggerObserver::~TabOrganizationTriggerObserver() = default; + +void TabOrganizationTriggerObserver::OnBrowserCreated( + BrowserWindowInterface* browser) { + CHECK(browser_context_ == browser->GetProfile()); + tab_strip_model_to_browser_map_.emplace( + browser->GetTabStripModel(), browser->GetBrowserForMigrationOnly()); + // TODO(crbug.com/452120900): TabStripModel auto-unregistered by dtor + browser->GetTabStripModel()->AddObserver(this); } -void TabOrganizationTriggerObserver::OnBrowserAdded(Browser* browser) { - if (browser_context_ != browser->profile()) { - return; - } +void TabOrganizationTriggerObserver::OnBrowserClosed( + BrowserWindowInterface* browser) { + CHECK(browser_context_ == browser->GetProfile()); - tab_strip_model_to_browser_map_.emplace(browser->tab_strip_model(), browser); - browser->tab_strip_model()->AddObserver(this); -} - -void TabOrganizationTriggerObserver::OnBrowserRemoved(Browser* browser) { - if (browser_context_ != browser->profile()) { - return; - } - - tab_strip_model_to_browser_map_.erase(browser->tab_strip_model()); - browser->tab_strip_model()->RemoveObserver(this); + tab_strip_model_to_browser_map_.erase(browser->GetTabStripModel()); } void TabOrganizationTriggerObserver::OnTabStripModelChanged(
diff --git a/chrome/browser/ui/tabs/organization/trigger_observer.h b/chrome/browser/ui/tabs/organization/trigger_observer.h index 91e86c3..c11e291 100644 --- a/chrome/browser/ui/tabs/organization/trigger_observer.h +++ b/chrome/browser/ui/tabs/organization/trigger_observer.h
@@ -11,19 +11,23 @@ #include "base/functional/callback.h" #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" -#include "chrome/browser/ui/browser_list_observer.h" +#include "base/scoped_observation.h" +#include "chrome/browser/ui/browser_window/public/browser_collection_observer.h" +#include "chrome/browser/ui/browser_window/public/profile_browser_collection.h" #include "chrome/browser/ui/tabs/organization/trigger.h" #include "chrome/browser/ui/tabs/tab_strip_model_observer.h" class TabStripModel; class TabOrganizationTrigger; +class Browser; +class BrowserWindowInterface; namespace content { class BrowserContext; } // Gets inputs for triggering, runs triggering logic when anything changes. -class TabOrganizationTriggerObserver : public BrowserListObserver, - TabStripModelObserver { +class TabOrganizationTriggerObserver : public BrowserCollectionObserver, + public TabStripModelObserver { public: explicit TabOrganizationTriggerObserver( base::RepeatingCallback<void(const Browser*)> on_trigger, @@ -31,9 +35,9 @@ std::unique_ptr<TabOrganizationTrigger> trigger_logic); ~TabOrganizationTriggerObserver() override; - // BrowserListObserver: - void OnBrowserAdded(Browser* browser) override; - void OnBrowserRemoved(Browser* browser) override; + // BrowserCollectionObserver: + void OnBrowserCreated(BrowserWindowInterface* browser) override; + void OnBrowserClosed(BrowserWindowInterface* browser) override; // TabStripModelObserver: void OnTabStripModelChanged( @@ -50,6 +54,8 @@ raw_ptr<content::BrowserContext> browser_context_; std::unordered_map<TabStripModel*, raw_ptr<Browser>> tab_strip_model_to_browser_map_; + base::ScopedObservation<ProfileBrowserCollection, BrowserCollectionObserver> + browser_collection_observation_{this}; // Whether trigger evaluation is scheduled. Used to debounce // TabStripModelObserver events. bool pending_eval_ = false;
diff --git a/chrome/browser/ui/tabs/tab_strip_model_browsertest.cc b/chrome/browser/ui/tabs/tab_strip_model_browsertest.cc index bc48553f..8e111f4 100644 --- a/chrome/browser/ui/tabs/tab_strip_model_browsertest.cc +++ b/chrome/browser/ui/tabs/tab_strip_model_browsertest.cc
@@ -19,6 +19,7 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_list_observer.h" #include "chrome/browser/ui/browser_tabstrip.h" #include "chrome/browser/ui/browser_window/public/browser_window_features.h" #include "chrome/browser/ui/tabs/organization/tab_organization_service.h"
diff --git a/chrome/browser/ui/thumbnails/thumbnail_tab_helper_interactive_uitest.cc b/chrome/browser/ui/thumbnails/thumbnail_tab_helper_interactive_uitest.cc index 0e66369..e7ff76dd 100644 --- a/chrome/browser/ui/thumbnails/thumbnail_tab_helper_interactive_uitest.cc +++ b/chrome/browser/ui/thumbnails/thumbnail_tab_helper_interactive_uitest.cc
@@ -4,9 +4,11 @@ #include <optional> +#include "base/callback_list.h" #include "base/functional/bind.h" #include "base/functional/callback.h" #include "base/memory/raw_ptr.h" +#include "base/scoped_observation.h" #include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" #include "base/test/scoped_feature_list.h" @@ -15,7 +17,6 @@ #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/browser_element_identifiers.h" -#include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_tabstrip.h" #include "chrome/browser/ui/browser_window/public/browser_window_interface.h" #include "chrome/browser/ui/interaction/browser_elements.h" @@ -59,25 +60,21 @@ base::WeakPtrFactory<ThumbnailObserver> weak_ptr_factory_{this}; }; -class BrowserRemovedObserver : public ui::test::StateObserver<bool>, - public BrowserListObserver { +class BrowserRemovedObserver : public ui::test::StateObserver<bool> { public: - explicit BrowserRemovedObserver(Browser* browser) : browser_(browser) { - BrowserList::AddObserver(this); + explicit BrowserRemovedObserver(BrowserWindowInterface* browser) { + browser_did_close_subscription_ = browser->RegisterBrowserDidClose( + base::BindRepeating(&BrowserRemovedObserver::OnBrowserDidClose, + base::Unretained(this))); } ~BrowserRemovedObserver() override = default; - protected: - void OnBrowserRemoved(Browser* browser) override { - if (browser_ == browser) { - OnStateObserverStateChanged(true); - browser_ = nullptr; - BrowserList::RemoveObserver(this); - } + private: + void OnBrowserDidClose(BrowserWindowInterface* browser) { + OnStateObserverStateChanged(true); } - private: - raw_ptr<Browser> browser_; + base::CallbackListSubscription browser_did_close_subscription_; }; DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kFirstTab); @@ -229,9 +226,7 @@ AddInstrumentedTab(kThirdTab, GURL(chrome::kChromeUINewTabURL), 3), WaitForWebContentsReady(kThirdTab)), CheckTabCountInBrowser(4), CheckActiveTabInBrowser(3), - ObserveState( - kBrowserRemovedState, - [this]() { return target_browser()->GetBrowserForMigrationOnly(); }), + ObserveState(kBrowserRemovedState, [this]() { return target_browser(); }), // Can't close browser when WebContents is notifying observers. Do([this]() { // Override manual value set in MemorySaverInteractiveTestMixin to
diff --git a/chrome/browser/ui/views/bubble_anchor_util_views.cc b/chrome/browser/ui/views/bubble_anchor_util_views.cc index 7d806d4..265cc23 100644 --- a/chrome/browser/ui/views/bubble_anchor_util_views.cc +++ b/chrome/browser/ui/views/bubble_anchor_util_views.cc
@@ -5,11 +5,13 @@ #include "chrome/browser/ui/views/bubble_anchor_util_views.h" #include "build/build_config.h" +#include "chrome/browser/ui/browser_element_identifiers.h" #include "chrome/browser/ui/bubble_anchor_util.h" #include "chrome/browser/ui/views/frame/app_menu_button.h" #include "chrome/browser/ui/views/frame/browser_view.h" #include "chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h" #include "chrome/browser/ui/views/location_bar/location_bar_view.h" +#include "chrome/browser/ui/views/permissions/chip/permission_chip_view.h" #include "chrome/browser/ui/views/toolbar/toolbar_view.h" #include "components/content_settings/core/common/features.h" #include "ui/base/interaction/element_highlighter.h" @@ -37,12 +39,12 @@ permission_dashboard_view->GetVisible()) { if (permission_dashboard_view->GetIndicatorChip()->GetVisible()) { return {permission_dashboard_view->GetIndicatorChip(), - permission_dashboard_view->GetIndicatorChip(), + PermissionChipView::kIndicatorChipElementId, views::BubbleBorder::TOP_LEFT}; } return {permission_dashboard_view->GetRequestChip(), - permission_dashboard_view->GetRequestChip(), + PermissionChipView::kPermissionRequestChipElementId, views::BubbleBorder::TOP_LEFT}; } } else { @@ -53,7 +55,7 @@ } if (anchor == Anchor::kLocationBar && location_bar_view->IsDrawn()) { - return {location_bar_view, location_bar_view->location_icon_view(), + return {location_bar_view, kLocationIconElementId, views::BubbleBorder::TOP_LEFT}; } @@ -61,14 +63,13 @@ browser_view->GetIsPictureInPictureType()) { auto* frame_view = static_cast<PictureInPictureBrowserFrameView*>( browser_view->browser_widget()->GetFrameView()); - return {frame_view->GetLocationIconView(), - frame_view->GetLocationIconView(), views::BubbleBorder::TOP_LEFT}; + return {frame_view->GetLocationIconView(), kLocationIconElementId, + views::BubbleBorder::TOP_LEFT}; } if (anchor == Anchor::kCustomTabBar && browser_view->toolbar()->custom_tab_bar()) { - return {browser_view->toolbar()->custom_tab_bar(), - browser_view->toolbar()->custom_tab_bar()->location_icon_view(), + return {browser_view->toolbar()->custom_tab_bar(), kLocationIconElementId, views::BubbleBorder::TOP_LEFT}; } @@ -86,7 +87,8 @@ return {}; } - return {app_menu_button, app_menu_button, views::BubbleBorder::TOP_RIGHT}; + return {app_menu_button, kToolbarAppMenuButtonElementId, + views::BubbleBorder::TOP_RIGHT}; } AnchorConfiguration GetPermissionPromptBubbleAnchorConfiguration( @@ -97,7 +99,7 @@ ->GetChipController() ->IsPermissionPromptChipVisible()) { return {browser_view->GetLocationBar()->GetAnchorOrNull(), - browser_view->GetLocationBar()->GetChipController()->chip(), + PermissionChipView::kPermissionRequestChipElementId, views::BubbleBorder::TOP_LEFT}; } return GetPageInfoAnchorConfiguration(browser);
diff --git a/chrome/browser/ui/views/bubble_anchor_util_views.h b/chrome/browser/ui/views/bubble_anchor_util_views.h index a872e84..e1484f4c 100644 --- a/chrome/browser/ui/views/bubble_anchor_util_views.h +++ b/chrome/browser/ui/views/bubble_anchor_util_views.h
@@ -5,15 +5,12 @@ #ifndef CHROME_BROWSER_UI_VIEWS_BUBBLE_ANCHOR_UTIL_VIEWS_H_ #define CHROME_BROWSER_UI_VIEWS_BUBBLE_ANCHOR_UTIL_VIEWS_H_ -#include "base/memory/raw_ptr.h" +#include <optional> + #include "chrome/browser/ui/bubble_anchor_util.h" #include "ui/views/bubble/bubble_border.h" #include "ui/views/bubble/bubble_dialog_delegate_view.h" -namespace views { -class Button; -} // namespace views - class Browser; namespace bubble_anchor_util { @@ -22,8 +19,8 @@ // The bubble anchor. views::BubbleAnchor anchor = nullptr; - // The view to be highlighted, or null if it should not be used. - raw_ptr<views::Button, DanglingUntriaged> highlighted_button = nullptr; + // The element to be highlighted, or nullopt if it should not be used. + std::optional<ui::ElementIdentifier> highlighted_element; // The arrow position for the bubble. views::BubbleBorder::Arrow bubble_arrow = views::BubbleBorder::TOP_LEFT;
diff --git a/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc b/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc index 4b24ea03..063bd350 100644 --- a/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc +++ b/chrome/browser/ui/views/desktop_capture/desktop_media_picker_views.cc
@@ -490,11 +490,9 @@ SetModalType(params.modality); int message_id = IDS_DESKTOP_MEDIA_PICKER_SHARE; -#if BUILDFLAG(ENABLE_GLIC) if (request_source_ == RequestSource::kGlic) { message_id = IDS_GLIC_SCREEN_PICKER_CTA; } -#endif SetButtonLabel(ui::mojom::DialogButton::kOk, l10n_util::GetStringUTF16(message_id)); SetButtonStyle(ui::mojom::DialogButton::kCancel, ui::ButtonStyle::kTonal); @@ -664,12 +662,10 @@ } } -#if BUILDFLAG(ENABLE_GLIC) if (request_source_ == RequestSource::kGlic) { description_label_->SetText( l10n_util::GetStringUTF16(IDS_GLIC_SCREEN_PICKER_DESCRIPTION)); } -#endif DCHECK(!categories_.empty()); @@ -1113,11 +1109,9 @@ return l10n_util::GetStringFUTF16(IDS_DISPLAY_MEDIA_PICKER_TITLE, app_name_); } -#if BUILDFLAG(ENABLE_GLIC) if (request_source_ == RequestSource::kGlic) { return l10n_util::GetStringUTF16(IDS_GLIC_SCREEN_PICKER_HEADLINE); } -#endif int title_id = IDS_DESKTOP_MEDIA_PICKER_TITLE;
diff --git a/chrome/browser/ui/views/file_system_access/file_system_access_restore_permission_bubble_view.cc b/chrome/browser/ui/views/file_system_access/file_system_access_restore_permission_bubble_view.cc index e48c554..7cc120ec 100644 --- a/chrome/browser/ui/views/file_system_access/file_system_access_restore_permission_bubble_view.cc +++ b/chrome/browser/ui/views/file_system_access/file_system_access_restore_permission_bubble_view.cc
@@ -167,7 +167,9 @@ auto configuration = bubble_anchor_util::GetPageInfoAnchorConfiguration(browser); SetAnchor(configuration.anchor); - SetHighlightedButton(configuration.highlighted_button); + if (configuration.highlighted_element) { + SetHighlightedElement(*configuration.highlighted_element); + } if (std::holds_alternative<std::nullptr_t>(configuration.anchor)) { SetAnchorRect(bubble_anchor_util::GetPageInfoAnchorRect(browser)); }
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc index 2171c25a..7dbeecd3 100644 --- a/chrome/browser/ui/views/frame/browser_view.cc +++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -167,6 +167,7 @@ #include "chrome/browser/ui/views/frame/vertical_tab_strip_region_view.h" #include "chrome/browser/ui/views/frame/web_contents_close_handler.h" #include "chrome/browser/ui/views/fullscreen_control/fullscreen_control_host.h" +#include "chrome/browser/ui/views/glic/glic_button_interface.h" #include "chrome/browser/ui/views/global_media_controls/media_toolbar_button_view.h" #include "chrome/browser/ui/views/hats/hats_next_web_dialog.h" #include "chrome/browser/ui/views/incognito_clear_browsing_data_dialog_coordinator.h" @@ -391,9 +392,6 @@ #undef LoadAccelerators #endif -#if BUILDFLAG(ENABLE_GLIC) -#include "chrome/browser/ui/views/glic/glic_button_interface.h" -#endif // BUILDFLAG(ENABLE_GLIC) #if BUILDFLAG(ENABLE_WEBUI_TAB_STRIP) #include "chrome/browser/ui/views/frame/webui_tab_strip_container_view.h" @@ -1291,7 +1289,6 @@ return horizontal_tab_strip_region_view_.get(); } -#if BUILDFLAG(ENABLE_GLIC) views::LabelButton* BrowserView::GetGlicButton() { auto* controller = tabs::VerticalTabStripStateController::From(browser_); if (vertical_tab_strip_region_view_ && controller && @@ -1303,7 +1300,6 @@ return horizontal_tab_strip_region_view_->GetGlicButton(); } -#endif // BUILDFLAG(ENABLE_GLIC) TabSearchBubbleHost* BrowserView::GetTabSearchBubbleHost() { return tab_search_bubble_host_.get();
diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h index eefe7e48..82e20b9 100644 --- a/chrome/browser/ui/views/frame/browser_view.h +++ b/chrome/browser/ui/views/frame/browser_view.h
@@ -330,9 +330,7 @@ return weak_ptr_factory_.GetWeakPtr(); } -#if BUILDFLAG(ENABLE_GLIC) views::LabelButton* GetGlicButton(); -#endif // BUILDFLAG(ENABLE_GLIC) // Accessor for the BrowserView's TabSearchBubbleHost instance. TabSearchBubbleHost* GetTabSearchBubbleHost();
diff --git a/chrome/browser/ui/views/frame/browser_view_ash.cc b/chrome/browser/ui/views/frame/browser_view_ash.cc index f1779742..ad1ee40 100644 --- a/chrome/browser/ui/views/frame/browser_view_ash.cc +++ b/chrome/browser/ui/views/frame/browser_view_ash.cc
@@ -7,6 +7,7 @@ #include <algorithm> #include "base/check.h" +#include "chrome/browser/glic/browser_ui/context_sharing_border_view.h" #include "chrome/browser/ui/sad_tab_controller.h" #include "chrome/browser/ui/sad_tab_helper.h" #include "chrome/browser/ui/ui_features.h" @@ -21,10 +22,6 @@ #include "ui/gfx/geometry/rounded_corners_f.h" #include "ui/views/controls/webview/webview.h" -#if BUILDFLAG(ENABLE_GLIC) -#include "chrome/browser/glic/browser_ui/context_sharing_border_view.h" -#endif // BUILDFLAG(ENABLE_GLIC) - namespace { void SetRoundedCornersOnHost(views::NativeViewHost* host, @@ -199,12 +196,10 @@ contents_webview->SetBackgroundRadii(contents_webview_radii); } -#if BUILDFLAG(ENABLE_GLIC) if (auto* glic_border = GetActiveContentsContainerView()->glic_border_view(); glic_border) { glic_border->SetRoundedCorners(contents_webview_radii); } -#endif // BUILDFLAG(ENABLE_GLIC) const gfx::RoundedCornersF contents_scrim_radii( round_content_webview_top_corner ? window_radii.upper_left() : 0,
diff --git a/chrome/browser/ui/views/frame/contents_container_view.cc b/chrome/browser/ui/views/frame/contents_container_view.cc index 290f565d..10246ce 100644 --- a/chrome/browser/ui/views/frame/contents_container_view.cc +++ b/chrome/browser/ui/views/frame/contents_container_view.cc
@@ -10,6 +10,9 @@ #include "chrome/browser/actor/ui/actor_overlay_web_view.h" #include "chrome/browser/devtools/devtools_contents_resizing_strategy.h" #include "chrome/browser/enterprise/watermark/watermark_view.h" +#include "chrome/browser/glic/browser_ui/context_sharing_border_view.h" +#include "chrome/browser/glic/browser_ui/context_sharing_border_view_controller_impl.h" +#include "chrome/browser/glic/public/glic_enabling.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser_element_identifiers.h" #include "chrome/browser/ui/color/chrome_color_id.h" @@ -44,12 +47,6 @@ #include "ui/views/view_class_properties.h" #include "ui/views/widget/widget.h" -#if BUILDFLAG(ENABLE_GLIC) -#include "chrome/browser/glic/browser_ui/context_sharing_border_view.h" -#include "chrome/browser/glic/browser_ui/context_sharing_border_view_controller_impl.h" -#include "chrome/browser/glic/public/glic_enabling.h" -#endif - #if BUILDFLAG(IS_WIN) #include "ui/views/widget/native_widget_aura.h" #endif @@ -121,7 +118,6 @@ actor_overlay_web_view_ = AddChildView(std::move(actor_overlay_web_view)); } -#if BUILDFLAG(ENABLE_GLIC) if (base::FeatureList::IsEnabled(features::kGlicRegionSelectionNew)) { auto glic_selection_overlay_view = std::make_unique<views::WebView>(); glic_selection_overlay_view->SetProperty( @@ -144,7 +140,6 @@ .SetCanProcessEventsWithinSubtree(false) .Build()); } -#endif mini_toolbar_ = AddChildView(std::make_unique<MultiContentsViewMiniToolbar>( browser_view, contents_view_)); @@ -279,7 +274,6 @@ actor_overlay_web_view_->holder()->SetCornerRadii(radii); } -#if BUILDFLAG(ENABLE_GLIC) if (glic_selection_overlay_view_) { glic_selection_overlay_view_->holder()->SetCornerRadii(radii); } @@ -287,7 +281,6 @@ if (glic_border_) { glic_border_->SetRoundedCorners(content_rounded_corners); } -#endif } void ContentsContainerView::ClearBorderRoundedCorners() { @@ -309,7 +302,6 @@ actor_overlay_web_view_->holder()->SetCornerRadii(kNoRoundedCorners); } -#if BUILDFLAG(ENABLE_GLIC) if (glic_selection_overlay_view_) { glic_selection_overlay_view_->holder()->SetCornerRadii(kNoRoundedCorners); } @@ -317,7 +309,6 @@ if (glic_border_) { glic_border_->SetRoundedCorners(kNoRoundedCorners); } -#endif } void ContentsContainerView::ChildVisibilityChanged(View* child) { @@ -602,14 +593,12 @@ layouts.child_layouts.emplace_back( contents_view_.get(), contents_view_->GetVisible(), contents_rect); -#if BUILDFLAG(ENABLE_GLIC) if (glic_border_) { // |glic_border_| should not be seen over devtools. layouts.child_layouts.emplace_back(glic_border_.get(), glic_border_->GetVisible(), non_devtools_contents_bounds); } -#endif // The content scrim view should cover the entire contents bounds. CHECK(contents_scrim_view_); @@ -629,14 +618,12 @@ non_devtools_contents_bounds, size_bounds); } -#if BUILDFLAG(ENABLE_GLIC) if (glic_selection_overlay_view_) { layouts.child_layouts.emplace_back( glic_selection_overlay_view_.get(), glic_selection_overlay_view_->GetVisible(), non_devtools_contents_bounds, size_bounds); } -#endif // Reading Mode overlay view bounds are the same as the contents view. if (features::IsImmersiveReadAnythingEnabled() &&
diff --git a/chrome/browser/ui/views/frame/horizontal_tab_strip_region_view.cc b/chrome/browser/ui/views/frame/horizontal_tab_strip_region_view.cc index 467c0bb..7e1a0778 100644 --- a/chrome/browser/ui/views/frame/horizontal_tab_strip_region_view.cc +++ b/chrome/browser/ui/views/frame/horizontal_tab_strip_region_view.cc
@@ -583,11 +583,9 @@ return nullptr; } -#if BUILDFLAG(ENABLE_GLIC) views::LabelButton* HorizontalTabStripRegionView::GetGlicButton() { return tab_strip_action_container_->GetGlicButton(); } -#endif // BUILDFLAG(ENABLE_GLIC) void HorizontalTabStripRegionView::InitializeTabStrip() { if (tab_strip_set_) {
diff --git a/chrome/browser/ui/views/frame/horizontal_tab_strip_region_view.h b/chrome/browser/ui/views/frame/horizontal_tab_strip_region_view.h index dd70454..d3f455b7c 100644 --- a/chrome/browser/ui/views/frame/horizontal_tab_strip_region_view.h +++ b/chrome/browser/ui/views/frame/horizontal_tab_strip_region_view.h
@@ -101,9 +101,7 @@ TabStripFlatEdgeButton* GetTabSearchButton(); TabStripComboButton* GetComboButton() { return combo_button_; } -#if BUILDFLAG(ENABLE_GLIC) views::LabelButton* GetGlicButton(); -#endif // BUILDFLAG(ENABLE_GLIC) // TabStripRegionView: void InitializeTabStrip() override;
diff --git a/chrome/browser/ui/views/frame/horizontal_tab_strip_region_view_browsertest.cc b/chrome/browser/ui/views/frame/horizontal_tab_strip_region_view_browsertest.cc index 20a7922..e661f6da 100644 --- a/chrome/browser/ui/views/frame/horizontal_tab_strip_region_view_browsertest.cc +++ b/chrome/browser/ui/views/frame/horizontal_tab_strip_region_view_browsertest.cc
@@ -312,9 +312,7 @@ public: void SetUpCommandLine(base::CommandLine* command_line) override { scoped_feature_list_.InitWithFeaturesAndParameters({}, { -#if BUILDFLAG(ENABLE_GLIC) features::kGlic -#endif }); HorizontalTabStripRegionViewTest::SetUpCommandLine(command_line); }
diff --git a/chrome/browser/ui/views/frame/immersive_mode_controller_mac.mm b/chrome/browser/ui/views/frame/immersive_mode_controller_mac.mm index 4831fc5..61577354 100644 --- a/chrome/browser/ui/views/frame/immersive_mode_controller_mac.mm +++ b/chrome/browser/ui/views/frame/immersive_mode_controller_mac.mm
@@ -13,6 +13,7 @@ #include "base/auto_reset.h" #include "base/check.h" #include "base/feature_list.h" +#include "chrome/browser/glic/widget/glic_widget.h" #include "chrome/browser/ui/browser_window/public/browser_window_features.h" #include "chrome/browser/ui/browser_window/public/browser_window_interface.h" #include "chrome/browser/ui/find_bar/find_bar.h" @@ -34,10 +35,6 @@ #include "ui/views/focus/focus_search.h" #include "ui/views/widget/native_widget.h" -#if BUILDFLAG(ENABLE_GLIC) -#include "chrome/browser/glic/widget/glic_widget.h" -#endif - namespace { // This FocusSearch connects BrowserView, the overlay widget and the tab @@ -439,9 +436,7 @@ child->GetNativeWindowProperty(views::kWidgetIdentifierKey); if (widget_identifier == constrained_window::kConstrainedWindowWidgetIdentifier -#if BUILDFLAG(ENABLE_GLIC) || widget_identifier == glic::kGlicWidgetIdentifier -#endif ) { return true; }
diff --git a/chrome/browser/ui/views/frame/system_menu_model_builder.cc b/chrome/browser/ui/views/frame/system_menu_model_builder.cc index 0d694e9c..c162bdd 100644 --- a/chrome/browser/ui/views/frame/system_menu_model_builder.cc +++ b/chrome/browser/ui/views/frame/system_menu_model_builder.cc
@@ -9,6 +9,7 @@ #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "chrome/app/chrome_command_ids.h" +#include "chrome/browser/glic/public/glic_enabling.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/browser_window/public/browser_window_features.h" @@ -50,10 +51,6 @@ #include "ui/ozone/public/ozone_platform.h" #endif -#if BUILDFLAG(ENABLE_GLIC) -#include "chrome/browser/glic/public/glic_enabling.h" -#endif - DEFINE_CLASS_ELEMENT_IDENTIFIER_VALUE(SystemMenuModelBuilder, kToggleVerticalTabsElementId); @@ -103,7 +100,6 @@ model->AddItemWithStringId(IDC_BOOKMARK_ALL_TABS, IDS_BOOKMARK_ALL_TABS); model->AddItemWithStringId(IDC_NAME_WINDOW, IDS_NAME_WINDOW); -#if BUILDFLAG(ENABLE_GLIC) #if BUILDFLAG(IS_WIN) // On Windows we can not remove an item when showing the menu. So only add // the glic toggle option if glic is enabled when building the menu. @@ -114,7 +110,6 @@ #if BUILDFLAG(IS_WIN) } #endif // BUILDFLAG(IS_WIN) -#endif // BUILDFLAG(ENABLE_GLIC) if (auto* controller = tabs::VerticalTabStripStateController::From(browser())) {
diff --git a/chrome/browser/ui/views/frame/system_menu_model_builder_browsertest.cc b/chrome/browser/ui/views/frame/system_menu_model_builder_browsertest.cc index 2e3be85..fb4db269 100644 --- a/chrome/browser/ui/views/frame/system_menu_model_builder_browsertest.cc +++ b/chrome/browser/ui/views/frame/system_menu_model_builder_browsertest.cc
@@ -3,7 +3,9 @@ // found in the LICENSE file. #include "chrome/app/chrome_command_ids.h" +#include "chrome/browser/glic/glic_pref_names.h" #include "chrome/browser/glic/test_support/glic_test_environment.h" +#include "chrome/browser/glic/test_support/glic_test_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/ui_features.h" @@ -16,12 +18,6 @@ #include "ui/base/l10n/l10n_util.h" #include "ui/base/models/menu_model.h" -#if BUILDFLAG(ENABLE_GLIC) -#include "chrome/browser/glic/glic_pref_names.h" -#include "chrome/browser/glic/test_support/glic_test_util.h" -#endif - -#if BUILDFLAG(ENABLE_GLIC) namespace { // Returns true if there exists a visible command with specified id and // (optionally) label in the given menu. False otherwise. @@ -71,4 +67,3 @@ profile_prefs->SetBoolean(glic::prefs::kGlicPinnedToTabstrip, true); EXPECT_TRUE(ContainsCommand(menu, IDC_GLIC_TOGGLE_PIN, IDS_GLIC_UNPIN)); } -#endif
diff --git a/chrome/browser/ui/views/frame/system_menu_model_delegate.cc b/chrome/browser/ui/views/frame/system_menu_model_delegate.cc index dcd4218..46718fe 100644 --- a/chrome/browser/ui/views/frame/system_menu_model_delegate.cc +++ b/chrome/browser/ui/views/frame/system_menu_model_delegate.cc
@@ -8,6 +8,8 @@ #include "build/build_config.h" #include "chrome/app/chrome_command_ids.h" #include "chrome/browser/command_updater.h" +#include "chrome/browser/glic/glic_pref_names.h" +#include "chrome/browser/glic/public/glic_enabling.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sessions/tab_restore_service_factory.h" #include "chrome/browser/ui/browser_commands.h" @@ -29,11 +31,6 @@ #include "chrome/common/pref_names.h" #endif -#if BUILDFLAG(ENABLE_GLIC) -#include "chrome/browser/glic/glic_pref_names.h" -#include "chrome/browser/glic/public/glic_enabling.h" -#endif - SystemMenuModelDelegate::SystemMenuModelDelegate( ui::AcceleratorProvider* provider, Browser* browser) @@ -57,12 +54,10 @@ return chromeos::MoveToDesksMenuDelegate::ShouldShowMoveToDesksMenu(); } #endif -#if BUILDFLAG(ENABLE_GLIC) // Disable the glic toggle pin if it is showing and glic is not enabled. if (command_id == IDC_GLIC_TOGGLE_PIN) { return glic::GlicEnabling::IsEnabledForProfile(browser_->profile()); } -#endif return chrome::IsCommandEnabled(browser_, command_id); } @@ -81,11 +76,9 @@ return chromeos::MoveToDesksMenuDelegate::ShouldShowMoveToDesksMenu(); } #endif -#if BUILDFLAG(ENABLE_GLIC) if (command_id == IDC_GLIC_TOGGLE_PIN) { return glic::GlicEnabling::IsEnabledForProfile(browser_->profile()); } -#endif return true; } @@ -133,14 +126,12 @@ : IDS_SWITCH_TO_VERTICAL_TAB; break; } -#if BUILDFLAG(ENABLE_GLIC) case IDC_GLIC_TOGGLE_PIN: string_id = browser_->profile()->GetPrefs()->GetBoolean( glic::prefs::kGlicPinnedToTabstrip) ? IDS_GLIC_UNPIN : IDS_GLIC_PIN; break; -#endif default: NOTREACHED(); }
diff --git a/chrome/browser/ui/views/frame/vertical_tab_strip_region_view_browsertest.cc b/chrome/browser/ui/views/frame/vertical_tab_strip_region_view_browsertest.cc index 777bac6..24aa30d 100644 --- a/chrome/browser/ui/views/frame/vertical_tab_strip_region_view_browsertest.cc +++ b/chrome/browser/ui/views/frame/vertical_tab_strip_region_view_browsertest.cc
@@ -208,7 +208,7 @@ } // TODO(https://crbug.com/481074869): Re-enable this test -#if BUILDFLAG(IS_WIN) || BUILDFLAG(ENABLE_GLIC) +#if BUILDFLAG(IS_WIN) #define MAYBE_ResizeViewBigger DISABLED_ResizeViewBigger #else #define MAYBE_ResizeViewBigger ResizeViewBigger
diff --git a/chrome/browser/ui/views/glic/BUILD.gn b/chrome/browser/ui/views/glic/BUILD.gn index 8ae9c60..94242389 100644 --- a/chrome/browser/ui/views/glic/BUILD.gn +++ b/chrome/browser/ui/views/glic/BUILD.gn
@@ -4,8 +4,6 @@ import("//chrome/common/features.gni") -assert(enable_glic) - source_set("glic") { sources = []
diff --git a/chrome/browser/ui/views/glic/glic_button.h b/chrome/browser/ui/views/glic/glic_button.h index ed741c3..69711f8 100644 --- a/chrome/browser/ui/views/glic/glic_button.h +++ b/chrome/browser/ui/views/glic/glic_button.h
@@ -17,6 +17,7 @@ #include "chrome/browser/background/glic/glic_launcher_configuration.h" #include "chrome/browser/glic/browser_ui/glic_button_controller_delegate.h" #include "chrome/browser/glic/browser_ui/glic_vector_icon_manager.h" +#include "chrome/browser/glic/fre/glic_fre.mojom.h" #include "chrome/browser/glic/glic_pref_names.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser_element_identifiers.h" @@ -46,10 +47,6 @@ #include "ui/views/layout/fill_layout.h" #include "ui/views/view_class_properties.h" -#if BUILDFLAG(ENABLE_GLIC) -#include "chrome/browser/glic/fre/glic_fre.mojom.h" -#endif // BUILDFLAG(ENABLE_GLIC) - class BrowserWindowInterface; class Profile; class TabStripNudgeButton; @@ -1004,9 +1001,7 @@ views::View* highlight_view() { return highlight_view_; } WidthState width_state() { return width_state_; } -#if BUILDFLAG(ENABLE_GLIC) virtual void OnLabelVisibilityChanged() {} -#endif // BUILDFLAG(ENABLE_GLIC) static bool EntrypointVariationsEnabled() { return base::FeatureList::IsEnabled(features::kGlicEntrypointVariations);
diff --git a/chrome/browser/ui/views/location_bar/BUILD.gn b/chrome/browser/ui/views/location_bar/BUILD.gn index 128ce5a..05d55fc 100644 --- a/chrome/browser/ui/views/location_bar/BUILD.gn +++ b/chrome/browser/ui/views/location_bar/BUILD.gn
@@ -107,6 +107,7 @@ "//chrome/browser/contextual_search", "//chrome/browser/extensions", "//chrome/browser/extensions/api/omnibox", + "//chrome/browser/glic", "//chrome/browser/page_info", "//chrome/browser/search_engines", "//chrome/browser/sharing_hub", @@ -171,10 +172,6 @@ "//ui/native_theme", "//ui/strings:ui_strings_grit", ] - - if (enable_glic) { - deps += [ "//chrome/browser/glic" ] - } } }
diff --git a/chrome/browser/ui/views/location_bar/lens_overlay_homework_page_action_controller.cc b/chrome/browser/ui/views/location_bar/lens_overlay_homework_page_action_controller.cc index 382c8c2..58f732a 100644 --- a/chrome/browser/ui/views/location_bar/lens_overlay_homework_page_action_controller.cc +++ b/chrome/browser/ui/views/location_bar/lens_overlay_homework_page_action_controller.cc
@@ -124,13 +124,11 @@ return false; } -#if BUILDFLAG(ENABLE_GLIC) if (lens::features::IsLensOverlayEduActionChipDisabledByGlic() && glic::GlicEnabling::IsEligibleForGlicTieredRollout( base::to_address(profile_))) { return false; } -#endif // BUILDFLAG(ENABLE_GLIC) // Hide the homework chip if the broader lens feature is disabled. const auto* lens_overlay_entry_point_controller =
diff --git a/chrome/browser/ui/views/location_bar/lens_overlay_homework_page_action_icon_view.cc b/chrome/browser/ui/views/location_bar/lens_overlay_homework_page_action_icon_view.cc index 4679481..04f0f7eb 100644 --- a/chrome/browser/ui/views/location_bar/lens_overlay_homework_page_action_icon_view.cc +++ b/chrome/browser/ui/views/location_bar/lens_overlay_homework_page_action_icon_view.cc
@@ -5,6 +5,7 @@ #include "chrome/browser/ui/views/location_bar/lens_overlay_homework_page_action_icon_view.h" #include "build/branding_buildflags.h" +#include "chrome/browser/glic/public/glic_enabling.h" #include "chrome/browser/lens/region_search/lens_region_search_controller.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/search/search.h" @@ -31,10 +32,6 @@ #include "ui/views/accessibility/view_accessibility.h" #include "ui/views/view_class_properties.h" -#if BUILDFLAG(ENABLE_GLIC) -#include "chrome/browser/glic/public/glic_enabling.h" -#endif // BUILDFLAG(ENABLE_GLIC) - LensOverlayHomeworkPageActionIconView::LensOverlayHomeworkPageActionIconView( IconLabelBubbleView::Delegate* parent_delegate, Delegate* delegate, @@ -90,13 +87,11 @@ return false; } -#if BUILDFLAG(ENABLE_GLIC) if (lens::features::IsLensOverlayEduActionChipDisabledByGlic() && glic::GlicEnabling::IsEligibleForGlicTieredRollout( browser_->GetProfile())) { return false; } -#endif // BUILDFLAG(ENABLE_GLIC) // Hide the homework chip if the broader lens feature is disabled. const auto* controller =
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.cc b/chrome/browser/ui/views/location_bar/location_bar_view.cc index 1ffd655..69d1468 100644 --- a/chrome/browser/ui/views/location_bar/location_bar_view.cc +++ b/chrome/browser/ui/views/location_bar/location_bar_view.cc
@@ -1242,7 +1242,8 @@ LocationBarView::GetChipAnchor() { auto* chip = GetChipController()->chip(); if (chip->GetVisible()) { - return {{chip, chip, views::BubbleBorder::TOP_LEFT}}; + return {{chip, PermissionChipView::kPermissionRequestChipElementId, + views::BubbleBorder::TOP_LEFT}}; } return std::nullopt; }
diff --git a/chrome/browser/ui/views/location_bar/webui_location_bar.cc b/chrome/browser/ui/views/location_bar/webui_location_bar.cc index 3f41304..1b3c7a5dc 100644 --- a/chrome/browser/ui/views/location_bar/webui_location_bar.cc +++ b/chrome/browser/ui/views/location_bar/webui_location_bar.cc
@@ -96,7 +96,7 @@ std::optional<bubble_anchor_util::AnchorConfiguration> WebUILocationBar::GetChipAnchor() { NOTIMPLEMENTED(); - return {{nullptr, nullptr, views::BubbleBorder::TOP_LEFT}}; + return {{nullptr, std::nullopt, views::BubbleBorder::TOP_LEFT}}; } ui::TrackedElement* WebUILocationBar::GetAnchorOrNull() {
diff --git a/chrome/browser/ui/views/omnibox/omnibox_context_menu.cc b/chrome/browser/ui/views/omnibox/omnibox_context_menu.cc index 2725ed91..cdf0800b 100644 --- a/chrome/browser/ui/views/omnibox/omnibox_context_menu.cc +++ b/chrome/browser/ui/views/omnibox/omnibox_context_menu.cc
@@ -9,11 +9,15 @@ #include "base/functional/callback_forward.h" #include "base/memory/raw_ptr.h" #include "build/build_config.h" +#include "chrome/browser/ui/browser_window/public/browser_window_features.h" #include "chrome/browser/ui/browser_window/public/browser_window_interface.h" #include "chrome/browser/ui/omnibox/omnibox_context_menu_controller.h" +#include "chrome/browser/ui/tabs/glic_nudge_controller.h" #include "chrome/browser/ui/webui/top_chrome/top_chrome_web_ui_controller.h" +#include "chrome/browser/ui/webui/webui_embedding_context.h" #include "chrome/common/buildflags.h" #include "components/favicon_base/favicon_types.h" +#include "components/tabs/public/tab_interface.h" #include "content/public/browser/web_contents.h" #include "ui/menus/simple_menu_model.h" #include "ui/views/controls/menu/menu_item_view.h" @@ -21,13 +25,6 @@ #include "ui/views/controls/menu/menu_runner.h" #include "ui/views/controls/menu/submenu_view.h" -#if BUILDFLAG(ENABLE_GLIC) -#include "chrome/browser/ui/browser_window/public/browser_window_features.h" -#include "chrome/browser/ui/tabs/glic_nudge_controller.h" -#include "chrome/browser/ui/webui/webui_embedding_context.h" -#include "components/tabs/public/tab_interface.h" -#endif - OmniboxContextMenu::OmniboxContextMenu(views::Widget* parent_widget, OmniboxPopupFileSelector* file_selector, content::WebContents* web_contents, @@ -71,7 +68,6 @@ gfx::Rect(point, gfx::Size()), views::MenuAnchorPosition::kTopLeft, source_type); -#if BUILDFLAG(ENABLE_GLIC) if (!web_contents_) { return; } @@ -97,7 +93,6 @@ std::nullopt, tabs::GlicNudgeActivity::kNudgeIgnoredOmniboxContextMenuInteraction, base::DoNothing()); -#endif } void OmniboxContextMenu::ExecuteCommand(int command_id, int event_flags) {
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc index 62576aa5..2e63d2e 100644 --- a/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc +++ b/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc
@@ -63,6 +63,7 @@ #include "ui/accessibility/accessibility_features.h" #include "ui/base/clipboard/clipboard.h" #include "ui/base/clipboard/scoped_clipboard_writer.h" +#include "ui/base/clipboard/test/clipboard_test_util.h" #include "ui/base/ime/input_method.h" #include "ui/base/ime/text_edit_commands.h" #include "ui/events/event_utils.h" @@ -1263,9 +1264,8 @@ /* data_dst = */ nullptr)); #endif - std::string read_from_clipboard; - clipboard->ReadAsciiText(clipboard_buffer, /* data_dst = */ nullptr, - &read_from_clipboard); + std::string read_from_clipboard = ui::clipboard_test_util::ReadAsciiText( + clipboard, clipboard_buffer, /* data_dst = */ nullptr); EXPECT_EQ("https://test.com/", read_from_clipboard); } @@ -1294,9 +1294,8 @@ clipboard_buffer, /* data_dst = */ nullptr)); - std::string read_from_clipboard; - clipboard->ReadAsciiText(clipboard_buffer, /* data_dst = */ nullptr, - &read_from_clipboard); + std::string read_from_clipboard = ui::clipboard_test_util::ReadAsciiText( + clipboard, clipboard_buffer, /* data_dst = */ nullptr); EXPECT_EQ("user text", read_from_clipboard); }
diff --git a/chrome/browser/ui/views/page_info/page_info_bubble_view.cc b/chrome/browser/ui/views/page_info/page_info_bubble_view.cc index 2e6879a..22224cc 100644 --- a/chrome/browser/ui/views/page_info/page_info_bubble_view.cc +++ b/chrome/browser/ui/views/page_info/page_info_bubble_view.cc
@@ -385,7 +385,9 @@ views::BubbleDialogDelegateView* const bubble = PageInfoBubbleView::CreatePageInfoBubble( page_info_bubble_builder.Build()); - bubble->SetHighlightedButton(configuration.highlighted_button); + if (configuration.highlighted_element) { + bubble->SetHighlightedElement(*configuration.highlighted_element); + } bubble->SetArrow(configuration.bubble_arrow); bubble->GetWidget()->Show(); }
diff --git a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view.cc b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view.cc index 6ec4376..b36d7aea 100644 --- a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view.cc +++ b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view.cc
@@ -248,7 +248,9 @@ configuration.anchor, anchor_rect, parent_view, web_contents, safety_tip_status, suggested_url, std::move(close_callback)); - bubble->SetHighlightedButton(configuration.highlighted_button); + if (configuration.highlighted_element) { + bubble->SetHighlightedElement(*configuration.highlighted_element); + } bubble->SetArrow(configuration.bubble_arrow); bubble->GetWidget()->Show(); }
diff --git a/chrome/browser/ui/views/passwords/password_bubble_view_test_base.cc b/chrome/browser/ui/views/passwords/password_bubble_view_test_base.cc index d6caa58..8646118 100644 --- a/chrome/browser/ui/views/passwords/password_bubble_view_test_base.cc +++ b/chrome/browser/ui/views/passwords/password_bubble_view_test_base.cc
@@ -74,7 +74,7 @@ void PasswordBubbleViewTestBase::CreateAnchorViewAndShow() { anchor_widget_ = - CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET, + CreateTestWidget(views::Widget::InitParams::CLIENT_OWNS_WIDGET, views::Widget::InitParams::TYPE_WINDOW); anchor_widget_->Show(); }
diff --git a/chrome/browser/ui/views/permissions/chooser_bubble_ui.cc b/chrome/browser/ui/views/permissions/chooser_bubble_ui.cc index d7661c7..53843a7e 100644 --- a/chrome/browser/ui/views/permissions/chooser_bubble_ui.cc +++ b/chrome/browser/ui/views/permissions/chooser_bubble_ui.cc
@@ -169,7 +169,9 @@ void ChooserBubbleUiViewDelegate::UpdateAnchor(Browser* browser) { AnchorConfiguration configuration = GetChooserAnchorConfiguration(browser); SetAnchor(configuration.anchor); - SetHighlightedButton(configuration.highlighted_button); + if (configuration.highlighted_element) { + SetHighlightedElement(*configuration.highlighted_element); + } if (std::holds_alternative<std::nullptr_t>(configuration.anchor)) { SetAnchorRect(GetChooserAnchorRect(browser)); }
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_base_view.cc b/chrome/browser/ui/views/permissions/permission_prompt_base_view.cc index 7af15d4b2..907ffdd 100644 --- a/chrome/browser/ui/views/permissions/permission_prompt_base_view.cc +++ b/chrome/browser/ui/views/permissions/permission_prompt_base_view.cc
@@ -147,7 +147,9 @@ set_parent_window( platform_util::GetViewForWindow(browser_->window()->GetNativeWindow())); } - SetHighlightedButton(configuration.highlighted_button); + if (configuration.highlighted_element) { + SetHighlightedElement(*configuration.highlighted_element); + } if (std::holds_alternative<std::nullptr_t>(configuration.anchor)) { SetAnchorRect(bubble_anchor_util::GetPageInfoAnchorRect(browser_)); }
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 214a515..57c2a93 100644 --- a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc +++ b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
@@ -467,27 +467,27 @@ // Browser extra part used to be notified early enough to track the // `ProfileManager` in `g_browser_process` before any profile creation. +// Uses `PreProfileInit()` to run after `GlobalBrowserCollection` is created. class ProfileManagerInitializationInterceptExtraParts : public ChromeBrowserMainExtraParts { public: explicit ProfileManagerInitializationInterceptExtraParts( - base::OnceClosure on_post_early_initialization_callback, + base::OnceClosure on_pre_profile_init_callback, base::OnceClosure on_post_main_message_loop_run_callback) - : on_post_early_initialization_callback_( - std::move(on_post_early_initialization_callback)), + : on_pre_profile_init_callback_(std::move(on_pre_profile_init_callback)), on_post_main_message_loop_run_callback_( std::move(on_post_main_message_loop_run_callback)) {} // ChromeBrowserMainExtraParts: - void PostEarlyInitialization() override { - std::move(on_post_early_initialization_callback_).Run(); + void PreProfileInit() override { + std::move(on_pre_profile_init_callback_).Run(); } void PostMainMessageLoopRun() override { std::move(on_post_main_message_loop_run_callback_).Run(); } private: - base::OnceClosure on_post_early_initialization_callback_; + base::OnceClosure on_pre_profile_init_callback_; base::OnceClosure on_post_main_message_loop_run_callback_; }; @@ -504,7 +504,7 @@ static_cast<ChromeBrowserMainParts*>(parts); chrome_browser_main_parts->AddParts( std::make_unique<ProfileManagerInitializationInterceptExtraParts>( - base::BindOnce(&ProfileManagementCounter::OnPostEarlyInitialization, + base::BindOnce(&ProfileManagementCounter::OnPreProfileInit, base::Unretained(this)), base::BindOnce(&ProfileManagementCounter::OnPostMainMessageLoopRun, base::Unretained(this)))); @@ -539,8 +539,8 @@ private: // Callbacks from `ProfileManagerInitializationInterceptExtraParts` to - // intercept ProfileManager initialization/descrution. - void OnPostEarlyInitialization() { + // intercept ProfileManager initialization/destruction. + void OnPreProfileInit() { CHECK(g_browser_process); ProfileManager* profile_manager = g_browser_process->profile_manager(); CHECK(profile_manager);
diff --git a/chrome/browser/ui/views/sad_tab_view.cc b/chrome/browser/ui/views/sad_tab_view.cc index 8301d71..7c48036 100644 --- a/chrome/browser/ui/views/sad_tab_view.cc +++ b/chrome/browser/ui/views/sad_tab_view.cc
@@ -15,7 +15,6 @@ #include "chrome/browser/ui/views/chrome_layout_provider.h" #include "chrome/browser/ui/views/chrome_typography.h" #include "chrome/browser/ui/views/frame/browser_view.h" -#include "content/public/browser/web_contents.h" #include "content/public/common/result_codes.h" #include "ui/accessibility/ax_enums.mojom.h" #include "ui/base/l10n/l10n_util.h" @@ -75,7 +74,6 @@ } // namespace SadTabView::SadTabView(SadTabController* controller, - content::WebContents* web_contents, SadTabKind kind, int title_id, int message_id, @@ -84,12 +82,7 @@ int error_code, int button_title_id, int help_link_title_id) - : controller_(controller), web_contents_(web_contents), kind_(kind) { - // This view gets inserted as a child of a WebView, but we don't want the - // WebView to delete us if the WebView gets deleted before the SadTabHelper - // does. - set_owned_by_client(OwnedByClientPassKey()); - + : controller_(controller), kind_(kind) { SetBackground(views::CreateSolidBackground(ui::kColorDialogBackground)); ChromeLayoutProvider* provider = ChromeLayoutProvider::Get(); auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>( @@ -193,38 +186,21 @@ // Needed to ensure this View is drawn even if a sibling (such as dev tools) // has a z-order. SetPaintToLayer(); - AttachToWebView(); - - if (owner_) { - // If the `owner_` ContentsWebView has a rounded background, the sad tab - // should also have matching rounded corners as well. - SetBackgroundRadii( - static_cast<ContentsWebView*>(owner_)->GetBackgroundRadii()); - } // Make the accessibility role of this view an alert dialog, and // put focus on the action button. This causes screen readers to // immediately announce the text of this view. GetViewAccessibility().SetRole(ax::mojom::Role::kDialog); +} + +SadTabView::~SadTabView() = default; + +void SadTabView::AddedToWidget() { if (action_button_->GetWidget() && action_button_->GetWidget()->IsActive()) { action_button_->RequestFocus(); } } -SadTabView::~SadTabView() { - if (owner_) { - owner_->SetCrashedOverlayView(nullptr); - } -} - -void SadTabView::ReinstallInWebView() { - if (owner_) { - owner_->SetCrashedOverlayView(nullptr); - owner_ = nullptr; - } - AttachToWebView(); -} - gfx::RoundedCornersF SadTabView::GetBackgroundRadii() const { CHECK(layer()); return layer()->rounded_corner_radii(); @@ -247,36 +223,6 @@ View::OnPaint(canvas); } -void SadTabView::RemovedFromWidget() { - owner_ = nullptr; -} - -void SadTabView::AttachToWebView() { - Browser* browser = chrome::FindBrowserWithTab(web_contents_); - // This can be null during prefetch. - if (!browser) { - return; - } - - // In unit tests, browser->window() might not be a real BrowserView. - if (!browser->window()->GetNativeWindow()) { - return; - } - - BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser); - DCHECK(browser_view); - - std::vector<ContentsWebView*> visible_contents_views = - browser_view->GetAllVisibleContentsWebViews(); - for (ContentsWebView* contents_view : visible_contents_views) { - if (contents_view->web_contents() == web_contents_) { - owner_ = contents_view; - owner_->SetCrashedOverlayView(this); - break; - } - } -} - void SadTabView::EnableHelpLink(views::FlexLayoutView* actions_container, int help_link_title_id) { #if BUILDFLAG(IS_CHROMEOS)
diff --git a/chrome/browser/ui/views/sad_tab_view.h b/chrome/browser/ui/views/sad_tab_view.h index 61b409c7..300f05e 100644 --- a/chrome/browser/ui/views/sad_tab_view.h +++ b/chrome/browser/ui/views/sad_tab_view.h
@@ -10,15 +10,10 @@ #include "ui/base/metadata/metadata_header_macros.h" #include "ui/views/view.h" -namespace content { -class WebContents; -} - namespace views { class FlexLayoutView; class Label; class MdTextButton; -class WebView; } // namespace views namespace test { @@ -44,7 +39,6 @@ public: SadTabView(SadTabController* controller, - content::WebContents* web_contents, SadTabKind kind, int title_id, int message_id, @@ -62,36 +56,28 @@ gfx::RoundedCornersF GetBackgroundRadii() const; void SetBackgroundRadii(const gfx::RoundedCornersF& radii); - void ReinstallInWebView(); - // Overridden from views::View: void OnBoundsChanged(const gfx::Rect& previous_bounds) override; + void AddedToWidget() override; protected: // Overridden from views::View: void OnPaint(gfx::Canvas* canvas) override; - void RemovedFromWidget() override; private: friend class test::SadTabViewTestApi; - // Set this View as the crashed overlay view for the WebView associated - // with this object's WebContents. - void AttachToWebView(); - // Enable help link if needed. void EnableHelpLink(views::FlexLayoutView* actions_container, int help_link_title_id); const raw_ptr<SadTabController> controller_; - const raw_ptr<content::WebContents> web_contents_; const SadTabKind kind_; bool painted_ = false; raw_ptr<views::Label> message_; std::vector<raw_ptr<views::Label, VectorExperimental>> bullet_labels_; raw_ptr<views::MdTextButton> action_button_; raw_ptr<views::Label> title_; - raw_ptr<views::WebView> owner_ = nullptr; }; #endif // CHROME_BROWSER_UI_VIEWS_SAD_TAB_VIEW_H_
diff --git a/chrome/browser/ui/views/side_panel/BUILD.gn b/chrome/browser/ui/views/side_panel/BUILD.gn index 77053173..cab77e1 100644 --- a/chrome/browser/ui/views/side_panel/BUILD.gn +++ b/chrome/browser/ui/views/side_panel/BUILD.gn
@@ -22,6 +22,10 @@ "extensions/extension_side_panel_manager.cc", "extensions/extension_side_panel_manager.h", "extensions/extension_side_panel_utils.cc", + "glic/glic_legacy_side_panel_coordinator.cc", + "glic/glic_legacy_side_panel_coordinator.h", + "glic/glic_side_panel_coordinator_impl.cc", + "glic/glic_side_panel_coordinator_impl.h", "history/history_side_panel_coordinator.cc", "history/history_side_panel_coordinator.h", "history_clusters/history_clusters_side_panel_controller.cc", @@ -63,14 +67,6 @@ "side_panel_web_ui_view.cc", "side_panel_web_ui_view.h", ] - if (enable_glic) { - sources += [ - "glic/glic_legacy_side_panel_coordinator.cc", - "glic/glic_legacy_side_panel_coordinator.h", - "glic/glic_side_panel_coordinator_impl.cc", - "glic/glic_side_panel_coordinator_impl.h", - ] - } public_deps = [ "//base", "//chrome/browser:browser_public_dependencies", @@ -115,6 +111,7 @@ "//chrome/app:generated_resources_grit", "//chrome/app/vector_icons", "//chrome/browser/favicon", + "//chrome/browser/glic", "//chrome/browser/media/webrtc", "//chrome/browser/profiles", "//chrome/browser/resources:component_extension_resources", @@ -188,10 +185,6 @@ deps += [ "//chrome/common/extensions/api" ] } - if (enable_glic) { - deps += [ "//chrome/browser/glic" ] - } - allow_circular_includes_from = [ "//chrome/browser/ui/views/toolbar", "//chrome/browser/ui/customize_chrome",
diff --git a/chrome/browser/ui/views/side_panel/side_panel_util.cc b/chrome/browser/ui/views/side_panel/side_panel_util.cc index dc5b156..ed0b708 100644 --- a/chrome/browser/ui/views/side_panel/side_panel_util.cc +++ b/chrome/browser/ui/views/side_panel/side_panel_util.cc
@@ -12,6 +12,7 @@ #include "base/notreached.h" #include "base/strings/strcat.h" #include "base/time/time.h" +#include "chrome/browser/glic/public/glic_enabling.h" #include "chrome/browser/history_clusters/history_clusters_service_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" @@ -23,6 +24,7 @@ #include "chrome/browser/ui/views/side_panel/bookmarks/bookmarks_side_panel_coordinator.h" #include "chrome/browser/ui/views/side_panel/comments/comments_side_panel_coordinator.h" #include "chrome/browser/ui/views/side_panel/extensions/extension_side_panel_manager.h" +#include "chrome/browser/ui/views/side_panel/glic/glic_legacy_side_panel_coordinator.h" #include "chrome/browser/ui/views/side_panel/history/history_side_panel_coordinator.h" #include "chrome/browser/ui/views/side_panel/history_clusters/history_clusters_side_panel_coordinator.h" #include "chrome/browser/ui/views/side_panel/reading_list/reading_list_side_panel_coordinator.h" @@ -35,11 +37,6 @@ #include "ui/accessibility/accessibility_features.h" #include "ui/actions/actions.h" -#if BUILDFLAG(ENABLE_GLIC) -#include "chrome/browser/glic/public/glic_enabling.h" -#include "chrome/browser/ui/views/side_panel/glic/glic_legacy_side_panel_coordinator.h" -#endif - namespace { std::string_view GetSidePanelNameFor(SidePanelEntry::PanelType panel_type) { @@ -110,7 +107,6 @@ ->comments_side_panel_coordinator() ->CreateAndRegisterEntry(window_registry); } -#if BUILDFLAG(ENABLE_GLIC) if (glic::GlicEnabling::IsEnabledForProfile(browser->profile()) && browser->is_type_normal() && !glic::GlicEnabling::IsMultiInstanceEnabled()) { @@ -118,7 +114,6 @@ ->glic_side_panel_coordinator() ->CreateAndRegisterEntry(browser, window_registry); } -#endif } SidePanelContentProxy* SidePanelUtil::GetSidePanelContentProxy(
diff --git a/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc index f68709b..d65acd47 100644 --- a/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc +++ b/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc
@@ -98,6 +98,7 @@ #include "ui/views/controls/native/native_view_host.h" #include "ui/views/test/widget_activation_waiter.h" #include "ui/views/view.h" +#include "ui/views/view_utils.h" #include "ui/views/widget/widget.h" #include "ui/views/window/dialog_delegate.h" @@ -4767,7 +4768,7 @@ bool WebContentsIsFastResized(BrowserWindowInterface* browser) { BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser); ContentsWebView* contents_web_view = - static_cast<ContentsWebView*>(browser_view->GetContentsView()); + views::AsViewClass<ContentsWebView>(browser_view->GetContentsView()); return contents_web_view->holder()->fast_resize(); }
diff --git a/chrome/browser/ui/views/tabs/glic/glic_button_browsertest.cc b/chrome/browser/ui/views/tabs/glic/glic_button_browsertest.cc index a9cb323..03d71b9 100644 --- a/chrome/browser/ui/views/tabs/glic/glic_button_browsertest.cc +++ b/chrome/browser/ui/views/tabs/glic/glic_button_browsertest.cc
@@ -28,6 +28,7 @@ #include "ui/base/l10n/l10n_util.h" #include "ui/base/mojom/menu_source_type.mojom-shared.h" #include "ui/events/event_constants.h" +#include "ui/views/view_utils.h" namespace glic { namespace { @@ -42,7 +43,7 @@ protected: glic::TabStripGlicButton* glic_button() { - return static_cast<glic::TabStripGlicButton*>( + return views::AsViewClass<glic::TabStripGlicButton>( glic::GlicButtonInterface::FromBrowser(browser())); }
diff --git a/chrome/browser/ui/views/tabs/glic/tab_strip_glic_button.cc b/chrome/browser/ui/views/tabs/glic/tab_strip_glic_button.cc index 43e5c1c..d7f5b4f 100644 --- a/chrome/browser/ui/views/tabs/glic/tab_strip_glic_button.cc +++ b/chrome/browser/ui/views/tabs/glic/tab_strip_glic_button.cc
@@ -44,6 +44,7 @@ #include "ui/views/layout/box_layout.h" #include "ui/views/layout/fill_layout.h" #include "ui/views/view_class_properties.h" +#include "ui/views/view_utils.h" namespace glic { @@ -285,7 +286,8 @@ UpdateIcon(); OnLabelVisibilityChanged(); - auto* image_view = static_cast<views::ImageView*>(image_container_view()); + auto* image_view = + views::AsViewClass<views::ImageView>(image_container_view()); image_view->SetImageSize({kIconSize, kIconSize}); image_view->SetPaintToLayer(); image_view->layer()->SetFillsBoundsOpaquely(false);
diff --git a/chrome/browser/ui/views/tabs/projects/projects_panel_recent_threads_view_unittest.cc b/chrome/browser/ui/views/tabs/projects/projects_panel_recent_threads_view_unittest.cc index 4b88b35..bf3ed95 100644 --- a/chrome/browser/ui/views/tabs/projects/projects_panel_recent_threads_view_unittest.cc +++ b/chrome/browser/ui/views/tabs/projects/projects_panel_recent_threads_view_unittest.cc
@@ -11,6 +11,7 @@ #include "testing/gmock/include/gmock/gmock.h" #include "ui/views/controls/label.h" #include "ui/views/test/views_test_base.h" +#include "ui/views/view_utils.h" namespace { @@ -40,7 +41,7 @@ } MATCHER_P(IsForThread, expected_thread, "") { - auto* label = static_cast<views::Label*>(arg->children()[2]); + auto* label = views::AsViewClass<views::Label>(arg->children()[2]); return base::UTF8ToUTF16(expected_thread.title) == label->GetText(); } @@ -70,7 +71,7 @@ // Verify that the labels are set correctly. for (size_t i = 0; i < threads.size(); ++i) { - auto* thread_item_view = static_cast<ProjectsPanelThreadItemView*>( + auto* thread_item_view = views::AsViewClass<ProjectsPanelThreadItemView>( recent_threads_view->children()[i]); EXPECT_THAT(thread_item_view, IsForThread(threads[i])); } @@ -89,7 +90,7 @@ EXPECT_EQ(2u, recent_threads_view->children().size()); for (size_t i = 0; i < new_threads.size(); ++i) { - auto* thread_item_view = static_cast<ProjectsPanelThreadItemView*>( + auto* thread_item_view = views::AsViewClass<ProjectsPanelThreadItemView>( recent_threads_view->children()[i]); EXPECT_THAT(thread_item_view, IsForThread(new_threads[i])); }
diff --git a/chrome/browser/ui/views/tabs/projects/projects_panel_tab_groups_view.cc b/chrome/browser/ui/views/tabs/projects/projects_panel_tab_groups_view.cc index 15bc748..5ceba17 100644 --- a/chrome/browser/ui/views/tabs/projects/projects_panel_tab_groups_view.cc +++ b/chrome/browser/ui/views/tabs/projects/projects_panel_tab_groups_view.cc
@@ -38,6 +38,7 @@ #include "ui/views/layout/box_layout.h" #include "ui/views/style/typography.h" #include "ui/views/view_class_properties.h" +#include "ui/views/view_utils.h" namespace { constexpr gfx::Insets kNoTabsInteriorMargins = gfx::Insets::VH(0, 8); @@ -271,7 +272,7 @@ views::View* sender, const gfx::Point& press_pt, ui::OSExchangeData* data) { - auto* item = static_cast<ProjectsPanelTabGroupsItemView*>(sender); + auto* item = views::AsViewClass<ProjectsPanelTabGroupsItemView>(sender); gfx::ImageSkia drag_image = item->GetDragImage(); if (!drag_image.isNull() && !drag_image.size().IsEmpty()) { data->provider().SetDragImage(drag_image, press_pt.OffsetFromOrigin());
diff --git a/chrome/browser/ui/views/tabs/projects/projects_panel_tab_groups_view_unittest.cc b/chrome/browser/ui/views/tabs/projects/projects_panel_tab_groups_view_unittest.cc index a511152c..180dae1 100644 --- a/chrome/browser/ui/views/tabs/projects/projects_panel_tab_groups_view_unittest.cc +++ b/chrome/browser/ui/views/tabs/projects/projects_panel_tab_groups_view_unittest.cc
@@ -30,6 +30,7 @@ #include "ui/views/controls/label.h" #include "ui/views/test/button_test_api.h" #include "ui/views/test/views_test_base.h" +#include "ui/views/view_utils.h" #include "ui/views/widget/widget.h" namespace { @@ -52,8 +53,8 @@ } MATCHER_P(IsForTabGroup, expected_tab_group, "") { - auto* label = - static_cast<ProjectsPanelTabGroupsItemView*>(arg)->title_for_testing(); + auto* label = views::AsViewClass<ProjectsPanelTabGroupsItemView>(arg) + ->title_for_testing(); return expected_tab_group.title() == label->GetText(); } @@ -103,7 +104,7 @@ // message. EXPECT_EQ(2u, tab_groups_view_->children().size()); // Index 0 is the "Create new tab group" button. - views::Label* no_tabs_label = static_cast<views::Label*>( + views::Label* no_tabs_label = views::AsViewClass<views::Label>( tab_groups_view_->no_tab_groups_view_for_testing()->children()[1]); EXPECT_EQ( u"Organize your tabs by grouping them together and label them with a "
diff --git a/chrome/browser/ui/views/tabs/projects/projects_panel_thread_item_view_unittest.cc b/chrome/browser/ui/views/tabs/projects/projects_panel_thread_item_view_unittest.cc index 1ee3d586..d40762fa 100644 --- a/chrome/browser/ui/views/tabs/projects/projects_panel_thread_item_view_unittest.cc +++ b/chrome/browser/ui/views/tabs/projects/projects_panel_thread_item_view_unittest.cc
@@ -13,6 +13,7 @@ #include "ui/views/controls/image_view.h" #include "ui/views/controls/label.h" #include "ui/views/test/views_test_base.h" +#include "ui/views/view_utils.h" namespace { @@ -39,7 +40,7 @@ ASSERT_EQ(3u, thread_item_view->children().size()); views::ImageView* image_view = - static_cast<views::ImageView*>(thread_item_view->children()[1]); + views::AsViewClass<views::ImageView>(thread_item_view->children()[1]); EXPECT_TRUE(image_view); // Check that the image view has the correct icon. @@ -53,7 +54,7 @@ image_view->GetImageModel()); views::Label* label = - static_cast<views::Label*>(thread_item_view->children()[2]); + views::AsViewClass<views::Label>(thread_item_view->children()[2]); EXPECT_TRUE(label); EXPECT_EQ(base::UTF8ToUTF16(thread.title), label->GetText()); }
diff --git a/chrome/browser/ui/views/tabs/recent_activity_bubble_dialog_view.cc b/chrome/browser/ui/views/tabs/recent_activity_bubble_dialog_view.cc index 33eec80..2d26cc23 100644 --- a/chrome/browser/ui/views/tabs/recent_activity_bubble_dialog_view.cc +++ b/chrome/browser/ui/views/tabs/recent_activity_bubble_dialog_view.cc
@@ -449,10 +449,10 @@ CHECK(n < (tab_activity_size + group_activity_size)); if (n < tab_activity_size) { - return static_cast<RecentActivityRowView*>( + return views::AsViewClass<RecentActivityRowView>( tab_activity_container()->children().at(n)); } - return static_cast<RecentActivityRowView*>( + return views::AsViewClass<RecentActivityRowView>( group_activity_container()->children().at(n - tab_activity_size)); }
diff --git a/chrome/browser/ui/views/tabs/shared/drop_arrow.cc b/chrome/browser/ui/views/tabs/shared/drop_arrow.cc index f5878a2..d3afe18 100644 --- a/chrome/browser/ui/views/tabs/shared/drop_arrow.cc +++ b/chrome/browser/ui/views/tabs/shared/drop_arrow.cc
@@ -15,17 +15,18 @@ namespace { -int GetDropArrowImageResourceId(bool is_down) { - return is_down ? IDR_TAB_DROP_DOWN : IDR_TAB_DROP_UP; +int GetDropArrowImageResourceId(DropArrow::Direction direction) { + return direction == DropArrow::Direction::kUp ? IDR_TAB_DROP_UP + : IDR_TAB_DROP_DOWN; } } // namespace DropArrow::DropArrow(const BrowserRootView::DropIndex& index, - bool point_down, - views::Widget* context) - : index_(index), point_down_(point_down) { - arrow_window_ = new views::Widget; + gfx::NativeWindow context, + BoundsCallback bounds_callback) + : index_(index), bounds_callback_(std::move(bounds_callback)) { + arrow_widget_ = new views::Widget; views::Widget::InitParams params( views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET, views::Widget::InitParams::TYPE_POPUP); @@ -33,21 +34,21 @@ params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent; params.accept_events = false; params.bounds = gfx::Rect(GetSize()); - params.context = context->GetNativeWindow(); - arrow_window_->Init(std::move(params)); + params.context = context; + arrow_widget_->Init(std::move(params)); arrow_view_ = - arrow_window_->SetContentsView(std::make_unique<views::ImageView>()); - arrow_view_->SetImage( - ui::ImageModel::FromResourceId(GetDropArrowImageResourceId(point_down_))); - scoped_observation_.Observe(arrow_window_.get()); + arrow_widget_->SetContentsView(std::make_unique<views::ImageView>()); + scoped_observation_.Observe(arrow_widget_.get()); - arrow_window_->Show(); + UpdateBounds(); + + arrow_widget_->Show(); } DropArrow::~DropArrow() { // Close eventually deletes the window, which deletes arrow_view too. - if (arrow_window_) { - arrow_window_->Close(); + if (arrow_widget_) { + arrow_widget_->Close(); } } @@ -57,28 +58,33 @@ // Direction doesn't matter, both images are the same size. const gfx::ImageSkia* drop_image = ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed( - GetDropArrowImageResourceId(true)); + GetDropArrowImageResourceId(Direction::kDown)); return gfx::Size(drop_image->width(), drop_image->height()); }(); return size; } -void DropArrow::SetPointDown(bool down) { - if (point_down_ == down) { - return; - } - - point_down_ = down; - arrow_view_->SetImage( - ui::ImageModel::FromResourceId(GetDropArrowImageResourceId(point_down_))); +void DropArrow::SetIndex(const BrowserRootView::DropIndex& index) { + index_ = index; + UpdateBounds(); } -void DropArrow::SetWindowBounds(const gfx::Rect& bounds) { - arrow_window_->SetBounds(bounds); +void DropArrow::UpdateBounds() { + Direction direction; + gfx::Rect drop_bounds = bounds_callback_.Run(index_, &direction); + + if (!direction_.has_value() || direction_ != direction) { + direction_ = direction; + arrow_view_->SetImage(ui::ImageModel::FromResourceId( + GetDropArrowImageResourceId(*direction_))); + } + + arrow_widget_->SetBounds(drop_bounds); } void DropArrow::OnWidgetDestroying(views::Widget* widget) { - DCHECK(scoped_observation_.IsObservingSource(arrow_window_.get())); + DCHECK(scoped_observation_.IsObservingSource(arrow_widget_.get())); scoped_observation_.Reset(); - arrow_window_ = nullptr; + arrow_view_ = nullptr; + arrow_widget_ = nullptr; }
diff --git a/chrome/browser/ui/views/tabs/shared/drop_arrow.h b/chrome/browser/ui/views/tabs/shared/drop_arrow.h index 36671a8..59721b3 100644 --- a/chrome/browser/ui/views/tabs/shared/drop_arrow.h +++ b/chrome/browser/ui/views/tabs/shared/drop_arrow.h
@@ -5,10 +5,14 @@ #ifndef CHROME_BROWSER_UI_VIEWS_TABS_SHARED_DROP_ARROW_H_ #define CHROME_BROWSER_UI_VIEWS_TABS_SHARED_DROP_ARROW_H_ +#include <optional> + +#include "base/functional/callback.h" #include "base/memory/raw_ptr.h" #include "base/scoped_observation.h" #include "chrome/browser/ui/views/frame/browser_root_view.h" #include "ui/gfx/geometry/rect.h" +#include "ui/gfx/native_ui_types.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_observer.h" @@ -17,42 +21,54 @@ } // Used during a drop session of a url. Tracks the position of the drop as -// well as a window used to highlight where the drop occurs. +// well as a widget used to highlight where the drop occurs. class DropArrow : public views::WidgetObserver { public: + // Used to represent the direction the arrow is pointing. + enum class Direction { + kDown, + kUp, + }; + + // Returns the bounds for the drop arrow at `index`. `direction` is set to + // the direction the arrow should point. + using BoundsCallback = + base::RepeatingCallback<gfx::Rect(const BrowserRootView::DropIndex& index, + Direction* direction)>; + DropArrow(const BrowserRootView::DropIndex& index, - bool point_down, - views::Widget* context); + gfx::NativeWindow context, + BoundsCallback bounds_callback); DropArrow(const DropArrow&) = delete; DropArrow& operator=(const DropArrow&) = delete; ~DropArrow() override; + // Returns the size of the arrow image. Height represents the length of the + // arrow in the direction it points and width is the opposite dimension. static gfx::Size GetSize(); - void set_index(const BrowserRootView::DropIndex& index) { index_ = index; } + void SetIndex(const BrowserRootView::DropIndex& index); BrowserRootView::DropIndex index() const { return index_; } - void SetPointDown(bool down); - bool point_down() const { return point_down_; } - - void SetWindowBounds(const gfx::Rect& bounds); - // views::WidgetObserver: void OnWidgetDestroying(views::Widget* widget) override; private: + void UpdateBounds(); + // Index of the tab to drop on. BrowserRootView::DropIndex index_; - // Direction the arrow should point in. If true, the arrow is displayed - // above the tab and points down. If false, the arrow is displayed beneath - // the tab and points up. - bool point_down_ = false; + // Callback to get the bounds of the drop arrow for a given `DropIndex`. + BoundsCallback bounds_callback_; + + // Direction the arrow should point in. + std::optional<Direction> direction_; // Renders the drop indicator. - raw_ptr<views::Widget, DanglingUntriaged> arrow_window_ = nullptr; + raw_ptr<views::Widget> arrow_widget_ = nullptr; - raw_ptr<views::ImageView, DanglingUntriaged> arrow_view_ = nullptr; + raw_ptr<views::ImageView> arrow_view_ = nullptr; base::ScopedObservation<views::Widget, views::WidgetObserver> scoped_observation_{this};
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc index 20761b7..b56a8f9 100644 --- a/chrome/browser/ui/views/tabs/tab.cc +++ b/chrome/browser/ui/views/tabs/tab.cc
@@ -101,6 +101,7 @@ #include "ui/views/view.h" #include "ui/views/view_class_properties.h" #include "ui/views/view_targeter.h" +#include "ui/views/view_utils.h" #include "ui/views/widget/tooltip_manager.h" #include "ui/views/widget/widget.h" #include "ui/views/window/frame_view.h" @@ -579,7 +580,8 @@ // the location of the event may no longer be valid. Create a copy of the // event in the parents coordinate, which won't change, and recreate an // event after changing so the coordinates are correct. - ui::MouseEvent event_in_parent(event, static_cast<View*>(this), parent()); + ui::MouseEvent event_in_parent(event, views::AsViewClass<View>(this), + parent()); if (event.IsShiftDown() && IsSelectionModifierDown(event)) { controller_->AddSelectionFromAnchorTo(this); } else if (event.IsShiftDown()) { @@ -595,7 +597,7 @@ base::RecordAction(UserMetricsAction("SwitchTab_Click")); } ui::MouseEvent cloned_event(event_in_parent, parent(), - static_cast<View*>(this)); + views::AsViewClass<View>(this)); if (!closing()) { controller_->MaybeStartDrag(this, cloned_event, original_selection); @@ -725,7 +727,7 @@ DCHECK_EQ(1, event->details().touch_points()); // See comment in OnMousePressed() as to why we copy the event. - ui::GestureEvent event_in_parent(*event, static_cast<View*>(this), + ui::GestureEvent event_in_parent(*event, views::AsViewClass<View>(this), parent()); ui::ListSelectionModel original_selection = controller_->GetSelectionModel(); @@ -735,7 +737,7 @@ gfx::Point loc(event->location()); views::View::ConvertPointToScreen(this, &loc); ui::GestureEvent cloned_event(event_in_parent, parent(), - static_cast<View*>(this)); + views::AsViewClass<View>(this)); if (!closing()) { #if BUILDFLAG(IS_WIN)
diff --git a/chrome/browser/ui/views/tabs/tab_container_impl.cc b/chrome/browser/ui/views/tabs/tab_container_impl.cc index 18fc1db6b..a202ba9 100644 --- a/chrome/browser/ui/views/tabs/tab_container_impl.cc +++ b/chrome/browser/ui/views/tabs/tab_container_impl.cc
@@ -72,7 +72,8 @@ void TabContainerImpl::RemoveTabDelegate::AnimationEnded( const gfx::Animation* animation) { StopObserving(); - tab_container()->OnTabCloseAnimationCompleted(static_cast<Tab*>(slot_view())); + tab_container()->OnTabCloseAnimationCompleted( + views::AsViewClass<Tab>(slot_view())); } void TabContainerImpl::RemoveTabDelegate::AnimationCanceled( @@ -926,7 +927,7 @@ } if (view->GetTabSlotViewType() == TabSlotView::ViewType::kTab) { - Tab* const tab = static_cast<Tab*>(view); + Tab* const tab = views::AsViewClass<Tab>(view); // Closing tabs should be skipped. if (tab->closing()) { continue; @@ -984,7 +985,8 @@ } } } else { - TabGroupHeader* const group_header = static_cast<TabGroupHeader*>(view); + TabGroupHeader* const group_header = + views::AsViewClass<TabGroupHeader>(view); const int first_tab_index = controller_->GetFirstTabInGroup(group_header->group().value()) .value(); @@ -1593,11 +1595,10 @@ return right_edge + active_tab_width - tab->width() <= tabstrip_right; } -gfx::Rect TabContainerImpl::GetDropBounds(int drop_index, - bool drop_before, - bool drop_in_group, - bool* is_beneath) { - DCHECK_NE(drop_index, -1); +gfx::Rect TabContainerImpl::GetDropBounds( + const BrowserRootView::DropIndex& drop_index, + DropArrow::Direction* direction) { + DCHECK_NE(drop_index.index, -1); // The X location the indicator points to. int center_x = -1; @@ -1608,19 +1609,25 @@ return gfx::Rect(); } - Tab* tab = GetTabAtModelIndex(std::min(drop_index, GetTabCount() - 1)); + Tab* tab = GetTabAtModelIndex(std::min(drop_index.index, GetTabCount() - 1)); const bool first_in_group = - drop_index < GetTabCount() && tab->group().has_value() && + drop_index.index < GetTabCount() && tab->group().has_value() && GetModelIndexOf(tab) == controller_->GetFirstTabInGroup(tab->group().value()); const int overlap = tab->tab_style()->GetTabOverlap(); + const bool drop_before = + drop_index.relative_to_index == + BrowserRootView::DropIndex::RelativeToIndex::kInsertBeforeIndex; + const bool drop_in_group = + drop_index.group_inclusion == + BrowserRootView::DropIndex::GroupInclusion::kIncludeInGroup; if (!drop_before || !first_in_group || drop_in_group) { // Dropping between tabs, or between a group header and the group's first // tab. center_x = tab->x(); const int width = tab->width(); - if (drop_index < GetTabCount()) { + if (drop_index.index < GetTabCount()) { center_x += drop_before ? (overlap / 2) : (width / 2); } else { center_x += width - (overlap / 2); @@ -1645,8 +1652,10 @@ // If the rect doesn't fit on the monitor, push the arrow to the bottom. display::Screen* screen = display::Screen::Get(); display::Display display = screen->GetDisplayMatching(drop_bounds); - *is_beneath = !display.bounds().Contains(drop_bounds); - if (*is_beneath) { + const bool is_beneath = !display.bounds().Contains(drop_bounds); + *direction = + is_beneath ? DropArrow::Direction::kUp : DropArrow::Direction::kDown; + if (is_beneath) { drop_bounds.Offset(0, drop_bounds.height() + height()); } @@ -1665,28 +1674,20 @@ const bool drop_before = index->relative_to_index == BrowserRootView::DropIndex::RelativeToIndex::kInsertBeforeIndex; - const bool group_inclusion = - index->group_inclusion == - BrowserRootView::DropIndex::GroupInclusion::kIncludeInGroup; controller_->OnDropIndexUpdate(index->index, drop_before); if (drop_arrow_ && (index == drop_arrow_->index())) { return; } - bool is_beneath; - gfx::Rect drop_bounds = - GetDropBounds(index->index, drop_before, group_inclusion, &is_beneath); - if (!drop_arrow_) { - drop_arrow_ = std::make_unique<DropArrow>(*index, !is_beneath, GetWidget()); + drop_arrow_ = std::make_unique<DropArrow>( + *index, GetWidget()->GetNativeWindow(), + base::BindRepeating(&TabContainerImpl::GetDropBounds, + base::Unretained(this))); } else { - drop_arrow_->set_index(*index); - drop_arrow_->SetPointDown(!is_beneath); + drop_arrow_->SetIndex(*index); } - - // Reposition the window. - drop_arrow_->SetWindowBounds(drop_bounds); } void TabContainerImpl::UpdateAccessibleTabIndices() {
diff --git a/chrome/browser/ui/views/tabs/tab_container_impl.h b/chrome/browser/ui/views/tabs/tab_container_impl.h index 6ad0bf2..77bb2c7 100644 --- a/chrome/browser/ui/views/tabs/tab_container_impl.h +++ b/chrome/browser/ui/views/tabs/tab_container_impl.h
@@ -11,6 +11,7 @@ #include "base/memory/raw_ref.h" #include "base/timer/timer.h" #include "chrome/browser/ui/views/frame/browser_root_view.h" +#include "chrome/browser/ui/views/tabs/shared/drop_arrow.h" #include "chrome/browser/ui/views/tabs/tab.h" #include "chrome/browser/ui/views/tabs/tab_container.h" #include "chrome/browser/ui/views/tabs/tab_group_underline.h" @@ -33,7 +34,6 @@ class TabStrip; class TabHoverCardController; class TabDragPositioningDelegateBase; -class DropArrow; // A View that contains a sequence of Tabs for the TabStrip. class TabContainerImpl : public TabContainer, @@ -274,12 +274,9 @@ // -- Link Drag & Drop ------------------------------------------------------ // Returns the bounds to render the drop at, in screen coordinates. Sets - // `is_beneath` to indicate whether the arrow is beneath the tab, or above - // it. - gfx::Rect GetDropBounds(int drop_index, - bool drop_before, - bool drop_in_group, - bool* is_beneath); + // `direction` to indicate which way the arrow should point. + gfx::Rect GetDropBounds(const BrowserRootView::DropIndex& drop_index, + DropArrow::Direction* direction); // Show drop arrow with passed `tab_data_index` and `drop_before`. // If `tab_data_index` is negative, the arrow will disappear.
diff --git a/chrome/browser/ui/views/tabs/tab_container_unittest.cc b/chrome/browser/ui/views/tabs/tab_container_unittest.cc index 261cdc39..2e011854 100644 --- a/chrome/browser/ui/views/tabs/tab_container_unittest.cc +++ b/chrome/browser/ui/views/tabs/tab_container_unittest.cc
@@ -1279,7 +1279,7 @@ } TEST_F(TabContainerTest, ZOrder_MixedScenario) { - auto* container_impl = static_cast<TabContainerImpl*>(tab_container_); + auto* container_impl = views::AsViewClass<TabContainerImpl>(tab_container_); Tab* pinned_tab = AddTab(0, std::nullopt, TabActive::kActive, TabPinned::kPinned); tab_groups::TabGroupId group = tab_groups::TabGroupId::GenerateNew(); @@ -1333,7 +1333,7 @@ } TEST_F(TabContainerTest, ZOrder_TabGroup) { - auto* container_impl = static_cast<TabContainerImpl*>(tab_container_); + auto* container_impl = views::AsViewClass<TabContainerImpl>(tab_container_); Tab* regular_tab = AddTab(0); tab_groups::TabGroupId group = tab_groups::TabGroupId::GenerateNew(); Tab* grouped_tab = AddTab(1, group); @@ -1365,7 +1365,7 @@ } TEST_F(TabContainerTest, ZOrder_PinnedTab) { - auto* container_impl = static_cast<TabContainerImpl*>(tab_container_); + auto* container_impl = views::AsViewClass<TabContainerImpl>(tab_container_); Tab* pinned_tab = AddTab(0, std::nullopt, TabActive::kInactive, TabPinned::kPinned); Tab* regular_tab = AddTab(1); @@ -1400,7 +1400,7 @@ } TEST_F(TabContainerTest, ZOrder_HoveredTabIsAfterNormalTab) { - auto* container_impl = static_cast<TabContainerImpl*>(tab_container_); + auto* container_impl = views::AsViewClass<TabContainerImpl>(tab_container_); Tab* tab1 = AddTab(0); Tab* tab2 = AddTab(1); container_impl->CompleteAnimationAndLayout(); @@ -1424,7 +1424,7 @@ } TEST_F(TabContainerTest, ZOrder_ActiveTabIsLast) { - auto* container_impl = static_cast<TabContainerImpl*>(tab_container_); + auto* container_impl = views::AsViewClass<TabContainerImpl>(tab_container_); AddTab(0); AddTab(1, std::nullopt, TabActive::kActive); AddTab(2); @@ -1436,7 +1436,7 @@ } TEST_F(TabContainerTest, ZOrderCacheUpdatesAfterCRUDOperations) { - auto* container_impl = static_cast<TabContainerImpl*>(tab_container_); + auto* container_impl = views::AsViewClass<TabContainerImpl>(tab_container_); container_impl->CompleteAnimationAndLayout(); container_impl->UpdateZOrderCacheForTesting(); EXPECT_EQ(container_impl->GetZOrderCacheForTesting().size(), 0u);
diff --git a/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view_browsertest.cc b/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view_browsertest.cc index 917f98f..be39dfa 100644 --- a/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view_browsertest.cc +++ b/chrome/browser/ui/views/tabs/tab_group_editor_bubble_view_browsertest.cc
@@ -44,6 +44,7 @@ #include "ui/views/test/button_test_api.h" #include "ui/views/test/widget_test.h" #include "ui/views/view.h" +#include "ui/views/view_utils.h" #include "ui/views/widget/any_widget_observer.h" #include "ui/views/widget/widget.h" @@ -463,7 +464,7 @@ editor_bubble->GetRootView())); ASSERT_NE(nullptr, focus_button_view); views::LabelButton* const focus_button = - static_cast<views::LabelButton*>(focus_button_view); + views::AsViewClass<views::LabelButton>(focus_button_view); EXPECT_EQ(focus_button->GetText(), l10n_util::GetStringUTF16(IDS_TAB_GROUP_HEADER_CXMENU_FOCUS_GROUP)); @@ -491,7 +492,7 @@ editor_bubble2->GetRootView())); ASSERT_NE(nullptr, unfocus_button_view); views::LabelButton* const unfocus_button = - static_cast<views::LabelButton*>(unfocus_button_view); + views::AsViewClass<views::LabelButton>(unfocus_button_view); EXPECT_EQ( unfocus_button->GetText(), l10n_util::GetStringUTF16(IDS_TAB_GROUP_HEADER_CXMENU_UNFOCUS_GROUP));
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc index be0b9d0..62d46d8 100644 --- a/chrome/browser/ui/views/tabs/tab_strip.cc +++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -1977,7 +1977,7 @@ view = view->parent(); } - return view && view->GetID() == VIEW_ID_TAB ? static_cast<Tab*>(view) + return view && view->GetID() == VIEW_ID_TAB ? views::AsViewClass<Tab>(view) : nullptr; } @@ -2436,8 +2436,8 @@ ui::mojom::MenuSourceType source_type) { // We are only intended to be installed as a context-menu handler for tabs, so // this cast should be safe. - CHECK(views::IsViewClass<Tab>(source)) << "The source must be a Tab class."; - Tab* const tab = static_cast<Tab*>(source); + Tab* const tab = views::AsViewClass<Tab>(source); + CHECK(tab) << "The source must be a Tab class."; if (tab->closing()) { return; }
diff --git a/chrome/browser/ui/views/tabs/tab_strip_action_container_browsertest.cc b/chrome/browser/ui/views/tabs/tab_strip_action_container_browsertest.cc index c5b0798..2418832 100644 --- a/chrome/browser/ui/views/tabs/tab_strip_action_container_browsertest.cc +++ b/chrome/browser/ui/views/tabs/tab_strip_action_container_browsertest.cc
@@ -65,6 +65,7 @@ #include "ui/events/base_event_utils.h" #include "ui/events/test/event_generator.h" #include "ui/gfx/animation/slide_animation.h" +#include "ui/views/view_utils.h" #if !BUILDFLAG(IS_ANDROID) #include "chrome/browser/private_ai/private_ai_service.h" @@ -143,7 +144,7 @@ protected: glic::TabStripGlicButton* GlicNudgeButton() { - return static_cast<glic::TabStripGlicButton*>( + return views::AsViewClass<glic::TabStripGlicButton>( tab_strip_action_container()->GetGlicButton()); }
diff --git a/chrome/browser/ui/views/tabs/tab_strip_layout_helper.cc b/chrome/browser/ui/views/tabs/tab_strip_layout_helper.cc index d29714a..7345b6b 100644 --- a/chrome/browser/ui/views/tabs/tab_strip_layout_helper.cc +++ b/chrome/browser/ui/views/tabs/tab_strip_layout_helper.cc
@@ -27,6 +27,7 @@ #include "tab_container_controller.h" #include "ui/gfx/range/range.h" #include "ui/views/view_model.h" +#include "ui/views/view_utils.h" struct TabStripLayoutHelper::TabSlot { static TabStripLayoutHelper::TabSlot CreateForTabSlotView(TabSlotView* view, @@ -59,7 +60,7 @@ std::vector<Tab*> tabs; for (const TabSlot& slot : slots_) { if (slot.type == TabSlotView::ViewType::kTab) { - tabs.push_back(static_cast<Tab*>(slot.view)); + tabs.push_back(views::AsViewClass<Tab>(slot.view)); } } @@ -334,8 +335,8 @@ // matches, the tab goes to the right of the header to keep it // contiguous. if (slots_[slot_index].type == TabSlotView::ViewType::kTabGroupHeader && - static_cast<const TabGroupHeader*>(slots_[slot_index].view)->group() == - group) { + views::AsViewClass<const TabGroupHeader>(slots_[slot_index].view) + ->group() == group) { return slot_index + 1; } @@ -398,7 +399,7 @@ tab_groups::TabGroupId group) const { const auto it = std::ranges::find_if(slots_, [group](const auto& slot) { return slot.type == TabSlotView::ViewType::kTabGroupHeader && - static_cast<TabGroupHeader*>(slot.view)->group() == group; + views::AsViewClass<TabGroupHeader>(slot.view)->group() == group; }); CHECK(it != slots_.end()); return it - slots_.begin();
diff --git a/chrome/browser/ui/views/tabs/vertical/tab_collection_node_browsertest.cc b/chrome/browser/ui/views/tabs/vertical/tab_collection_node_browsertest.cc index 4c1f9fe..d3520eff 100644 --- a/chrome/browser/ui/views/tabs/vertical/tab_collection_node_browsertest.cc +++ b/chrome/browser/ui/views/tabs/vertical/tab_collection_node_browsertest.cc
@@ -27,6 +27,7 @@ #include "testing/gtest/include/gtest/gtest.h" #include "ui/views/controls/scroll_view.h" #include "ui/views/view.h" +#include "ui/views/view_utils.h" class TabCollectionNodeBrowserTest : public VerticalTabsBrowserTestMixin<InProcessBrowserTest> { @@ -330,7 +331,7 @@ // The pinned_node_scroll_view's contents should have no children. ASSERT_TRUE(views::IsViewClass<views::ScrollView>(pinned_node_scroll_view)); - ASSERT_EQ(static_cast<views::ScrollView*>(pinned_node_scroll_view) + ASSERT_EQ(views::AsViewClass<views::ScrollView>(pinned_node_scroll_view) ->contents() ->children() .size(), @@ -339,7 +340,7 @@ // The unpinned_node_scroll_view's contents should have two children, the two // tab views. ASSERT_TRUE(views::IsViewClass<views::ScrollView>(unpinned_node_scroll_view)); - ASSERT_EQ(static_cast<views::ScrollView*>(unpinned_node_scroll_view) + ASSERT_EQ(views::AsViewClass<views::ScrollView>(unpinned_node_scroll_view) ->contents() ->children() .size(),
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_pinned_tab_container_view.cc b/chrome/browser/ui/views/tabs/vertical/vertical_pinned_tab_container_view.cc index 2dbbe23..60de34928 100644 --- a/chrome/browser/ui/views/tabs/vertical/vertical_pinned_tab_container_view.cc +++ b/chrome/browser/ui/views/tabs/vertical/vertical_pinned_tab_container_view.cc
@@ -22,6 +22,7 @@ #include "ui/views/layout/proposed_layout.h" #include "ui/views/view.h" #include "ui/views/view_class_properties.h" +#include "ui/views/view_utils.h" namespace { constexpr int kTabPadding = 4; @@ -212,7 +213,7 @@ } else if (child_node->type() == TabCollectionNode::Type::SPLIT) { // If landing in the middle of the split, let the split view decide // which tab to replace. - auto* split_view = static_cast<VerticalSplitTabView*>(view); + auto* split_view = views::AsViewClass<VerticalSplitTabView>(view); gfx::Point loc_in_split = views::View::ConvertPointToTarget(this, split_view, loc_in_container); return split_view->GetLinkDropIndex(loc_in_split); @@ -263,7 +264,7 @@ } else if (child_node->type() == TabCollectionNode::Type::SPLIT) { // If landing in the middle of the split, let the split view decide // which tab to replace. - auto* split_view = static_cast<VerticalSplitTabView*>(view); + auto* split_view = views::AsViewClass<VerticalSplitTabView>(view); gfx::Point loc_in_split = views::View::ConvertPointToTarget(this, split_view, loc_in_container); return split_view->GetLinkDropIndex(loc_in_split);
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_split_tab_view.cc b/chrome/browser/ui/views/tabs/vertical/vertical_split_tab_view.cc index 86cdbc4d..b0e11e4 100644 --- a/chrome/browser/ui/views/tabs/vertical/vertical_split_tab_view.cc +++ b/chrome/browser/ui/views/tabs/vertical/vertical_split_tab_view.cc
@@ -25,6 +25,7 @@ #include "ui/views/layout/proposed_layout.h" #include "ui/views/view.h" #include "ui/views/view_class_properties.h" +#include "ui/views/view_utils.h" #include "ui/views/widget/widget.h" VerticalSplitTabView::VerticalSplitTabView(TabCollectionNode* collection_node) @@ -86,9 +87,9 @@ collection_node_ ? collection_node_->GetDirectChildren() : std::vector<views::View*>(); std::optional<SkColor> background_color = - !children.empty() - ? static_cast<VerticalTabView*>(children[0])->GetBackgroundColor() - : std::nullopt; + !children.empty() ? views::AsViewClass<VerticalTabView>(children[0]) + ->GetBackgroundColor() + : std::nullopt; if (background_color.has_value()) { cc::PaintFlags flags; flags.setAntiAlias(true);
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_split_tab_view_browsertest.cc b/chrome/browser/ui/views/tabs/vertical/vertical_split_tab_view_browsertest.cc index 21db572..51e0b911 100644 --- a/chrome/browser/ui/views/tabs/vertical/vertical_split_tab_view_browsertest.cc +++ b/chrome/browser/ui/views/tabs/vertical/vertical_split_tab_view_browsertest.cc
@@ -31,7 +31,7 @@ ->view(); EXPECT_TRUE(views::IsViewClass<VerticalSplitTabView>(split)); VerticalSplitTabView* split_tab_view = - static_cast<VerticalSplitTabView*>(split); + views::AsViewClass<VerticalSplitTabView>(split); auto children = split_tab_view->children(); EXPECT_EQ(children.size(), 2u); @@ -62,7 +62,7 @@ ->view(); EXPECT_TRUE(views::IsViewClass<VerticalSplitTabView>(split)); VerticalSplitTabView* split_tab_view = - static_cast<VerticalSplitTabView*>(split); + views::AsViewClass<VerticalSplitTabView>(split); auto children = split_tab_view->children(); EXPECT_EQ(children.size(), 2u); @@ -100,7 +100,7 @@ ->view(); EXPECT_TRUE(views::IsViewClass<VerticalSplitTabView>(split)); VerticalSplitTabView* split_tab_view = - static_cast<VerticalSplitTabView*>(split); + views::AsViewClass<VerticalSplitTabView>(split); auto children = split_tab_view->children(); EXPECT_EQ(children.size(), 2u);
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_tab_group_view.cc b/chrome/browser/ui/views/tabs/vertical/vertical_tab_group_view.cc index eb71e87..ab61062 100644 --- a/chrome/browser/ui/views/tabs/vertical/vertical_tab_group_view.cc +++ b/chrome/browser/ui/views/tabs/vertical/vertical_tab_group_view.cc
@@ -42,6 +42,7 @@ #include "ui/views/layout/proposed_layout.h" #include "ui/views/view.h" #include "ui/views/view_class_properties.h" +#include "ui/views/view_utils.h" #include "ui/views/widget/widget.h" namespace { @@ -226,9 +227,9 @@ // When the tab strip is collapsed, anchor to the group header, otherwise // anchor to the editor bubble button. views::View* anchor_view = - is_tab_strip_collapsed - ? static_cast<views::View*>(group_header_) - : static_cast<views::View*>(group_header_->editor_bubble_button()); + is_tab_strip_collapsed ? views::AsViewClass<views::View>(group_header_) + : views::AsViewClass<views::View>( + group_header_->editor_bubble_button()); return collection_node_->GetController()->ShowGroupEditorBubble( GetTabGroupFromNode(collection_node_)->id(), anchor_view, stop_context_menu_propagation); @@ -431,7 +432,7 @@ } else if (child_node->type() == TabCollectionNode::Type::SPLIT) { // If landing in the middle of the split, let the split view decide which // tab to replace. - auto* split_view = static_cast<VerticalSplitTabView*>(view); + auto* split_view = views::AsViewClass<VerticalSplitTabView>(view); gfx::Point loc_in_split = views::View::ConvertPointToTarget(this, split_view, loc_in_group); return split_view->GetLinkDropIndex(loc_in_split); @@ -464,10 +465,8 @@ return; } - TabHoverCardController* hover_card_controller = - collection_node_->GetController()->GetHoverCardController(); - - if (hover_card_controller && hover_card_controller->IsHoverCardVisible()) { + if (TabHoverCardController* hover_card_controller = + collection_node_->GetController()->GetHoverCardController()) { hover_card_controller->UpdateHoverCard( nullptr, TabSlotController::HoverCardUpdateType::kEvent); }
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_tab_group_view_browsertest.cc b/chrome/browser/ui/views/tabs/vertical/vertical_tab_group_view_browsertest.cc index b8598f8..f811654 100644 --- a/chrome/browser/ui/views/tabs/vertical/vertical_tab_group_view_browsertest.cc +++ b/chrome/browser/ui/views/tabs/vertical/vertical_tab_group_view_browsertest.cc
@@ -23,6 +23,7 @@ #include "ui/base/models/image_model.h" #include "ui/events/base_event_utils.h" #include "ui/events/event_constants.h" +#include "ui/views/view_utils.h" class VerticalTabGroupViewTest : public VerticalTabsBrowserTestMixin<InProcessBrowserTest> { @@ -106,7 +107,7 @@ ->GetChildNodeOfType(TabCollectionNode::Type::GROUP) ->children()[0] .get(); - VerticalTabView* tab = static_cast<VerticalTabView*>(tab_node->view()); + VerticalTabView* tab = views::AsViewClass<VerticalTabView>(tab_node->view()); // Verify the tab in the group is visible. EXPECT_TRUE(tab->GetVisible()); @@ -129,7 +130,8 @@ unpinned_collection_node()->GetChildNodeOfType( TabCollectionNode::Type::GROUP); VerticalTabGroupHeaderView* group_header = - static_cast<VerticalTabGroupView*>(group_node->view())->group_header(); + views::AsViewClass<VerticalTabGroupView>(group_node->view()) + ->group_header(); EXPECT_EQ(group_header->collapse_icon_for_testing() ->GetImageModel() .GetVectorIcon() @@ -157,7 +159,7 @@ ->GetChildNodeOfType(TabCollectionNode::Type::GROUP) ->children()[0] .get(); - VerticalTabView* tab = static_cast<VerticalTabView*>(tab_node->view()); + VerticalTabView* tab = views::AsViewClass<VerticalTabView>(tab_node->view()); const tabs::TabInterface* tab_interface = GetTabInterfaceForNode(tab_node); // Verify the tab in the group is visible and active. EXPECT_TRUE(tab->GetVisible()); @@ -267,7 +269,7 @@ TabCollectionNode* tab_node = root_node()->children()[1]->children()[1]->children()[0].get(); - VerticalTabView* tab = static_cast<VerticalTabView*>(tab_node->view()); + VerticalTabView* tab = views::AsViewClass<VerticalTabView>(tab_node->view()); // Verify the tab in the group is visible. EXPECT_TRUE(tab->GetVisible()); @@ -283,7 +285,7 @@ ->attention_indicator() ->SetHasAttention(true); VerticalTabGroupHeaderView* const tab_group_header = - static_cast<VerticalTabGroupHeaderView*>( + views::AsViewClass<VerticalTabGroupHeaderView>( BrowserElementsViews::From(browser())->GetView( kTabGroupHeaderElementId)); EXPECT_TRUE(base::test::RunUntil([&]() {
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_bottom_container_interactive_uitest.cc b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_bottom_container_interactive_uitest.cc index d1990ccd..2112544 100644 --- a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_bottom_container_interactive_uitest.cc +++ b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_bottom_container_interactive_uitest.cc
@@ -17,6 +17,7 @@ #include "ui/base/interaction/element_identifier.h" #include "ui/base/interaction/interaction_test_util.h" #include "ui/views/controls/menu/menu_item_view.h" +#include "ui/views/view_utils.h" namespace base::test { @@ -99,7 +100,7 @@ [&new_tab_button_center, &point_above_new_tab_button](views::View* region_view) { auto* vt_region_view = - static_cast<VerticalTabStripRegionView*>(region_view); + views::AsViewClass<VerticalTabStripRegionView>(region_view); gfx::Point pt_center = new_tab_button_center; views::View::ConvertPointFromScreen(vt_region_view, &pt_center);
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller.cc index ea79d68..3d575ae3 100644 --- a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller.cc +++ b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller.cc
@@ -35,6 +35,7 @@ #include "components/tabs/public/tab_collection_types.h" #include "components/tabs/public/tab_group.h" #include "components/tabs/public/tab_interface.h" +#include "ui/views/view_utils.h" #if BUILDFLAG(IS_CHROMEOS) #include "chrome/browser/ui/web_applications/app_browser_controller.h" @@ -243,7 +244,8 @@ void VerticalTabStripController::ShowGroupEditorBubble( const TabCollectionNode* group_node) { auto* group_header_view = - static_cast<VerticalTabGroupView*>(group_node->view())->group_header(); + views::AsViewClass<VerticalTabGroupView>(group_node->view()) + ->group_header(); group_header_view->ShowContextMenuForViewImpl( group_header_view, gfx::Point(), ui::mojom::MenuSourceType::kNone); }
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller_interactive_uitest.cc index 7393ae6d..bbc5471b 100644 --- a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller_interactive_uitest.cc +++ b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller_interactive_uitest.cc
@@ -29,6 +29,7 @@ #include "ui/menus/simple_menu_model.h" #include "ui/views/controls/scroll_view.h" #include "ui/views/interaction/interactive_views_test.h" +#include "ui/views/view_utils.h" namespace { @@ -469,7 +470,7 @@ ->vertical_tab_strip_region_view_for_testing() ->GetTabStripView(); VerticalTabStripView* vertical_tab_strip_view = - static_cast<VerticalTabStripView*>(tab_strip_view); + views::AsViewClass<VerticalTabStripView>(tab_strip_view); vertical_tab_strip_view->unpinned_tabs_scroll_view_for_testing() ->ScrollByOffset({0, -100}); }),
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_scroll_bar.cc b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_scroll_bar.cc index 4eec5e86..ea7eb758 100644 --- a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_scroll_bar.cc +++ b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_scroll_bar.cc
@@ -23,6 +23,7 @@ #include "ui/views/background.h" #include "ui/views/controls/scrollbar/base_scroll_bar_thumb.h" #include "ui/views/layout/fill_layout.h" +#include "ui/views/view_utils.h" namespace { @@ -122,13 +123,13 @@ void VerticalTabStripScrollBar::OnMouseEntered(const ui::MouseEvent& event) { VerticalTabStripScrollBar::Thumb* thumb = - static_cast<VerticalTabStripScrollBar::Thumb*>(GetThumb()); + views::AsViewClass<VerticalTabStripScrollBar::Thumb>(GetThumb()); thumb->Show(); } void VerticalTabStripScrollBar::OnMouseExited(const ui::MouseEvent& event) { VerticalTabStripScrollBar::Thumb* thumb = - static_cast<VerticalTabStripScrollBar::Thumb*>(GetThumb()); + views::AsViewClass<VerticalTabStripScrollBar::Thumb>(GetThumb()); thumb->StartHideCountdown(); }
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_tab_view.cc b/chrome/browser/ui/views/tabs/vertical/vertical_tab_view.cc index 2e7b114..e8b930d 100644 --- a/chrome/browser/ui/views/tabs/vertical/vertical_tab_view.cc +++ b/chrome/browser/ui/views/tabs/vertical/vertical_tab_view.cc
@@ -67,6 +67,7 @@ #include "ui/views/layout/proposed_layout.h" #include "ui/views/view.h" #include "ui/views/view_class_properties.h" +#include "ui/views/view_utils.h" #if BUILDFLAG(ENABLE_GLIC) #include "chrome/browser/glic/browser_ui/tab_underline_view.h" @@ -608,8 +609,9 @@ preferred_width = container.width() - config.padding; // The only expandable view is the views::Label. Just get the line height to // make calculating bounds cheaper. - CHECK(views::IsViewClass<views::Label>(config.view)); - preferred_height = static_cast<views::Label*>(config.view)->GetLineHeight(); + views::Label* label = views::AsViewClass<views::Label>(config.view); + CHECK(label); + preferred_height = label->GetLineHeight(); } else { const gfx::Size preferred_size = config.view->GetPreferredSize(); preferred_width = preferred_size.width();
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_unpinned_tab_container_view.cc b/chrome/browser/ui/views/tabs/vertical/vertical_unpinned_tab_container_view.cc index 569e1c07..b069cef 100644 --- a/chrome/browser/ui/views/tabs/vertical/vertical_unpinned_tab_container_view.cc +++ b/chrome/browser/ui/views/tabs/vertical/vertical_unpinned_tab_container_view.cc
@@ -194,7 +194,7 @@ } if (child_node->type() == TabCollectionNode::Type::GROUP) { - auto* group_view = static_cast<VerticalTabGroupView*>(view); + auto* group_view = views::AsViewClass<VerticalTabGroupView>(view); if (group_view->IsCollapsed()) { gfx::Point loc_in_group = views::View::ConvertPointToTarget( this, group_view, loc_in_container); @@ -225,7 +225,7 @@ } else if (child_node->type() == TabCollectionNode::Type::SPLIT) { // If landing in the middle of the split, let the split view decide which // tab to replace. - auto* split_view = static_cast<VerticalSplitTabView*>(view); + auto* split_view = views::AsViewClass<VerticalSplitTabView>(view); gfx::Point loc_in_split = views::View::ConvertPointToTarget(this, split_view, loc_in_container); return split_view->GetLinkDropIndex(loc_in_split);
diff --git a/chrome/browser/ui/views/toolbar/glic/BUILD.gn b/chrome/browser/ui/views/toolbar/glic/BUILD.gn index cf5bb6b2..c9b0ca9 100644 --- a/chrome/browser/ui/views/toolbar/glic/BUILD.gn +++ b/chrome/browser/ui/views/toolbar/glic/BUILD.gn
@@ -4,8 +4,6 @@ import("//chrome/common/features.gni") -assert(enable_glic) - source_set("glic") { sources = []
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.cc b/chrome/browser/ui/views/toolbar/toolbar_view.cc index 360e8acc..10f59bd8 100644 --- a/chrome/browser/ui/views/toolbar/toolbar_view.cc +++ b/chrome/browser/ui/views/toolbar/toolbar_view.cc
@@ -536,7 +536,6 @@ media_button_ = AddChildView(std::move(media_button)); } -#if BUILDFLAG(ENABLE_GLIC) if (glic::GlicEnabling::IsProfileEligible(browser_view_->GetProfile())) { auto* vertical_tab_strip_state_controller = tabs::VerticalTabStripStateController::From(browser_view_->browser()); @@ -551,7 +550,6 @@ } UpdateGlicButtonVisibility(); } -#endif // BUILDFLAG(ENABLE_GLIC) avatar_ = AddChildView(std::make_unique<AvatarToolbarButton>(browser_view_)); bool show_avatar_toolbar_button = true; @@ -643,7 +641,6 @@ UpdateGlicButtonVisibility(); } -#if BUILDFLAG(ENABLE_GLIC) std::unique_ptr<glic::ToolbarGlicButton> ToolbarView::CreateGlicButton() { glic::GlicKeyedService* service = glic::GlicKeyedService::Get(browser_view_->GetProfile()); @@ -786,7 +783,6 @@ glic_button_->SetGlicPanelIsOpen(open); } -#endif // ENABLE_GLIC void ToolbarView::AnimationEnded(const gfx::Animation* animation) { if (animation->GetCurrentValue() == 0) {
diff --git a/chrome/browser/ui/views/translate/partial_translate_bubble_view_unittest.cc b/chrome/browser/ui/views/translate/partial_translate_bubble_view_unittest.cc index 23efbff..21398e0 100644 --- a/chrome/browser/ui/views/translate/partial_translate_bubble_view_unittest.cc +++ b/chrome/browser/ui/views/translate/partial_translate_bubble_view_unittest.cc
@@ -106,7 +106,7 @@ // The bubble needs the parent as an anchor. anchor_widget_ = - CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET, + CreateTestWidget(views::Widget::InitParams::CLIENT_OWNS_WIDGET, views::Widget::InitParams::TYPE_WINDOW); anchor_widget_->Show();
diff --git a/chrome/browser/ui/views/translate/translate_bubble_controller_unittest.cc b/chrome/browser/ui/views/translate/translate_bubble_controller_unittest.cc index 0abd59f..8cbc81f4 100644 --- a/chrome/browser/ui/views/translate/translate_bubble_controller_unittest.cc +++ b/chrome/browser/ui/views/translate/translate_bubble_controller_unittest.cc
@@ -211,7 +211,7 @@ // Create an anchor for the bubble. anchor_widget_ = - CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET, + CreateTestWidget(views::Widget::InitParams::CLIENT_OWNS_WIDGET, views::Widget::InitParams::TYPE_WINDOW); anchor_widget_->Show(); web_contents_ =
diff --git a/chrome/browser/ui/views/translate/translate_bubble_view_unittest.cc b/chrome/browser/ui/views/translate/translate_bubble_view_unittest.cc index bd816a3..e5efc134 100644 --- a/chrome/browser/ui/views/translate/translate_bubble_view_unittest.cc +++ b/chrome/browser/ui/views/translate/translate_bubble_view_unittest.cc
@@ -178,7 +178,7 @@ // The bubble needs the parent as an anchor. anchor_widget_ = - CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET, + CreateTestWidget(views::Widget::InitParams::CLIENT_OWNS_WIDGET, views::Widget::InitParams::TYPE_WINDOW); anchor_widget_->Show();
diff --git a/chrome/browser/ui/views/translate/translate_page_action_interactive_uitest.cc b/chrome/browser/ui/views/translate/translate_page_action_interactive_uitest.cc index 2a16695..0ab59e97 100644 --- a/chrome/browser/ui/views/translate/translate_page_action_interactive_uitest.cc +++ b/chrome/browser/ui/views/translate/translate_page_action_interactive_uitest.cc
@@ -92,7 +92,7 @@ TranslateBubbleController* controller = TranslateBubbleController::From(browser()); auto anchor_widget = - CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET); + CreateTestWidget(views::Widget::InitParams::CLIENT_OWNS_WIDGET); views::View* anchor_view = anchor_widget->GetContentsView(); controller->StartPartialTranslate( browser()->tab_strip_model()->GetActiveWebContents(), anchor_view,
diff --git a/chrome/browser/ui/views/user_education/browser_user_education_service.cc b/chrome/browser/ui/views/user_education/browser_user_education_service.cc index a92f680..f9b3b8a0 100644 --- a/chrome/browser/ui/views/user_education/browser_user_education_service.cc +++ b/chrome/browser/ui/views/user_education/browser_user_education_service.cc
@@ -18,6 +18,8 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/devtools/features.h" #include "chrome/browser/feature_engagement/tracker_factory.h" +#include "chrome/browser/glic/host/glic.mojom.h" +#include "chrome/browser/glic/public/glic_keyed_service.h" #include "chrome/browser/performance_manager/public/user_tuning/user_performance_tuning_manager.h" #include "chrome/browser/privacy_sandbox/privacy_sandbox_queue_manager.h" #include "chrome/browser/privacy_sandbox/privacy_sandbox_service.h" @@ -145,11 +147,6 @@ #include "chrome/browser/ui/webui/extensions_zero_state_promo/zero_state_promo_ui.h" #endif // BUILDFLAG(ENABLE_EXTENSIONS) -#if BUILDFLAG(ENABLE_GLIC) -#include "chrome/browser/glic/host/glic.mojom.h" -#include "chrome/browser/glic/public/glic_keyed_service.h" -#endif - #if BUILDFLAG(ENABLE_PDF_INK2) #include "chrome/browser/pdf/pdf_help_bubble_handler_factory.h" #endif @@ -692,7 +689,6 @@ FeaturePromoSpecification::AcceleratorInfo()) .SetBubbleArrow(HelpBubbleArrow::kTopCenter))); -#if BUILDFLAG(ENABLE_GLIC) // kIPHGlicPromoFeature: registry.RegisterFeature(std::move( FeaturePromoSpecification::CreateForSnoozePromo( @@ -768,7 +764,6 @@ .SetMetadata(144, "zalmashni@google.com", "Triggered after the Glic side panel is closed or the " "user navigates to a new tab."))); -#endif // BUILDFLAG(ENABLE_GLIC) // kIPHGMCCastStartStopFeature: registry.RegisterFeature(FeaturePromoSpecification::CreateForLegacyPromo( @@ -2080,7 +2075,6 @@ 132, "emshack@chromium.org", "Shown in app menu when Tab Declutter menu item is enabled."))); -#if BUILDFLAG(ENABLE_GLIC) // This is a custom UI new badge that uses a small help bubble to annotate the // element instead of a badge. registry.RegisterFeature(user_education::NewBadgeSpecification( @@ -2103,7 +2097,6 @@ features::kGlicAppMenuNewBadge, user_education::Metadata(136, "sophey@chromium.org", "Shown in the three dot menu."))); -#endif // BUILDFLAG(ENABLE_GLIC) registry.RegisterFeature(user_education::NewBadgeSpecification( features::kSideBySide,
diff --git a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_browsertest.cc b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_browsertest.cc index febffdd..fa511dfd4 100644 --- a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_browsertest.cc +++ b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop_browsertest.cc
@@ -316,7 +316,7 @@ views::Widget::InitParams init_params( views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET, views::Widget::InitParams::TYPE_WINDOW); - init_params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + init_params.ownership = views::Widget::InitParams::CLIENT_OWNS_WIDGET; init_params.bounds = non_occluding_bounds; auto pip_widget = std::make_unique<views::Widget>(std::move(init_params)); pip_widget->Show();
diff --git a/chrome/browser/ui/webui/cr_components/composebox/composebox_handler.cc b/chrome/browser/ui/webui/cr_components/composebox/composebox_handler.cc index 812a12d..47ad1a6f 100644 --- a/chrome/browser/ui/webui/cr_components/composebox/composebox_handler.cc +++ b/chrome/browser/ui/webui/cr_components/composebox/composebox_handler.cc
@@ -210,9 +210,9 @@ void ComposeboxHandler::ClearFiles(bool should_block_auto_suggested_tabs) { ContextualSearchboxHandler::ClearFiles(should_block_auto_suggested_tabs); // Reset the AIM tool mode to not include file upload if it currently does. - if (GetInputState().active_tool == + if (input_state_model()->GetInputState().active_tool == omnibox::ToolMode::TOOL_MODE_IMAGE_GEN_UPLOAD) { - input_state_model_->setActiveTool(omnibox::ToolMode::TOOL_MODE_IMAGE_GEN); + input_state_model()->setActiveTool(omnibox::ToolMode::TOOL_MODE_IMAGE_GEN); } } @@ -246,7 +246,7 @@ std::map<std::string, std::string> additional_params) { if (auto* metrics_recorder = GetMetricsRecorder()) { // Record AIM tool and model mode on query submission. - const auto& input_state = GetInputState(); + const auto& input_state = input_state_model()->GetInputState(); metrics_recorder->RecordModesOnSubmission( mojo::EnumTraits<composebox_query::mojom::ToolMode, omnibox::ToolMode>::ToMojom(input_state.active_tool), @@ -264,9 +264,9 @@ // TODO(crbug.com/476137316): Update vector icons returned by server. // The default icon for contextual suggestions is the subdirectory arrow right // icon. For the Lens composebox and realbox, we want to stay consistent with - // the search loupe instead. + // the search spark loupe instead. if (icon.name == omnibox::kSubdirectoryArrowRightIcon.name) { - return searchbox_internal::kSearchIconResourceName; + return searchbox_internal::kSearchSparkIconResourceName; } return SearchboxHandler::AutocompleteIconToResourceName(icon);
diff --git a/chrome/browser/ui/webui/cr_components/searchbox/contextual_searchbox_handler.cc b/chrome/browser/ui/webui/cr_components/searchbox/contextual_searchbox_handler.cc index 937768b0b5..fdec592 100644 --- a/chrome/browser/ui/webui/cr_components/searchbox/contextual_searchbox_handler.cc +++ b/chrome/browser/ui/webui/cr_components/searchbox/contextual_searchbox_handler.cc
@@ -386,6 +386,13 @@ : std::nullopt; } +omnibox::InputState ContextualSearchboxHandler::GetInputState() const { + if (input_state_model_) { + return input_state_model_->GetInputState(); + } + return omnibox::InputState(); +} + void ContextualSearchboxHandler::NotifySessionStarted() { auto* contextual_session_handle = GetContextualSessionHandle(); if (contextual_session_handle) { @@ -564,8 +571,8 @@ } void ContextualSearchboxHandler::GetInputState(GetInputStateCallback callback) { - if (input_state_) { - std::move(callback).Run(*input_state_); + if (input_state_model_) { + std::move(callback).Run(input_state_model_->GetInputState()); } else { std::move(callback).Run(std::nullopt); } @@ -573,7 +580,6 @@ void ContextualSearchboxHandler::OnInputStateChanged( const contextual_search::InputState& state) { - input_state_ = std::make_unique<contextual_search::InputState>(state); if (!IsRemoteBound()) { return; } @@ -982,9 +988,3 @@ } contextual_session_handle->ClearSubmittedContextTokens(); } - -// TODO(crbug.com/479566933): Might be better to just get this from the -// InputStateModel rather than storing it in this handler. -omnibox::InputState ContextualSearchboxHandler::GetInputState() const { - return input_state_ ? *input_state_ : omnibox::InputState(); -}
diff --git a/chrome/browser/ui/webui/cr_components/searchbox/contextual_searchbox_handler.h b/chrome/browser/ui/webui/cr_components/searchbox/contextual_searchbox_handler.h index 04b1f20..a5bcdc6 100644 --- a/chrome/browser/ui/webui/cr_components/searchbox/contextual_searchbox_handler.h +++ b/chrome/browser/ui/webui/cr_components/searchbox/contextual_searchbox_handler.h
@@ -167,9 +167,6 @@ return context_input_data_; } - // SearchboxHandler: - omnibox::InputState GetInputState() const override; - std::vector<base::UnguessableToken> GetUploadedContextTokens(); contextual_search::InputStateModel* input_state_model() { @@ -188,6 +185,9 @@ } protected: + // SearchboxHandler: + omnibox::InputState GetInputState() const override; + void ComputeAndOpenQueryUrl( const std::string& query_text, WindowOpenDisposition disposition, @@ -279,9 +279,6 @@ std::optional<lens::ContextualInputData> context_input_data_; - std::unique_ptr<contextual_search::InputState> input_state_; - - // Callback to get the contextual session handle from WebUI controller. GetSessionHandleCallback get_session_callback_;
diff --git a/chrome/browser/ui/webui/cr_components/searchbox/searchbox_handler.cc b/chrome/browser/ui/webui/cr_components/searchbox/searchbox_handler.cc index 20c6d892..93e5741a 100644 --- a/chrome/browser/ui/webui/cr_components/searchbox/searchbox_handler.cc +++ b/chrome/browser/ui/webui/cr_components/searchbox/searchbox_handler.cc
@@ -67,7 +67,9 @@ #include "url/gurl.h" namespace searchbox_internal { -const char* kSearchIconResourceName = "//resources/images/icon_search.svg"; + +const char* kSearchSparkIconResourceName = + "//resources/cr_components/searchbox/icons/search_spark.svg"; } // namespace searchbox_internal namespace { @@ -149,8 +151,7 @@ const char* kPageIconResourceName = "//resources/cr_components/searchbox/icons/page.svg"; const char* kPedalsIconResourceName = "//theme/current-channel-logo"; -const char* kSearchSparkIconResourceName = - "//resources/cr_components/searchbox/icons/search_spark.svg"; +const char* kSearchIconResourceName = "//resources/images/icon_search.svg"; const char* kSparkIconResourceName = "//resources/cr_components/searchbox/icons/spark.svg"; const char* kStarActiveIconResourceName = @@ -215,7 +216,7 @@ "//resources/cr_components/searchbox/icons/page_cr23.svg"; kPedalsIconResourceName = "//resources/cr_components/searchbox/icons/chrome_product_cr23.svg"; - searchbox_internal::kSearchIconResourceName = + kSearchIconResourceName = "//resources/cr_components/searchbox/icons/search_cr23.svg"; kTabIconResourceName = "//resources/cr_components/searchbox/icons/tab_cr23.svg"; @@ -416,9 +417,9 @@ #if BUILDFLAG(GOOGLE_CHROME_BRANDING) ? kGoogleGIconResourceName #else - ? searchbox_internal::kSearchIconResourceName + ? kSearchIconResourceName #endif - : searchbox_internal::kSearchIconResourceName); + : kSearchIconResourceName); source->AddBoolean("searchboxVoiceSearch", enable_voice_search); source->AddBoolean("searchboxLensSearch", enable_lens_search); @@ -551,7 +552,7 @@ } else if (icon.name == omnibox::kProductChromeRefreshIcon.name) { return kPedalsIconResourceName; } else if (icon.name == omnibox::kSearchSparkIcon.name) { - return kSearchSparkIconResourceName; + return searchbox_internal::kSearchSparkIconResourceName; } else if (icon.name == omnibox::kSparkIcon.name) { return kSparkIconResourceName; } else if (icon.name == omnibox::kStarActiveChromeRefreshIcon.name) { @@ -565,7 +566,7 @@ } else if (icon.name == vector_icons::kHistoryChromeRefreshIcon.name) { return kHistoryIconResourceName; } else if (icon.name == vector_icons::kSearchChromeRefreshIcon.name) { - return searchbox_internal::kSearchIconResourceName; + return kSearchIconResourceName; } // Don't add new icons here. Add them alphabetically by `if` predicate. E.g.
diff --git a/chrome/browser/ui/webui/cr_components/searchbox/searchbox_handler.h b/chrome/browser/ui/webui/cr_components/searchbox/searchbox_handler.h index b03c4fa..bde39ed6 100644 --- a/chrome/browser/ui/webui/cr_components/searchbox/searchbox_handler.h +++ b/chrome/browser/ui/webui/cr_components/searchbox/searchbox_handler.h
@@ -37,7 +37,7 @@ namespace searchbox_internal { // Internal constants for icon resource paths shared by SearchboxHandler and its // subclasses. -extern const char* kSearchIconResourceName; +extern const char* kSearchSparkIconResourceName; } // namespace searchbox_internal // Base class for browser-side handlers that handle bi-directional communication
diff --git a/chrome/browser/ui/webui/searchbox/lens_searchbox_handler.cc b/chrome/browser/ui/webui/searchbox/lens_searchbox_handler.cc index 1080907..191a1d6 100644 --- a/chrome/browser/ui/webui/searchbox/lens_searchbox_handler.cc +++ b/chrome/browser/ui/webui/searchbox/lens_searchbox_handler.cc
@@ -126,9 +126,9 @@ const gfx::VectorIcon& icon) const { // The default icon for contextual suggestions is the subdirectory arrow right // icon. For the Lens searchbox, we want to stay consistent with the search - // loupe instead. + // spark loupe instead. if (icon.name == omnibox::kSubdirectoryArrowRightIcon.name) { - return searchbox_internal::kSearchIconResourceName; + return searchbox_internal::kSearchSparkIconResourceName; } return SearchboxHandler::AutocompleteIconToResourceName(icon);
diff --git a/chrome/browser/ui/webui/searchbox/realbox_handler.cc b/chrome/browser/ui/webui/searchbox/realbox_handler.cc index 64c3765..c81c89f8 100644 --- a/chrome/browser/ui/webui/searchbox/realbox_handler.cc +++ b/chrome/browser/ui/webui/searchbox/realbox_handler.cc
@@ -97,9 +97,9 @@ const gfx::VectorIcon& icon) const { // The default icon for contextual suggestions is the subdirectory arrow right // icon. For the Lens composebox and realbox, we want to stay consistent with - // the search loupe instead. + // the search spark loupe instead. if (icon.name == omnibox::kSubdirectoryArrowRightIcon.name) { - return searchbox_internal::kSearchIconResourceName; + return searchbox_internal::kSearchSparkIconResourceName; } return SearchboxHandler::AutocompleteIconToResourceName(icon);
diff --git a/chrome/browser/ui/webui/searchbox/searchbox_handler_unittest.cc b/chrome/browser/ui/webui/searchbox/searchbox_handler_unittest.cc index 6faf592c..83011765 100644 --- a/chrome/browser/ui/webui/searchbox/searchbox_handler_unittest.cc +++ b/chrome/browser/ui/webui/searchbox/searchbox_handler_unittest.cc
@@ -397,7 +397,8 @@ { SCOPED_TRACE("Icon override"); - const char search_icon[] = "//resources/images/icon_search.svg"; + const char search_icon[] = + "//resources/cr_components/searchbox/icons/search_spark.svg"; const std::string& svg_name = handler_->AutocompleteIconToResourceName( omnibox::kSubdirectoryArrowRightIcon);
diff --git a/chrome/browser/ui/webui/searchbox/webui_omnibox_handler.cc b/chrome/browser/ui/webui/searchbox/webui_omnibox_handler.cc index 758be069..70dc464 100644 --- a/chrome/browser/ui/webui/searchbox/webui_omnibox_handler.cc +++ b/chrome/browser/ui/webui/searchbox/webui_omnibox_handler.cc
@@ -436,8 +436,8 @@ std::string WebuiOmniboxHandler::AutocompleteIconToResourceName( const gfx::VectorIcon& icon) const { // The default icon for contextual suggestions is the subdirectory arrow right - // icon. If there is no header enabled (which is when the lens chip is not - // showing), use the search loupe instead. + // icon. If there is a header enabled (which is when the lens chip is + // showing), use the search spark loupe instead. const auto& input = autocomplete_controller()->input(); bool has_toolbelt_lens_action = autocomplete_controller()->contextual_search_provider() && @@ -449,9 +449,9 @@ bool has_lens_search_chip = client->IsOmniboxNextLensSearchChipEnabled() && ContextualSearchProvider::LensEntrypointEligible(input, client); - if (!(has_toolbelt_lens_action || has_lens_search_chip) && + if ((has_toolbelt_lens_action || has_lens_search_chip) && icon.name == omnibox::kSubdirectoryArrowRightIcon.name) { - return searchbox_internal::kSearchIconResourceName; + return searchbox_internal::kSearchSparkIconResourceName; } return SearchboxHandler::AutocompleteIconToResourceName(icon);
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc index 33c1630a..fe7a1d36 100644 --- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc +++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -1640,6 +1640,8 @@ IDS_AUTOFILL_AI_ADD_OR_EDIT_DIALOG_REQUIRED_FIELD_ERROR}, {"saveInfoToWalletAccountNotice", IDS_AUTOFILL_AI_SAVE_ENTITY_TO_WALLET_DIALOG_SUBTITLE}, + {"saveInfoToWalletSettingsAccountNotice", + IDS_AUTOFILL_AI_SAVE_ENTITY_TO_WALLET_SETTINGS_SUBTITLE}, {"autofillAiSubpageSublabelLoggingManagedDisabled", IDS_SETTINGS_AUTOFILL_AI_ENTERPRISE_LOGGING_MANAGED_DISABLED}, {"autofillPayOverTimeSettingsLabel", IDS_AUTOFILL_BNPL_SETTINGS_LABEL},
diff --git a/chrome/browser/ui/webui/signin/turn_sync_on_helper_delegate_impl.cc b/chrome/browser/ui/webui/signin/turn_sync_on_helper_delegate_impl.cc index aca1ab3..31ef532 100644 --- a/chrome/browser/ui/webui/signin/turn_sync_on_helper_delegate_impl.cc +++ b/chrome/browser/ui/webui/signin/turn_sync_on_helper_delegate_impl.cc
@@ -22,10 +22,10 @@ #include "chrome/browser/signin/signin_util.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_finder.h" -#include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_tabstrip.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/browser_window/public/browser_window_features.h" +#include "chrome/browser/ui/browser_window/public/browser_window_interface.h" #include "chrome/browser/ui/chrome_pages.h" #include "chrome/browser/ui/profiles/profile_colors_util.h" #include "chrome/browser/ui/signin/signin_view_controller.h" @@ -95,12 +95,12 @@ user_already_signed_in_(user_already_signed_in) { DCHECK(browser); DCHECK(profile_); - BrowserList::AddObserver(this); + browser_close_subscription_ = browser_->RegisterBrowserDidClose( + base::BindRepeating(&TurnSyncOnHelperDelegateImpl::OnBrowserDidClose, + base::Unretained(this))); } -TurnSyncOnHelperDelegateImpl::~TurnSyncOnHelperDelegateImpl() { - BrowserList::RemoveObserver(this); -} +TurnSyncOnHelperDelegateImpl::~TurnSyncOnHelperDelegateImpl() = default; bool TurnSyncOnHelperDelegateImpl::IsProfileCreationRequiredByPolicy() const { return profile_creation_required_by_policy_; @@ -207,8 +207,9 @@ std::move(sync_confirmation_callback_).Run(result); } -void TurnSyncOnHelperDelegateImpl::OnBrowserRemoved(Browser* browser) { - if (browser == browser_) { +void TurnSyncOnHelperDelegateImpl::OnBrowserDidClose( + BrowserWindowInterface* browser) { + if (browser_ == browser) { browser_ = nullptr; } }
diff --git a/chrome/browser/ui/webui/signin/turn_sync_on_helper_delegate_impl.h b/chrome/browser/ui/webui/signin/turn_sync_on_helper_delegate_impl.h index 906989f..d0342a3 100644 --- a/chrome/browser/ui/webui/signin/turn_sync_on_helper_delegate_impl.h +++ b/chrome/browser/ui/webui/signin/turn_sync_on_helper_delegate_impl.h
@@ -5,16 +5,17 @@ #ifndef CHROME_BROWSER_UI_WEBUI_SIGNIN_TURN_SYNC_ON_HELPER_DELEGATE_IMPL_H_ #define CHROME_BROWSER_UI_WEBUI_SIGNIN_TURN_SYNC_ON_HELPER_DELEGATE_IMPL_H_ +#include "base/callback_list.h" #include "base/functional/callback_forward.h" #include "base/memory/raw_ptr.h" #include "base/scoped_observation.h" -#include "chrome/browser/ui/browser_list_observer.h" #include "chrome/browser/ui/webui/signin/login_ui_service.h" #include "chrome/browser/ui/webui/signin/turn_sync_on_helper.h" class Browser; class Profile; class SigninUIError; +class BrowserWindowInterface; struct AccountInfo; namespace policy { @@ -24,7 +25,6 @@ // Default implementation for TurnSyncOnHelper::Delegate. class TurnSyncOnHelperDelegateImpl : public TurnSyncOnHelper::Delegate, - public BrowserListObserver, public LoginUIService::Observer { public: explicit TurnSyncOnHelperDelegateImpl(Browser* browser, @@ -68,8 +68,7 @@ void OnSyncConfirmationUIClosed( LoginUIService::SyncConfirmationUIClosedResult result) override; - // BrowserListObserver: - void OnBrowserRemoved(Browser* browser) override; + void OnBrowserDidClose(BrowserWindowInterface* browser); void OnProfileSigninRestrictionsFetched( const AccountInfo& account_info, @@ -92,6 +91,7 @@ sync_confirmation_callback_; base::ScopedObservation<LoginUIService, LoginUIService::Observer> scoped_login_ui_service_observation_{this}; + base::CallbackListSubscription browser_close_subscription_; const bool is_sync_promo_; const bool user_already_signed_in_; bool profile_creation_required_by_policy_ = false;
diff --git a/chrome/browser/ui/webui/top_chrome/webui_contents_preload_manager.cc b/chrome/browser/ui/webui/top_chrome/webui_contents_preload_manager.cc index cf8adfb..59b558a 100644 --- a/chrome/browser/ui/webui/top_chrome/webui_contents_preload_manager.cc +++ b/chrome/browser/ui/webui/top_chrome/webui_contents_preload_manager.cc
@@ -12,6 +12,7 @@ #include "base/auto_reset.h" #include "base/feature_list.h" #include "base/memory/weak_ptr.h" +#include "base/memory_coordinator/utils.h" #include "base/metrics/histogram_functions.h" #include "base/no_destructor.h" #include "base/scoped_observation.h" @@ -551,7 +552,7 @@ } // Don't preload if under heavy memory pressure. - if (memory_pressure_level() >= base::MEMORY_PRESSURE_LEVEL_MODERATE) { + if (GetMemoryLimit() <= base::kModerateMemoryPressureThreshold) { return false; }
diff --git a/chrome/browser/ui/webui_browser/webui_stub_location_bar.cc b/chrome/browser/ui/webui_browser/webui_stub_location_bar.cc index f9e156dd..f1a62210 100644 --- a/chrome/browser/ui/webui_browser/webui_stub_location_bar.cc +++ b/chrome/browser/ui/webui_browser/webui_stub_location_bar.cc
@@ -83,7 +83,7 @@ BrowserElements::From(window_->browser()) ->GetElement(kLocationIconElementId); CHECK(location_button) << "Location button not found"; - return {{location_button, nullptr, views::BubbleBorder::TOP_LEFT}}; + return {{location_button, std::nullopt, views::BubbleBorder::TOP_LEFT}}; } ui::TrackedElement* WebUIStubLocationBar::GetAnchorOrNull() {
diff --git a/chrome/browser/user_education/OWNERS b/chrome/browser/user_education/OWNERS index 3984fd6..71fefd0 100644 --- a/chrome/browser/user_education/OWNERS +++ b/chrome/browser/user_education/OWNERS
@@ -6,7 +6,7 @@ per-file ntp_promo_identifiers.h=file://components/search/OWNERS # Android User Education -per-file java=mdjones@chromium.org -per-file java=dtrainor@chromium.org -per-file java=twellington@chromium.org -per-file java=pnoland@chromium.org +per-file java/...=mdjones@chromium.org +per-file java/...=dtrainor@chromium.org +per-file java/...=twellington@chromium.org +per-file java/...=pnoland@chromium.org
diff --git a/chrome/browser/webapps/webapps_client_android.cc b/chrome/browser/webapps/webapps_client_android.cc index e4eb7a7..fa4124e 100644 --- a/chrome/browser/webapps/webapps_client_android.cc +++ b/chrome/browser/webapps/webapps_client_android.cc
@@ -73,7 +73,8 @@ AppBannerManager* WebappsClientAndroid::GetAppBannerManager( content::WebContents* web_contents) { - return AppBannerManagerAndroid::FromWebContents(web_contents); + return AppBannerManagerAndroid::FromWebContents(web_contents) + ->app_banner_manager(); } void WebappsClientAndroid::DoesNewWebAppConflictWithExistingInstallation(
diff --git a/chrome/browser/webapps/webapps_client_desktop.cc b/chrome/browser/webapps/webapps_client_desktop.cc index b310eae..f2c1462 100644 --- a/chrome/browser/webapps/webapps_client_desktop.cc +++ b/chrome/browser/webapps/webapps_client_desktop.cc
@@ -139,7 +139,10 @@ AppBannerManager* WebappsClientDesktop::GetAppBannerManager( content::WebContents* web_contents) { CHECK(web_contents); - return AppBannerManagerDesktop::FromWebContents(web_contents); + if (auto* manager = AppBannerManagerDesktop::FromWebContents(web_contents)) { + return manager->app_banner_manager(); + } + return nullptr; } void WebappsClientDesktop::DoesNewWebAppConflictWithExistingInstallation(
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt index 03f3346..cb43870a 100644 --- a/chrome/build/mac-arm.pgo.txt +++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@ -chrome-mac-arm-main-1772459977-390bff46b0bd97999caa2c4de4c8554322adcb6f-cd4df0e4126e41e0c339a74e16a7e49ab19ac27a.profdata +chrome-mac-arm-main-1772467097-fda27f21bbd234f587024bf5b51f875f549ed401-badf89d3ae6d762c6bba40a435718433f319f083.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt index d6c0841..552709e 100644 --- a/chrome/build/win32.pgo.txt +++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@ -chrome-win32-main-1772452664-69fb216286b06520e4678e3fb5ddd17012ec9875-70e9665a0f0459190b244583429f06218d92b93b.profdata +chrome-win32-main-1772463577-469dd2ce8ffb93906798f94831b7d8444cc6e072-42e75cf209227032ab9a3327363299ce2c73e741.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt index 9368497..3a588e3 100644 --- a/chrome/build/win64.pgo.txt +++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@ -chrome-win64-main-1772452664-9e682d60de0350b2f7eadde1f584c07df87e5a69-70e9665a0f0459190b244583429f06218d92b93b.profdata +chrome-win64-main-1772463577-83c5c9b110bcb4e94d25c8f594ab863f3322b46e-42e75cf209227032ab9a3327363299ce2c73e741.profdata
diff --git a/chrome/common/apps/platform_apps/api/_permission_features.json b/chrome/common/apps/platform_apps/api/_permission_features.json index 0285d94..b774bdc0 100644 --- a/chrome/common/apps/platform_apps/api/_permission_features.json +++ b/chrome/common/apps/platform_apps/api/_permission_features.json
@@ -50,7 +50,9 @@ "89B67AA6D43A6D5DDF4718375C325217E38EC60F", // Final Prod "21A31B0290A456FF27425090BDC4E4903234AEB3", // Final QA // https://crbug.com/433867146 - "4B7E9789426EBD1BB3F99D72690DAE303FC042DF" // AuthX Secure for ChromeOS + "4B7E9789426EBD1BB3F99D72690DAE303FC042DF", // AuthX Secure for ChromeOS + // https://crbug.com/485245112 + "36F5326E7683E204CE9CE1532093D8BB9CD5D280" // AuthX in-session extension ] }], "firstRunPrivate": {
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc index 37954548..f40fe7ff 100644 --- a/chrome/common/chrome_features.cc +++ b/chrome/common/chrome_features.cc
@@ -2000,4 +2000,11 @@ base::FEATURE_DISABLED_BY_DEFAULT); #endif // BUILDFLAG(IS_CHROMEOS) +#if !BUILDFLAG(IS_ANDROID) +// A feature to enable smart restart metrics collection. The collected metrics +// will be used to make informed decisions about the future of the smart restart +// feature. +BASE_FEATURE(kSmartRestartMetrics, base::FEATURE_ENABLED_BY_DEFAULT); +#endif // BUILDFLAG(IS_ANDROID) + } // namespace features
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h index 5affe31..8567f8dc 100644 --- a/chrome/common/chrome_features.h +++ b/chrome/common/chrome_features.h
@@ -1445,6 +1445,11 @@ BASE_DECLARE_FEATURE(kClassManagementEnabledMetricsProvider); #endif // BUILDFLAG(IS_CHROMEOS) +#if !BUILDFLAG(IS_ANDROID) +COMPONENT_EXPORT(CHROME_FEATURES) +BASE_DECLARE_FEATURE(kSmartRestartMetrics); +#endif // BUILDFLAG(IS_ANDROID) + bool PrefServiceEnabled(); // DON'T ADD RANDOM STUFF HERE. Put it in the main section above in
diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json index 185d625..d0dd320 100644 --- a/chrome/common/extensions/api/_api_features.json +++ b/chrome/common/extensions/api/_api_features.json
@@ -690,7 +690,9 @@ "7FE4A999359A456C4B0FB7B7AD85CEA29CA50519", // Login screen APIs test extension "3F5995FE79A861F019C6F093BEF98D73FA9D3A5F", // Login screen APIs in-session test extension // https://crbug.com/433867146 - "4B7E9789426EBD1BB3F99D72690DAE303FC042DF" // AuthX Secure for ChromeOS + "4B7E9789426EBD1BB3F99D72690DAE303FC042DF", // AuthX Secure for ChromeOS + // https://crbug.com/485245112 + "36F5326E7683E204CE9CE1532093D8BB9CD5D280" // AuthX in-session extension ] }], "login.exitCurrentSession": [{ @@ -704,6 +706,8 @@ "3F5995FE79A861F019C6F093BEF98D73FA9D3A5F", // Login screen APIs in-session test extension // https://crbug.com/433867146 "4B7E9789426EBD1BB3F99D72690DAE303FC042DF", // AuthX Secure for ChromeOS + // https://crbug.com/485245112 + "36F5326E7683E204CE9CE1532093D8BB9CD5D280", // AuthX in-session extension // Only has access to login.exitCurrentSession "8ECFC754A70BE499325FA4BB705E62EFEEC1BC80", // b/314208017 "ECD28F2B60BDB3B4E566D6C60BD88BE0774044C1", // b/314208017
diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json index b43fe9e..16ef4381 100644 --- a/chrome/common/extensions/api/_permission_features.json +++ b/chrome/common/extensions/api/_permission_features.json
@@ -591,7 +591,9 @@ "allowlist": [ "93B7178E8C275515C38D4F43D9DF4648F0EA9B03", // Identity Card Connector // https://crbug.com/433867146 - "4B7E9789426EBD1BB3F99D72690DAE303FC042DF" // AuthX Secure for ChromeOS + "4B7E9789426EBD1BB3F99D72690DAE303FC042DF", // AuthX Secure for ChromeOS + // https://crbug.com/485245112 + "36F5326E7683E204CE9CE1532093D8BB9CD5D280" // AuthX in-session extension ] }], "loginScreenUi": [{
diff --git a/chrome/common/importer/firefox_importer_utils_linux.cc b/chrome/common/importer/firefox_importer_utils_linux.cc index 53a2936d..de5b71d 100644 --- a/chrome/common/importer/firefox_importer_utils_linux.cc +++ b/chrome/common/importer/firefox_importer_utils_linux.cc
@@ -5,8 +5,10 @@ #include "chrome/common/importer/firefox_importer_utils.h" #include "base/base_paths.h" +#include "base/environment.h" #include "base/files/file_path.h" #include "base/files/file_util.h" +#include "base/nix/xdg_util.h" #include "base/path_service.h" namespace { @@ -51,20 +53,44 @@ constexpr const base::FilePath::CharType* const kFirefoxProfilesIniSubpath = FILE_PATH_LITERAL(".mozilla/firefox/profiles.ini"); +// Same as `kFirefoxProfilesIniSubpath`, just without the leading dot. +// Introduced in Firefox 147: +// https://github.com/mozilla-firefox/firefox/commit/dea79b8a60f1. +constexpr const base::FilePath::StringViewType kFirefoxProfilesXdgIniSubpath = + kFirefoxProfilesIniSubpath + 1; + +base::FilePath GetXdgProfilesINI() { + auto env = base::Environment::Create(); + base::FilePath xdg_config_dir = base::nix::GetXDGDirectory( + env.get(), base::nix::kXdgConfigHomeEnvVar, base::nix::kDotConfigDir); + base::FilePath ini_file = + xdg_config_dir.Append(kFirefoxProfilesXdgIniSubpath); + if (base::PathExists(ini_file)) { + return ini_file; + } + + return {}; +} + } // namespace base::FilePath GetProfilesINI() { + // First, check the XDG-style (~/.config/mozilla/firefox) path. + if (auto xdg_ini_file = GetXdgProfilesINI(); !xdg_ini_file.empty()) { + return xdg_ini_file; + } + + // If that doesn't exist, iterate through possible Firefox profile roots + // (locations containing `profiles.ini`), starting with the standard location. + // Note: If a user has a stale standard profile (e.g. the application was + // uninstalled, but user data was preserved) and an active Snap/Flatpak + // installation, we will pick up the stale one. base::FilePath home; base::PathService::Get(base::DIR_HOME, &home); if (home.empty()) { return base::FilePath(); } - // Iterate through possible Firefox profile roots (locations containing - // `profiles.ini`), starting with the standard location. - // Note: If a user has a stale standard profile (e.g. the application was - // uninstalled, but user data was preserved) and an active Snap/Flatpak - // installation, we will pick up the stale one. for (const auto* profile_storage_base : kFirefoxProfileStorageBasePaths) { base::FilePath ini_file = home; ini_file = ini_file.Append(profile_storage_base)
diff --git a/chrome/installer/util/google_update_settings.h b/chrome/installer/util/google_update_settings.h index e737190..4ce8f798 100644 --- a/chrome/installer/util/google_update_settings.h +++ b/chrome/installer/util/google_update_settings.h
@@ -85,13 +85,6 @@ // synchronously on first run, startup, etc.). static base::SequencedTaskRunner* CollectStatsConsentTaskRunner(); -#if BUILDFLAG(IS_POSIX) - // Returns whether the user has given consent to collect UMA data and send - // crash dumps to Google. This method reads the information from a custom - // directory. - static bool GetCollectStatsConsentFromDir(const base::FilePath& consent_dir); -#endif // BUILDFLAG(IS_POSIX) - // Returns whether the user has given consent to collect UMA data and send // crash dumps to Google. This information is collected by the web server // used to download the chrome installer. @@ -101,7 +94,6 @@ // false if the setting could not be recorded. static bool SetCollectStatsConsent(bool consented); -#if BUILDFLAG(IS_WIN) // Returns the default (original) state of the "send usage stats" checkbox // shown to the user when they downloaded Chrome. The value is returned via // the out parameter |stats_consent_default|. This function returns true if @@ -109,7 +101,6 @@ // will not be set. [[nodiscard]] static bool GetCollectStatsConsentDefault( bool* stats_consent_default); -#endif // Returns a hash of the current update cohort ID string to which the // browser is assigned, if any. Discards any cohort data past the final ":".
diff --git a/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc b/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc index 5adbda1..33c854a 100644 --- a/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc +++ b/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc
@@ -14,7 +14,9 @@ #include "base/memory/raw_ptr.h" #include "base/memory/ref_counted_memory.h" #include "base/memory/scoped_refptr.h" +#include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" +#include "base/test/gmock_callback_support.h" #include "base/test/scoped_feature_list.h" #include "chrome/test/base/chrome_render_view_test.h" #include "chrome/test/base/chrome_unit_test_suite.h" @@ -218,9 +220,11 @@ const auto page_text = MakeRefPtrString(u"dummy"); const auto page_text2 = MakeRefPtrString(u"dummy2"); { - InSequence s; - EXPECT_CALL(*classifier_, BeginClassification(_)); + base::RunLoop run_loop; + EXPECT_CALL(*classifier_, BeginClassification(_)) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); delegate_->PageCaptured(page_text, false); + run_loop.Run(); Mock::VerifyAndClearExpectations(classifier_); } @@ -231,20 +235,37 @@ // Start phishing detection without a fresh page text should still classify, // because the top level URL still match. - EXPECT_CALL(*classifier_, BeginClassification(_)); - OnStartPhishingDetection(url); - delegate_->PageCaptured(page_text, false); - Mock::VerifyAndClearExpectations(classifier_); + { + base::RunLoop run_loop; + EXPECT_CALL(*classifier_, BeginClassification(_)) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); + OnStartPhishingDetection(url); + delegate_->PageCaptured(page_text, false); + run_loop.Run(); + Mock::VerifyAndClearExpectations(classifier_); + } - EXPECT_CALL(*classifier_, BeginClassification(_)); - OnStartPhishingDetection(url); - delegate_->PageCaptured(page_text, false); - Mock::VerifyAndClearExpectations(classifier_); + // Even if the page text is captured first, it shouldn't matter since the + // browser request will start the classification. + { + base::RunLoop run_loop; + EXPECT_CALL(*classifier_, BeginClassification(_)) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); + delegate_->PageCaptured(page_text, false); + OnStartPhishingDetection(url); + run_loop.Run(); + Mock::VerifyAndClearExpectations(classifier_); + } - EXPECT_CALL(*classifier_, BeginClassification(_)); - OnStartPhishingDetection(url); - delegate_->PageCaptured(page_text, false); - Mock::VerifyAndClearExpectations(classifier_); + { + base::RunLoop run_loop; + EXPECT_CALL(*classifier_, BeginClassification(_)) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); + OnStartPhishingDetection(url); + delegate_->PageCaptured(page_text, false); + run_loop.Run(); + Mock::VerifyAndClearExpectations(classifier_); + } // Now load a new toplevel page, which should trigger another classification. EXPECT_CALL(*classifier_, CancelPendingClassification()); @@ -255,9 +276,11 @@ OnStartPhishingDetection(new_url); { - InSequence s; - EXPECT_CALL(*classifier_, BeginClassification(_)); + base::RunLoop run_loop; + EXPECT_CALL(*classifier_, BeginClassification(_)) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); delegate_->PageCaptured(page_text2, false); + run_loop.Run(); Mock::VerifyAndClearExpectations(classifier_); } @@ -366,14 +389,20 @@ // Now set a scorer, which should cause a classifier to be created, and // classification will happen again because the scorer is set within timeout. - EXPECT_CALL(*classifier_, BeginClassification(_)); + base::RunLoop run_loop; + EXPECT_CALL(*classifier_, BeginClassification(_)) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); SetScorer(/*model_version=*/1); + run_loop.Run(); Mock::VerifyAndClearExpectations(classifier_); // Manually start a classification, so that when a new scorer is set, it // should cancel. - EXPECT_CALL(*classifier_, BeginClassification(_)); + base::RunLoop run_loop2; + EXPECT_CALL(*classifier_, BeginClassification(_)) + .WillOnce(base::test::RunOnceClosure(run_loop2.QuitClosure())); OnStartPhishingDetection(url2); + run_loop2.Run(); // If we set a new scorer while a classification is going on the // classification should be cancelled. @@ -403,16 +432,24 @@ OnStartPhishingDetection(url); delegate_->PageCaptured(page_text, false); + task_environment_.RunUntilIdle(); + // Now set a scorer, which should cause a classifier to be created, and // classification will happen again because the scorer is set within timeout. - EXPECT_CALL(*classifier_, BeginClassification(_)); + base::RunLoop run_loop; + EXPECT_CALL(*classifier_, BeginClassification(_)) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); SetScorer(/*model_version=*/1); + run_loop.Run(); Mock::VerifyAndClearExpectations(classifier_); // Manually start a classification, so that when a new scorer is set, it // should cancel. - EXPECT_CALL(*classifier_, BeginClassification(_)); + base::RunLoop run_loop2; + EXPECT_CALL(*classifier_, BeginClassification(_)) + .WillOnce(base::test::RunOnceClosure(run_loop2.QuitClosure())); OnStartPhishingDetection(url); + run_loop2.Run(); // If we set a new scorer while a classification is going on the // classification should be cancelled. @@ -455,8 +492,11 @@ // Manually start a classification, so that when a new scorer is set, it // should cancel. - EXPECT_CALL(*classifier_, BeginClassification(_)); + base::RunLoop run_loop; + EXPECT_CALL(*classifier_, BeginClassification(_)) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); OnStartPhishingDetection(url2); + run_loop.Run(); // If we set a new scorer while a classification is going on the // classification should be cancelled. @@ -497,8 +537,11 @@ Mock::VerifyAndClearExpectations(classifier_); // Manually start a classification - EXPECT_CALL(*classifier_, BeginClassification(_)); + base::RunLoop run_loop; + EXPECT_CALL(*classifier_, BeginClassification(_)) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); OnStartPhishingDetection(url); + run_loop.Run(); // If we set a new scorer while a classification is going on the // classification should be cancelled. @@ -551,8 +594,11 @@ Mock::VerifyAndClearExpectations(classifier_); // Now simulate the StartPhishingDetection IPC. We expect classification // to begin. - EXPECT_CALL(*classifier_, BeginClassification(_)); + base::RunLoop run_loop; + EXPECT_CALL(*classifier_, BeginClassification(_)) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); OnStartPhishingDetection(url); + run_loop.Run(); Mock::VerifyAndClearExpectations(classifier_); // Now try again, but this time we will navigate the page away before @@ -586,18 +632,30 @@ Mock::VerifyAndClearExpectations(classifier_); EXPECT_CALL(*classifier_, CancelPendingClassification()); + // Now the redirecting URL HTML has been loaded. GURL redir_url("http://host4.com/redir"); LoadHTMLWithUrlOverride("123", redir_url.spec().c_str()); Mock::VerifyAndClearExpectations(classifier_); - EXPECT_CALL(*classifier_, BeginClassification(_)); + // Although the redirecting URL HTML has already loaded, browser requested + // from the original URL, but it shouldn't trigger anything, if with new + // observers. + if (!base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + EXPECT_CALL(*classifier_, BeginClassification(_)); + } OnStartPhishingDetection(url4); page_text = MakeRefPtrString(u"123"); { - InSequence s; - EXPECT_CALL(*classifier_, BeginClassification(_)); + base::RunLoop run_loop2; + EXPECT_CALL(*classifier_, BeginClassification(_)) + .WillOnce(base::test::RunOnceClosure(run_loop2.QuitClosure())); + // The below essentially called OnStartPhishingDetection by replacing the + // URL. SimulateRedirection(redir_url); + // Page has finally captured for the redirecting URL. With the layout + // complete, it will start classification on landing page. delegate_->PageCaptured(page_text, false); + run_loop2.Run(); Mock::VerifyAndClearExpectations(classifier_); } @@ -623,9 +681,11 @@ // Once the non-preliminary capture happens, classification should begin. { - InSequence s; - EXPECT_CALL(*classifier_, BeginClassification(_)); + base::RunLoop run_loop; + EXPECT_CALL(*classifier_, BeginClassification(_)) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); delegate_->PageCaptured(page_text, false); + run_loop.Run(); Mock::VerifyAndClearExpectations(classifier_); } @@ -649,9 +709,11 @@ Mock::VerifyAndClearExpectations(classifier_); OnStartPhishingDetection(url); { - InSequence s; - EXPECT_CALL(*classifier_, BeginClassification(_)); + base::RunLoop run_loop; + EXPECT_CALL(*classifier_, BeginClassification(_)) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); delegate_->PageCaptured(page_text, false); + run_loop.Run(); Mock::VerifyAndClearExpectations(classifier_); } @@ -678,9 +740,11 @@ Mock::VerifyAndClearExpectations(classifier_); OnStartPhishingDetection(url); { - InSequence s; - EXPECT_CALL(*classifier_, BeginClassification(_)); + base::RunLoop run_loop; + EXPECT_CALL(*classifier_, BeginClassification(_)) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); delegate_->PageCaptured(page_text, false); + run_loop.Run(); Mock::VerifyAndClearExpectations(classifier_); }
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index 6b34ce6..01968fc 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn
@@ -3532,6 +3532,7 @@ "../browser/language_detection/language_detection_model_service_browsertest.cc", "../browser/lifetime/browser_close_manager_browsertest.cc", "../browser/lifetime/browser_shutdown_browsertest.cc", + "../browser/lifetime/smart_restart_metrics_observer_browsertest.cc", "../browser/loader/keep_alive_category_request_browsertest.cc", "../browser/loader/keep_alive_request_browsertest_util.cc", "../browser/loader/keep_alive_request_browsertest_util.h", @@ -8348,6 +8349,7 @@ "../browser/hid/hid_policy_allowed_devices_unittest.cc", "../browser/hid/hid_test_utils.cc", "../browser/hid/hid_test_utils.h", + "../browser/lifetime/smart_restart_metrics_observer_unittest.cc", "../browser/media/unified_autoplay_config_unittest.cc", "../browser/media/webrtc/current_tab_desktop_media_list_unittest.cc", "../browser/media/webrtc/desktop_capture_access_handler_unittest.cc",
diff --git a/chrome/test/base/test_browser_window.cc b/chrome/test/base/test_browser_window.cc index d2a861297..6ae7b9a92 100644 --- a/chrome/test/base/test_browser_window.cc +++ b/chrome/test/base/test_browser_window.cc
@@ -9,9 +9,9 @@ #include "base/feature_list.h" #include "base/values.h" #include "build/build_config.h" -#include "chrome/browser/ui/browser_list.h" -#include "chrome/browser/ui/browser_list_observer.h" +#include "chrome/browser/ui/browser_window/public/browser_collection_observer.h" #include "chrome/browser/ui/browser_window/public/browser_window_features.h" +#include "chrome/browser/ui/browser_window/public/global_browser_collection.h" #include "chrome/browser/ui/find_bar/find_bar.h" #include "chrome/browser/ui/user_education/browser_user_education_interface.h" #include "chrome/browser/ui/views/bubble_anchor_util_views.h" @@ -129,7 +129,8 @@ // TestBrowserWindow will always be instantiated before its Browser. // TODO(crbug.com/413168662): This can be removed once Browser is updated to // always own its BrowserWindow. - browser_list_observer_.Observe(BrowserList::GetInstance()); + browser_collection_observation_.Observe( + GlobalBrowserCollection::GetInstance()); } TestBrowserWindow::~TestBrowserWindow() { @@ -435,9 +436,10 @@ is_tab_modal_popup_deprecated_ = is_tab_modal_popup_deprecated; } -void TestBrowserWindow::OnBrowserAdded(Browser* browser) { - if (browser->create_params().window == this) { - browser_ = browser; - browser_list_observer_.Reset(); +void TestBrowserWindow::OnBrowserCreated(BrowserWindowInterface* browser) { + Browser* created_browser = browser->GetBrowserForMigrationOnly(); + if (created_browser->create_params().window == this) { + browser_ = created_browser; + browser_collection_observation_.Reset(); } }
diff --git a/chrome/test/base/test_browser_window.h b/chrome/test/base/test_browser_window.h index 023990d..570aa2d 100644 --- a/chrome/test/base/test_browser_window.h +++ b/chrome/test/base/test_browser_window.h
@@ -14,9 +14,9 @@ #include "build/build_config.h" #include "chrome/browser/ui/autofill/test/test_autofill_bubble_handler.h" #include "chrome/browser/ui/browser.h" -#include "chrome/browser/ui/browser_list.h" -#include "chrome/browser/ui/browser_list_observer.h" #include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/browser_window/public/browser_collection_observer.h" +#include "chrome/browser/ui/browser_window/public/global_browser_collection.h" #include "chrome/browser/ui/dialogs/browser_dialogs.h" #include "chrome/browser/ui/location_bar/location_bar.h" #include "chrome/browser/ui/translate/partial_translate_bubble_model.h" @@ -32,6 +32,7 @@ class LocationBarTesting; class OmniboxView; +class BrowserWindowInterface; namespace qrcode_generator { class QRCodeGeneratorBubbleView; @@ -55,7 +56,8 @@ // contains a valid LocationBar, all other getters return NULL. // However, some of them can be preset to a specific value. // See BrowserWithTestWindowTest for an example of using this class. -class TestBrowserWindow : public BrowserWindow, public BrowserListObserver { +class TestBrowserWindow : public BrowserWindow, + public BrowserCollectionObserver { public: TestBrowserWindow(); TestBrowserWindow(const TestBrowserWindow&) = delete; @@ -309,8 +311,8 @@ bool HasSecurityStateChanged() override; }; - // BrowserListObserver: - void OnBrowserAdded(Browser* browser) override; + // BrowserCollectionObserver: + void OnBrowserCreated(BrowserWindowInterface* browser) override; autofill::TestAutofillBubbleHandler autofill_bubble_handler_; TestLocationBar location_bar_; @@ -324,8 +326,8 @@ bool is_tab_strip_editable_ = true; bool is_tab_modal_popup_deprecated_ = false; - base::ScopedObservation<BrowserList, BrowserListObserver> - browser_list_observer_{this}; + base::ScopedObservation<GlobalBrowserCollection, BrowserCollectionObserver> + browser_collection_observation_{this}; raw_ptr<Browser> browser_; base::OnceClosure close_callback_; };
diff --git a/chrome/test/base/testing_browser_process.cc b/chrome/test/base/testing_browser_process.cc index ec5e24c..0841d71 100644 --- a/chrome/test/base/testing_browser_process.cc +++ b/chrome/test/base/testing_browser_process.cc
@@ -248,6 +248,8 @@ testing_profile_manager_.reset(); + profile_manager_.reset(); + // ResourceCoordinatorParts owns TabLifecycleUnitSource, which depends on a // Global Feature (GlobalBrowserCollection). Thus, we need to make sure // ResourceCoordinatorParts is destroyed before GlobalFeatures is completely
diff --git a/chrome/test/data/webui/chromeos/projector_app/BUILD.gn b/chrome/test/data/webui/chromeos/projector_app/BUILD.gn index 20313cbb..9045654a 100644 --- a/chrome/test/data/webui/chromeos/projector_app/BUILD.gn +++ b/chrome/test/data/webui/chromeos/projector_app/BUILD.gn
@@ -12,9 +12,6 @@ is_chrome_untrusted = true ts_path_mappings = [ - "chrome-untrusted://projector/untrusted_projector_browser_proxy.js|" + rebase_path( - "//ash/webui/projector_app/resources/app/untrusted/untrusted_projector_browser_proxy.js", - target_gen_dir), "chrome-untrusted://projector/*|" + rebase_path( "$root_gen_dir/ash/webui/projector_app/resources/app/untrusted/tsc/*", target_gen_dir),
diff --git a/chrome/test/data/webui/chromeos/projector_app/untrusted_projector_browser_proxy_test.ts b/chrome/test/data/webui/chromeos/projector_app/untrusted_projector_browser_proxy_test.ts index 15b6438..0498c29 100644 --- a/chrome/test/data/webui/chromeos/projector_app/untrusted_projector_browser_proxy_test.ts +++ b/chrome/test/data/webui/chromeos/projector_app/untrusted_projector_browser_proxy_test.ts
@@ -3,10 +3,6 @@ // found in the LICENSE file. import {JsNetErrorCode} from 'chrome-untrusted://projector/ash/webui/projector_app/public/mojom/projector_types.mojom-webui.js'; -// TODO(crbug.com/485894073): Migrate untrusted_projector_browser_proxy to TS, -// and remove the lint disable. -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore: Legacy JS file missing type declarations. import {browserProxy} from 'chrome-untrusted://projector/untrusted_projector_browser_proxy.js'; import {assertDeepEquals} from 'chrome-untrusted://webui-test/chai_assert.js';
diff --git a/chrome/test/data/webui/glic/browser_tests/glic_api_browsertest.ts b/chrome/test/data/webui/glic/browser_tests/glic_api_browsertest.ts index b9d3d739..1f19967f 100644 --- a/chrome/test/data/webui/glic/browser_tests/glic_api_browsertest.ts +++ b/chrome/test/data/webui/glic/browser_tests/glic_api_browsertest.ts
@@ -2764,6 +2764,8 @@ return 'SHARE_ADDITIONAL_IMAGE_CONTEXT'; case HostCapability.PDF_ZERO_STATE: return 'PDF_ZERO_STATE'; + case HostCapability.INVOKE: + return 'INVOKE'; default: return 'NEW_ENUM_NOT_IMPLEMENTED'; }
diff --git a/chrome/test/data/webui/new_tab_page/composebox/composebox.gni b/chrome/test/data/webui/new_tab_page/composebox/composebox.gni index 6bf1a33..883a5fa 100644 --- a/chrome/test/data/webui/new_tab_page/composebox/composebox.gni +++ b/chrome/test/data/webui/new_tab_page/composebox/composebox.gni
@@ -3,8 +3,11 @@ # found in the LICENSE file. composebox_test_files = [ + "composebox/composebox_autocomplete_test.ts", + "composebox/composebox_context_menu_test.ts", "composebox/composebox_file_inputs_test.ts", "composebox/composebox_test.ts", + "composebox/composebox_upload_test.ts", "composebox/file_carousel_test.ts", "composebox/file_thumbnail_test.ts", "composebox/test_support.ts",
diff --git a/chrome/test/data/webui/new_tab_page/composebox/composebox_autocomplete_test.ts b/chrome/test/data/webui/new_tab_page/composebox/composebox_autocomplete_test.ts new file mode 100644 index 0000000..b9f3d457 --- /dev/null +++ b/chrome/test/data/webui/new_tab_page/composebox/composebox_autocomplete_test.ts
@@ -0,0 +1,1014 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import {ComposeboxElement, VoiceSearchAction} from 'chrome://new-tab-page/lazy_load.js'; +import {$$} from 'chrome://new-tab-page/new_tab_page.js'; +import {createAutocompleteResultForTesting, createSearchMatchForTesting} from 'chrome://resources/cr_components/searchbox/searchbox_browser_proxy.js'; +import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; +import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js'; +import {eventToPromise, microtasksFinished} from 'chrome://webui-test/test_util.js'; + +import {assertStyle} from '../test_support.js'; + +import {areMatchesShowing, createComposeboxElement, FAKE_TOKEN_STRING, setupComposeboxTest} from './test_support.js'; + +enum Attributes { + SELECTED = 'selected', +} + +suite('NewTabPageComposeboxAutocompleteTest', () => { + const testProxy = setupComposeboxTest(); + + test('escape key behavior with suggestions', async () => { + loadTimeData.overrideValues({composeboxShowZps: true}); + createComposeboxElement(testProxy); + await microtasksFinished(); + + const matches = [ + createSearchMatchForTesting(), + createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), + ]; + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({matches})); + await microtasksFinished(); + assertTrue(await areMatchesShowing( + testProxy.element, testProxy.searchboxCallbackRouterRemote)); + + // Case 1: composeboxCloseByEscape_ = false. Escape should clear the text. + (testProxy.element as any).composeboxCloseByEscape_ = false; + const closePromise = eventToPromise('close-composebox', testProxy.element); + let closed = false; + closePromise.then(() => closed = true); + + testProxy.element.$.input.value = 'test'; + testProxy.element.$.input.dispatchEvent(new Event('input')); + await microtasksFinished(); + + testProxy.element.$.input.dispatchEvent(new KeyboardEvent( + 'keydown', {key: 'Escape', bubbles: true, composed: true})); + await microtasksFinished(); + + assertEquals(testProxy.searchboxHandler.getCallCount('clearFiles'), 1); + assertFalse(closed); + assertEquals('', testProxy.element.$.input.value); + + // Case 2: composeboxCloseByEscape_ = true. Escape should close the + // composebox. + (testProxy.element as any).composeboxCloseByEscape_ = true; + const whenCloseComposebox = + eventToPromise('close-composebox', testProxy.element); + testProxy.element.$.input.dispatchEvent(new KeyboardEvent( + 'keydown', {key: 'Escape', bubbles: true, composed: true})); + await whenCloseComposebox; + assertEquals(testProxy.searchboxHandler.getCallCount('clearFiles'), 2); + }); + + test('composebox queries autocomplete on load', async () => { + loadTimeData.overrideValues({composeboxShowZps: true}); + createComposeboxElement(testProxy); + await microtasksFinished(); + + // Autocomplete should be queried when the composebox is created. + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 1); + + // Restore. + loadTimeData.overrideValues({composeboxShowZps: false}); + }); + + test('dropdown shows when suggestions enabled', async () => { + loadTimeData.overrideValues( + {composeboxShowZps: true, composeboxShowTypedSuggest: true}); + createComposeboxElement(testProxy); + await microtasksFinished(); + + // Add zps input. + testProxy.element.$.input.value = ''; + testProxy.element.$.input.dispatchEvent(new Event('input')); + await microtasksFinished(); + + const composeboxDropdown = + testProxy.element.shadowRoot.querySelector<HTMLElement>('#matches'); + + // Composebox dropdown should not show for no matches. + assertTrue(composeboxDropdown!.hidden); + + const matches = [ + createSearchMatchForTesting(), + createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), + ]; + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + matches: matches, + })); + await microtasksFinished(); + + // Dropdown should show for when matches are available. + assertFalse(composeboxDropdown!.hidden); + }); + + test('dropdown does not show for multiline input', async () => { + loadTimeData.overrideValues( + {composeboxShowZps: true, composeboxShowTypedSuggest: true}); + createComposeboxElement(testProxy); + await microtasksFinished(); + + // Add typed input. + testProxy.element.$.input.value = 'Test'; + testProxy.element.$.input.style.height = '64px'; + testProxy.element.$.input.dispatchEvent(new Event('input')); + await microtasksFinished(); + + const composeboxDropdown = + testProxy.element.shadowRoot.querySelector<HTMLElement>('#matches'); + + const matches = [ + createSearchMatchForTesting(), + createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), + ]; + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + matches: matches, + })); + await microtasksFinished(); + + // Dropdown should show for when matches are not available. + assertTrue(composeboxDropdown!.hidden); + + // Arrow down should do default action. + const arrowDownEvent = new KeyboardEvent('keydown', { + bubbles: true, + cancelable: true, + composed: true, // So it propagates across shadow DOM boundary. + key: 'ArrowDown', + }); + + testProxy.element.$.input.dispatchEvent(arrowDownEvent); + await microtasksFinished(); + assertFalse(arrowDownEvent.defaultPrevented); + }); + + test('dropdown does not show with multiple context files', async () => { + loadTimeData.overrideValues( + {composeboxShowZps: true, composeboxShowTypedSuggest: true}); + createComposeboxElement(testProxy); + await microtasksFinished(); + + // Add zps input. + testProxy.element.$.input.value = ''; + testProxy.element.$.input.dispatchEvent(new Event('input')); + await microtasksFinished(); + + const composeboxDropdown = + testProxy.element.shadowRoot.querySelector<HTMLElement>('#matches'); + + // Add matches and verify dropdown shows. + const matches = [ + createSearchMatchForTesting(), + createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), + ]; + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + matches: matches, + })); + await microtasksFinished(); + assertFalse(composeboxDropdown!.hidden); + + // If multiple context files are added, the dropdown should hide. + testProxy.element.addFileContextForTesting({ + uuid: FAKE_TOKEN_STRING, + name: 'foo.jpg', + status: 0, + type: 'image/jpeg', + isDeletable: true, + objectUrl: null, + dataUrl: null, + url: null, + tabId: null, + }); + testProxy.element.addFileContextForTesting({ + uuid: FAKE_TOKEN_STRING + '2', + name: 'foo2.jpg', + status: 0, + type: 'image/jpeg', + isDeletable: true, + objectUrl: null, + dataUrl: null, + url: null, + tabId: null, + }); + await microtasksFinished(); + assertTrue(composeboxDropdown!.hidden); + }); + + test('arrow keys work for typed suggest', async () => { + loadTimeData.overrideValues( + {composeboxShowZps: true, composeboxShowTypedSuggest: true}); + createComposeboxElement(testProxy); + await microtasksFinished(); + + // Add typed input. + testProxy.element.$.input.value = 'Test'; + testProxy.element.$.input.dispatchEvent(new Event('input')); + await microtasksFinished(); + + const composeboxDropdown = + testProxy.element.shadowRoot.querySelector<HTMLElement>('#matches'); + + const matches = [ + createSearchMatchForTesting( + {fillIntoEdit: 'hello world 1', allowedToBeDefaultMatch: true}), + createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), + createSearchMatchForTesting({fillIntoEdit: 'hello world 3'}), + createSearchMatchForTesting({fillIntoEdit: 'hello world 4'}), + ]; + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + matches: matches, + input: 'Test', + })); + await microtasksFinished(); + + // Dropdown should show for when matches are available. + assertFalse(composeboxDropdown!.hidden); + + const matchEls = testProxy.element.$.matches.shadowRoot.querySelectorAll( + 'cr-composebox-match'); + assertEquals(4, matchEls.length); + const matchEl = matchEls[0]; + // Verbatim match does not show for typed suggest. + assertStyle(matchEl!, 'display', 'none'); + + // Arrow down should do default action. + const arrowDownEvent = new KeyboardEvent('keydown', { + bubbles: true, + cancelable: true, + composed: true, // So it propagates across shadow DOM boundary. + key: 'ArrowDown', + }); + + testProxy.element.$.input.dispatchEvent(arrowDownEvent); + await microtasksFinished(); + assertTrue(arrowDownEvent.defaultPrevented); + + // First SHOWN match (second match) is selected. + assertTrue(matchEls[1]!.hasAttribute(Attributes.SELECTED)); + assertEquals('hello world 2', testProxy.element.$.input.value); + + // Arrow down should do default action. + const arrowUpEvent = new KeyboardEvent('keydown', { + bubbles: true, + cancelable: true, + composed: true, // So it propagates across shadow DOM boundary. + key: 'ArrowUp', + }); + + testProxy.element.$.input.dispatchEvent(arrowUpEvent); + await microtasksFinished(); + assertTrue(arrowUpEvent.defaultPrevented); + // Last match gets selected when arrowing up from the first + // shown match. + assertTrue(matchEls[3]!.hasAttribute(Attributes.SELECTED)); + assertEquals('hello world 4', testProxy.element.$.input.value); + + // When arrowing up from last match, first SHOWN match should be selected. + testProxy.element.$.input.dispatchEvent(arrowDownEvent); + await microtasksFinished(); + assertTrue(arrowDownEvent.defaultPrevented); + assertTrue(matchEls[1]!.hasAttribute(Attributes.SELECTED)); + assertEquals('hello world 2', testProxy.element.$.input.value); + }); + + test('dropdown does not show when no typed suggestions enabled', async () => { + loadTimeData.overrideValues( + {composeboxShowZps: true, composeboxShowTypedSuggest: false}); + createComposeboxElement(testProxy); + await microtasksFinished(); + + // Add zps input. + testProxy.element.$.input.value = ''; + testProxy.element.$.input.dispatchEvent(new Event('input')); + await microtasksFinished(); + + const composeboxDropdown = + testProxy.element.shadowRoot.querySelector<HTMLElement>('#matches'); + + const matches = [ + createSearchMatchForTesting(), + createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), + ]; + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + matches: matches, + })); + await microtasksFinished(); + + // Dropdown should show for when matches are available. + assertFalse(composeboxDropdown!.hidden); + + testProxy.element.$.input.value = 'Hello'; + testProxy.element.$.input.dispatchEvent(new Event('input')); + await microtasksFinished(); + + // Dropdown should not show for typed input when typed suggest is + // disabled. + assertTrue(composeboxDropdown!.hidden); + }); + + test('dropdown does not show for typed suggest with context', async () => { + loadTimeData.overrideValues( + {composeboxShowZps: true, composeboxShowTypedSuggest: true}); + createComposeboxElement(testProxy); + await microtasksFinished(); + + // Add typed input. + testProxy.element.$.input.value = 'Test'; + testProxy.element.$.input.dispatchEvent(new Event('input')); + await microtasksFinished(); + + const composeboxDropdown = + testProxy.element.shadowRoot.querySelector<HTMLElement>('#matches'); + + const matches = [ + createSearchMatchForTesting( + {fillIntoEdit: 'hello world 1', allowedToBeDefaultMatch: true}), + createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), + createSearchMatchForTesting({fillIntoEdit: 'hello world 3'}), + createSearchMatchForTesting({fillIntoEdit: 'hello world 4'}), + ]; + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + matches: matches, + input: 'Test', + })); + await microtasksFinished(); + + // Dropdown should show for when matches are available. + assertFalse(composeboxDropdown!.hidden); + + // If context files are added, the dropdown should no longer be visible. + testProxy.element.addFileContextForTesting({ + uuid: FAKE_TOKEN_STRING, + name: 'foo.jpg', + status: 0, + type: 'image/jpeg', + isDeletable: true, + objectUrl: null, + dataUrl: null, + url: null, + tabId: null, + }); + await microtasksFinished(); + assertTrue(composeboxDropdown!.hidden); + }); + + test( + 'dropdown does not show for typed suggest with verbatim match only', + async () => { + loadTimeData.overrideValues( + {composeboxShowZps: true, composeboxShowTypedSuggest: true}); + createComposeboxElement(testProxy); + await microtasksFinished(); + + // Add typed input. + testProxy.element.$.input.value = 'Test'; + testProxy.element.$.input.dispatchEvent(new Event('input')); + await microtasksFinished(); + + const composeboxDropdown = + testProxy.element.shadowRoot.querySelector<HTMLElement>('#matches'); + + const matches = [ + createSearchMatchForTesting(), + ]; + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + matches: matches, + input: 'Test', + })); + await microtasksFinished(); + + // Dropdown should not show when only the verbatim match is present. + assertTrue(composeboxDropdown!.hidden); + }); + + test('arrow up/down moves selection / focus', async () => { + loadTimeData.overrideValues({composeboxShowZps: true}); + createComposeboxElement(testProxy); + await microtasksFinished(); + + // Add zps input. + testProxy.element.$.input.value = ''; + testProxy.element.$.input.dispatchEvent(new Event('input')); + + const matches = [ + createSearchMatchForTesting(), + createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), + ]; + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + matches: matches, + })); + + assertTrue(await areMatchesShowing( + testProxy.element, testProxy.searchboxCallbackRouterRemote)); + + const matchEls = testProxy.element.$.matches.shadowRoot.querySelectorAll( + 'cr-composebox-match'); + assertEquals(2, matchEls.length); + + const arrowDownEvent = new KeyboardEvent('keydown', { + bubbles: true, + cancelable: true, + composed: true, // So it propagates across shadow DOM boundary. + key: 'ArrowDown', + }); + + testProxy.element.$.input.dispatchEvent(arrowDownEvent); + await microtasksFinished(); + assertTrue(arrowDownEvent.defaultPrevented); + + // First match is selected + assertTrue(matchEls[0]!.hasAttribute(Attributes.SELECTED)); + assertEquals('hello world', testProxy.element.$.input.value); + + // Move the focus to the second match. + matchEls[1]!.focus(); + matchEls[1]!.dispatchEvent(new Event('focusin', { + bubbles: true, + cancelable: true, + composed: true, // So it propagates across shadow DOM boundary. + })); + await microtasksFinished(); + + // Second match is selected and has focus. + assertTrue(matchEls[1]!.hasAttribute(Attributes.SELECTED)); + assertEquals('hello world 2', testProxy.element.$.input.value); + assertEquals( + matchEls[1], testProxy.element.$.matches.shadowRoot.activeElement); + + const arrowUpEvent = new KeyboardEvent('keydown', { + bubbles: true, + cancelable: true, + composed: true, // So it propagates across shadow DOM boundary. + key: 'ArrowUp', + }); + + matchEls[1]!.dispatchEvent(arrowUpEvent); + await microtasksFinished(); + assertTrue(arrowUpEvent.defaultPrevented); + + // First match gets selected and gets focus while focus is in the matches. + assertTrue(matchEls[0]!.hasAttribute(Attributes.SELECTED)); + assertEquals('hello world', testProxy.element.$.input.value); + assertEquals( + matchEls[0], testProxy.element.$.matches.shadowRoot.activeElement); + + // Restore. + loadTimeData.overrideValues({composeboxShowZps: false}); + }); + + test( + 'arrow up/down enables submit for suggestion with no query', async () => { + loadTimeData.overrideValues({composeboxShowZps: true}); + createComposeboxElement(testProxy); + await microtasksFinished(); + + // Add zps input. + testProxy.element.$.input.value = ''; + testProxy.element.$.input.dispatchEvent(new Event('input')); + + const matches = [ + createSearchMatchForTesting({fillIntoEdit: ''}), + ]; + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + matches: matches, + })); + + assertTrue(await areMatchesShowing( + testProxy.element, testProxy.searchboxCallbackRouterRemote)); + + const matchEls = + testProxy.element.$.matches.shadowRoot.querySelectorAll( + 'cr-composebox-match'); + assertEquals(1, matchEls.length); + + const arrowDownEvent = new KeyboardEvent('keydown', { + bubbles: true, + cancelable: true, + composed: true, // So it propagates across shadow DOM boundary. + key: 'ArrowDown', + }); + + testProxy.element.$.input.dispatchEvent(arrowDownEvent); + await microtasksFinished(); + assertTrue(arrowDownEvent.defaultPrevented); + + // First match is selected + assertTrue(matchEls[0]!.hasAttribute(Attributes.SELECTED)); + assertEquals('', testProxy.element.$.input.value); + + // Assert submit is enabled. + const submitButton = + testProxy.element.shadowRoot.querySelector<HTMLElement>( + '#submitIcon'); + assertFalse(submitButton!.hasAttribute('disabled')); + + // By pressing 'Enter' on the button. + const keydownEvent = (new KeyboardEvent('keydown', { + bubbles: true, + cancelable: true, + composed: true, + key: 'Enter', + })); + matchEls[0]!.dispatchEvent(keydownEvent); + assertTrue(keydownEvent.defaultPrevented); + + await microtasksFinished(); + + // Assert call occurs. + assertEquals( + testProxy.searchboxHandler.getCallCount('openAutocompleteMatch'), + 1); + + // Restore. + loadTimeData.overrideValues({composeboxShowZps: false}); + }); + + test('Selection is restored after selected match is removed', async () => { + loadTimeData.overrideValues( + {composeboxShowZps: true, composeboxShowTypedSuggest: true}); + createComposeboxElement(testProxy); + await microtasksFinished(); + + testProxy.element.$.input.value = ''; + testProxy.element.$.input.dispatchEvent(new InputEvent('input')); + + let matches = [ + createSearchMatchForTesting({ + supportsDeletion: true, + }), + ]; + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + input: testProxy.element.$.input.value.trimStart(), + matches, + })); + await microtasksFinished(); + assertTrue(await areMatchesShowing( + testProxy.element, testProxy.searchboxCallbackRouterRemote)); + + let matchEls = testProxy.element.$.matches.shadowRoot.querySelectorAll( + 'cr-composebox-match'); + assertEquals(1, matchEls.length); + // First match is not selected. + assertFalse(matchEls[0]!.hasAttribute(Attributes.SELECTED)); + + // Remove the first match. + matchEls[0]!.$.remove.click(); + const args = + await testProxy.searchboxHandler.whenCalled('deleteAutocompleteMatch'); + assertEquals(0, args[0]); + assertEquals( + 1, testProxy.searchboxHandler.getCallCount('deleteAutocompleteMatch')); + + testProxy.searchboxHandler.reset(); + + matches = [ + createSearchMatchForTesting({supportsDeletion: true}), + createSearchMatchForTesting({ + supportsDeletion: true, + fillIntoEdit: 'hello world 2', + }), + ]; + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + input: '', + matches: matches, + })); + assertTrue(await areMatchesShowing( + testProxy.element, testProxy.searchboxCallbackRouterRemote)); + + matchEls = testProxy.element.$.matches.shadowRoot.querySelectorAll( + 'cr-composebox-match'); + assertEquals(2, matchEls.length); + + // First match is not selected. + assertFalse(matchEls[0]!.hasAttribute(Attributes.SELECTED)); + + const arrowDownEvent = new KeyboardEvent('keydown', { + bubbles: true, + cancelable: true, + composed: true, // So it propagates across shadow DOM boundary. + key: 'ArrowDown', + }); + + testProxy.element.$.input.dispatchEvent(arrowDownEvent); + await microtasksFinished(); + assertTrue(arrowDownEvent.defaultPrevented); + + // First match is selected + assertTrue(matchEls[0]!.hasAttribute(Attributes.SELECTED)); + assertEquals('hello world', testProxy.element.$.input.value); + + // By pressing 'Enter' on the button. + const keydownEvent = (new KeyboardEvent('keydown', { + bubbles: true, + cancelable: true, + composed: true, + key: 'Enter', + })); + matchEls[0]!.$.remove.dispatchEvent(keydownEvent); + assertTrue(keydownEvent.defaultPrevented); + const keydownArgs = + await testProxy.searchboxHandler.whenCalled('deleteAutocompleteMatch'); + await microtasksFinished(); + assertEquals(0, keydownArgs[0]); + assertEquals( + 1, testProxy.searchboxHandler.getCallCount('deleteAutocompleteMatch')); + + matches = [createSearchMatchForTesting({ + supportsDeletion: true, + fillIntoEdit: 'hello world 2', + })]; + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + input: '', + matches: matches, + })); + assertTrue(await areMatchesShowing( + testProxy.element, testProxy.searchboxCallbackRouterRemote)); + + assertTrue(matchEls[0]!.hasAttribute(Attributes.SELECTED)); + assertEquals('hello world 2', testProxy.element.$.input.value); + }); + + test('smart compose response added', async () => { + createComposeboxElement(testProxy); + await microtasksFinished(); + + // Add input. + testProxy.element.$.input.value = 'smart '; + testProxy.element.$.input.dispatchEvent(new Event('input')); + + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + input: 'smart ', + matches: [], + smartComposeInlineHint: 'compose', + })); + await microtasksFinished(); + + assertEquals('compose', testProxy.element.getSmartComposeForTesting()); + }); + + test('tab adds smart compose to input', async () => { + createComposeboxElement(testProxy); + await microtasksFinished(); + // Autocomplete queried once when composebox is opened. + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 1); + + // Add input. + testProxy.element.$.input.value = 'smart '; + testProxy.element.$.input.dispatchEvent(new Event('input')); + + // Autocomplete queried on input. + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 2); + + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + input: 'smart ', + matches: [], + smartComposeInlineHint: 'compose', + })); + await microtasksFinished(); + + const tabEvent = new KeyboardEvent('keydown', { + bubbles: true, + cancelable: true, + composed: true, // So it propagates across shadow DOM boundary. + key: 'Tab', + }); + + testProxy.element.$.input.dispatchEvent(tabEvent); + await microtasksFinished(); + assertTrue(tabEvent.defaultPrevented); + + assertEquals('smart compose', testProxy.element.$.input.value); + // Autocomplete queried when smart compose accepted. + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 3); + }); + + test('arrow up/down moves clears smart compose', async () => { + loadTimeData.overrideValues({composeboxShowTypedSuggest: true}); + createComposeboxElement(testProxy); + await microtasksFinished(); + + const matches = [ + createSearchMatchForTesting(), + createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), + ]; + + // Add typed input + testProxy.element.$.input.value = 'awesome'; + testProxy.element.$.input.dispatchEvent(new Event('input')); + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + input: 'awesome', + matches: matches, + smartComposeInlineHint: 'compose', + })); + assertTrue(await areMatchesShowing( + testProxy.element, testProxy.searchboxCallbackRouterRemote)); + + const smartCompose = $$<HTMLElement>(testProxy.element, '#smartCompose'); + assertTrue(!!smartCompose); + + const arrowDownEvent = new KeyboardEvent('keydown', { + bubbles: true, + cancelable: true, + composed: true, // So it propagates across shadow DOM boundary. + key: 'ArrowDown', + }); + + testProxy.element.$.input.dispatchEvent(arrowDownEvent); + await microtasksFinished(); + assertTrue(arrowDownEvent.defaultPrevented); + + assertFalse(!!$$<HTMLElement>(testProxy.element, '#smartCompose')); + }); + + test('composebox does not show verbatim match', async () => { + loadTimeData.overrideValues( + {composeboxShowZps: true, composeboxShowTypedSuggest: true}); + createComposeboxElement(testProxy); + await microtasksFinished(); + + // Add zps input. + testProxy.element.$.input.value = ''; + testProxy.element.$.input.dispatchEvent(new Event('input')); + + const matches = [ + createSearchMatchForTesting(), + createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), + ]; + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + matches: matches, + })); + assertTrue(await areMatchesShowing( + testProxy.element, testProxy.searchboxCallbackRouterRemote)); + + let matchEls = testProxy.element.$.matches.shadowRoot.querySelectorAll( + 'cr-composebox-match'); + assertEquals(2, matchEls.length); + let matchEl = matchEls[0]; + assertTrue(!!matchEl); + // First match shows for zps. + assertStyle(matchEl, 'display', 'block'); + + // Add typed input + testProxy.element.$.input.value = 'awesome'; + testProxy.element.$.input.dispatchEvent(new Event('input')); + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + input: 'awesome', + matches: matches, + })); + assertTrue(await areMatchesShowing( + testProxy.element, testProxy.searchboxCallbackRouterRemote)); + + matchEls = testProxy.element.$.matches.shadowRoot.querySelectorAll( + 'cr-composebox-match'); + assertEquals(2, matchEls.length); + matchEl = matchEls[0]; + assertTrue(!!matchEl); + // Verbatim match does not show for typed suggest. + assertStyle(matchEl, 'display', 'none'); + }); + + test('delete button removes match', async () => { + loadTimeData.overrideValues({composeboxShowZps: true}); + createComposeboxElement(testProxy); + await microtasksFinished(); + + const matches = [ + createSearchMatchForTesting(), + createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), + ]; + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + input: '', + matches, + suggestionGroupsMap: {}, + })); + + assertTrue(await areMatchesShowing( + testProxy.element, testProxy.searchboxCallbackRouterRemote)); + + const matchEls = testProxy.element.$.matches.shadowRoot.querySelectorAll( + 'cr-composebox-match'); + assertEquals(2, matchEls.length); + const matchEl = matchEls[0]; + assertTrue(!!matchEl); + + const matchIndex = 0; + const destinationUrl = 'http://google.com'; + matchEl.matchIndex = matchIndex; + matchEl.match.destinationUrl = destinationUrl; + + // By pressing 'Enter' on the button. + const keydownEvent = (new KeyboardEvent('keydown', { + bubbles: true, + cancelable: true, + composed: true, + key: 'Enter', + })); + assertTrue(!!matchEl.$.remove); + matchEl.$.remove.dispatchEvent(keydownEvent); + assertTrue(keydownEvent.defaultPrevented); + const keydownArgs = + await testProxy.searchboxHandler.whenCalled('deleteAutocompleteMatch'); + await microtasksFinished(); + assertEquals(matchIndex, keydownArgs[0]); + assertEquals(destinationUrl, keydownArgs[1]); + assertEquals( + 1, testProxy.searchboxHandler.getCallCount('deleteAutocompleteMatch')); + // Pressing the 'Enter' button doesn't accidentally trigger navigation. + assertEquals(0, testProxy.searchboxHandler.getCallCount('submitQuery')); + testProxy.searchboxHandler.reset(); + testProxy.handler.reset(); + + matchEl.$.remove.click(); + const clickArgs = + await testProxy.searchboxHandler.whenCalled('deleteAutocompleteMatch'); + await microtasksFinished(); + assertEquals(matchIndex, clickArgs[0]); + assertEquals(destinationUrl, clickArgs[1]); + assertEquals( + 1, testProxy.searchboxHandler.getCallCount('deleteAutocompleteMatch')); + // Clicking the button doesn't accidentally trigger navigation. + assertEquals(0, testProxy.searchboxHandler.getCallCount('submitQuery')); + }); + + test('composebox stops autocomplete when clearing input', async () => { + createComposeboxElement(testProxy); + await microtasksFinished(); + + // Autocomplete should be queried when the composebox is created. + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 1); + assertEquals( + testProxy.searchboxHandler.getCallCount('stopAutocomplete'), 0); + + // Autocomplete complete should be queried when input is typed. + testProxy.element.$.input.value = 'T'; + testProxy.element.$.input.dispatchEvent(new Event('input')); + await microtasksFinished(); + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 2); + + // Deleting to empty input should stop autocomplete before querying it + // again. + testProxy.element.$.input.value = ''; + testProxy.element.$.input.dispatchEvent(new Event('input')); + await microtasksFinished(); + + assertEquals( + testProxy.searchboxHandler.getCallCount('stopAutocomplete'), 1); + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 3); + }); + + test('setSearchContext sets input and queries autocomplete', async () => { + loadTimeData.overrideValues({composeboxShowZps: true}); + testProxy.element = new ComposeboxElement(); + testProxy.element.searchboxNextEnabled = true; + document.body.appendChild(testProxy.element); + + await microtasksFinished(); + + // Autocomplete waits + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 0); + + const context = { + input: 'hello world', + files: [], + attachments: [], + toolMode: 0, + }; + testProxy.element.addSearchContext(context); + await microtasksFinished(); + + // Check that input and lastQueriedInput are set. + assertEquals(testProxy.element.getText(), 'hello world'); + assertEquals((testProxy.element as any).lastQueriedInput_, 'hello world'); + // Autocomplete should be queried again. + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 1); + }); + + test('`autoSubmitVoiceSearchQuery` disabled updates input', async () => { + // Set loadTimeData so that voice search does not auto submit. + loadTimeData.overrideValues({ + autoSubmitVoiceSearchQuery: false, + expandedComposeboxShowVoiceSearch: true, + steadyComposeboxShowVoiceSearch: true, + composeboxShowZps: true, // For predictable queryAutocomplete count. + }); + createComposeboxElement(testProxy); + await microtasksFinished(); + testProxy.searchboxHandler.reset(); + + const voiceQuery = 'hello'; + testProxy.element.$.voiceSearch.dispatchEvent(new CustomEvent( + 'voice-search-final-result', + {detail: voiceQuery, bubbles: true, composed: true})); + await microtasksFinished(); + + // Assertions. + assertEquals(testProxy.element.$.input.value, voiceQuery); + // Ensure the query isn't auto submitted. + assertEquals(testProxy.searchboxHandler.getCallCount('submitQuery'), 0); + // Ensure autocomplete is queried since there's input in the composebox. + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 1); + assertEquals( + voiceQuery, + testProxy.searchboxHandler.getArgs('queryAutocomplete')[0][0]); + + // Mock an autocomplete result so that submitQuery assertion passes. + const matches = + [createSearchMatchForTesting({allowedToBeDefaultMatch: true})]; + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + input: voiceQuery, + matches, + })); + await testProxy.searchboxCallbackRouterRemote.$.flushForTesting(); + await microtasksFinished(); + + assertFalse(testProxy.element.$.input.hidden); + assertEquals( + testProxy.element.shadowRoot.activeElement, testProxy.element.$.input); + + // Simulate submit button click. + testProxy.element.$.submitContainer.dispatchEvent( + new FocusEvent('focusin')); + testProxy.element.$.submitContainer.click(); + + // Since a match is selected, openAutocompleteMatch is called instead of + // submitQuery. + await testProxy.searchboxHandler.whenCalled('openAutocompleteMatch'); + await microtasksFinished(); + + assertEquals(testProxy.searchboxHandler.getCallCount('submitQuery'), 0); + assertEquals( + testProxy.searchboxHandler.getCallCount('openAutocompleteMatch'), 1); + const [index] = + testProxy.searchboxHandler.getArgs('openAutocompleteMatch')[0]; + assertEquals(index, 0); + }); + + test( + '`autoSubmitVoiceSearchQuery` enabled submits w/o querying autocomplete', + async () => { + // Set loadTimeData so that voice search does auto submit. + loadTimeData.overrideValues({ + autoSubmitVoiceSearchQuery: true, + expandedComposeboxShowVoiceSearch: true, + steadyComposeboxShowVoiceSearch: true, + composeboxShowZps: true, // For predictable queryAutocomplete count. + }); + createComposeboxElement(testProxy); + await microtasksFinished(); + testProxy.searchboxHandler.reset(); + + const voiceSearchActionPromise = + eventToPromise('voice-search-action', testProxy.element); + const voiceQuery = 'hello'; + testProxy.element.$.voiceSearch.dispatchEvent(new CustomEvent( + 'voice-search-final-result', + {detail: voiceQuery, bubbles: true, composed: true})); + + // Assert event fired. + const voiceSearchActionEvent = await voiceSearchActionPromise; + assertEquals( + VoiceSearchAction.QUERY_SUBMITTED, + voiceSearchActionEvent.detail.value); + await microtasksFinished(); + + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 0); + assertEquals(testProxy.searchboxHandler.getCallCount('submitQuery'), 1); + assertEquals( + voiceQuery, + testProxy.searchboxHandler.getArgs('submitQuery')[0][0]); + }); +});
diff --git a/chrome/test/data/webui/new_tab_page/composebox/composebox_context_menu_test.ts b/chrome/test/data/webui/new_tab_page/composebox/composebox_context_menu_test.ts new file mode 100644 index 0000000..4fa51cd --- /dev/null +++ b/chrome/test/data/webui/new_tab_page/composebox/composebox_context_menu_test.ts
@@ -0,0 +1,386 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import {$$} from 'chrome://new-tab-page/new_tab_page.js'; +import {FileUploadErrorType, FileUploadStatus, InputType} from 'chrome://resources/cr_components/composebox/composebox_query.mojom-webui.js'; +import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; +import type {TabInfo} from 'chrome://resources/mojo/components/omnibox/browser/searchbox.mojom-webui.js'; +import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js'; +import {microtasksFinished} from 'chrome://webui-test/test_util.js'; + +import {ADD_FILE_CONTEXT_FN, ADD_TAB_CONTEXT_FN, createComposeboxElement, FAKE_TOKEN_STRING, FAKE_TOKEN_STRING_2, generateZeroId, mockInputState, setupComposeboxTest} from './test_support.js'; + +suite('NewTabPageComposeboxContextMenuTest', () => { + const testProxy = setupComposeboxTest(); + + async function addTab() { + testProxy.searchboxHandler.setPromiseResolveFor( + ADD_TAB_CONTEXT_FN, FAKE_TOKEN_STRING); + + // Assert no files. + assertFalse(!!$$<HTMLElement>(testProxy.element, '#carousel')); + + const contextMenuButton = $$(testProxy.element, '#contextEntrypoint'); + assertTrue(!!contextMenuButton); + const sampleTabTitle = 'Sample Tab'; + contextMenuButton.dispatchEvent(new CustomEvent('add-tab-context', { + detail: {id: 1, title: sampleTabTitle}, + bubbles: true, + composed: true, + })); + + await testProxy.searchboxHandler.whenCalled(ADD_TAB_CONTEXT_FN); + await microtasksFinished(); + const files = testProxy.element.$.carousel.files; + assertEquals(files.length, 1); + assertEquals(files[0]!.type, 'tab'); + assertEquals(files[0]!.name, sampleTabTitle); + return FAKE_TOKEN_STRING; + } + + suite('Context menu', () => { + suiteSetup(() => { + loadTimeData.overrideValues({ + composeboxShowRecentTabChip: true, + composeboxShowContextMenu: true, + }); + }); + + test('context button visible', () => { + createComposeboxElement(testProxy); + + const contextMenuButton = $$(testProxy.element, '#contextEntrypoint'); + assertTrue(!!contextMenuButton); + }); + + test('add tab context', async () => { + createComposeboxElement(testProxy); + testProxy.searchboxHandler.setPromiseResolveFor( + ADD_TAB_CONTEXT_FN, {low: BigInt(1), high: BigInt(2)}); + + // Assert no files. + assertFalse(!!$$<HTMLElement>(testProxy.element, '#carousel')); + + const contextMenuButton = $$(testProxy.element, '#contextEntrypoint'); + assertTrue(!!contextMenuButton); + const sampleTabTitle = 'Sample Tab'; + contextMenuButton.dispatchEvent(new CustomEvent('add-tab-context', { + detail: {id: 1, title: sampleTabTitle}, + bubbles: true, + composed: true, + })); + + await testProxy.searchboxHandler.whenCalled(ADD_TAB_CONTEXT_FN); + await microtasksFinished(); + const files = testProxy.element.$.carousel.files; + assertEquals(files.length, 1); + assertEquals(files[0]!.type, 'tab'); + assertEquals(files[0]!.name, sampleTabTitle); + }); + + test('add tab context fails', async () => { + createComposeboxElement(testProxy); + // Set the promise to reject to simulate a failure. + testProxy.searchboxHandler.setResultMapperFor(ADD_TAB_CONTEXT_FN, () => { + return Promise.reject(FileUploadErrorType.kBrowserProcessingError); + }); + + // Assert no files. + assertFalse(!!$$<HTMLElement>(testProxy.element, '#carousel')); + + const contextMenuButton = $$(testProxy.element, '#contextEntrypoint'); + assertTrue(!!contextMenuButton); + const sampleTabTitle = 'Sample Tab'; + let contextAdded = false; + const callback = (_file: any) => { + contextAdded = true; + }; + + contextMenuButton.dispatchEvent(new CustomEvent('add-tab-context', { + detail: {id: 1, title: sampleTabTitle, onContextAdded: callback}, + bubbles: true, + composed: true, + })); + + await testProxy.searchboxHandler.whenCalled(ADD_TAB_CONTEXT_FN); + await microtasksFinished(); + + // Assert callback was not called and no files in carousel. + assertFalse(contextAdded); + assertFalse(!!$$<HTMLElement>(testProxy.element, '#carousel')); + + assertEquals( + loadTimeData.getString('composeboxFileUploadFailed'), + testProxy.element.$.errorScrim.errorMessage); + }); + + test('tab changes calls getRecentTabs', async () => { + createComposeboxElement(testProxy); + loadTimeData.overrideValues({ + realboxLayoutMode: 'TallTopContext', + composeboxShowRecentTabChip: true, + }); + const sampleTabs = [ + { + tabId: 1, + title: 'Sample Tab 1', + url: 'https://example.com/1', + showInRecentTabChip: true, + lastActive: {internalValue: BigInt(1)}, + }, + { + tabId: 2, + title: 'Sample Tab 2', + url: 'https://example.com/2', + showInRecentTabChip: true, + lastActive: {internalValue: BigInt(2)}, + }, + ]; + + testProxy.searchboxHandler.setResultFor( + 'getRecentTabs', Promise.resolve({tabs: sampleTabs})); + const entrypointAndMenu = testProxy.element.shadowRoot.querySelector( + 'cr-composebox-contextual-entrypoint-and-menu'); + assertTrue(!!entrypointAndMenu, 'contextual-entrypoint-and-menu'); + const contextMenuEntrypoint = entrypointAndMenu.shadowRoot.querySelector( + 'cr-composebox-contextual-entrypoint-button'); + assertTrue(!!contextMenuEntrypoint, 'contextual entrypoint button'); + const entrypointButton = + contextMenuEntrypoint.shadowRoot.querySelector<HTMLElement>( + '#entrypoint'); + assertTrue(!!entrypointButton, 'Entrypoint button'); + entrypointButton.click(); + await microtasksFinished(); + + // There is an initial call to `getRecentTabs` on entrypoint click. + assertEquals(testProxy.searchboxHandler.getCallCount('getRecentTabs'), 1); + + // Assert another call to `getRecentTabs` is made on tab changes. + testProxy.searchboxCallbackRouterRemote.onTabStripChanged(); + await testProxy.searchboxCallbackRouterRemote.$.flushForTesting(); + assertEquals(testProxy.searchboxHandler.getCallCount('getRecentTabs'), 2); + }); + }); + + test('autocomplete queried when autochip removed', async () => { + createComposeboxElement(testProxy); + await microtasksFinished(); + + // Autocomplete queried once on load. + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 1); + testProxy.searchboxHandler.setPromiseResolveFor( + ADD_TAB_CONTEXT_FN, {low: BigInt(1), high: BigInt(2)}); + + const tab = { + tabId: 1, + title: 'Tab 1', + url: 'https://example.com/1', + showInCurrentTabChip: true, + showInPreviousTabChip: false, + lastActive: {internalValue: BigInt(1)}, + } as any as TabInfo; + + // Add autochip. + testProxy.searchboxCallbackRouterRemote.updateAutoSuggestedTabContext(tab); + await microtasksFinished(); + + // Should have cleared matches. + assertEquals( + 1, testProxy.searchboxHandler.getCallCount('stopAutocomplete')); + + // Remove autochip. + testProxy.searchboxCallbackRouterRemote.updateAutoSuggestedTabContext(null); + await microtasksFinished(); + + // Autocomplete should be queried again when an auto chip is removed. + assertEquals( + 3, testProxy.searchboxHandler.getCallCount('stopAutocomplete')); + assertEquals( + 2, testProxy.searchboxHandler.getCallCount('queryAutocomplete')); + }); + + test( + 'autocomplete not requeried if file removed and autochip remains', + async () => { + const testInputState = { + ...mockInputState, + maxInstances: { + [InputType.kBrowserTab]: 1, + [InputType.kLensImage]: 3, + [InputType.kLensFile]: 1, + }, + maxTotalInputs: 3, + }; + loadTimeData.overrideValues({composeboxShowZps: true}); + createComposeboxElement(testProxy); + testProxy.searchboxCallbackRouterRemote.onInputStateChanged( + testInputState); + await microtasksFinished(); + + // Autocomplete queried once on load. + assertEquals( + 1, testProxy.searchboxHandler.getCallCount('queryAutocomplete')); + + const tab = { + tabId: 1, + title: 'Tab 1', + url: 'https://example.com/1', + showInCurrentTabChip: true, + showInPreviousTabChip: false, + lastActive: {internalValue: BigInt(1)}, + } as any as TabInfo; + + // Add autochip. + const autochipToken = generateZeroId(); + testProxy.searchboxHandler.setPromiseResolveFor( + ADD_TAB_CONTEXT_FN, {token: autochipToken}); + testProxy.searchboxCallbackRouterRemote.updateAutoSuggestedTabContext( + tab); + await testProxy.searchboxCallbackRouterRemote.$.flushForTesting(); + await testProxy.searchboxHandler.whenCalled(ADD_TAB_CONTEXT_FN); + await microtasksFinished(); + + // Autocomplete should NOT have been queried again when the chip was + // added. + assertEquals( + 1, testProxy.searchboxHandler.getCallCount('queryAutocomplete')); + + // Add a file. + const fileId = generateZeroId(); + testProxy.searchboxHandler.setPromiseResolveFor( + ADD_FILE_CONTEXT_FN, {token: fileId}); + + testProxy.element.addFileContextForTesting({ + uuid: FAKE_TOKEN_STRING, + name: 'foo.jpg', + status: 0, + type: 'image/jpeg', + isDeletable: true, + objectUrl: null, + dataUrl: null, + url: null, + tabId: null, + }); + await microtasksFinished(); + + // Delete the uploaded file. + const deletedId = testProxy.element.$.carousel.files[1]!.uuid; + testProxy.element.$.carousel.dispatchEvent( + new CustomEvent('delete-file', { + detail: { + uuid: deletedId, + }, + bubbles: true, + composed: true, + })); + + await microtasksFinished(); + + // Autocomplete should NOT be queried again when there is an autochip + // remaining. + assertEquals( + 1, testProxy.searchboxHandler.getCallCount('queryAutocomplete')); + }); + + test('matches cleared when new autochip added', async () => { + createComposeboxElement(testProxy); + await microtasksFinished(); + + testProxy.searchboxHandler.reset(); + testProxy.searchboxHandler.setPromiseResolveFor( + ADD_TAB_CONTEXT_FN, {low: BigInt(1), high: BigInt(2)}); + + const tab = { + tabId: 1, + title: 'Tab 1', + url: 'https://example.com/1', + showInCurrentTabChip: true, + showInPreviousTabChip: false, + lastActive: {internalValue: BigInt(1)}, + } as any as TabInfo; + + // Add valid autochip. + testProxy.searchboxCallbackRouterRemote.updateAutoSuggestedTabContext(tab); + await microtasksFinished(); + + // Should clear matches when a new autochip is added. + assertEquals( + testProxy.searchboxHandler.getCallCount('stopAutocomplete'), 1); + }); + + test( + 'autocomplete not requeried if no autochip to start and updated with null', + async () => { + createComposeboxElement(testProxy); + await microtasksFinished(); + + // Autocomplete queried once on load. + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 1); + + // Remove autochip when none exists. + testProxy.searchboxCallbackRouterRemote.updateAutoSuggestedTabContext( + null); + await microtasksFinished(); + + // Autocomplete should not be queried again when there was no autochip + // to start, and an update comes with a null tab. + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 1); + assertEquals( + testProxy.searchboxHandler.getCallCount('stopAutocomplete'), 0); + }); + + test('when flag enabled, adds tab context of ghost file', async () => { + createComposeboxElement(testProxy); + testProxy.element.shouldShowGhostFiles = true; + + await addTab(); + + await testProxy.element.updateComplete; + await microtasksFinished(); + + assertTrue( + testProxy.element.getNumOfFilesForTesting() === 1, + 'Tab should be added'); + + const bad_token = FAKE_TOKEN_STRING_2; + testProxy.searchboxCallbackRouterRemote.onContextualInputStatusChanged( + bad_token, + FileUploadStatus.kUploadSuccessful, + null, + ); + await testProxy.element.updateComplete; + await microtasksFinished(); + assertTrue( + testProxy.element.getNumOfFilesForTesting() === 2, + 'Ghost file should be added'); + }); + + test('does not add tab context of ghost file', async () => { + createComposeboxElement(testProxy); + testProxy.element.shouldShowGhostFiles = false; + + await addTab(); + await testProxy.element.updateComplete; + await microtasksFinished(); + + + assertTrue( + testProxy.element.getNumOfFilesForTesting() === 1, + 'Tab should be added'); + const bad_token = FAKE_TOKEN_STRING_2; + testProxy.searchboxCallbackRouterRemote.onContextualInputStatusChanged( + bad_token, + FileUploadStatus.kUploadSuccessful, + null, + ); + await testProxy.element.updateComplete; + await microtasksFinished(); + assertTrue( + testProxy.element.getNumOfFilesForTesting() === 1, + 'Ghost file should not be added'); + }); +});
diff --git a/chrome/test/data/webui/new_tab_page/composebox/composebox_test.ts b/chrome/test/data/webui/new_tab_page/composebox/composebox_test.ts index 677c7503..044c83bf 100644 --- a/chrome/test/data/webui/new_tab_page/composebox/composebox_test.ts +++ b/chrome/test/data/webui/new_tab_page/composebox/composebox_test.ts
@@ -2,725 +2,148 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import type {SelectedFileInfo} from '//resources/mojo/components/omnibox/browser/searchbox.mojom-webui.js'; -import {ComposeboxElement, ComposeboxProxyImpl, VoiceSearchAction} from 'chrome://new-tab-page/lazy_load.js'; import {$$} from 'chrome://new-tab-page/new_tab_page.js'; -import {PageCallbackRouter, PageHandlerRemote} from 'chrome://resources/cr_components/composebox/composebox.mojom-webui.js'; -import {FileUploadErrorType, FileUploadStatus, InputType, ToolMode as ComposeboxToolMode} from 'chrome://resources/cr_components/composebox/composebox_query.mojom-webui.js'; +import {ToolMode as ComposeboxToolMode} from 'chrome://resources/cr_components/composebox/composebox_query.mojom-webui.js'; import {createAutocompleteResultForTesting, createSearchMatchForTesting} from 'chrome://resources/cr_components/searchbox/searchbox_browser_proxy.js'; import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; -import {PageCallbackRouter as SearchboxPageCallbackRouter, PageHandlerRemote as SearchboxPageHandlerRemote} from 'chrome://resources/mojo/components/omnibox/browser/searchbox.mojom-webui.js'; -import type {PageRemote as SearchboxPageRemote, TabInfo} from 'chrome://resources/mojo/components/omnibox/browser/searchbox.mojom-webui.js'; +import type {SelectedFileInfo} from 'chrome://resources/mojo/components/omnibox/browser/searchbox.mojom-webui.js'; import type {InputState} from 'chrome://resources/mojo/components/omnibox/composebox/composebox_query.mojom-webui.js'; import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js'; -import type {MetricsTracker} from 'chrome://webui-test/metrics_test_support.js'; -import {fakeMetricsPrivate} from 'chrome://webui-test/metrics_test_support.js'; -import type {TestMock} from 'chrome://webui-test/test_mock.js'; import {eventToPromise, microtasksFinished} from 'chrome://webui-test/test_util.js'; -import {assertStyle, installMock} from '../test_support.js'; +import {assertStyle} from '../test_support.js'; -enum Attributes { - SELECTED = 'selected', -} - -const ADD_FILE_CONTEXT_FN = 'addFileContext'; -const ADD_TAB_CONTEXT_FN = 'addTabContext'; -const FAKE_TOKEN_STRING = '00000000000000001234567890ABCDEF'; -const FAKE_TOKEN_STRING_2 = '00000000000000001234567890ABCDEE'; - -const CONTEXT_ADDED_NTP = - 'ContextualSearch.ContextAdded.ContextAddedMethod.NewTabPage'; - -function generateZeroId(): string { - // Generate 128 bit unique identifier. - const components = new Uint32Array(4); - return components.reduce( - (id = '', component) => id + component.toString(16).padStart(8, '0'), ''); -} +import {ADD_FILE_CONTEXT_FN, createComposeboxElement, FAKE_TOKEN_STRING, mockInputState, setupComposeboxTest} from './test_support.js'; suite('NewTabPageComposeboxTest', () => { - let composeboxElement: ComposeboxElement; - let handler: TestMock<PageHandlerRemote>; - let searchboxHandler: TestMock<SearchboxPageHandlerRemote>; - let searchboxCallbackRouterRemote: SearchboxPageRemote; - let metrics: MetricsTracker; + const testProxy = setupComposeboxTest(); - const deepSearchHint = 'Research anything'; - const imageGenHint = 'Describe your image'; - const canvasHint = 'Create anything'; - const defaultApiHint = loadTimeData.getString('searchboxComposePlaceholder'); - const mockInputState: InputState = { - hintText: defaultApiHint, - toolConfigs: [ - { - tool: ComposeboxToolMode.kDeepSearch, - hintText: deepSearchHint, - menuLabel: '', - chipLabel: '', - disableActiveModelSelection: false, - aimUrlParams: [], - }, - { - tool: ComposeboxToolMode.kImageGen, - hintText: imageGenHint, - menuLabel: '', - chipLabel: '', - disableActiveModelSelection: false, - aimUrlParams: [], - }, - { - tool: ComposeboxToolMode.kCanvas, - hintText: canvasHint, - menuLabel: '', - chipLabel: '', - disableActiveModelSelection: false, - aimUrlParams: [], - }, - ], - modelConfigs: [], - allowedModels: [], - allowedTools: [], - allowedInputTypes: [], - activeModel: 0, - activeTool: 0, - disabledModels: [], - disabledTools: [], - disabledInputTypes: [], - inputTypeConfigs: [], - toolsSectionConfig: null, - modelSectionConfig: null, - maxInstances: {}, - maxTotalInputs: 0, - }; - - setup(() => { - loadTimeData.overrideValues({ - 'composeboxImageFileTypes': 'image/avif,image/bmp,image/jpeg,image/png,image/webp,image/heif,image/heic', - 'composeboxAttachmentFileTypes': '.pdf,application/pdf', - 'contextualMenuUsePecApi': false, - }); - document.body.innerHTML = window.trustedTypes!.emptyHTML; - handler = installMock( - PageHandlerRemote, - mock => ComposeboxProxyImpl.setInstance(new ComposeboxProxyImpl( - mock, new PageCallbackRouter(), new SearchboxPageHandlerRemote(), - new SearchboxPageCallbackRouter()))); - searchboxHandler = installMock( - SearchboxPageHandlerRemote, - mock => ComposeboxProxyImpl.getInstance().searchboxHandler = mock); - searchboxHandler.setPromiseResolveFor('getRecentTabs', {tabs: []}); - searchboxHandler.setPromiseResolveFor('getInputState', { - state: { - allowedModels: [], - allowedTools: [], - allowedInputTypes: [], - activeModel: 0, - activeTool: 0, - disabledModels: [], - disabledTools: [], - disabledInputTypes: [], - inputTypeConfigs: [], - toolConfigs: [], - modelConfigs: [], - toolsSectionConfig: null, - modelSectionConfig: null, - hintText: '', - maxInstances: {}, - maxTotalInputs: 0, - }, - }); - searchboxCallbackRouterRemote = - ComposeboxProxyImpl.getInstance() - .searchboxCallbackRouter.$.bindNewPipeAndPassRemote(); - metrics = fakeMetricsPrivate(); - }); - - function createComposeboxElement() { - composeboxElement = new ComposeboxElement(); - document.body.appendChild(composeboxElement); - } - - async function waitForAddFileCallCount(expectedCount: number): Promise<void> { - const startTime = Date.now(); - return new Promise((resolve, reject) => { - const checkCount = () => { - const currentCount = searchboxHandler.getCallCount(ADD_FILE_CONTEXT_FN); - if (currentCount === expectedCount) { - resolve(); - return; - } - - if (Date.now() - startTime >= 5000) { - reject(new Error(`Could not add file ${expectedCount} times.`)); - return; - } - - setTimeout(checkCount, 50); - }; - checkCount(); - }); - } - - async function addTab() { - searchboxHandler.setPromiseResolveFor( - ADD_TAB_CONTEXT_FN, FAKE_TOKEN_STRING); - - // Assert no files. - assertFalse(!!$$<HTMLElement>(composeboxElement, '#carousel')); - - const contextMenuButton = $$(composeboxElement, '#contextEntrypoint'); - assertTrue(!!contextMenuButton); - const sampleTabTitle = 'Sample Tab'; - contextMenuButton.dispatchEvent(new CustomEvent('add-tab-context', { - detail: {id: 1, title: sampleTabTitle}, - bubbles: true, - composed: true, - })); - - await searchboxHandler.whenCalled(ADD_TAB_CONTEXT_FN); - await microtasksFinished(); - const files = composeboxElement.$.carousel.files; - assertEquals(files.length, 1); - assertEquals(files[0]!.type, 'tab'); - assertEquals(files[0]!.name, sampleTabTitle); - return FAKE_TOKEN_STRING; - } - - function getInputForFileType(fileType: string): HTMLInputElement { - return fileType === 'application/pdf' ? - composeboxElement.$.fileInputs.$.fileInput : - composeboxElement.$.fileInputs.$.imageInput; - } - - function getMockFileChangeEventForType(fileType: string): Event { - if (fileType === 'application/pdf') { - return new Event('change'); - } - - const mockFileChange = new Event('change', {bubbles: true}); - Object.defineProperty(mockFileChange, 'target', { - writable: false, - value: composeboxElement.$.fileInputs.$.imageInput, - }); - return mockFileChange; - } - - async function areMatchesShowing(): Promise<boolean> { - // Force a synchronous render. - await searchboxCallbackRouterRemote.$.flushForTesting(); - await microtasksFinished(); - return window.getComputedStyle(composeboxElement.$.matches).display !== - 'none'; - } - - async function uploadFileAndVerify(token: Object, file: File) { - // Assert no files. - assertFalse(!!$$<HTMLElement>(composeboxElement, '#carousel')); - - searchboxHandler.setPromiseResolveFor(ADD_FILE_CONTEXT_FN, token); - - // Act. - const dataTransfer = new DataTransfer(); - dataTransfer.items.add(file); - - const input: HTMLInputElement = getInputForFileType(file.type); - input.files = dataTransfer.files; - input.dispatchEvent(getMockFileChangeEventForType(file.type)); - - await searchboxHandler.whenCalled(ADD_FILE_CONTEXT_FN); - await microtasksFinished(); - - assertEquals(searchboxHandler.getCallCount('notifySessionStarted'), 1); - await verifyFileUpload(file); - } - - async function verifyFileUpload(file: File) { - // Assert one file. - const files = composeboxElement.$.carousel.files; - assertEquals(files.length, 1); - - assertEquals(files[0]!.type, file.type); - assertEquals(files[0]!.name, file.name); - - // Assert file is uploaded. - assertEquals(searchboxHandler.getCallCount(ADD_FILE_CONTEXT_FN), 1); - - const fileBuffer = await file.arrayBuffer(); - const fileArray = Array.from(new Uint8Array(fileBuffer)); - - const [[fileInfo, fileData]] = searchboxHandler.getArgs(ADD_FILE_CONTEXT_FN); - assertEquals(fileInfo.fileName, file.name); - assertDeepEquals(fileData.bytes, fileArray); - } test( 'submit disabled when tool is Deep Search (default entrypoint)', async () => { - createComposeboxElement(); + createComposeboxElement(testProxy); - assertEquals(searchboxHandler.getCallCount('openAutocompleteMatch'), 0); + assertEquals( + testProxy.searchboxHandler.getCallCount('openAutocompleteMatch'), + 0); // Default: submit is disabled with empty input, clicking does nothing. - composeboxElement.$.submitContainer.click(); + testProxy.element.$.submitContainer.click(); await microtasksFinished(); - assertEquals(searchboxHandler.getCallCount('openAutocompleteMatch'), 0); + assertEquals( + testProxy.searchboxHandler.getCallCount('openAutocompleteMatch'), + 0); // Change tool to Deep Search const inputState = Object.assign({}, mockInputState, { activeTool: ComposeboxToolMode.kDeepSearch, }); - searchboxCallbackRouterRemote.onInputStateChanged(inputState); - await searchboxCallbackRouterRemote.$.flushForTesting(); + testProxy.searchboxCallbackRouterRemote.onInputStateChanged(inputState); + await testProxy.searchboxCallbackRouterRemote.$.flushForTesting(); await microtasksFinished(); // Submit should still be DISABLED because entrypoint is not // ContextualTasks. - composeboxElement.$.submitContainer.click(); + testProxy.element.$.submitContainer.click(); await microtasksFinished(); - assertEquals(searchboxHandler.getCallCount('submitQuery'), 0); + assertEquals(testProxy.searchboxHandler.getCallCount('submitQuery'), 0); }); test('clear functionality', async () => { loadTimeData.overrideValues({composeboxShowSubmit: true}); - createComposeboxElement(); - searchboxHandler.setPromiseResolveFor( + createComposeboxElement(testProxy); + testProxy.searchboxHandler.setPromiseResolveFor( ADD_FILE_CONTEXT_FN, {low: BigInt(1), high: BigInt(2)}); // Check submit button disabled. - assertStyle(composeboxElement.$.submitContainer, 'cursor', 'not-allowed'); + assertStyle(testProxy.element.$.submitContainer, 'cursor', 'not-allowed'); // Add input. - composeboxElement.$.input.value = 'test'; - composeboxElement.$.input.dispatchEvent(new Event('input')); + testProxy.element.$.input.value = 'test'; + testProxy.element.$.input.dispatchEvent(new Event('input')); const dataTransfer = new DataTransfer(); dataTransfer.items.add( new File(['foo1'], 'foo1.pdf', {type: 'application/pdf'})); - composeboxElement.$.fileInputs.$.fileInput.files = - dataTransfer.files; - composeboxElement.$.fileInputs.$.fileInput.dispatchEvent( + testProxy.element.$.fileInputs.$.fileInput.files = dataTransfer.files; + testProxy.element.$.fileInputs.$.fileInput.dispatchEvent( new Event('change')); - await searchboxHandler.whenCalled(ADD_FILE_CONTEXT_FN); + await testProxy.searchboxHandler.whenCalled(ADD_FILE_CONTEXT_FN); await microtasksFinished(); /* Submit button will not be enabled since frontend has not been * notified that file is done uploading. Carousel should * still have the file marked as added. */ - assertEquals(composeboxElement.$.carousel.files.length, 1); + assertEquals(testProxy.element.$.carousel.files.length, 1); // Clear input. - $$<HTMLElement>(composeboxElement, '#cancelIcon')!.click(); + $$<HTMLElement>(testProxy.element, '#cancelIcon')!.click(); await microtasksFinished(); // Assert - assertEquals(searchboxHandler.getCallCount('clearFiles'), 1); + assertEquals(testProxy.searchboxHandler.getCallCount('clearFiles'), 1); // Check submit button disabled and files empty. - assertStyle(composeboxElement.$.submitContainer, 'cursor', 'not-allowed'); - assertFalse(!!$$<HTMLElement>(composeboxElement, '#carousel')); + assertStyle(testProxy.element.$.submitContainer, 'cursor', 'not-allowed'); + assertFalse(!!$$<HTMLElement>(testProxy.element, '#carousel')); // Close composebox. const whenCloseComposebox = - eventToPromise('close-composebox', composeboxElement); - $$<HTMLElement>(composeboxElement, '#cancelIcon')!.click(); + eventToPromise('close-composebox', testProxy.element); + $$<HTMLElement>(testProxy.element, '#cancelIcon')!.click(); await whenCloseComposebox; - assertEquals(searchboxHandler.getCallCount('clearFiles'), 2); - }); - - test('upload image', async () => { - createComposeboxElement(); - // Submit button is disabled without any input. - assertStyle(composeboxElement.$.submitContainer, 'cursor', 'not-allowed'); - await uploadFileAndVerify( - FAKE_TOKEN_STRING, new File(['foo'], 'foo.jpg', {type: 'image/jpeg'})); - searchboxCallbackRouterRemote.onContextualInputStatusChanged( - FAKE_TOKEN_STRING, - FileUploadStatus.kUploadSuccessful, - null, - ); - await composeboxElement.updateComplete; - await microtasksFinished(); - - assertStyle(composeboxElement.$.submitContainer, 'cursor', 'pointer'); - }); - - test('upload image works when config is set to wildcard image/*', async () => { - loadTimeData.overrideValues({ - 'composeboxImageFileTypes': 'image/*', - }); - createComposeboxElement(); - const token = {low: BigInt(1), high: BigInt(2)}; - const file = new File(['foo'], 'foo.jpg', {type: 'image/jpeg'}); - await uploadFileAndVerify(token, file); - }); - - test('uploading/deleting pdf file queries zps', async () => { - loadTimeData.overrideValues( - {composeboxShowZps: true, composeboxShowSubmit: true}); - createComposeboxElement(); - await microtasksFinished(); - - // Autocomplete queried once when composebox is opened. - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 1); - const id = generateZeroId(); - await uploadFileAndVerify( - id, new File(['foo'], 'foo.pdf', {type: 'application/pdf'})); - searchboxCallbackRouterRemote.onContextualInputStatusChanged( - id, FileUploadStatus.kProcessingSuggestSignalsReady, null); - await microtasksFinished(); - - // Autocomplete should be stopped (with matches cleared) and then - // queried again when a file is uploaded. - assertEquals(searchboxHandler.getCallCount('stopAutocomplete'), 1); - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 2); - - // The suggest request should be triggered before the file has finished - // uploading. - searchboxCallbackRouterRemote.onContextualInputStatusChanged( - id, FileUploadStatus.kUploadSuccessful, null); - - // Delete the uploaded file. - const deletedId = composeboxElement.$.carousel.files[0]!.uuid; - composeboxElement.$.carousel.dispatchEvent( - new CustomEvent('delete-file', { - detail: { - uuid: deletedId, - }, - bubbles: true, - composed: true, - })); - - await microtasksFinished(); - - // Deleting a file should also stop autocomplete (and clear matches) and - // then query autocomplete again for unimodal zps results. - assertEquals(searchboxHandler.getCallCount('stopAutocomplete'), 2); - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 3); - }); - - test('uploading image file without flag does nothing', async () => { - loadTimeData.overrideValues( - {composeboxShowZps: true, composeboxShowImageSuggest: false}); - createComposeboxElement(); - await microtasksFinished(); - - // Autocomplete queried once when composebox is opened. - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 1); - const id = generateZeroId(); - await uploadFileAndVerify( - id, new File(['foo'], 'foo.jpg', {type: 'image/jpeg'})); - searchboxCallbackRouterRemote.onContextualInputStatusChanged( - id, FileUploadStatus.kProcessingSuggestSignalsReady, null); - await microtasksFinished(); - - // Autocomplete should not be queried again since the uploaded file is an - // image and the image suggest flag is disabled. - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 1); - }); - - test('uploading image file with image suggest flag queries zps', async () => { - loadTimeData.overrideValues( - {composeboxShowZps: true, composeboxShowImageSuggest: true}); - createComposeboxElement(); - await microtasksFinished(); - - // Autocomplete queried once when composebox is opened. - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 1); - const id = generateZeroId(); - await uploadFileAndVerify( - id, new File(['foo'], 'foo.jpg', {type: 'image/jpeg'})); - searchboxCallbackRouterRemote.onContextualInputStatusChanged( - id, FileUploadStatus.kProcessingSuggestSignalsReady, null); - await microtasksFinished(); - - // Autocomplete should be stopped (with matches cleared) and then - // queried again when a file is uploaded. - assertEquals(searchboxHandler.getCallCount('stopAutocomplete'), 1); - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 2); - }); - - [new File(['foo'], 'foo.jpg', {type: 'image/jpeg'}), - new File(['foo'], 'foo.pdf', {type: 'application/pdf'})] - .forEach((file) => { - test( - `announce file upload started and completed: ${file.type}`, - async () => { - createComposeboxElement(); - - let announcementCount = 0; - const updateAnnouncementCount = () => { - announcementCount += 1; - }; - document.body.addEventListener( - 'cr-a11y-announcer-messages-sent', updateAnnouncementCount); - let announcementPromise = eventToPromise( - 'cr-a11y-announcer-messages-sent', document.body); - - const id = generateZeroId(); - await uploadFileAndVerify(id, file); - - let announcement = await announcementPromise; - assertEquals(announcementCount, 1); - assertTrue(!!announcement); - assertEquals(announcement.detail.messages.length, 1); - - searchboxCallbackRouterRemote.onContextualInputStatusChanged( - id, FileUploadStatus.kUploadSuccessful, null); - await searchboxCallbackRouterRemote.$.flushForTesting(); - - announcementPromise = eventToPromise( - 'cr-a11y-announcer-messages-sent', document.body); - announcement = await announcementPromise; - assertEquals(announcementCount, 2); - assertTrue(!!announcement); - assertEquals(announcement.detail.messages.length, 1); - - // Cleanup event listener. - document.body.removeEventListener( - 'cr-a11y-announcer-messages-sent', updateAnnouncementCount); - assertEquals( - 1, - metrics.count( - 'ContextualSearch.File.WebUI.UploadAttemptFailure.NewTabPage', - 0)); - }); - }); - - test('upload empty file fails', async () => { - createComposeboxElement(); - const file = new File([''], 'foo.jpg', {type: 'image/jpeg'}); - - // Act. - const dataTransfer = new DataTransfer(); - dataTransfer.items.add(file); - const input: HTMLInputElement = getInputForFileType(file.type); - input.files = dataTransfer.files; - input.dispatchEvent(getMockFileChangeEventForType(file.type)); - await microtasksFinished(); - - // Assert no files uploaded or rendered on the carousel - assertEquals(searchboxHandler.getCallCount(ADD_FILE_CONTEXT_FN), 0); - assertFalse(!!$$<HTMLElement>(composeboxElement, '#carousel')); - assertEquals( - 1, - metrics.count( - 'ContextualSearch.File.WebUI.UploadAttemptFailure.NewTabPage', 2)); - }); - - test('upload large file fails', async () => { - const sampleFileMaxSize = 10; - loadTimeData.overrideValues({'composeboxFileMaxSize': sampleFileMaxSize}); - createComposeboxElement(); - const blob = new Blob( - [new Uint8Array(sampleFileMaxSize + 1)], - {type: 'application/octet-stream'}); - const file = new File([blob], 'foo.jpg', {type: 'image/jpeg'}); - - // Act. - const dataTransfer = new DataTransfer(); - dataTransfer.items.add(file); - const input: HTMLInputElement = getInputForFileType(file.type); - input.files = dataTransfer.files; - input.dispatchEvent(getMockFileChangeEventForType(file.type)); - await microtasksFinished(); - - // Assert no files uploaded or rendered on the carousel - assertEquals(searchboxHandler.getCallCount(ADD_FILE_CONTEXT_FN), 0); - assertFalse(!!$$<HTMLElement>(composeboxElement, '#carousel')); - assertEquals( - 1, - metrics.count( - 'ContextualSearch.File.WebUI.UploadAttemptFailure.NewTabPage', 3)); - }); - - [[ - FileUploadStatus.kValidationFailed, - FileUploadErrorType.kImageProcessingError, - ], - [ - FileUploadStatus.kUploadFailed, - null, - ], - [ - FileUploadStatus.kUploadExpired, - null, - ], - ].forEach(([fileUploadStatus, fileUploadErrorType, ..._]) => { - test( - `Image upload is removed on failed upload status ${fileUploadStatus}`, - async () => { - createComposeboxElement(); - const id = generateZeroId(); - const file = new File(['foo'], 'foo.jpg', {type: 'image/jpeg'}); - await uploadFileAndVerify(id, file); - - searchboxCallbackRouterRemote.onContextualInputStatusChanged( - id, fileUploadStatus as FileUploadStatus, - fileUploadErrorType as FileUploadErrorType | null); - await searchboxCallbackRouterRemote.$.flushForTesting(); - - // Assert no files in the carousel. - assertFalse( - !!$$<HTMLElement>(composeboxElement, '#carousel')); - - if (fileUploadErrorType !== null) { - assertEquals( - loadTimeData.getString('composeFileTypesAllowedError'), - composeboxElement.$.errorScrim.errorMessage); - } - }); - }); - - test('upload pdf', async () => { - createComposeboxElement(); - searchboxHandler.setPromiseResolveFor( - ADD_FILE_CONTEXT_FN, {low: BigInt(1), high: BigInt(2)}); - - // Assert no files. - assertFalse(!!$$<HTMLElement>(composeboxElement, '#carousel')); - - // Arrange. - const dataTransfer = new DataTransfer(); - const file = new File(['foo'], 'foo.pdf', {type: 'application/pdf'}); - dataTransfer.items.add(file); - composeboxElement.$.fileInputs.$.fileInput.files = dataTransfer.files; - composeboxElement.$.fileInputs.$.fileInput.dispatchEvent( - new Event('change')); - - await searchboxHandler.whenCalled(ADD_FILE_CONTEXT_FN); - await microtasksFinished(); - - // Assert one pdf file. - const files = composeboxElement.$.carousel.files; - assertEquals(files.length, 1); - assertEquals(files[0]!.type, 'application/pdf'); - assertEquals(files[0]!.name, 'foo.pdf'); - assertFalse(!!files[0]!.objectUrl); - - assertEquals(searchboxHandler.getCallCount('notifySessionStarted'), 1); - - const fileBuffer = await file.arrayBuffer(); - const fileArray = Array.from(new Uint8Array(fileBuffer)); - - // Assert file is uploaded. - assertEquals(searchboxHandler.getCallCount(ADD_FILE_CONTEXT_FN), 1); - const [[fileInfo, fileData]] = - searchboxHandler.getArgs(ADD_FILE_CONTEXT_FN); - assertEquals(fileInfo.fileName, 'foo.pdf'); - assertDeepEquals(fileData.bytes, fileArray); - // Assert context added method was context menu. - assertEquals(1, metrics.count(CONTEXT_ADDED_NTP)); - assertEquals( - 1, - metrics.count( - CONTEXT_ADDED_NTP, - /* CONTEXT_MENU */ 0)); - }); - - test('delete file', async () => { - loadTimeData.overrideValues({composeboxFileMaxCount: 5}); - createComposeboxElement(); - let i = 0; - searchboxHandler.setResultMapperFor(ADD_FILE_CONTEXT_FN, () => { - i += 1; - return Promise.resolve({low: BigInt(i + 1), high: BigInt(i + 2)}); - }); - - // Arrange. - const dataTransfer = new DataTransfer(); - dataTransfer.items.add( - new File(['foo'], 'foo.pdf', {type: 'application/pdf'})); - dataTransfer.items.add( - new File(['foo2'], 'foo2.pdf', {type: 'application/pdf'})); - - // Since the `onFileChange_` method checks the event target when creating - // the `objectUrl`, we have to mock it here. - const mockFileChange = new Event('change', {bubbles: true}); - Object.defineProperty(mockFileChange, 'target', { - writable: false, - value: composeboxElement.$.fileInputs.$.fileInput, - }); - - composeboxElement.$.fileInputs.$.fileInput.files = dataTransfer.files; - composeboxElement.$.fileInputs.$.fileInput.dispatchEvent(mockFileChange); - - await waitForAddFileCallCount(2); - await composeboxElement.updateComplete; - await microtasksFinished(); - - // Assert two files are present initially. - assertEquals(composeboxElement.$.carousel.files.length, 2); - - // Act. - const deletedId = composeboxElement.$.carousel.files[0]!.uuid; - composeboxElement.$.carousel.dispatchEvent( - new CustomEvent('delete-file', { - detail: { - uuid: deletedId, - }, - bubbles: true, - composed: true, - })); - - await microtasksFinished(); - - // Assert. - assertEquals(composeboxElement.$.carousel.files.length, 1); - assertEquals(searchboxHandler.getCallCount('deleteContext'), 1); - const [idArg, fromChip] = searchboxHandler.getArgs('deleteContext')[0]; - assertEquals(idArg, deletedId); - assertFalse(fromChip); + assertEquals(testProxy.searchboxHandler.getCallCount('clearFiles'), 2); }); test('NotifySessionStarted called on composebox created', () => { // Assert call has not occurred. - assertEquals(searchboxHandler.getCallCount('notifySessionStarted'), 0); + assertEquals( + testProxy.searchboxHandler.getCallCount('notifySessionStarted'), 0); - createComposeboxElement(); + createComposeboxElement(testProxy); // Assert call occurs. - assertEquals(searchboxHandler.getCallCount('notifySessionStarted'), 1); + assertEquals( + testProxy.searchboxHandler.getCallCount('notifySessionStarted'), 1); }); test('lens icon click calls handler', async () => { - createComposeboxElement(); + createComposeboxElement(testProxy); - const lensIcon = $$<HTMLElement>(composeboxElement, '#lensIcon'); - assertTrue(!!lensIcon); + const lensIcon = $$<HTMLElement>(testProxy.element, '#lensIcon'); - lensIcon.click(); - await handler.whenCalled('handleFileUpload'); - assertEquals(1, handler.getCallCount('handleFileUpload')); - const [isImage] = handler.getArgs('handleFileUpload'); + lensIcon!.click(); + await testProxy.handler.whenCalled('handleFileUpload'); + assertEquals(1, testProxy.handler.getCallCount('handleFileUpload')); + const [isImage] = testProxy.handler.getArgs('handleFileUpload'); assertTrue(isImage); }); test('lens icon mousedown prevents default', async () => { - createComposeboxElement(); + createComposeboxElement(testProxy); await microtasksFinished(); - const lensIcon = $$<HTMLElement>(composeboxElement, '#lensIcon'); - assertTrue(!!lensIcon); + const lensIcon = $$<HTMLElement>(testProxy.element, '#lensIcon'); const event = new MouseEvent( 'mousedown', {bubbles: true, cancelable: true, composed: true}); - lensIcon.dispatchEvent(event); + lensIcon!.dispatchEvent(event); await microtasksFinished(); assertTrue(event.defaultPrevented); }); test('set and delete visual selection thumbnail', async () => { - createComposeboxElement(); + createComposeboxElement(testProxy); await microtasksFinished(); // Initially, carousel is not shown. - assertFalse(composeboxElement.hasAttribute('show-file-carousel_')); + assertFalse(testProxy.element.hasAttribute('show-file-carousel_')); // Set a thumbnail. const thumbnailUrl = 'data:image/png;base64,sometestdata'; - searchboxCallbackRouterRemote.addFileContext(FAKE_TOKEN_STRING, { + testProxy.searchboxCallbackRouterRemote.addFileContext(FAKE_TOKEN_STRING, { fileName: 'Visual Selection', mimeType: 'image/png', imageDataUrl: thumbnailUrl, @@ -730,9 +153,8 @@ await microtasksFinished(); // Assert thumbnail is shown. - assertTrue(composeboxElement.hasAttribute('show-file-carousel_')); - const fileCarousel = composeboxElement.$.carousel; - assertTrue(!!fileCarousel); + assertTrue(testProxy.element.hasAttribute('show-file-carousel_')); + const fileCarousel = testProxy.element.$.carousel; await microtasksFinished(); assertEquals(fileCarousel.files.length, 1); @@ -747,28 +169,28 @@ const removeImgButton = fileThumbnail.shadowRoot.querySelector<HTMLElement>('#removeImgButton'); - assertTrue(!!removeImgButton); - removeImgButton.click(); + removeImgButton!.click(); await microtasksFinished(); // Assert thumbnail is removed. - assertEquals(searchboxHandler.getCallCount('deleteContext'), 1); - const [idArg, fromChip] = searchboxHandler.getArgs('deleteContext')[0]; + assertEquals(testProxy.searchboxHandler.getCallCount('deleteContext'), 1); + const [idArg, fromChip] = + testProxy.searchboxHandler.getArgs('deleteContext')[0]; assertEquals(idArg, FAKE_TOKEN_STRING); assertFalse(fromChip); // The carousel is removed from the DOM when there are no files, so // assert its absence. - assertFalse(!!composeboxElement.shadowRoot.querySelector('#carousel')); - assertFalse(composeboxElement.hasAttribute('show-file-carousel_')); + assertFalse(!!testProxy.element.shadowRoot.querySelector('#carousel')); + assertFalse(testProxy.element.hasAttribute('show-file-carousel_')); }); test('setVisualSelectionThumbnail not deletable', async () => { - createComposeboxElement(); + createComposeboxElement(testProxy); await microtasksFinished(); // Set a thumbnail that is not deletable. const thumbnailUrl = 'data:image/png;base64,sometestdata'; - searchboxCallbackRouterRemote.addFileContext(FAKE_TOKEN_STRING, { + testProxy.searchboxCallbackRouterRemote.addFileContext(FAKE_TOKEN_STRING, { fileName: 'Visual Selection', mimeType: 'image/png', imageDataUrl: thumbnailUrl, @@ -778,9 +200,8 @@ await microtasksFinished(); // Assert thumbnail is shown. - assertTrue(composeboxElement.hasAttribute('show-file-carousel_')); - const fileCarousel = composeboxElement.$.carousel; - assertTrue(!!fileCarousel); + assertTrue(testProxy.element.hasAttribute('show-file-carousel_')); + const fileCarousel = testProxy.element.$.carousel; assertEquals(fileCarousel.files.length, 1); assertFalse(fileCarousel.files[0]!.isDeletable); @@ -793,288 +214,138 @@ assertEquals(null, removeButton); }); - test('image upload button clicks file input', () => { - loadTimeData.overrideValues({ - 'composeboxShowContextMenu': true, - }); - createComposeboxElement(); - let clickCalled = false; - composeboxElement.$.fileInputs.$.imageInput.click = () => { - clickCalled = true; - }; - const contextEntrypoint = $$(composeboxElement, '#contextEntrypoint'); - assertTrue(!!contextEntrypoint); - contextEntrypoint.dispatchEvent( - new CustomEvent('open-image-upload', {bubbles: true, composed: true})); - - // Assert. - assertTrue(clickCalled); - }); - - test('file upload button clicks file input', () => { - loadTimeData.overrideValues({ - 'composeboxShowContextMenu': true, - }); - createComposeboxElement(); - let clickCalled = false; - composeboxElement.$.fileInputs.$.fileInput.click = () => { - clickCalled = true; - }; - const contextEntrypoint = $$(composeboxElement, '#contextEntrypoint'); - assertTrue(!!contextEntrypoint); - contextEntrypoint.dispatchEvent( - new CustomEvent('open-file-upload', {bubbles: true, composed: true})); - - // Assert. - assertTrue(clickCalled); - }); - - test( - 'upload button should not be disabled except when upload is in progress', - async () => { - loadTimeData.overrideValues({ - 'composeboxShowCreateImageButton': true, - }); - const testInputState = { - ...mockInputState, - maxInstances: { - [InputType.kBrowserTab]: 1, - [InputType.kLensImage]: 1, - [InputType.kLensFile]: 1, - }, - maxTotalInputs: 1, - }; - createComposeboxElement(); - searchboxCallbackRouterRemote.onInputStateChanged(testInputState); - await microtasksFinished(); - - searchboxHandler.setPromiseResolveFor( - ADD_FILE_CONTEXT_FN, - {token: {low: BigInt(1), high: BigInt(2)}}); - - // Upload a PDF file. - const pdfFile = new File(['foo'], 'foo.pdf', {type: 'application/pdf'}); - const dataTransfer = new DataTransfer(); - dataTransfer.items.add(pdfFile); - composeboxElement.$.fileInputs.$.fileInput.files = dataTransfer.files; - composeboxElement.$.fileInputs.$.fileInput.dispatchEvent( - new Event('change')); - - await searchboxHandler.whenCalled(ADD_FILE_CONTEXT_FN); - await microtasksFinished(); - assertFalse(composeboxElement['uploadButtonDisabled_']); - - // Delete the file. `uploadButtonDisabled` should be false. - const deletedId = composeboxElement.$.carousel.files[0]!.uuid; - composeboxElement.$.carousel.dispatchEvent(new CustomEvent( - 'delete-file', - {detail: {uuid: deletedId}, bubbles: true, composed: true})); - await microtasksFinished(); - assertFalse(composeboxElement['uploadButtonDisabled_']); - searchboxHandler.resetResolver(ADD_FILE_CONTEXT_FN); - searchboxHandler.setPromiseResolveFor( - ADD_FILE_CONTEXT_FN, - {token: {low: BigInt(3), high: BigInt(4)}}); - - // Upload an image file. `uploadButtonDisabled` should be false. - const imageFile = new File(['foo'], 'foo.png', {type: 'image/png'}); - const dataTransfer2 = new DataTransfer(); - dataTransfer2.items.add(imageFile); - - const imageInput = - composeboxElement.$.fileInputs.$.imageInput; - imageInput.files = dataTransfer2.files; - imageInput.dispatchEvent(new Event('change')); - - await searchboxHandler.whenCalled(ADD_FILE_CONTEXT_FN); - await microtasksFinished(); - assertFalse(composeboxElement['uploadButtonDisabled_']); - - // Enter create image mode. - composeboxElement['activeToolMode_'] = ComposeboxToolMode.kImageGen; - await composeboxElement.updateComplete; - assertFalse(composeboxElement['uploadButtonDisabled_']); - - // Exit create image mode. `uploadButtonDisabled` should be false. - composeboxElement['activeToolMode_'] = ComposeboxToolMode.kUnspecified; - await composeboxElement.updateComplete; - assertFalse(composeboxElement['uploadButtonDisabled_']); - }); - test('session abandoned on esc click', async () => { // Arrange. loadTimeData.overrideValues({composeboxCloseByEscape: true}); - createComposeboxElement(); + createComposeboxElement(testProxy); - composeboxElement.$.input.value = 'test'; - composeboxElement.$.input.dispatchEvent(new Event('input')); + testProxy.element.$.input.value = 'test'; + testProxy.element.$.input.dispatchEvent(new Event('input')); await microtasksFinished(); const whenCloseComposebox = - eventToPromise('close-composebox', composeboxElement); + eventToPromise('close-composebox', testProxy.element); // Assert call occurs. - composeboxElement.$.composebox.dispatchEvent( + testProxy.element.$.composebox.dispatchEvent( new KeyboardEvent('keydown', {key: 'Escape'})); await microtasksFinished(); const event = await whenCloseComposebox; assertEquals(event.detail.composeboxText, 'test'); - assertEquals(searchboxHandler.getCallCount('clearFiles'), 1); - }); - - test('escape key behavior with suggestions', async () => { - loadTimeData.overrideValues({composeboxShowZps: true}); - createComposeboxElement(); - await microtasksFinished(); - - const matches = [ - createSearchMatchForTesting(), - createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), - ]; - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({matches})); - await microtasksFinished(); - assertTrue(await areMatchesShowing()); - - // Case 1: composeboxCloseByEscape_ = false. Escape should clear the text. - (composeboxElement as any).composeboxCloseByEscape_ = false; - const closePromise = eventToPromise('close-composebox', composeboxElement); - let closed = false; - closePromise.then(() => closed = true); - - composeboxElement.$.input.value = 'test'; - composeboxElement.$.input.dispatchEvent(new Event('input')); - await microtasksFinished(); - - composeboxElement.$.input.dispatchEvent(new KeyboardEvent( - 'keydown', {key: 'Escape', bubbles: true, composed: true})); - await microtasksFinished(); - - assertEquals(searchboxHandler.getCallCount('clearFiles'), 1); - assertFalse(closed); - assertEquals('', composeboxElement.$.input.value); - - // Case 2: composeboxCloseByEscape_ = true. Escape should close the - // composebox. - (composeboxElement as any).composeboxCloseByEscape_ = true; - const whenCloseComposebox = - eventToPromise('close-composebox', composeboxElement); - composeboxElement.$.input.dispatchEvent(new KeyboardEvent( - 'keydown', {key: 'Escape', bubbles: true, composed: true})); - await whenCloseComposebox; - assertEquals(searchboxHandler.getCallCount('clearFiles'), 2); + assertEquals(testProxy.searchboxHandler.getCallCount('clearFiles'), 1); }); test('session abandoned on cancel button click', async () => { // Arrange. - createComposeboxElement(); + createComposeboxElement(testProxy); await microtasksFinished(); // Close composebox. const whenCloseComposebox = - eventToPromise('close-composebox', composeboxElement); - const cancelIcon = $$<HTMLElement>(composeboxElement, '#cancelIcon'); - assertTrue(!!cancelIcon); - cancelIcon.click(); + eventToPromise('close-composebox', testProxy.element); + const cancelIcon = $$<HTMLElement>(testProxy.element, '#cancelIcon'); + cancelIcon!.click(); const event = await whenCloseComposebox; assertEquals(event.detail.composeboxText, ''); - assertEquals(searchboxHandler.getCallCount('clearFiles'), 1); + assertEquals(testProxy.searchboxHandler.getCallCount('clearFiles'), 1); }); test('submit button click leads to handler called', async () => { - createComposeboxElement(); + createComposeboxElement(testProxy); // Assert. - assertEquals(searchboxHandler.getCallCount('openAutocompleteMatch'), 0); + assertEquals( + testProxy.searchboxHandler.getCallCount('openAutocompleteMatch'), 0); // Arrange. - composeboxElement.$.input.value = 'test'; - composeboxElement.$.input.dispatchEvent(new Event('input')); + testProxy.element.$.input.value = 'test'; + testProxy.element.$.input.dispatchEvent(new Event('input')); const matches = [createSearchMatchForTesting({allowedToBeDefaultMatch: true})]; - searchboxCallbackRouterRemote.autocompleteResultChanged( + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( createAutocompleteResultForTesting({ input: 'test', matches, })); - await searchboxCallbackRouterRemote.$.flushForTesting(); + await testProxy.searchboxCallbackRouterRemote.$.flushForTesting(); await microtasksFinished(); - composeboxElement.$.submitContainer.click(); + testProxy.element.$.submitContainer.click(); await microtasksFinished(); // Assert call occurs. - assertEquals(searchboxHandler.getCallCount('openAutocompleteMatch'), 1); + assertEquals( + testProxy.searchboxHandler.getCallCount('openAutocompleteMatch'), 1); }); test('submit button is a no-op when disabled', async () => { - createComposeboxElement(); - assertEquals(searchboxHandler.getCallCount('submitQuery'), 0); - assertEquals(searchboxHandler.getCallCount('openAutocompleteMatch'), 0); + createComposeboxElement(testProxy); + assertEquals(testProxy.searchboxHandler.getCallCount('submitQuery'), 0); + assertEquals( + testProxy.searchboxHandler.getCallCount('openAutocompleteMatch'), 0); // Arrange. - composeboxElement.$.input.value = ''; - composeboxElement.$.input.dispatchEvent(new Event('input')); + testProxy.element.$.input.value = ''; + testProxy.element.$.input.dispatchEvent(new Event('input')); await microtasksFinished(); // Assert submit is disabled. const submitButton = - composeboxElement.shadowRoot.querySelector<HTMLElement>('#submitIcon'); - assertTrue(!!submitButton); - assertTrue(submitButton.hasAttribute('disabled')); + testProxy.element.shadowRoot.querySelector<HTMLElement>('#submitIcon'); + assertTrue(submitButton!.hasAttribute('disabled')); // Act. - composeboxElement.$.submitContainer.click(); + testProxy.element.$.submitContainer.click(); await microtasksFinished(); // Assert no calls were made. - assertEquals(searchboxHandler.getCallCount('submitQuery'), 0); - assertEquals(searchboxHandler.getCallCount('openAutocompleteMatch'), 0); + assertEquals(testProxy.searchboxHandler.getCallCount('submitQuery'), 0); + assertEquals( + testProxy.searchboxHandler.getCallCount('openAutocompleteMatch'), 0); }); test('empty input has disabled submit button', async () => { - createComposeboxElement(); + createComposeboxElement(testProxy); // Arrange. - composeboxElement.$.input.value = ''; - composeboxElement.$.input.dispatchEvent(new Event('input')); + testProxy.element.$.input.value = ''; + testProxy.element.$.input.dispatchEvent(new Event('input')); await microtasksFinished(); // Assert call cannot occur. const submitButton = - composeboxElement.shadowRoot.querySelector<HTMLElement>('#submitIcon'); - assertTrue(!!submitButton); - assertTrue(submitButton.hasAttribute('disabled')); + testProxy.element.shadowRoot.querySelector<HTMLElement>('#submitIcon'); + assertTrue(submitButton!.hasAttribute('disabled')); }); test('submit button is disabled', async () => { // Arrange. - composeboxElement.$.input.value = ' '; - composeboxElement.$.input.dispatchEvent(new Event('input')); + testProxy.element.$.input.value = ' '; + testProxy.element.$.input.dispatchEvent(new Event('input')); await microtasksFinished(); // Assert. const submitButton = - composeboxElement.shadowRoot.querySelector<HTMLElement>('#submitIcon'); - assertTrue(!!submitButton); - assertTrue(submitButton.hasAttribute('disabled')); + testProxy.element.shadowRoot.querySelector<HTMLElement>('#submitIcon'); + assertTrue(submitButton!.hasAttribute('disabled')); }); test('keydown submit only works for enter', async () => { - createComposeboxElement(); + createComposeboxElement(testProxy); // Assert. - assertEquals(searchboxHandler.getCallCount('openAutocompleteMatch'), 0); + assertEquals( + testProxy.searchboxHandler.getCallCount('openAutocompleteMatch'), 0); // Arrange. - composeboxElement.$.input.value = 'test'; - composeboxElement.$.input.dispatchEvent(new Event('input')); + testProxy.element.$.input.value = 'test'; + testProxy.element.$.input.dispatchEvent(new Event('input')); const matches = [createSearchMatchForTesting({allowedToBeDefaultMatch: true})]; - searchboxCallbackRouterRemote.autocompleteResultChanged( + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( createAutocompleteResultForTesting({ input: 'test', matches: matches, })); - await searchboxCallbackRouterRemote.$.flushForTesting(); + await testProxy.searchboxCallbackRouterRemote.$.flushForTesting(); await microtasksFinished(); const shiftEnterEvent = new KeyboardEvent('keydown', { key: 'Enter', @@ -1082,1318 +353,45 @@ bubbles: true, cancelable: true, }); - composeboxElement.$.input.dispatchEvent(shiftEnterEvent); + testProxy.element.$.input.dispatchEvent(shiftEnterEvent); await microtasksFinished(); // Assert. - assertEquals(searchboxHandler.getCallCount('openAutocompleteMatch'), 0); + assertEquals( + testProxy.searchboxHandler.getCallCount('openAutocompleteMatch'), 0); const enterEvent = new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true, }); - composeboxElement.$.input.dispatchEvent(enterEvent); + testProxy.element.$.input.dispatchEvent(enterEvent); await microtasksFinished(); // Assert call occurs. - assertEquals(searchboxHandler.getCallCount('openAutocompleteMatch'), 1); + assertEquals( + testProxy.searchboxHandler.getCallCount('openAutocompleteMatch'), 1); }); test('clear button title changes with input', async () => { - createComposeboxElement(); + createComposeboxElement(testProxy); assertEquals( - composeboxElement.$.cancelIcon.getAttribute('title'), + testProxy.element.$.cancelIcon.getAttribute('title'), loadTimeData.getString('composeboxCancelButtonTitle')); // Arrange. - composeboxElement.$.input.value = 'Test'; - composeboxElement.$.input.dispatchEvent(new Event('input')); + testProxy.element.$.input.value = 'Test'; + testProxy.element.$.input.dispatchEvent(new Event('input')); await microtasksFinished(); // Assert. assertEquals( - composeboxElement.$.cancelIcon.getAttribute('title'), + testProxy.element.$.cancelIcon.getAttribute('title'), loadTimeData.getString('composeboxCancelButtonTitleInput')); }); - test('composebox queries autocomplete on load', async () => { - loadTimeData.overrideValues({composeboxShowZps: true}); - createComposeboxElement(); - await microtasksFinished(); - - // Autocomplete should be queried when the composebox is created. - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 1); - - // Restore. - loadTimeData.overrideValues({composeboxShowZps: false}); - }); - - test('dropdown shows when suggestions enabled', async () => { - loadTimeData.overrideValues( - {composeboxShowZps: true, composeboxShowTypedSuggest: true}); - createComposeboxElement(); - await microtasksFinished(); - - // Add zps input. - composeboxElement.$.input.value = ''; - composeboxElement.$.input.dispatchEvent(new Event('input')); - await microtasksFinished(); - - const composeboxDropdown = - composeboxElement.shadowRoot.querySelector<HTMLElement>('#matches'); - assertTrue(!!composeboxDropdown); - - // Composebox dropdown should not show for no matches. - assertTrue(composeboxDropdown.hidden); - - const matches = [ - createSearchMatchForTesting(), - createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), - ]; - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - matches: matches, - })); - await microtasksFinished(); - - // Dropdown should show for when matches are available. - assertFalse(composeboxDropdown.hidden); - }); - - test('dropdown does not show for multiline input', async () => { - loadTimeData.overrideValues( - {composeboxShowZps: true, composeboxShowTypedSuggest: true}); - createComposeboxElement(); - await microtasksFinished(); - - // Add typed input. - composeboxElement.$.input.value = 'Test'; - composeboxElement.$.input.style.height = '64px'; - composeboxElement.$.input.dispatchEvent(new Event('input')); - await microtasksFinished(); - - const composeboxDropdown = - composeboxElement.shadowRoot.querySelector<HTMLElement>('#matches'); - assertTrue(!!composeboxDropdown); - - const matches = [ - createSearchMatchForTesting(), - createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), - ]; - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - matches: matches, - })); - await microtasksFinished(); - - // Dropdown should show for when matches are not available. - assertTrue(composeboxDropdown.hidden); - - // Arrow down should do default action. - const arrowDownEvent = new KeyboardEvent('keydown', { - bubbles: true, - cancelable: true, - composed: true, // So it propagates across shadow DOM boundary. - key: 'ArrowDown', - }); - - composeboxElement.$.input.dispatchEvent(arrowDownEvent); - await microtasksFinished(); - assertFalse(arrowDownEvent.defaultPrevented); - }); - - test('dropdown does not show with multiple context files', async () => { - loadTimeData.overrideValues( - {composeboxShowZps: true, composeboxShowTypedSuggest: true}); - createComposeboxElement(); - await microtasksFinished(); - - // Add zps input. - composeboxElement.$.input.value = ''; - composeboxElement.$.input.dispatchEvent(new Event('input')); - await microtasksFinished(); - - const composeboxDropdown = - composeboxElement.shadowRoot.querySelector<HTMLElement>('#matches'); - assertTrue(!!composeboxDropdown); - - // Add matches and verify dropdown shows. - const matches = [ - createSearchMatchForTesting(), - createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), - ]; - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - matches: matches, - })); - await microtasksFinished(); - assertFalse(composeboxDropdown.hidden); - - // If multiple context files are added, the dropdown should hide. - composeboxElement.addFileContextForTesting({ - uuid: FAKE_TOKEN_STRING, - name: 'foo.jpg', - status: 0, - type: 'image/jpeg', - isDeletable: true, - objectUrl: null, - dataUrl: null, - url: null, - tabId: null, - }); - composeboxElement.addFileContextForTesting({ - uuid: FAKE_TOKEN_STRING + '2', - name: 'foo2.jpg', - status: 0, - type: 'image/jpeg', - isDeletable: true, - objectUrl: null, - dataUrl: null, - url: null, - tabId: null, - }); - await microtasksFinished(); - assertTrue(composeboxDropdown.hidden); - }); - - test('arrow keys work for typed suggest', async () => { - loadTimeData.overrideValues( - {composeboxShowZps: true, composeboxShowTypedSuggest: true}); - createComposeboxElement(); - await microtasksFinished(); - - // Add typed input. - composeboxElement.$.input.value = 'Test'; - composeboxElement.$.input.dispatchEvent(new Event('input')); - await microtasksFinished(); - - const composeboxDropdown = - composeboxElement.shadowRoot.querySelector<HTMLElement>('#matches'); - assertTrue(!!composeboxDropdown); - - const matches = [ - createSearchMatchForTesting( - {fillIntoEdit: 'hello world 1', allowedToBeDefaultMatch: true}), - createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), - createSearchMatchForTesting({fillIntoEdit: 'hello world 3'}), - createSearchMatchForTesting({fillIntoEdit: 'hello world 4'}), - ]; - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - matches: matches, - input: 'Test', - })); - await microtasksFinished(); - - // Dropdown should show for when matches are available. - assertFalse(composeboxDropdown.hidden); - - const matchEls = composeboxElement.$.matches.shadowRoot.querySelectorAll( - 'cr-composebox-match'); - assertEquals(4, matchEls.length); - const matchEl = matchEls[0]; - assertTrue(!!matchEl); - // Verbatim match does not show for typed suggest. - assertStyle(matchEl, 'display', 'none'); - - // Arrow down should do default action. - const arrowDownEvent = new KeyboardEvent('keydown', { - bubbles: true, - cancelable: true, - composed: true, // So it propagates across shadow DOM boundary. - key: 'ArrowDown', - }); - - composeboxElement.$.input.dispatchEvent(arrowDownEvent); - await microtasksFinished(); - assertTrue(arrowDownEvent.defaultPrevented); - - // First SHOWN match (second match) is selected. - assertTrue(matchEls[1]!.hasAttribute(Attributes.SELECTED)); - assertEquals('hello world 2', composeboxElement.$.input.value); - - // Arrow down should do default action. - const arrowUpEvent = new KeyboardEvent('keydown', { - bubbles: true, - cancelable: true, - composed: true, // So it propagates across shadow DOM boundary. - key: 'ArrowUp', - }); - - composeboxElement.$.input.dispatchEvent(arrowUpEvent); - await microtasksFinished(); - assertTrue(arrowUpEvent.defaultPrevented); - // Last match gets selected when arrowing up from the first - // shown match. - assertTrue(matchEls[3]!.hasAttribute(Attributes.SELECTED)); - assertEquals('hello world 4', composeboxElement.$.input.value); - - // When arrowing up from last match, first SHOWN match should be selected. - composeboxElement.$.input.dispatchEvent(arrowDownEvent); - await microtasksFinished(); - assertTrue(arrowDownEvent.defaultPrevented); - assertTrue(matchEls[1]!.hasAttribute(Attributes.SELECTED)); - assertEquals('hello world 2', composeboxElement.$.input.value); - }); - - test('dropdown does not show when no typed suggestions enabled', async () => { - loadTimeData.overrideValues( - {composeboxShowZps: true, composeboxShowTypedSuggest: false}); - createComposeboxElement(); - await microtasksFinished(); - - // Add zps input. - composeboxElement.$.input.value = ''; - composeboxElement.$.input.dispatchEvent(new Event('input')); - await microtasksFinished(); - - const composeboxDropdown = - composeboxElement.shadowRoot.querySelector<HTMLElement>('#matches'); - assertTrue(!!composeboxDropdown); - - const matches = [ - createSearchMatchForTesting(), - createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), - ]; - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - matches: matches, - })); - await microtasksFinished(); - - // Dropdown should show for when matches are available. - assertFalse(composeboxDropdown.hidden); - - composeboxElement.$.input.value = 'Hello'; - composeboxElement.$.input.dispatchEvent(new Event('input')); - await microtasksFinished(); - - // Dropdown should not show for typed input when typed suggest is - // disabled. - assertTrue(composeboxDropdown.hidden); - }); - - test('dropdown does not show for typed suggest with context', async () => { - loadTimeData.overrideValues( - {composeboxShowZps: true, composeboxShowTypedSuggest: true}); - createComposeboxElement(); - await microtasksFinished(); - - // Add typed input. - composeboxElement.$.input.value = 'Test'; - composeboxElement.$.input.dispatchEvent(new Event('input')); - await microtasksFinished(); - - const composeboxDropdown = - composeboxElement.shadowRoot.querySelector<HTMLElement>('#matches'); - assertTrue(!!composeboxDropdown); - - const matches = [ - createSearchMatchForTesting( - {fillIntoEdit: 'hello world 1', allowedToBeDefaultMatch: true}), - createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), - createSearchMatchForTesting({fillIntoEdit: 'hello world 3'}), - createSearchMatchForTesting({fillIntoEdit: 'hello world 4'}), - ]; - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - matches: matches, - input: 'Test', - })); - await microtasksFinished(); - - // Dropdown should show for when matches are available. - assertFalse(composeboxDropdown.hidden); - - // If context files are added, the dropdown should no longer be visible. - composeboxElement.addFileContextForTesting({ - uuid: FAKE_TOKEN_STRING, - name: 'foo.jpg', - status: 0, - type: 'image/jpeg', - isDeletable: true, - objectUrl: null, - dataUrl: null, - url: null, - tabId: null, - }); - await microtasksFinished(); - assertTrue(composeboxDropdown.hidden); - }); - - test('dropdown does not show for typed suggest with verbatim match only', - async () => { - loadTimeData.overrideValues( - {composeboxShowZps: true, composeboxShowTypedSuggest: true}); - createComposeboxElement(); - await microtasksFinished(); - - // Add typed input. - composeboxElement.$.input.value = 'Test'; - composeboxElement.$.input.dispatchEvent(new Event('input')); - await microtasksFinished(); - - const composeboxDropdown = - composeboxElement.shadowRoot.querySelector<HTMLElement>('#matches'); - assertTrue(!!composeboxDropdown); - - const matches = [ - createSearchMatchForTesting(), - ]; - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - matches: matches, - input: 'Test', - })); - await microtasksFinished(); - - // Dropdown should not show when only the verbatim match is present. - assertTrue(composeboxDropdown.hidden); - }); - - test('notify browser when image is added in create image mode', async () => { - loadTimeData.overrideValues({ - composeboxShowZps: true, - composeboxShowTypedSuggest: false, - 'composeboxFileMaxCount': 1, - }); - createComposeboxElement(); - await microtasksFinished(); - - // Enter create image mode. - const contextEntrypoint = $$(composeboxElement, '#contextEntrypoint'); - assertTrue(!!contextEntrypoint); - contextEntrypoint.dispatchEvent( - new CustomEvent('tool-click', { - detail: {toolMode: ComposeboxToolMode.kImageGen}, - })); - await microtasksFinished(); - assertEquals(searchboxHandler.getCallCount('setActiveToolMode'), 1); - assertEquals( - ComposeboxToolMode.kImageGen, - searchboxHandler.getArgs('setActiveToolMode')[0]); - - // Upload an image file. `uploadButtonDisabled` should be false. - const id = generateZeroId(); - await uploadFileAndVerify( - id, new File(['foo'], 'foo.jpg', {type: 'image/jpeg'})); - searchboxCallbackRouterRemote.onContextualInputStatusChanged( - id, FileUploadStatus.kProcessingSuggestSignalsReady, null); - await microtasksFinished(); - - assertEquals(searchboxHandler.getCallCount('setActiveToolMode'), 2); - assertEquals( - ComposeboxToolMode.kImageGen, - searchboxHandler.getArgs('setActiveToolMode')[0]); - - // Deleting the image should call setCreateImageMode again but with - // imagePresent false. - const deletedId = composeboxElement.$.carousel.files[0]!.uuid; - composeboxElement.$.carousel.dispatchEvent( - new CustomEvent('delete-file', { - detail: { - uuid: deletedId, - }, - bubbles: true, - composed: true, - })); - - await microtasksFinished(); - assertEquals(searchboxHandler.getCallCount('setActiveToolMode'), 3); - assertEquals( - ComposeboxToolMode.kImageGen, - searchboxHandler.getArgs('setActiveToolMode')[0]); - }); - - test('arrow up/down moves selection / focus', async () => { - loadTimeData.overrideValues({composeboxShowZps: true}); - createComposeboxElement(); - await microtasksFinished(); - - // Add zps input. - composeboxElement.$.input.value = ''; - composeboxElement.$.input.dispatchEvent(new Event('input')); - - const matches = [ - createSearchMatchForTesting(), - createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), - ]; - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - matches: matches, - })); - - assertTrue(await areMatchesShowing()); - - const matchEls = composeboxElement.$.matches.shadowRoot.querySelectorAll( - 'cr-composebox-match'); - assertEquals(2, matchEls.length); - - const arrowDownEvent = new KeyboardEvent('keydown', { - bubbles: true, - cancelable: true, - composed: true, // So it propagates across shadow DOM boundary. - key: 'ArrowDown', - }); - - composeboxElement.$.input.dispatchEvent(arrowDownEvent); - await microtasksFinished(); - assertTrue(arrowDownEvent.defaultPrevented); - - // First match is selected - assertTrue(matchEls[0]!.hasAttribute(Attributes.SELECTED)); - assertEquals('hello world', composeboxElement.$.input.value); - - // Move the focus to the second match. - matchEls[1]!.focus(); - matchEls[1]!.dispatchEvent(new Event('focusin', { - bubbles: true, - cancelable: true, - composed: true, // So it propagates across shadow DOM boundary. - })); - await microtasksFinished(); - - // Second match is selected and has focus. - assertTrue(matchEls[1]!.hasAttribute(Attributes.SELECTED)); - assertEquals('hello world 2', composeboxElement.$.input.value); - assertEquals( - matchEls[1], composeboxElement.$.matches.shadowRoot.activeElement); - - const arrowUpEvent = new KeyboardEvent('keydown', { - bubbles: true, - cancelable: true, - composed: true, // So it propagates across shadow DOM boundary. - key: 'ArrowUp', - }); - - matchEls[1]!.dispatchEvent(arrowUpEvent); - await microtasksFinished(); - assertTrue(arrowUpEvent.defaultPrevented); - - // First match gets selected and gets focus while focus is in the matches. - assertTrue(matchEls[0]!.hasAttribute(Attributes.SELECTED)); - assertEquals('hello world', composeboxElement.$.input.value); - assertEquals( - matchEls[0], composeboxElement.$.matches.shadowRoot.activeElement); - - // Restore. - loadTimeData.overrideValues({composeboxShowZps: false}); - }); - - test( - 'arrow up/down enables submit for suggestion with no query', async () => { - loadTimeData.overrideValues({composeboxShowZps: true}); - createComposeboxElement(); - await microtasksFinished(); - - // Add zps input. - composeboxElement.$.input.value = ''; - composeboxElement.$.input.dispatchEvent(new Event('input')); - - const matches = [ - createSearchMatchForTesting({fillIntoEdit: ''}), - ]; - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - matches: matches, - })); - - assertTrue(await areMatchesShowing()); - - const matchEls = - composeboxElement.$.matches.shadowRoot.querySelectorAll( - 'cr-composebox-match'); - assertEquals(1, matchEls.length); - - const arrowDownEvent = new KeyboardEvent('keydown', { - bubbles: true, - cancelable: true, - composed: true, // So it propagates across shadow DOM boundary. - key: 'ArrowDown', - }); - - composeboxElement.$.input.dispatchEvent(arrowDownEvent); - await microtasksFinished(); - assertTrue(arrowDownEvent.defaultPrevented); - - // First match is selected - assertTrue(matchEls[0]!.hasAttribute(Attributes.SELECTED)); - assertEquals('', composeboxElement.$.input.value); - - // Assert submit is enabled. - const submitButton = - composeboxElement.shadowRoot.querySelector<HTMLElement>( - '#submitIcon'); - assertTrue(!!submitButton); - assertFalse(submitButton.hasAttribute('disabled')); - - // By pressing 'Enter' on the button. - const keydownEvent = (new KeyboardEvent('keydown', { - bubbles: true, - cancelable: true, - composed: true, - key: 'Enter', - })); - matchEls[0]!.dispatchEvent(keydownEvent); - assertTrue(keydownEvent.defaultPrevented); - - await microtasksFinished(); - - // Assert call occurs. - assertEquals(searchboxHandler.getCallCount('openAutocompleteMatch'), 1); - - // Restore. - loadTimeData.overrideValues({composeboxShowZps: false}); - }); - - test('Selection is restored after selected match is removed', async () => { - loadTimeData.overrideValues( - {composeboxShowZps: true, composeboxShowTypedSuggest: true}); - createComposeboxElement(); - await microtasksFinished(); - - composeboxElement.$.input.value = ''; - composeboxElement.$.input.dispatchEvent(new InputEvent('input')); - - let matches = [ - createSearchMatchForTesting({ - supportsDeletion: true, - }), - ]; - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - input: composeboxElement.$.input.value.trimStart(), - matches, - })); - await microtasksFinished(); - assertTrue(await areMatchesShowing()); - - let matchEls = composeboxElement.$.matches.shadowRoot.querySelectorAll( - 'cr-composebox-match'); - assertEquals(1, matchEls.length); - // First match is not selected. - assertFalse(matchEls[0]!.hasAttribute(Attributes.SELECTED)); - - // Remove the first match. - matchEls[0]!.$.remove.click(); - const args = await searchboxHandler.whenCalled('deleteAutocompleteMatch'); - assertEquals(0, args[0]); - assertEquals(1, searchboxHandler.getCallCount('deleteAutocompleteMatch')); - - searchboxHandler.reset(); - - matches = [ - createSearchMatchForTesting({supportsDeletion: true}), - createSearchMatchForTesting({ - supportsDeletion: true, - fillIntoEdit: 'hello world 2', - }), - ]; - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - input: '', - matches: matches, - })); - assertTrue(await areMatchesShowing()); - - matchEls = composeboxElement.$.matches.shadowRoot.querySelectorAll( - 'cr-composebox-match'); - assertEquals(2, matchEls.length); - - // First match is not selected. - assertFalse(matchEls[0]!.hasAttribute(Attributes.SELECTED)); - - const arrowDownEvent = new KeyboardEvent('keydown', { - bubbles: true, - cancelable: true, - composed: true, // So it propagates across shadow DOM boundary. - key: 'ArrowDown', - }); - - composeboxElement.$.input.dispatchEvent(arrowDownEvent); - await microtasksFinished(); - assertTrue(arrowDownEvent.defaultPrevented); - - // First match is selected - assertTrue(matchEls[0]!.hasAttribute(Attributes.SELECTED)); - assertEquals('hello world', composeboxElement.$.input.value); - - // By pressing 'Enter' on the button. - const keydownEvent = (new KeyboardEvent('keydown', { - bubbles: true, - cancelable: true, - composed: true, - key: 'Enter', - })); - assertTrue(!!matchEls[0]!.$.remove); - matchEls[0]!.$.remove.dispatchEvent(keydownEvent); - assertTrue(keydownEvent.defaultPrevented); - const keydownArgs = - await searchboxHandler.whenCalled('deleteAutocompleteMatch'); - await microtasksFinished(); - assertEquals(0, keydownArgs[0]); - assertEquals(1, searchboxHandler.getCallCount('deleteAutocompleteMatch')); - - matches = [createSearchMatchForTesting({ - supportsDeletion: true, - fillIntoEdit: 'hello world 2', - })]; - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - input: '', - matches: matches, - })); - assertTrue(await areMatchesShowing()); - - assertTrue(matchEls[0]!.hasAttribute(Attributes.SELECTED)); - assertEquals('hello world 2', composeboxElement.$.input.value); - }); - - test('smart compose response added', async () => { - createComposeboxElement(); - await microtasksFinished(); - - // Add input. - composeboxElement.$.input.value = 'smart '; - composeboxElement.$.input.dispatchEvent(new Event('input')); - - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - input: 'smart ', - matches: [], - smartComposeInlineHint: 'compose', - })); - await microtasksFinished(); - - assertEquals('compose', composeboxElement.getSmartComposeForTesting()); - }); - - test('tab adds smart compose to input', async () => { - createComposeboxElement(); - await microtasksFinished(); - // Autocomplete queried once when composebox is opened. - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 1); - - // Add input. - composeboxElement.$.input.value = 'smart '; - composeboxElement.$.input.dispatchEvent(new Event('input')); - - // Autocomplete queried on input. - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 2); - - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - input: 'smart ', - matches: [], - smartComposeInlineHint: 'compose', - })); - await microtasksFinished(); - - const tabEvent = new KeyboardEvent('keydown', { - bubbles: true, - cancelable: true, - composed: true, // So it propagates across shadow DOM boundary. - key: 'Tab', - }); - - composeboxElement.$.input.dispatchEvent(tabEvent); - await microtasksFinished(); - assertTrue(tabEvent.defaultPrevented); - - assertEquals('smart compose', composeboxElement.$.input.value); - // Autocomplete queried when smart compose accepted. - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 3); - }); - - test('arrow up/down moves clears smart compose', async () => { - loadTimeData.overrideValues({composeboxShowTypedSuggest: true}); - createComposeboxElement(); - await microtasksFinished(); - - const matches = [ - createSearchMatchForTesting(), - createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), - ]; - - // Add typed input - composeboxElement.$.input.value = 'awesome'; - composeboxElement.$.input.dispatchEvent(new Event('input')); - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - input: 'awesome', - matches: matches, - smartComposeInlineHint: 'compose', - })); - assertTrue(await areMatchesShowing()); - - const smartCompose = $$<HTMLElement>(composeboxElement, '#smartCompose'); - assertTrue(!!smartCompose); - - const arrowDownEvent = new KeyboardEvent('keydown', { - bubbles: true, - cancelable: true, - composed: true, // So it propagates across shadow DOM boundary. - key: 'ArrowDown', - }); - - composeboxElement.$.input.dispatchEvent(arrowDownEvent); - await microtasksFinished(); - assertTrue(arrowDownEvent.defaultPrevented); - - assertFalse(!!$$<HTMLElement>(composeboxElement, '#smartCompose')); - }); - - test('composebox does not open match when only file present', async () => { - createComposeboxElement(); - - assertEquals(searchboxHandler.getCallCount('submitQuery'), 0); - await uploadFileAndVerify( - FAKE_TOKEN_STRING, new File(['foo'], 'foo.jpg', {type: 'image/jpeg'})); - searchboxCallbackRouterRemote.onContextualInputStatusChanged( - FAKE_TOKEN_STRING, - FileUploadStatus.kUploadSuccessful, - /*error_type=*/ null, - ); - await microtasksFinished(); - - composeboxElement.$.submitContainer.click(); - await microtasksFinished(); - - // Assert call occurs. - assertEquals( - searchboxHandler.getCallCount('submitQuery'), 1, - 'submitQuery count should be 1'); - assertEquals( - searchboxHandler.getCallCount('openAutocompleteMatch'), 0, - 'openAutocompleteMatch count should be 0'); - }); - - test('composebox does not show when image is present', async () => { - loadTimeData.overrideValues({ - composeboxShowZps: true, - composeboxShowTypedSuggest: true, - composeboxShowImageSuggest: false, - }); - createComposeboxElement(); - // Autocomplete queried once when composebox is created. - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 1); - - const matches = [createSearchMatchForTesting()]; - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - input: '', - matches, - })); - assertTrue(await areMatchesShowing()); - - // Upload an image. - const id = generateZeroId(); - await uploadFileAndVerify( - id, new File(['foo'], 'foo.jpg', {type: 'image/jpeg'})); - - searchboxCallbackRouterRemote.onContextualInputStatusChanged( - id, FileUploadStatus.kProcessingSuggestSignalsReady, null); - - // Matches should not show when image is present. - assertFalse(await areMatchesShowing()); - - // Query autocomplete with image present to get verbatim match. - composeboxElement.$.input.value = 'T'; - composeboxElement.$.input.dispatchEvent(new Event('input')); - await microtasksFinished(); - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 2); - }); - - test('composebox does not show verbatim match', async () => { - loadTimeData.overrideValues( - {composeboxShowZps: true, composeboxShowTypedSuggest: true}); - createComposeboxElement(); - await microtasksFinished(); - - // Add zps input. - composeboxElement.$.input.value = ''; - composeboxElement.$.input.dispatchEvent(new Event('input')); - - const matches = [ - createSearchMatchForTesting(), - createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), - ]; - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - matches: matches, - })); - assertTrue(await areMatchesShowing()); - - let matchEls = composeboxElement.$.matches.shadowRoot.querySelectorAll( - 'cr-composebox-match'); - assertEquals(2, matchEls.length); - let matchEl = matchEls[0]; - assertTrue(!!matchEl); - // First match shows for zps. - assertStyle(matchEl, 'display', 'block'); - - // Add typed input - composeboxElement.$.input.value = 'awesome'; - composeboxElement.$.input.dispatchEvent(new Event('input')); - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - input: 'awesome', - matches: matches, - })); - assertTrue(await areMatchesShowing()); - - matchEls = composeboxElement.$.matches.shadowRoot.querySelectorAll( - 'cr-composebox-match'); - assertEquals(2, matchEls.length); - matchEl = matchEls[0]; - assertTrue(!!matchEl); - // Verbatim match does not show for typed suggest. - assertStyle(matchEl, 'display', 'none'); - }); - - test('delete button removes match', async () => { - loadTimeData.overrideValues({composeboxShowZps: true}); - createComposeboxElement(); - await microtasksFinished(); - - const matches = [ - createSearchMatchForTesting(), - createSearchMatchForTesting({fillIntoEdit: 'hello world 2'}), - ]; - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - input: '', - matches, - suggestionGroupsMap: {}, - })); - - assertTrue(await areMatchesShowing()); - - const matchEls = composeboxElement.$.matches.shadowRoot.querySelectorAll( - 'cr-composebox-match'); - assertEquals(2, matchEls.length); - const matchEl = matchEls[0]; - assertTrue(!!matchEl); - - const matchIndex = 0; - const destinationUrl = 'http://google.com'; - matchEl.matchIndex = matchIndex; - matchEl.match.destinationUrl = destinationUrl; - - // By pressing 'Enter' on the button. - const keydownEvent = (new KeyboardEvent('keydown', { - bubbles: true, - cancelable: true, - composed: true, - key: 'Enter', - })); - assertTrue(!!matchEl.$.remove); - matchEl.$.remove.dispatchEvent(keydownEvent); - assertTrue(keydownEvent.defaultPrevented); - const keydownArgs = - await searchboxHandler.whenCalled('deleteAutocompleteMatch'); - await microtasksFinished(); - assertEquals(matchIndex, keydownArgs[0]); - assertEquals(destinationUrl, keydownArgs[1]); - assertEquals(1, searchboxHandler.getCallCount('deleteAutocompleteMatch')); - // Pressing the 'Enter' button doesn't accidentally trigger navigation. - assertEquals(0, searchboxHandler.getCallCount('submitQuery')); - searchboxHandler.reset(); - handler.reset(); - - matchEl.$.remove.click(); - const clickArgs = - await searchboxHandler.whenCalled('deleteAutocompleteMatch'); - await microtasksFinished(); - assertEquals(matchIndex, clickArgs[0]); - assertEquals(destinationUrl, clickArgs[1]); - assertEquals(1, searchboxHandler.getCallCount('deleteAutocompleteMatch')); - // Clicking the button doesn't accidentally trigger navigation. - assertEquals(0, searchboxHandler.getCallCount('submitQuery')); - }); - - test('composebox stops autocomplete when clearing input', async () => { - createComposeboxElement(); - await microtasksFinished(); - - // Autocomplete should be queried when the composebox is created. - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 1); - assertEquals(searchboxHandler.getCallCount('stopAutocomplete'), 0); - - // Autocomplete complete should be queried when input is typed. - composeboxElement.$.input.value = 'T'; - composeboxElement.$.input.dispatchEvent(new Event('input')); - await microtasksFinished(); - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 2); - - // Deleting to empty input should stop autocomplete before querying it - // again. - composeboxElement.$.input.value = ''; - composeboxElement.$.input.dispatchEvent(new Event('input')); - await microtasksFinished(); - - assertEquals(searchboxHandler.getCallCount('stopAutocomplete'), 1); - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 3); - }); - - - test('pasting valid files calls addFileContext', async () => { - // Arrange. - loadTimeData.overrideValues({'composeboxFileMaxCount': 5}); - createComposeboxElement(); - searchboxHandler.setPromiseResolveFor( - ADD_FILE_CONTEXT_FN, - {token: {low: BigInt(1), high: BigInt(2)}}); - - const pngFile = new File(['foo'], 'foo.png', {type: 'image/png'}); - const pdfFile = new File(['foo'], 'foo.pdf', {type: 'application/pdf'}); - const dataTransfer = new DataTransfer(); - dataTransfer.items.add(pngFile); - dataTransfer.items.add(pdfFile); - const pasteEvent = new ClipboardEvent('paste', { - clipboardData: dataTransfer, - bubbles: true, - cancelable: true, - composed: true, - }); - - // Act. - composeboxElement.$.input.dispatchEvent(pasteEvent); - - // Assert. - // Check that addFileContext (ADD_FILE_CONTEXT_FN) was called twice. - await waitForAddFileCallCount(2); - const [[fileInfo1], [fileInfo2]] = - searchboxHandler.getArgs(ADD_FILE_CONTEXT_FN); - assertEquals('foo.png', fileInfo1.fileName); - assertEquals('foo.pdf', fileInfo2.fileName); - - // Check that the default paste event was prevented. - assertTrue(pasteEvent.defaultPrevented); - assertEquals(1, metrics.count(CONTEXT_ADDED_NTP)); - assertEquals( - 1, - metrics.count( - CONTEXT_ADDED_NTP, - /* COPY_PASTE */ 1)); - }); - - test('pasting too many files records metric and prevents paste', async () => { - // Arrange. - const testInputState = { - ...mockInputState, - maxInstances: { - [InputType.kBrowserTab]: 1, - [InputType.kLensImage]: 1, - [InputType.kLensFile]: 1, - }, - maxTotalInputs: 2, - }; - createComposeboxElement(); - searchboxCallbackRouterRemote.onInputStateChanged(testInputState); - await microtasksFinished(); - - searchboxHandler.setResultMapperFor(ADD_FILE_CONTEXT_FN, () => { - return Promise.resolve({token: {low: BigInt(123), high: BigInt(0)}}); - }); - - const pngFile1 = new File(['foo'], 'foo1.png', {type: 'image/png'}); - const pngFile2 = new File(['foo'], 'foo2.png', {type: 'image/png'}); - const dataTransfer = new DataTransfer(); - dataTransfer.items.add(pngFile1); - dataTransfer.items.add(pngFile2); - const pasteEvent = new ClipboardEvent('paste', { - clipboardData: dataTransfer, - bubbles: true, - cancelable: true, - composed: true, - }); - - // Act. - composeboxElement.$.input.dispatchEvent(pasteEvent); - await searchboxHandler.whenCalled(ADD_FILE_CONTEXT_FN); - await microtasksFinished(); - - // Assert. - // Check that only one files were added. - assertEquals(1, searchboxHandler.getCallCount(ADD_FILE_CONTEXT_FN)); - - // Check that the 'too many files' metric was recorded (Enum value 1). - assertEquals( - 1, - metrics.count( - 'ContextualSearch.File.WebUI.UploadAttemptFailure.NewTabPage', 1)); - - // Check that the paste event was prevented. - assertTrue(pasteEvent.defaultPrevented); - - // Check whether the right error would show up. - assertEquals( - loadTimeData.getString('maxImagesReachedError'), - composeboxElement.$.errorScrim.errorMessage); - }); - - test('pasting unsupported files fires validation error', async () => { - // Arrange. - createComposeboxElement(); - const txtFile = new File(['foo'], 'foo.txt', {type: 'text/plain'}); - const dataTransfer = new DataTransfer(); - dataTransfer.items.add(txtFile); - const pasteEvent = new ClipboardEvent('paste', { - clipboardData: dataTransfer, - bubbles: true, - cancelable: true, - composed: true, - }); - - // Act. - composeboxElement.$.input.dispatchEvent(pasteEvent); - await microtasksFinished(); - - // Assert. - // Check that the correct error event was fired. - assertEquals( - loadTimeData.getString('composeFileTypesAllowedError'), - composeboxElement.$.errorScrim.errorMessage); - - // Check that no files were added. - assertEquals(0, searchboxHandler.getCallCount(ADD_FILE_CONTEXT_FN)); - - // Check that the paste event was prevented. - assertTrue(pasteEvent.defaultPrevented); - }); - - test( - 'pasting only text does not call addFiles or prevent default', - async () => { - // Arrange. - createComposeboxElement(); - const dataTransfer = new DataTransfer(); - dataTransfer.setData('text/plain', 'hello'); - const pasteEvent = new ClipboardEvent('paste', { - clipboardData: dataTransfer, - bubbles: true, - cancelable: true, - composed: true, - }); - - // Act. - composeboxElement.$.input.dispatchEvent(pasteEvent); - await microtasksFinished(); - - // Assert. - // Check that no files were added. - assertEquals(0, searchboxHandler.getCallCount(ADD_FILE_CONTEXT_FN)); - - // Check the paste event was not prevented (onPaste_ returns early). - assertFalse(pasteEvent.defaultPrevented); - }); - - test( - 'pasting mixed files is processed correctly ', async () => { - // Arrange. - createComposeboxElement(); - let i = 0; - searchboxHandler.setResultMapperFor(ADD_FILE_CONTEXT_FN, () => { - i += 1; - return Promise.resolve( - {token: {low: BigInt(i + 1), high: BigInt(i + 2)}}); - }); - const pngFile = new File(['foo'], 'foo.png', {type: 'image/png'}); - const pdfFile = new File(['foo'], 'foo.pdf', {type: 'application/pdf'}); - - const dataTransfer = new DataTransfer(); - dataTransfer.items.add(pngFile); - dataTransfer.items.add(pdfFile); - const pasteEvent = new ClipboardEvent('paste', { - clipboardData: dataTransfer, - bubbles: true, - cancelable: true, - composed: true, - }); - - // Act. - composeboxElement.$.input.dispatchEvent(pasteEvent); - - //Wait for both files to be processed (addFileContext called twice). - await waitForAddFileCallCount(2); - await microtasksFinished(); - - // Assert. - // Check if the Carousel received 2 files. - const files = composeboxElement.$.carousel.files; - assertEquals(files.length, 2); - - // Check if the image was identified as an image. - // (has objectUrl) and the PDF was identified as a PDF (no objectUrl). - const imageFile = files.find(f => f.type.includes('image')); - const pdfFileInCarousel = files.find(f => f.type.includes('pdf')); - - // Ensure we found both. - assertTrue(!!imageFile); - assertTrue(!!pdfFileInCarousel); - - // Validate the image (it must have an objectUrl for preview). - assertTrue( - !!imageFile.objectUrl, - 'Image file should have an objectUrl for preview'); - - // Validate the PDF (it must have null objectUrl to show the icon). - assertEquals( - pdfFileInCarousel.objectUrl, null, - 'PDF file should have null objectUrl'); - }); - - test('uploading 6 valid files when limit is 5 uploads 5 and shows error', async () => { - // Arrange. - const testInputState = { - ...mockInputState, - maxTotalInputs: 5, - }; - createComposeboxElement(); - searchboxCallbackRouterRemote.onInputStateChanged(testInputState); - await microtasksFinished(); - - let i = 0; - searchboxHandler.setResultMapperFor(ADD_FILE_CONTEXT_FN, () => { - i++; - return Promise.resolve({low: BigInt(i), high: BigInt(0)}); - }); - - const validFiles = Array.from({length: 6}, (_, i) => - new File(['foo'], `good${i}.png`, {type: 'image/png'})); - - const dataTransfer = new DataTransfer(); - validFiles.forEach(file => dataTransfer.items.add(file)); - - const pasteEvent = new ClipboardEvent('paste', { - clipboardData: dataTransfer, - bubbles: true, - cancelable: true, - composed: true, - }); - - // Act. - composeboxElement.$.input.dispatchEvent(pasteEvent); - - await waitForAddFileCallCount(5); - await microtasksFinished(); - - // Assert. - assertEquals(5, composeboxElement.$.carousel.files.length); - - assertEquals( - loadTimeData.getString('maxImagesReachedError'), - composeboxElement.$.errorScrim.errorMessage); - - assertEquals( - 1, - metrics.count( - 'ContextualSearch.File.WebUI.UploadAttemptFailure.NewTabPage', 1)); - }); - - test('upload mixed files over limit prioritizes max files error and uploads valid ones', async () => { - // Arrange. - const testInputState = { - ...mockInputState, - maxInstances: { - [InputType.kBrowserTab]: 1, - [InputType.kLensImage]: 3, - [InputType.kLensFile]: 1, - }, - maxTotalInputs: 3, - }; - createComposeboxElement(); - searchboxCallbackRouterRemote.onInputStateChanged(testInputState); - await microtasksFinished(); - - let i = 0; - searchboxHandler.setResultMapperFor(ADD_FILE_CONTEXT_FN, () => { - i++; - return Promise.resolve({token: {low: BigInt(i), high: BigInt(0)}}); - }); - - const files = [ - new File(['foo'], 'good1.png', {type: 'image/png'}), - new File(['foo'], 'good2.png', {type: 'image/png'}), - new File(['foo'], 'good3.png', {type: 'image/png'}), - new File(['foo'], 'bad.txt', {type: 'text/plain'}), - ]; - - const dataTransfer = new DataTransfer(); - files.forEach(file => dataTransfer.items.add(file)); - - const pasteEvent = new ClipboardEvent('paste', { - clipboardData: dataTransfer, - bubbles: true, - cancelable: true, - composed: true, - }); - - // Act. - composeboxElement.$.input.dispatchEvent(pasteEvent); - - await waitForAddFileCallCount(3); - await microtasksFinished(); - - // Assert. - assertEquals(3, composeboxElement.$.carousel.files.length); - - assertEquals( - loadTimeData.getString('maxFilesReachedError'), - composeboxElement.$.errorScrim.errorMessage); - - assertEquals( - 1, - metrics.count( - 'ContextualSearch.File.WebUI.UploadAttemptFailure.NewTabPage', 1)); - }); - - test( - 'uploading valid heif and invalid svg adds valid file and shows error', - async () => { - createComposeboxElement(); - - - let i = 0; - searchboxHandler.setResultMapperFor(ADD_FILE_CONTEXT_FN, () => { - i++; - return Promise.resolve({low: BigInt(i), high: BigInt(0)}); - }); - - const validFile = new File(['foo'], 'image.png', {type: 'image/png'}); - const invalidFile = - new File(['bar'], 'icon.svg', {type: 'image/svg+xml'}); - - const dataTransfer = new DataTransfer(); - dataTransfer.items.add(validFile); - dataTransfer.items.add(invalidFile); - - const pasteEvent = new ClipboardEvent('paste', { - clipboardData: dataTransfer, - bubbles: true, - cancelable: true, - composed: true, - }); - - composeboxElement.$.input.dispatchEvent(pasteEvent); - - await waitForAddFileCallCount(1); - await microtasksFinished(); - - assertEquals(1, composeboxElement.$.carousel.files.length); - assertEquals( - - 'image.png', composeboxElement.$.carousel.files[0]!.name); - - assertEquals( - loadTimeData.getString('composeFileTypesAllowedError'), - composeboxElement.$.errorScrim.errorMessage); - }); - test('isCollapsible attribute sets expanding state when true', async () => { - createComposeboxElement(); - const collapsibleBox = composeboxElement; + createComposeboxElement(testProxy); + const collapsibleBox = testProxy.element; (collapsibleBox as any).isCollapsible = true; document.body.appendChild(collapsibleBox); await collapsibleBox.updateComplete; @@ -2432,22 +430,22 @@ }); test('isCollapsible attribute sets expanded state with file', async () => { - createComposeboxElement(); - (composeboxElement as any).isCollapsible = true; + createComposeboxElement(testProxy); + (testProxy.element as any).isCollapsible = true; await microtasksFinished(); - composeboxElement.$.composebox.dispatchEvent(new FocusEvent('focusin')); - await composeboxElement.updateComplete; + testProxy.element.$.composebox.dispatchEvent(new FocusEvent('focusin')); + await testProxy.element.updateComplete; assertTrue( - composeboxElement.hasAttribute('expanding_'), + testProxy.element.hasAttribute('expanding_'), 'Collapsible should be expanded initially due to focus event'); // Initially, carousel is not shown. - assertFalse(composeboxElement.hasAttribute('show-file-carousel_')); + assertFalse(testProxy.element.hasAttribute('show-file-carousel_')); // Set a thumbnail. const thumbnailUrl = 'data:image/png;base64,sometestdata'; - searchboxCallbackRouterRemote.addFileContext(FAKE_TOKEN_STRING, { + testProxy.searchboxCallbackRouterRemote.addFileContext(FAKE_TOKEN_STRING, { fileName: 'Visual Selection', mimeType: 'image/png', imageDataUrl: thumbnailUrl, @@ -2457,16 +455,15 @@ await microtasksFinished(); // Assert thumbnail is shown. - assertTrue(composeboxElement.hasAttribute('show-file-carousel_')); - const fileCarousel = composeboxElement.$.carousel; - assertTrue(!!fileCarousel); + assertTrue(testProxy.element.hasAttribute('show-file-carousel_')); + const fileCarousel = testProxy.element.$.carousel; await microtasksFinished(); - composeboxElement.$.composebox.dispatchEvent( + testProxy.element.$.composebox.dispatchEvent( new FocusEvent('focusout', {relatedTarget: document.body})); - await composeboxElement.updateComplete; + await testProxy.element.updateComplete; assertTrue( - composeboxElement.hasAttribute('expanding_'), + testProxy.element.hasAttribute('expanding_'), 'Collapsible should remain expanded on blur with file'); // Delete the thumbnail. @@ -2476,29 +473,28 @@ const removeImgButton = fileThumbnail.shadowRoot.querySelector<HTMLElement>('#removeImgButton'); - assertTrue(!!removeImgButton); - removeImgButton.click(); + removeImgButton!.click(); await microtasksFinished(); // Focus the composebox again. - composeboxElement.$.composebox.dispatchEvent(new FocusEvent('focusin')); - await composeboxElement.updateComplete; + testProxy.element.$.composebox.dispatchEvent(new FocusEvent('focusin')); + await testProxy.element.updateComplete; assertTrue( - composeboxElement.hasAttribute('expanding_'), + testProxy.element.hasAttribute('expanding_'), 'Collapsible should still expand when focused in'); // Blur the composebox again. - composeboxElement.$.composebox.dispatchEvent( + testProxy.element.$.composebox.dispatchEvent( new FocusEvent('focusout', {relatedTarget: document.body})); - await composeboxElement.updateComplete; + await testProxy.element.updateComplete; assertFalse( - composeboxElement.hasAttribute('expanding_'), + testProxy.element.hasAttribute('expanding_'), 'Collapsible should collapse on blur with no file'); }); test('isCollapsible attribute sets expanded state when false', async () => { - createComposeboxElement(); - const collapsibleBox = composeboxElement; + createComposeboxElement(testProxy); + const collapsibleBox = testProxy.element; const collapsibleInput = collapsibleBox.$.input; (collapsibleBox as any).isCollapsible = false; await collapsibleBox.updateComplete; @@ -2514,8 +510,8 @@ }); test('collapsible composebox collapses after query submitted', async () => { - createComposeboxElement(); - const collapsibleBox = composeboxElement; + createComposeboxElement(testProxy); + const collapsibleBox = testProxy.element; const collapsibleInput = collapsibleBox.$.input; (collapsibleBox as any).isCollapsible = true; await collapsibleBox.updateComplete; @@ -2531,12 +527,12 @@ // Mock an autocomplete result to allow submission. const matches = [createSearchMatchForTesting({allowedToBeDefaultMatch: true})]; - searchboxCallbackRouterRemote.autocompleteResultChanged( + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( createAutocompleteResultForTesting({ input: 'some text', matches, })); - await searchboxCallbackRouterRemote.$.flushForTesting(); + await testProxy.searchboxCallbackRouterRemote.$.flushForTesting(); await collapsibleBox.updateComplete; // Submit query. @@ -2545,122 +541,12 @@ await microtasksFinished(); // Submit container should be disabled. - assertStyle(composeboxElement.$.submitContainer, 'cursor', 'not-allowed'); + assertStyle(testProxy.element.$.submitContainer, 'cursor', 'not-allowed'); assertEquals('', collapsibleInput.value, 'Input should be cleared'); }); - test('isCollapsible attribute sets expanded state when false', async () => { - createComposeboxElement(); - const collapsibleBox = composeboxElement; - const collapsibleInput = collapsibleBox.$.input; - (collapsibleBox as any).isCollapsible = false; - await collapsibleBox.updateComplete; - - // Blur the input first, since connectedCallback focuses it by default. This - // ensures the component is in a state where it can be collapsed. - collapsibleInput.blur(); - await collapsibleBox.updateComplete; - - assertTrue( - collapsibleBox.hasAttribute('expanding_'), - 'Non-collapsible should be expanded'); - }); - - test( - '`autoSubmitVoiceSearchQuery` disabled updates input', async () => { - // Set loadTimeData so that voice search does not auto submit. - loadTimeData.overrideValues({ - autoSubmitVoiceSearchQuery: false, - expandedComposeboxShowVoiceSearch: true, - steadyComposeboxShowVoiceSearch: true, - composeboxShowZps: true, // For predictable queryAutocomplete count. - }); - createComposeboxElement(); - await microtasksFinished(); - searchboxHandler.reset(); - - const voiceQuery = 'hello'; - composeboxElement.$.voiceSearch.dispatchEvent(new CustomEvent( - 'voice-search-final-result', - {detail: voiceQuery, bubbles: true, composed: true})); - await microtasksFinished(); - - // Assertions. - assertEquals(composeboxElement.$.input.value, voiceQuery); - // Ensure the query isn't auto submitted. - assertEquals(searchboxHandler.getCallCount('submitQuery'), 0); - // Ensure autocomplete is queried since there's input in the composebox. - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 1); - assertEquals( - voiceQuery, searchboxHandler.getArgs('queryAutocomplete')[0][0]); - - // Mock an autocomplete result so that submitQuery assertion passes. - const matches = - [createSearchMatchForTesting({allowedToBeDefaultMatch: true})]; - searchboxCallbackRouterRemote.autocompleteResultChanged( - createAutocompleteResultForTesting({ - input: voiceQuery, - matches, - })); - await searchboxCallbackRouterRemote.$.flushForTesting(); - await microtasksFinished(); - - assertFalse(composeboxElement.$.input.hidden); - assertEquals( - composeboxElement.shadowRoot.activeElement, - composeboxElement.$.input); - - // Simulate submit button click. - composeboxElement.$.submitContainer.dispatchEvent( - new FocusEvent('focusin')); - composeboxElement.$.submitContainer.click(); - - // Since a match is selected, openAutocompleteMatch is called instead of - // submitQuery. - await searchboxHandler.whenCalled('openAutocompleteMatch'); - await microtasksFinished(); - - assertEquals(searchboxHandler.getCallCount('submitQuery'), 0); - assertEquals(searchboxHandler.getCallCount('openAutocompleteMatch'), 1); - const [index] = searchboxHandler.getArgs('openAutocompleteMatch')[0]; - assertEquals(index, 0); - }); - - test( - '`autoSubmitVoiceSearchQuery` enabled submits w/o querying autocomplete', - async () => { - // Set loadTimeData so that voice search does auto submit. - loadTimeData.overrideValues({ - autoSubmitVoiceSearchQuery: true, - expandedComposeboxShowVoiceSearch: true, - steadyComposeboxShowVoiceSearch: true, - composeboxShowZps: true, // For predictable queryAutocomplete count. - }); - createComposeboxElement(); - await microtasksFinished(); - searchboxHandler.reset(); - - const voiceSearchActionPromise = - eventToPromise('voice-search-action', composeboxElement); - const voiceQuery = 'hello'; - composeboxElement.$.voiceSearch.dispatchEvent(new CustomEvent( - 'voice-search-final-result', - {detail: voiceQuery, bubbles: true, composed: true})); - - // Assert event fired. - const voiceSearchActionEvent = await voiceSearchActionPromise; - assertEquals( - VoiceSearchAction.QUERY_SUBMITTED, - voiceSearchActionEvent.detail.value); - await microtasksFinished(); - - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 0); - assertEquals(searchboxHandler.getCallCount('submitQuery'), 1); - assertEquals(voiceQuery, searchboxHandler.getArgs('submitQuery')[0][0]); - }); - test('onInputStateChanged updates inputState', async () => { - createComposeboxElement(); + createComposeboxElement(testProxy); const inputState = { allowedModels: [], allowedTools: [], @@ -2679,380 +565,8 @@ maxInstances: {}, maxTotalInputs: 0, } as InputState; - searchboxCallbackRouterRemote.onInputStateChanged(inputState); + testProxy.searchboxCallbackRouterRemote.onInputStateChanged(inputState); await microtasksFinished(); - assertDeepEquals((composeboxElement as any).inputState_, inputState); - }); - - suite('Context menu', () => { - suiteSetup(() => { - loadTimeData.overrideValues({ - composeboxShowRecentTabChip: true, - composeboxShowContextMenu: true, - }); - }); - - test('context button visible', () => { - createComposeboxElement(); - - const contextMenuButton = $$(composeboxElement, '#contextEntrypoint'); - assertTrue(!!contextMenuButton); - }); - - test('add tab context', async () => { - createComposeboxElement(); - await addTab(); - }); - - test('add tab context fails', async () => { - createComposeboxElement(); - // Set the promise to reject to simulate a failure. - searchboxHandler.setResultMapperFor(ADD_TAB_CONTEXT_FN, () => { - return Promise.reject(FileUploadErrorType.kBrowserProcessingError); - }); - - // Assert no files. - assertFalse(!!$$<HTMLElement>(composeboxElement, '#carousel')); - - const contextMenuButton = $$(composeboxElement, '#contextEntrypoint'); - assertTrue(!!contextMenuButton); - const sampleTabTitle = 'Sample Tab'; - let contextAdded = false; - const callback = (_file: any) => { - contextAdded = true; - }; - - contextMenuButton.dispatchEvent(new CustomEvent('add-tab-context', { - detail: {id: 1, title: sampleTabTitle, onContextAdded: callback}, - bubbles: true, - composed: true, - })); - - await searchboxHandler.whenCalled(ADD_TAB_CONTEXT_FN); - await microtasksFinished(); - - // Assert callback was not called and no files in carousel. - assertFalse(contextAdded); - assertFalse(!!$$<HTMLElement>(composeboxElement, '#carousel')); - - assertEquals( - loadTimeData.getString('composeboxFileUploadFailed'), - composeboxElement.$.errorScrim.errorMessage); - }); - - test('add file context fails', async () => { - loadTimeData.overrideValues({composeboxShowPdfUpload: true}); - createComposeboxElement(); - // Set the promise to reject to simulate a failure. - searchboxHandler.setResultMapperFor(ADD_FILE_CONTEXT_FN, () => { - return Promise.reject(FileUploadErrorType.kBrowserProcessingError); - }); - - // Assert no files. - assertFalse(!!$$<HTMLElement>(composeboxElement, '#carousel')); - - // Act. - const dataTransfer = new DataTransfer(); - const file = new File(['foo'], 'foo.pdf', {type: 'application/pdf'}); - dataTransfer.items.add(file); - composeboxElement.$.fileInputs.$.fileInput.files = dataTransfer.files; - composeboxElement.$.fileInputs.$.fileInput.dispatchEvent( - new Event('change')); - - await searchboxHandler.whenCalled(ADD_FILE_CONTEXT_FN); - await microtasksFinished(); - - // Assert no files in carousel. - assertFalse(!!$$<HTMLElement>(composeboxElement, '#carousel')); - - assertEquals( - loadTimeData.getString('composeboxFileUploadFailed'), - composeboxElement.$.errorScrim.errorMessage); - }); - - test('setSearchContext sets input and queries autocomplete', async () => { - loadTimeData.overrideValues({composeboxShowZps: true}); - composeboxElement = new ComposeboxElement(); - composeboxElement.searchboxNextEnabled = true; - document.body.appendChild(composeboxElement); - - await microtasksFinished(); - - // Autocomplete waits - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 0); - - const context = { - input: 'hello world', - files: [], - attachments: [], - toolMode: 0, - }; - composeboxElement.addSearchContext(context); - await microtasksFinished(); - - // Check that input and lastQueriedInput are set. - assertEquals(composeboxElement.getText(), 'hello world'); - assertEquals((composeboxElement as any).lastQueriedInput_, 'hello world'); - // Autocomplete should be queried again. - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 1); - }); - - test('tab changes calls getRecentTabs', async () => { - createComposeboxElement(); - loadTimeData.overrideValues({ - realboxLayoutMode: 'TallTopContext', - composeboxShowRecentTabChip: true, - }); - const sampleTabs = [ - { - tabId: 1, - title: 'Sample Tab 1', - url: 'https://example.com/1', - showInRecentTabChip: true, - lastActive: {internalValue: BigInt(1)}, - }, - { - tabId: 2, - title: 'Sample Tab 2', - url: 'https://example.com/2', - showInRecentTabChip: true, - lastActive: {internalValue: BigInt(2)}, - }, - ]; - - searchboxHandler.setResultFor( - 'getRecentTabs', Promise.resolve({tabs: sampleTabs})); - - const entrypointAndMenu = composeboxElement.shadowRoot.querySelector( - 'cr-composebox-contextual-entrypoint-and-menu'); - assertTrue(!!entrypointAndMenu, 'contextual-entrypoint-and-menu'); - const contextMenuEntrypoint = entrypointAndMenu.shadowRoot.querySelector( - 'cr-composebox-contextual-entrypoint-button'); - assertTrue(!!contextMenuEntrypoint, 'contextual entrypoint button'); - const entrypointButton = - contextMenuEntrypoint.shadowRoot.querySelector<HTMLElement>( - '#entrypoint'); - assertTrue(!!entrypointButton, 'Entrypoint button'); - entrypointButton.click(); - - await microtasksFinished(); - - // There is an initial call to `getRecentTabs` on entrypoint click. - assertEquals(searchboxHandler.getCallCount('getRecentTabs'), 1); - - // Assert another call to `getRecentTabs` is made on tab changes. - searchboxCallbackRouterRemote.onTabStripChanged(); - await searchboxCallbackRouterRemote.$.flushForTesting(); - assertEquals(searchboxHandler.getCallCount('getRecentTabs'), 2); - }); - }); - - test('autocomplete queried when autochip removed', async () => { - createComposeboxElement(); - await microtasksFinished(); - - // Autocomplete queried once on load. - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 1); - searchboxHandler.setPromiseResolveFor( - ADD_TAB_CONTEXT_FN, {low: BigInt(1), high: BigInt(2)}); - - const tab = { - tabId: 1, - title: 'Tab 1', - url: 'https://example.com/1', - showInCurrentTabChip: true, - showInPreviousTabChip: false, - lastActive: {internalValue: BigInt(1)}, - } as any as TabInfo; - - // Add autochip. - searchboxCallbackRouterRemote.updateAutoSuggestedTabContext(tab); - await microtasksFinished(); - - // Should have cleared matches. - assertEquals(1, searchboxHandler.getCallCount('stopAutocomplete')); - - // Remove autochip. - searchboxCallbackRouterRemote.updateAutoSuggestedTabContext(null); - await microtasksFinished(); - - // Autocomplete should be queried again when an auto chip is removed. - assertEquals(3, searchboxHandler.getCallCount('stopAutocomplete')); - assertEquals(2, searchboxHandler.getCallCount('queryAutocomplete')); - }); - - test( - 'autocomplete not requeried if file removed and autochip remains', - async () => { - const testInputState = { - ...mockInputState, - maxInstances: { - [InputType.kBrowserTab]: 1, - [InputType.kLensImage]: 3, - [InputType.kLensFile]: 1, - }, - maxTotalInputs: 3, - }; - loadTimeData.overrideValues({composeboxShowZps: true}); - createComposeboxElement(); - searchboxCallbackRouterRemote.onInputStateChanged(testInputState); - await microtasksFinished(); - - // Autocomplete queried once on load. - assertEquals(1, searchboxHandler.getCallCount('queryAutocomplete')); - - const tab = { - tabId: 1, - title: 'Tab 1', - url: 'https://example.com/1', - showInCurrentTabChip: true, - showInPreviousTabChip: false, - lastActive: {internalValue: BigInt(1)}, - } as any as TabInfo; - - // Add autochip. - const autochipToken = generateZeroId(); - searchboxHandler.setPromiseResolveFor( - ADD_TAB_CONTEXT_FN, {token: autochipToken}); - searchboxCallbackRouterRemote.updateAutoSuggestedTabContext(tab); - await searchboxCallbackRouterRemote.$.flushForTesting(); - await searchboxHandler.whenCalled(ADD_TAB_CONTEXT_FN); - await microtasksFinished(); - - // Autocomplete should NOT have been queried again when the chip was - // added. - assertEquals(1, searchboxHandler.getCallCount('queryAutocomplete')); - - // Add a file. - const fileId = generateZeroId(); - searchboxHandler.setPromiseResolveFor( - ADD_FILE_CONTEXT_FN, {token: fileId}); - - composeboxElement.addFileContextForTesting({ - uuid: FAKE_TOKEN_STRING, - name: 'foo.jpg', - status: 0, - type: 'image/jpeg', - isDeletable: true, - objectUrl: null, - dataUrl: null, - url: null, - tabId: null, - }); - await microtasksFinished(); - - // Delete the uploaded file. - const deletedId = composeboxElement.$.carousel.files[1]!.uuid; - composeboxElement.$.carousel.dispatchEvent( - new CustomEvent('delete-file', { - detail: { - uuid: deletedId, - }, - bubbles: true, - composed: true, - })); - - await microtasksFinished(); - - // Autocomplete should NOT be queried again when there is an autochip - // remaining. - assertEquals(1, searchboxHandler.getCallCount('queryAutocomplete')); - }); - - test('matches cleared when new autochip added', async () => { - createComposeboxElement(); - await microtasksFinished(); - - searchboxHandler.reset(); - searchboxHandler.setPromiseResolveFor( - ADD_TAB_CONTEXT_FN, {low: BigInt(1), high: BigInt(2)}); - - const tab = { - tabId: 1, - title: 'Tab 1', - url: 'https://example.com/1', - showInCurrentTabChip: true, - showInPreviousTabChip: false, - lastActive: {internalValue: BigInt(1)}, - } as any as TabInfo; - - // Add valid autochip. - searchboxCallbackRouterRemote.updateAutoSuggestedTabContext(tab); - await microtasksFinished(); - - // Should clear matches when a new autochip is added. - assertEquals(searchboxHandler.getCallCount('stopAutocomplete'), 1); - }); - - test( - 'autocomplete not requeried if no autochip to start and updated with null', - async () => { - createComposeboxElement(); - await microtasksFinished(); - - // Autocomplete queried once on load. - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 1); - - // Remove autochip when none exists. - searchboxCallbackRouterRemote.updateAutoSuggestedTabContext(null); - await microtasksFinished(); - - // Autocomplete should not be queried again when there was no autochip - // to start, and an update comes with a null tab. - assertEquals(searchboxHandler.getCallCount('queryAutocomplete'), 1); - assertEquals(searchboxHandler.getCallCount('stopAutocomplete'), 0); - }); - - test('when flag enabled, adds tab context of ghost file', async () => { - createComposeboxElement(); - document.body.appendChild(composeboxElement); - composeboxElement.shouldShowGhostFiles = true; - - await addTab(); - - await composeboxElement.updateComplete; - await microtasksFinished(); - - assertTrue( - composeboxElement.getNumOfFilesForTesting() === 1, - 'Tab should be added'); - - const bad_token = FAKE_TOKEN_STRING_2; - searchboxCallbackRouterRemote.onContextualInputStatusChanged( - bad_token, - FileUploadStatus.kUploadSuccessful, - null, - ); - await composeboxElement.updateComplete; - await microtasksFinished(); - assertTrue( - composeboxElement.getNumOfFilesForTesting() === 2, - 'Ghost file should be added'); - }); - - test('does not add tab context of ghost file', async () => { - createComposeboxElement(); - document.body.appendChild(composeboxElement); - composeboxElement.shouldShowGhostFiles = false; - - await addTab(); - await composeboxElement.updateComplete; - await microtasksFinished(); - - - assertTrue( - composeboxElement.getNumOfFilesForTesting() === 1, - 'Tab should be added'); - const bad_token = FAKE_TOKEN_STRING_2; - searchboxCallbackRouterRemote.onContextualInputStatusChanged( - bad_token, - FileUploadStatus.kUploadSuccessful, - null, - ); - await composeboxElement.updateComplete; - await microtasksFinished(); - assertTrue( - composeboxElement.getNumOfFilesForTesting() === 1, - 'Ghost file should not be added'); + assertDeepEquals((testProxy.element as any).inputState_, inputState); }); });
diff --git a/chrome/test/data/webui/new_tab_page/composebox/composebox_upload_test.ts b/chrome/test/data/webui/new_tab_page/composebox/composebox_upload_test.ts new file mode 100644 index 0000000..db22f2d1 --- /dev/null +++ b/chrome/test/data/webui/new_tab_page/composebox/composebox_upload_test.ts
@@ -0,0 +1,1013 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import {$$} from 'chrome://new-tab-page/new_tab_page.js'; +import {FileUploadErrorType, FileUploadStatus, InputType, ToolMode as ComposeboxToolMode} from 'chrome://resources/cr_components/composebox/composebox_query.mojom-webui.js'; +import {createAutocompleteResultForTesting, createSearchMatchForTesting} from 'chrome://resources/cr_components/searchbox/searchbox_browser_proxy.js'; +import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; +import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js'; +import {eventToPromise, microtasksFinished} from 'chrome://webui-test/test_util.js'; + +import {assertStyle} from '../test_support.js'; + +import {ADD_FILE_CONTEXT_FN, areMatchesShowing, createComposeboxElement, FAKE_TOKEN_STRING, generateZeroId, getInputForFileType, getMockFileChangeEventForType, mockInputState, setupComposeboxTest, uploadFileAndVerify, waitForAddFileCallCount} from './test_support.js'; + +suite('NewTabPageComposeboxUploadTest', () => { + const testProxy = setupComposeboxTest(); + + test('upload image', async () => { + createComposeboxElement(testProxy); + // Submit button is disabled without any input. + assertStyle(testProxy.element.$.submitContainer, 'cursor', 'not-allowed'); + await uploadFileAndVerify( + testProxy, FAKE_TOKEN_STRING, + new File(['foo'], 'foo.jpg', {type: 'image/jpeg'})); + testProxy.searchboxCallbackRouterRemote.onContextualInputStatusChanged( + FAKE_TOKEN_STRING, + FileUploadStatus.kUploadSuccessful, + null, + ); + await testProxy.element.updateComplete; + await microtasksFinished(); + + assertStyle(testProxy.element.$.submitContainer, 'cursor', 'pointer'); + }); + + test( + 'upload image works when config is set to wildcard image/*', async () => { + loadTimeData.overrideValues({ + 'composeboxImageFileTypes': 'image/*', + }); + createComposeboxElement(testProxy); + const token = {low: BigInt(1), high: BigInt(2)}; + const file = new File(['foo'], 'foo.jpg', {type: 'image/jpeg'}); + await uploadFileAndVerify(testProxy, token, file); + }); + + test('uploading/deleting pdf file queries zps', async () => { + loadTimeData.overrideValues( + {composeboxShowZps: true, composeboxShowSubmit: true}); + createComposeboxElement(testProxy); + await microtasksFinished(); + + // Autocomplete queried once when composebox is opened. + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 1); + const id = generateZeroId(); + await uploadFileAndVerify( + testProxy, id, new File(['foo'], 'foo.pdf', {type: 'application/pdf'})); + testProxy.searchboxCallbackRouterRemote.onContextualInputStatusChanged( + id, FileUploadStatus.kProcessingSuggestSignalsReady, null); + await microtasksFinished(); + + // Autocomplete should be stopped (with matches cleared) and then + // queried again when a file is uploaded. + assertEquals( + testProxy.searchboxHandler.getCallCount('stopAutocomplete'), 1); + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 2); + + // The suggest request should be triggered before the file has finished + // uploading. + testProxy.searchboxCallbackRouterRemote.onContextualInputStatusChanged( + id, FileUploadStatus.kUploadSuccessful, null); + + // Delete the uploaded file. + const deletedId = testProxy.element.$.carousel.files[0]!.uuid; + testProxy.element.$.carousel.dispatchEvent(new CustomEvent('delete-file', { + detail: { + uuid: deletedId, + }, + bubbles: true, + composed: true, + })); + + await microtasksFinished(); + + // Deleting a file should also stop autocomplete (and clear matches) and + // then query autocomplete again for unimodal zps results. + assertEquals( + testProxy.searchboxHandler.getCallCount('stopAutocomplete'), 2); + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 3); + }); + + test('uploading image file without flag does nothing', async () => { + loadTimeData.overrideValues( + {composeboxShowZps: true, composeboxShowImageSuggest: false}); + createComposeboxElement(testProxy); + await microtasksFinished(); + + // Autocomplete queried once when composebox is opened. + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 1); + const id = generateZeroId(); + await uploadFileAndVerify( + testProxy, id, new File(['foo'], 'foo.jpg', {type: 'image/jpeg'})); + testProxy.searchboxCallbackRouterRemote.onContextualInputStatusChanged( + id, FileUploadStatus.kProcessingSuggestSignalsReady, null); + await microtasksFinished(); + + // Autocomplete should not be queried again since the uploaded file is an + // image and the image suggest flag is disabled. + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 1); + }); + + test('uploading image file with image suggest flag queries zps', async () => { + loadTimeData.overrideValues( + {composeboxShowZps: true, composeboxShowImageSuggest: true}); + createComposeboxElement(testProxy); + await microtasksFinished(); + + // Autocomplete queried once when composebox is opened. + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 1); + const id = generateZeroId(); + await uploadFileAndVerify( + testProxy, id, new File(['foo'], 'foo.jpg', {type: 'image/jpeg'})); + testProxy.searchboxCallbackRouterRemote.onContextualInputStatusChanged( + id, FileUploadStatus.kProcessingSuggestSignalsReady, null); + await microtasksFinished(); + + // Autocomplete should be stopped (with matches cleared) and then + // queried again when a file is uploaded. + assertEquals( + testProxy.searchboxHandler.getCallCount('stopAutocomplete'), 1); + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 2); + }); + + [new File(['foo'], 'foo.jpg', {type: 'image/jpeg'}), + new File(['foo'], 'foo.pdf', { + type: 'application/pdf', + })].forEach((file) => { + test( + `announce file upload started and completed: ${file.type}`, + async () => { + createComposeboxElement(testProxy); + + let announcementCount = 0; + const updateAnnouncementCount = () => { + announcementCount += 1; + }; + document.body.addEventListener( + 'cr-a11y-announcer-messages-sent', updateAnnouncementCount); + let announcementPromise = + eventToPromise('cr-a11y-announcer-messages-sent', document.body); + + const id = generateZeroId(); + await uploadFileAndVerify(testProxy, id, file); + + let announcement = await announcementPromise; + assertEquals(announcementCount, 1); + assertTrue(!!announcement); + assertEquals(announcement.detail.messages.length, 1); + + testProxy.searchboxCallbackRouterRemote + .onContextualInputStatusChanged( + id, FileUploadStatus.kUploadSuccessful, null); + await testProxy.searchboxCallbackRouterRemote.$.flushForTesting(); + + announcementPromise = + eventToPromise('cr-a11y-announcer-messages-sent', document.body); + announcement = await announcementPromise; + assertEquals(announcementCount, 2); + assertTrue(!!announcement); + assertEquals(announcement.detail.messages.length, 1); + + // Cleanup event listener. + document.body.removeEventListener( + 'cr-a11y-announcer-messages-sent', updateAnnouncementCount); + assertEquals( + 1, + testProxy.metrics.count( + 'ContextualSearch.File.WebUI.UploadAttemptFailure.NewTabPage', + 0)); + }); + }); + + test('upload empty file fails', async () => { + createComposeboxElement(testProxy); + const file = new File([''], 'foo.jpg', {type: 'image/jpeg'}); + + // Act. + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(file); + const input: HTMLInputElement = + getInputForFileType(testProxy.element, file.type); + input.files = dataTransfer.files; + input.dispatchEvent( + getMockFileChangeEventForType(testProxy.element, file.type)); + await microtasksFinished(); + + // Assert no files uploaded or rendered on the carousel + assertEquals( + testProxy.searchboxHandler.getCallCount(ADD_FILE_CONTEXT_FN), 0); + assertFalse(!!$$<HTMLElement>(testProxy.element, '#carousel')); + assertEquals( + 1, + testProxy.metrics.count( + 'ContextualSearch.File.WebUI.UploadAttemptFailure.NewTabPage', 2)); + }); + + test('upload large file fails', async () => { + const sampleFileMaxSize = 10; + loadTimeData.overrideValues({'composeboxFileMaxSize': sampleFileMaxSize}); + createComposeboxElement(testProxy); + const blob = new Blob( + [new Uint8Array(sampleFileMaxSize + 1)], + {type: 'application/octet-stream'}); + const file = new File([blob], 'foo.jpg', {type: 'image/jpeg'}); + + // Act. + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(file); + const input: HTMLInputElement = + getInputForFileType(testProxy.element, file.type); + input.files = dataTransfer.files; + input.dispatchEvent( + getMockFileChangeEventForType(testProxy.element, file.type)); + await microtasksFinished(); + + // Assert no files uploaded or rendered on the carousel + assertEquals( + testProxy.searchboxHandler.getCallCount(ADD_FILE_CONTEXT_FN), 0); + assertFalse(!!$$<HTMLElement>(testProxy.element, '#carousel')); + assertEquals( + 1, + testProxy.metrics.count( + 'ContextualSearch.File.WebUI.UploadAttemptFailure.NewTabPage', 3)); + }); + + [[ + FileUploadStatus.kValidationFailed, + FileUploadErrorType.kImageProcessingError, + ], + [ + FileUploadStatus.kUploadFailed, + null, + ], + [ + FileUploadStatus.kUploadExpired, + null, + ], + ].forEach(([fileUploadStatus, fileUploadErrorType, ..._]) => { + test( + `Image upload is removed on failed upload status ${fileUploadStatus}`, + async () => { + createComposeboxElement(testProxy); + const id = generateZeroId(); + const file = new File(['foo'], 'foo.jpg', {type: 'image/jpeg'}); + await uploadFileAndVerify(testProxy, id, file); + + testProxy.searchboxCallbackRouterRemote + .onContextualInputStatusChanged( + id, fileUploadStatus as FileUploadStatus, + fileUploadErrorType as FileUploadErrorType | null); + await testProxy.searchboxCallbackRouterRemote.$.flushForTesting(); + + // Assert no files in the carousel. + assertFalse(!!$$<HTMLElement>(testProxy.element, '#carousel')); + + if (fileUploadErrorType !== null) { + assertEquals( + loadTimeData.getString('composeFileTypesAllowedError'), + testProxy.element.$.errorScrim.errorMessage); + } + }); + }); + + test('upload pdf', async () => { + createComposeboxElement(testProxy); + testProxy.searchboxHandler.setPromiseResolveFor( + ADD_FILE_CONTEXT_FN, {low: BigInt(1), high: BigInt(2)}); + + // Assert no files. + assertFalse(!!$$<HTMLElement>(testProxy.element, '#carousel')); + + // Arrange. + const dataTransfer = new DataTransfer(); + const file = new File(['foo'], 'foo.pdf', {type: 'application/pdf'}); + dataTransfer.items.add(file); + testProxy.element.$.fileInputs.$.fileInput.files = dataTransfer.files; + testProxy.element.$.fileInputs.$.fileInput.dispatchEvent( + new Event('change')); + + await testProxy.searchboxHandler.whenCalled(ADD_FILE_CONTEXT_FN); + await microtasksFinished(); + + // Assert one pdf file. + const files = testProxy.element.$.carousel.files; + assertEquals(files.length, 1); + assertEquals(files[0]!.type, 'application/pdf'); + assertEquals(files[0]!.name, 'foo.pdf'); + assertFalse(!!files[0]!.objectUrl); + + assertEquals( + testProxy.searchboxHandler.getCallCount('notifySessionStarted'), 1); + + const fileBuffer = await file.arrayBuffer(); + const fileArray = Array.from(new Uint8Array(fileBuffer)); + + // Assert file is uploaded. + assertEquals( + testProxy.searchboxHandler.getCallCount(ADD_FILE_CONTEXT_FN), 1); + const [[fileInfo, fileData]] = + testProxy.searchboxHandler.getArgs(ADD_FILE_CONTEXT_FN); + assertEquals(fileInfo.fileName, 'foo.pdf'); + assertDeepEquals(fileData.bytes, fileArray); + // Assert context added method was context menu. + const CONTEXT_ADDED_NTP = + 'ContextualSearch.ContextAdded.ContextAddedMethod.NewTabPage'; + assertEquals(1, testProxy.metrics.count(CONTEXT_ADDED_NTP)); + assertEquals( + 1, + testProxy.metrics.count( + CONTEXT_ADDED_NTP, + /* CONTEXT_MENU */ 0)); + }); + + test('delete file', async () => { + loadTimeData.overrideValues({composeboxFileMaxCount: 5}); + createComposeboxElement(testProxy); + let i = 0; + testProxy.searchboxHandler.setResultMapperFor(ADD_FILE_CONTEXT_FN, () => { + i += 1; + return Promise.resolve({low: BigInt(i + 1), high: BigInt(i + 2)}); + }); + + // Arrange. + const dataTransfer = new DataTransfer(); + dataTransfer.items.add( + new File(['foo'], 'foo.pdf', {type: 'application/pdf'})); + dataTransfer.items.add( + new File(['foo2'], 'foo2.pdf', {type: 'application/pdf'})); + + // Since the `onFileChange_` method checks the event target when creating + // the `objectUrl`, we have to mock it here. + const mockFileChange = new Event('change', {bubbles: true}); + Object.defineProperty(mockFileChange, 'target', { + writable: false, + value: testProxy.element.$.fileInputs.$.fileInput, + }); + + testProxy.element.$.fileInputs.$.fileInput.files = dataTransfer.files; + testProxy.element.$.fileInputs.$.fileInput.dispatchEvent(mockFileChange); + + await waitForAddFileCallCount(testProxy.searchboxHandler, 2); + await testProxy.element.updateComplete; + await microtasksFinished(); + + // Assert two files are present initially. + assertEquals(testProxy.element.$.carousel.files.length, 2); + + // Act. + const deletedId = testProxy.element.$.carousel.files[0]!.uuid; + testProxy.element.$.carousel.dispatchEvent(new CustomEvent('delete-file', { + detail: { + uuid: deletedId, + }, + bubbles: true, + composed: true, + })); + + await microtasksFinished(); + + // Assert. + assertEquals(testProxy.element.$.carousel.files.length, 1); + assertEquals(testProxy.searchboxHandler.getCallCount('deleteContext'), 1); + const [idArg, fromChip] = + testProxy.searchboxHandler.getArgs('deleteContext')[0]; + assertEquals(idArg, deletedId); + assertFalse(fromChip); + }); + + test('image upload button clicks file input', () => { + loadTimeData.overrideValues({ + 'composeboxShowContextMenu': true, + }); + createComposeboxElement(testProxy); + let clickCalled = false; + testProxy.element.$.fileInputs.$.imageInput.click = () => { + clickCalled = true; + }; + const contextEntrypoint = $$(testProxy.element, '#contextEntrypoint'); + assertTrue(!!contextEntrypoint); + contextEntrypoint.dispatchEvent( + new CustomEvent('open-image-upload', {bubbles: true, composed: true})); + + // Assert. + assertTrue(clickCalled); + }); + + test('file upload button clicks file input', () => { + loadTimeData.overrideValues({ + 'composeboxShowContextMenu': true, + }); + createComposeboxElement(testProxy); + let clickCalled = false; + testProxy.element.$.fileInputs.$.fileInput.click = () => { + clickCalled = true; + }; + const contextEntrypoint = $$(testProxy.element, '#contextEntrypoint'); + assertTrue(!!contextEntrypoint); + contextEntrypoint.dispatchEvent( + new CustomEvent('open-file-upload', {bubbles: true, composed: true})); + + // Assert. + assertTrue(clickCalled); + }); + + test( + 'upload button should not be disabled except when upload is in progress', + async () => { + loadTimeData.overrideValues({ + 'composeboxShowCreateImageButton': true, + }); + const testInputState = { + ...mockInputState, + maxInstances: { + [InputType.kBrowserTab]: 1, + [InputType.kLensImage]: 1, + [InputType.kLensFile]: 1, + }, + maxTotalInputs: 1, + }; + createComposeboxElement(testProxy); + testProxy.searchboxCallbackRouterRemote.onInputStateChanged( + testInputState); + await microtasksFinished(); + + testProxy.searchboxHandler.setPromiseResolveFor( + ADD_FILE_CONTEXT_FN, {token: {low: BigInt(1), high: BigInt(2)}}); + + // Upload a PDF file. + const pdfFile = new File(['foo'], 'foo.pdf', {type: 'application/pdf'}); + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(pdfFile); + testProxy.element.$.fileInputs.$.fileInput.files = dataTransfer.files; + testProxy.element.$.fileInputs.$.fileInput.dispatchEvent( + new Event('change')); + + await testProxy.searchboxHandler.whenCalled(ADD_FILE_CONTEXT_FN); + await microtasksFinished(); + assertFalse(testProxy.element['uploadButtonDisabled_']); + + // Delete the file. `uploadButtonDisabled` should be false. + const deletedId = testProxy.element.$.carousel.files[0]!.uuid; + testProxy.element.$.carousel.dispatchEvent(new CustomEvent( + 'delete-file', + {detail: {uuid: deletedId}, bubbles: true, composed: true})); + await microtasksFinished(); + assertFalse(testProxy.element['uploadButtonDisabled_']); + testProxy.searchboxHandler.resetResolver(ADD_FILE_CONTEXT_FN); + testProxy.searchboxHandler.setPromiseResolveFor( + ADD_FILE_CONTEXT_FN, {token: {low: BigInt(3), high: BigInt(4)}}); + + // Upload an image file. `uploadButtonDisabled` should be false. + const imageFile = new File(['foo'], 'foo.png', {type: 'image/png'}); + const dataTransfer2 = new DataTransfer(); + dataTransfer2.items.add(imageFile); + + const imageInput = testProxy.element.$.fileInputs.$.imageInput; + imageInput.files = dataTransfer2.files; + imageInput.dispatchEvent(new Event('change')); + + await testProxy.searchboxHandler.whenCalled(ADD_FILE_CONTEXT_FN); + await microtasksFinished(); + assertFalse(testProxy.element['uploadButtonDisabled_']); + + // Enter create image mode. + testProxy.element['activeToolMode_'] = ComposeboxToolMode.kImageGen; + await testProxy.element.updateComplete; + assertFalse(testProxy.element['uploadButtonDisabled_']); + + // Exit create image mode. `uploadButtonDisabled` should be false. + testProxy.element['activeToolMode_'] = ComposeboxToolMode.kUnspecified; + await testProxy.element.updateComplete; + assertFalse(testProxy.element['uploadButtonDisabled_']); + }); + + test('pasting valid files calls addFileContext', async () => { + // Arrange. + loadTimeData.overrideValues({'composeboxFileMaxCount': 5}); + createComposeboxElement(testProxy); + testProxy.searchboxHandler.setPromiseResolveFor( + ADD_FILE_CONTEXT_FN, {token: {low: BigInt(1), high: BigInt(2)}}); + + const pngFile = new File(['foo'], 'foo.png', {type: 'image/png'}); + const pdfFile = new File(['foo'], 'foo.pdf', {type: 'application/pdf'}); + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(pngFile); + dataTransfer.items.add(pdfFile); + const pasteEvent = new ClipboardEvent('paste', { + clipboardData: dataTransfer, + bubbles: true, + cancelable: true, + composed: true, + }); + + // Act. + testProxy.element.$.input.dispatchEvent(pasteEvent); + + // Assert. + // Check that addFileContext (ADD_FILE_CONTEXT_FN) was called twice. + await waitForAddFileCallCount(testProxy.searchboxHandler, 2); + const [[fileInfo1], [fileInfo2]] = + testProxy.searchboxHandler.getArgs(ADD_FILE_CONTEXT_FN); + assertEquals('foo.png', fileInfo1.fileName); + assertEquals('foo.pdf', fileInfo2.fileName); + + // Check that the default paste event was prevented. + assertTrue(pasteEvent.defaultPrevented); + const CONTEXT_ADDED_NTP = + 'ContextualSearch.ContextAdded.ContextAddedMethod.NewTabPage'; + assertEquals(1, testProxy.metrics.count(CONTEXT_ADDED_NTP)); + assertEquals( + 1, + testProxy.metrics.count( + CONTEXT_ADDED_NTP, + /* COPY_PASTE */ 1)); + }); + + test('pasting too many files records metric and prevents paste', async () => { + // Arrange. + const testInputState = { + ...mockInputState, + maxInstances: { + [InputType.kBrowserTab]: 1, + [InputType.kLensImage]: 1, + [InputType.kLensFile]: 1, + }, + maxTotalInputs: 2, + }; + createComposeboxElement(testProxy); + testProxy.searchboxCallbackRouterRemote.onInputStateChanged(testInputState); + await microtasksFinished(); + + testProxy.searchboxHandler.setResultMapperFor(ADD_FILE_CONTEXT_FN, () => { + return Promise.resolve({token: {low: BigInt(123), high: BigInt(0)}}); + }); + + const pngFile1 = new File(['foo'], 'foo1.png', {type: 'image/png'}); + const pngFile2 = new File(['foo'], 'foo2.png', {type: 'image/png'}); + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(pngFile1); + dataTransfer.items.add(pngFile2); + const pasteEvent = new ClipboardEvent('paste', { + clipboardData: dataTransfer, + bubbles: true, + cancelable: true, + composed: true, + }); + + // Act. + testProxy.element.$.input.dispatchEvent(pasteEvent); + await testProxy.searchboxHandler.whenCalled(ADD_FILE_CONTEXT_FN); + await microtasksFinished(); + + // Assert. + // Check that only one files were added. + assertEquals( + 1, testProxy.searchboxHandler.getCallCount(ADD_FILE_CONTEXT_FN)); + + // Check that the "too many files" metric was recorded (Enum value 1). + assertEquals( + 1, + testProxy.metrics.count( + 'ContextualSearch.File.WebUI.UploadAttemptFailure.NewTabPage', 1)); + + // Check that the paste event was prevented. + assertTrue(pasteEvent.defaultPrevented); + + // Check whether the right error would show up. + assertEquals( + loadTimeData.getString('maxImagesReachedError'), + testProxy.element.$.errorScrim.errorMessage); + }); + + test('pasting unsupported files fires validation error', async () => { + // Arrange. + createComposeboxElement(testProxy); + const txtFile = new File(['foo'], 'foo.txt', {type: 'text/plain'}); + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(txtFile); + const pasteEvent = new ClipboardEvent('paste', { + clipboardData: dataTransfer, + bubbles: true, + cancelable: true, + composed: true, + }); + + // Act. + testProxy.element.$.input.dispatchEvent(pasteEvent); + await microtasksFinished(); + + // Assert. + // Check that the correct error event was fired. + assertEquals( + loadTimeData.getString('composeFileTypesAllowedError'), + testProxy.element.$.errorScrim.errorMessage); + + // Check that no files were added. + assertEquals( + 0, testProxy.searchboxHandler.getCallCount(ADD_FILE_CONTEXT_FN)); + + // Check that the paste event was prevented. + assertTrue(pasteEvent.defaultPrevented); + }); + + test( + 'pasting only text does not call addFiles or prevent default', + async () => { + // Arrange. + createComposeboxElement(testProxy); + const dataTransfer = new DataTransfer(); + dataTransfer.setData('text/plain', 'hello'); + const pasteEvent = new ClipboardEvent('paste', { + clipboardData: dataTransfer, + bubbles: true, + cancelable: true, + composed: true, + }); + + // Act. + testProxy.element.$.input.dispatchEvent(pasteEvent); + await microtasksFinished(); + + // Assert. + // Check that no files were added. + assertEquals( + 0, testProxy.searchboxHandler.getCallCount(ADD_FILE_CONTEXT_FN)); + + // Check the paste event was not prevented (onPaste_ returns early). + assertFalse(pasteEvent.defaultPrevented); + }); + + test('pasting mixed files is processed correctly ', async () => { + // Arrange. + createComposeboxElement(testProxy); + let i = 0; + testProxy.searchboxHandler.setResultMapperFor(ADD_FILE_CONTEXT_FN, () => { + i += 1; + return Promise.resolve( + {token: {low: BigInt(i + 1), high: BigInt(i + 2)}}); + }); + const pngFile = new File(['foo'], 'foo.png', {type: 'image/png'}); + const pdfFile = new File(['foo'], 'foo.pdf', {type: 'application/pdf'}); + + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(pngFile); + dataTransfer.items.add(pdfFile); + const pasteEvent = new ClipboardEvent('paste', { + clipboardData: dataTransfer, + bubbles: true, + cancelable: true, + composed: true, + }); + + // Act. + testProxy.element.$.input.dispatchEvent(pasteEvent); + + // Wait for both files to be processed (addFileContext called twice). + await waitForAddFileCallCount(testProxy.searchboxHandler, 2); + await microtasksFinished(); + + // Assert. + // Check if the Carousel received 2 files. + const files = testProxy.element.$.carousel.files; + assertEquals(files.length, 2); + + // Check if the image was identified as an image. + // (has objectUrl) and the PDF was identified as a PDF (no objectUrl). + const imageFile = files.find(f => f.type.includes('image')); + const pdfFileInCarousel = files.find(f => f.type.includes('pdf')); + + // Ensure we found both. + assertTrue(!!imageFile); + assertTrue(!!pdfFileInCarousel); + + // Validate the image (it must have an objectUrl for preview). + assertTrue( + !!imageFile.objectUrl, + 'Image file should have an objectUrl for preview'); + + // Validate the PDF (it must have null objectUrl to show the icon). + assertEquals( + pdfFileInCarousel.objectUrl, null, + 'PDF file should have null objectUrl'); + }); + + test( + 'uploading 6 valid files when limit is 5 uploads 5 and shows error', + async () => { + // Arrange. + const testInputState = { + ...mockInputState, + maxTotalInputs: 5, + }; + createComposeboxElement(testProxy); + testProxy.searchboxCallbackRouterRemote.onInputStateChanged( + testInputState); + await microtasksFinished(); + + let i = 0; + testProxy.searchboxHandler.setResultMapperFor( + ADD_FILE_CONTEXT_FN, () => { + i++; + return Promise.resolve({low: BigInt(i), high: BigInt(0)}); + }); + + const validFiles = Array.from( + {length: 6}, + (_, i) => new File(['foo'], `good${i}.png`, {type: 'image/png'})); + + const dataTransfer = new DataTransfer(); + validFiles.forEach(file => dataTransfer.items.add(file)); + + const pasteEvent = new ClipboardEvent('paste', { + clipboardData: dataTransfer, + bubbles: true, + cancelable: true, + composed: true, + }); + + // Act. + testProxy.element.$.input.dispatchEvent(pasteEvent); + + await waitForAddFileCallCount(testProxy.searchboxHandler, 5); + await microtasksFinished(); + + // Assert. + assertEquals(5, testProxy.element.$.carousel.files.length); + + assertEquals( + loadTimeData.getString('maxImagesReachedError'), + testProxy.element.$.errorScrim.errorMessage); + + assertEquals( + 1, + testProxy.metrics.count( + 'ContextualSearch.File.WebUI.UploadAttemptFailure.NewTabPage', + 1)); + }); + + test( + 'upload mixed files over limit prioritizes max files error and uploads valid ones', + async () => { + // Arrange. + const testInputState = { + ...mockInputState, + maxInstances: { + [InputType.kBrowserTab]: 1, + [InputType.kLensImage]: 3, + [InputType.kLensFile]: 1, + }, + maxTotalInputs: 3, + }; + createComposeboxElement(testProxy); + testProxy.searchboxCallbackRouterRemote.onInputStateChanged( + testInputState); + await microtasksFinished(); + + let i = 0; + testProxy.searchboxHandler.setResultMapperFor( + ADD_FILE_CONTEXT_FN, () => { + i++; + return Promise.resolve( + {token: {low: BigInt(i), high: BigInt(0)}}); + }); + + const files = [ + new File(['foo'], 'good1.png', {type: 'image/png'}), + new File(['foo'], 'good2.png', {type: 'image/png'}), + new File(['foo'], 'good3.png', {type: 'image/png'}), + new File(['foo'], 'bad.txt', {type: 'text/plain'}), + ]; + + const dataTransfer = new DataTransfer(); + files.forEach(file => dataTransfer.items.add(file)); + + const pasteEvent = new ClipboardEvent('paste', { + clipboardData: dataTransfer, + bubbles: true, + cancelable: true, + composed: true, + }); + + // Act. + testProxy.element.$.input.dispatchEvent(pasteEvent); + + await waitForAddFileCallCount(testProxy.searchboxHandler, 3); + await microtasksFinished(); + + // Assert. + assertEquals(3, testProxy.element.$.carousel.files.length); + + assertEquals( + loadTimeData.getString('maxFilesReachedError'), + testProxy.element.$.errorScrim.errorMessage); + + assertEquals( + 1, + testProxy.metrics.count( + 'ContextualSearch.File.WebUI.UploadAttemptFailure.NewTabPage', + 1)); + }); + + test( + 'uploading valid heif and invalid svg adds valid file and shows error', + async () => { + createComposeboxElement(testProxy); + + + let i = 0; + testProxy.searchboxHandler.setResultMapperFor( + ADD_FILE_CONTEXT_FN, () => { + i++; + return Promise.resolve({low: BigInt(i), high: BigInt(0)}); + }); + + const validFile = new File(['foo'], 'image.png', {type: 'image/png'}); + const invalidFile = + new File(['bar'], 'icon.svg', {type: 'image/svg+xml'}); + + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(validFile); + dataTransfer.items.add(invalidFile); + + const pasteEvent = new ClipboardEvent('paste', { + clipboardData: dataTransfer, + bubbles: true, + cancelable: true, + composed: true, + }); + + testProxy.element.$.input.dispatchEvent(pasteEvent); + + await waitForAddFileCallCount(testProxy.searchboxHandler, 1); + await microtasksFinished(); + + assertEquals(1, testProxy.element.$.carousel.files.length); + assertEquals( + + 'image.png', testProxy.element.$.carousel.files[0]!.name); + + assertEquals( + loadTimeData.getString('composeFileTypesAllowedError'), + testProxy.element.$.errorScrim.errorMessage); + }); + + test('notify browser when image is added in create image mode', async () => { + loadTimeData.overrideValues({ + composeboxShowZps: true, + composeboxShowTypedSuggest: false, + 'composeboxFileMaxCount': 1, + }); + createComposeboxElement(testProxy); + await microtasksFinished(); + + // Enter create image mode. + const contextEntrypoint = $$(testProxy.element, '#contextEntrypoint'); + assertTrue(!!contextEntrypoint); + contextEntrypoint.dispatchEvent(new CustomEvent('tool-click', { + detail: {toolMode: ComposeboxToolMode.kImageGen}, + })); + await microtasksFinished(); + assertEquals( + testProxy.searchboxHandler.getCallCount('setActiveToolMode'), 1); + assertEquals( + ComposeboxToolMode.kImageGen, + testProxy.searchboxHandler.getArgs('setActiveToolMode')[0]); + + // Upload an image file. `uploadButtonDisabled` should be false. + const id = generateZeroId(); + await uploadFileAndVerify( + testProxy, id, new File(['foo'], 'foo.jpg', {type: 'image/jpeg'})); + testProxy.searchboxCallbackRouterRemote.onContextualInputStatusChanged( + id, FileUploadStatus.kProcessingSuggestSignalsReady, null); + await microtasksFinished(); + + assertEquals( + testProxy.searchboxHandler.getCallCount('setActiveToolMode'), 2); + assertEquals( + ComposeboxToolMode.kImageGen, + testProxy.searchboxHandler.getArgs('setActiveToolMode')[0]); + + // Deleting the image should call setCreateImageMode again but with + // imagePresent false. + const deletedId = testProxy.element.$.carousel.files[0]!.uuid; + testProxy.element.$.carousel.dispatchEvent(new CustomEvent('delete-file', { + detail: { + uuid: deletedId, + }, + bubbles: true, + composed: true, + })); + + await microtasksFinished(); + assertEquals( + testProxy.searchboxHandler.getCallCount('setActiveToolMode'), 3); + assertEquals( + ComposeboxToolMode.kImageGen, + testProxy.searchboxHandler.getArgs('setActiveToolMode')[0]); + }); + + test('composebox does not open match when only file present', async () => { + createComposeboxElement(testProxy); + + assertEquals(testProxy.searchboxHandler.getCallCount('submitQuery'), 0); + await uploadFileAndVerify( + testProxy, FAKE_TOKEN_STRING, + new File(['foo'], 'foo.jpg', {type: 'image/jpeg'})); + testProxy.searchboxCallbackRouterRemote.onContextualInputStatusChanged( + FAKE_TOKEN_STRING, + FileUploadStatus.kUploadSuccessful, + /*error_type=*/ null, + ); + await microtasksFinished(); + + testProxy.element.$.submitContainer.click(); + await microtasksFinished(); + + // Assert call occurs. + assertEquals( + testProxy.searchboxHandler.getCallCount('submitQuery'), 1, + 'submitQuery count should be 1'); + assertEquals( + testProxy.searchboxHandler.getCallCount('openAutocompleteMatch'), 0, + 'openAutocompleteMatch count should be 0'); + }); + + test('composebox does not show when image is present', async () => { + loadTimeData.overrideValues({ + composeboxShowZps: true, + composeboxShowTypedSuggest: true, + composeboxShowImageSuggest: false, + }); + createComposeboxElement(testProxy); + // Autocomplete queried once when composebox is created. + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 1); + + const matches = [createSearchMatchForTesting()]; + testProxy.searchboxCallbackRouterRemote.autocompleteResultChanged( + createAutocompleteResultForTesting({ + input: '', + matches, + })); + assertTrue(await areMatchesShowing( + testProxy.element, testProxy.searchboxCallbackRouterRemote)); + + // Upload an image. + const id = generateZeroId(); + await uploadFileAndVerify( + testProxy, id, new File(['foo'], 'foo.jpg', {type: 'image/jpeg'})); + + testProxy.searchboxCallbackRouterRemote.onContextualInputStatusChanged( + id, FileUploadStatus.kProcessingSuggestSignalsReady, null); + + // Matches should not show when image is present. + assertFalse(await areMatchesShowing( + testProxy.element, testProxy.searchboxCallbackRouterRemote)); + + // Query autocomplete with image present to get verbatim match. + testProxy.element.$.input.value = 'T'; + testProxy.element.$.input.dispatchEvent(new Event('input')); + await microtasksFinished(); + assertEquals( + testProxy.searchboxHandler.getCallCount('queryAutocomplete'), 2); + }); + + test('add file context fails', async () => { + loadTimeData.overrideValues({composeboxShowPdfUpload: true}); + createComposeboxElement(testProxy); + // Set the promise to reject to simulate a failure. + testProxy.searchboxHandler.setResultMapperFor(ADD_FILE_CONTEXT_FN, () => { + return Promise.reject(FileUploadErrorType.kBrowserProcessingError); + }); + + // Assert no files. + assertFalse(!!$$<HTMLElement>(testProxy.element, '#carousel')); + + // Act. + const dataTransfer = new DataTransfer(); + const file = new File(['foo'], 'foo.pdf', {type: 'application/pdf'}); + dataTransfer.items.add(file); + testProxy.element.$.fileInputs.$.fileInput.files = dataTransfer.files; + testProxy.element.$.fileInputs.$.fileInput.dispatchEvent( + new Event('change')); + + await testProxy.searchboxHandler.whenCalled(ADD_FILE_CONTEXT_FN); + await microtasksFinished(); + + // Assert no files in carousel. + assertFalse(!!$$<HTMLElement>(testProxy.element, '#carousel')); + + assertEquals( + loadTimeData.getString('composeboxFileUploadFailed'), + testProxy.element.$.errorScrim.errorMessage); + }); +});
diff --git a/chrome/test/data/webui/new_tab_page/composebox/test_support.ts b/chrome/test/data/webui/new_tab_page/composebox/test_support.ts index 33e46fb..415242a 100644 --- a/chrome/test/data/webui/new_tab_page/composebox/test_support.ts +++ b/chrome/test/data/webui/new_tab_page/composebox/test_support.ts
@@ -2,8 +2,34 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import type {ComposeboxFile} from 'chrome://new-tab-page/lazy_load.js'; -import {FileUploadStatus} from 'chrome://resources/cr_components/composebox/composebox_query.mojom-webui.js'; +import {ComposeboxElement, ComposeboxProxyImpl} from 'chrome://new-tab-page/lazy_load.js'; +import {$$} from 'chrome://new-tab-page/new_tab_page.js'; +import type {ComposeboxFile} from 'chrome://resources/cr_components/composebox/common.js'; +import {PageCallbackRouter, PageHandlerRemote} from 'chrome://resources/cr_components/composebox/composebox.mojom-webui.js'; +import {FileUploadStatus, ToolMode as ComposeboxToolMode} from 'chrome://resources/cr_components/composebox/composebox_query.mojom-webui.js'; +import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; +import {PageCallbackRouter as SearchboxPageCallbackRouter, PageHandlerRemote as SearchboxPageHandlerRemote} from 'chrome://resources/mojo/components/omnibox/browser/searchbox.mojom-webui.js'; +import type {PageRemote as SearchboxPageRemote} from 'chrome://resources/mojo/components/omnibox/browser/searchbox.mojom-webui.js'; +import type {InputState} from 'chrome://resources/mojo/components/omnibox/composebox/composebox_query.mojom-webui.js'; +import {assertDeepEquals, assertEquals, assertFalse} from 'chrome://webui-test/chai_assert.js'; +import {fakeMetricsPrivate} from 'chrome://webui-test/metrics_test_support.js'; +import type {MetricsTracker} from 'chrome://webui-test/metrics_test_support.js'; +import type {TestMock} from 'chrome://webui-test/test_mock.js'; +import {microtasksFinished} from 'chrome://webui-test/test_util.js'; + +import {installMock} from '../test_support.js'; + +export const FAKE_TOKEN_STRING = '00000000000000001234567890ABCDEF'; +export const FAKE_TOKEN_STRING_2 = '00000000000000001234567890ABCDEE'; +export const ADD_FILE_CONTEXT_FN = 'addFileContext'; +export const ADD_TAB_CONTEXT_FN = 'addTabContext'; + +export function generateZeroId(): string { + // Generate 128 bit unique identifier. + const components = new Uint32Array(4); + return components.reduce( + (id = '', component) => id + component.toString(16).padStart(8, '0'), ''); +} export function createComposeboxFile( index: number, override: Partial<ComposeboxFile> = {}): ComposeboxFile { @@ -22,3 +48,217 @@ }, override); } + +export interface ComposeboxTestElement { + element: ComposeboxElement; + handler: TestMock<PageHandlerRemote>; + searchboxHandler: TestMock<SearchboxPageHandlerRemote>; + searchboxCallbackRouterRemote: SearchboxPageRemote; + metrics: MetricsTracker; +} + +export const mockInputState: InputState = { + hintText: '', // Will be set in setup + toolConfigs: [ + { + tool: ComposeboxToolMode.kDeepSearch, + hintText: 'Research anything', + menuLabel: '', + chipLabel: '', + disableActiveModelSelection: false, + aimUrlParams: [], + }, + { + tool: ComposeboxToolMode.kImageGen, + hintText: 'Describe your image', + menuLabel: '', + chipLabel: '', + disableActiveModelSelection: false, + aimUrlParams: [], + }, + { + tool: ComposeboxToolMode.kCanvas, + hintText: 'Create anything', + menuLabel: '', + chipLabel: '', + disableActiveModelSelection: false, + aimUrlParams: [], + }, + ], + modelConfigs: [], + allowedModels: [], + allowedTools: [], + allowedInputTypes: [], + activeModel: 0, + activeTool: 0, + disabledModels: [], + disabledTools: [], + disabledInputTypes: [], + inputTypeConfigs: [], + toolsSectionConfig: null, + modelSectionConfig: null, + maxInstances: {}, + maxTotalInputs: 0, +}; + +export function setupComposeboxTest(): ComposeboxTestElement { + // We can't return the variables initialized in setup() directly because + // setup() runs later. We can return an object that will be populated. + const testProxy = {} as ComposeboxTestElement; + + setup(() => { + loadTimeData.overrideValues({ + 'composeboxImageFileTypes': + 'image/avif,image/bmp,image/jpeg,image/png,image/webp,image/heif,image/heic', + 'composeboxAttachmentFileTypes': '.pdf,application/pdf', + 'contextualMenuUsePecApi': false, + 'searchboxComposePlaceholder': 'Placeholder', + }); + document.body.innerHTML = window.trustedTypes!.emptyHTML; + const handler = installMock( + PageHandlerRemote, + mock => ComposeboxProxyImpl.setInstance(new ComposeboxProxyImpl( + mock, new PageCallbackRouter(), new SearchboxPageHandlerRemote(), + new SearchboxPageCallbackRouter()))); + const searchboxHandler = installMock( + SearchboxPageHandlerRemote, + mock => ComposeboxProxyImpl.getInstance().searchboxHandler = mock); + searchboxHandler.setPromiseResolveFor('getRecentTabs', {tabs: []}); + searchboxHandler.setPromiseResolveFor('getInputState', { + state: { + allowedModels: [], + allowedTools: [], + allowedInputTypes: [], + activeModel: 0, + activeTool: 0, + disabledModels: [], + disabledTools: [], + disabledInputTypes: [], + inputTypeConfigs: [], + toolConfigs: [], + modelConfigs: [], + toolsSectionConfig: null, + modelSectionConfig: null, + hintText: '', + maxInstances: {}, + maxTotalInputs: 0, + }, + }); + const searchboxCallbackRouterRemote = + ComposeboxProxyImpl.getInstance() + .searchboxCallbackRouter.$.bindNewPipeAndPassRemote(); + const metrics = fakeMetricsPrivate(); + + testProxy.handler = handler; + testProxy.searchboxHandler = searchboxHandler; + testProxy.searchboxCallbackRouterRemote = searchboxCallbackRouterRemote; + testProxy.metrics = metrics; + }); + + return testProxy; +} + +export function createComposeboxElement(testProxy: ComposeboxTestElement) { + testProxy.element = new ComposeboxElement(); + document.body.appendChild(testProxy.element); +} + +export async function waitForAddFileCallCount( + searchboxHandler: TestMock<SearchboxPageHandlerRemote>, + expectedCount: number): Promise<void> { + const startTime = Date.now(); + return new Promise((resolve, reject) => { + const checkCount = () => { + const currentCount = searchboxHandler.getCallCount(ADD_FILE_CONTEXT_FN); + if (currentCount === expectedCount) { + resolve(); + return; + } + + if (Date.now() - startTime >= 5000) { + reject(new Error(`Could not add file ${expectedCount} times.`)); + return; + } + + setTimeout(checkCount, 50); + }; + checkCount(); + }); +} + +export function getInputForFileType( + composeboxElement: ComposeboxElement, fileType: string): HTMLInputElement { + return fileType === 'application/pdf' ? + composeboxElement.$.fileInputs.$.fileInput : + composeboxElement.$.fileInputs.$.imageInput; +} + +export function getMockFileChangeEventForType( + composeboxElement: ComposeboxElement, fileType: string): Event { + if (fileType === 'application/pdf') { + return new Event('change'); + } + + const mockFileChange = new Event('change', {bubbles: true}); + Object.defineProperty(mockFileChange, 'target', { + writable: false, + value: composeboxElement.$.fileInputs.$.imageInput, + }); + return mockFileChange; +} + +export async function areMatchesShowing( + composeboxElement: ComposeboxElement, + searchboxCallbackRouterRemote: SearchboxPageRemote): Promise<boolean> { + // Force a synchronous render. + await searchboxCallbackRouterRemote.$.flushForTesting(); + await microtasksFinished(); + return window.getComputedStyle(composeboxElement.$.matches).display !== + 'none'; +} + +export async function uploadFileAndVerify( + testProxy: ComposeboxTestElement, token: Object, file: File) { + // Assert no files. + assertFalse(!!$$<HTMLElement>(testProxy.element, '#carousel')); + + testProxy.searchboxHandler.setPromiseResolveFor(ADD_FILE_CONTEXT_FN, token); + + // Act. + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(file); + + const input: HTMLInputElement = + getInputForFileType(testProxy.element, file.type); + input.files = dataTransfer.files; + input.dispatchEvent( + getMockFileChangeEventForType(testProxy.element, file.type)); + + await testProxy.searchboxHandler.whenCalled(ADD_FILE_CONTEXT_FN); + await microtasksFinished(); + + assertEquals( + testProxy.searchboxHandler.getCallCount('notifySessionStarted'), 1); + await verifyFileUpload(testProxy, file); +} + +export async function verifyFileUpload( + testProxy: ComposeboxTestElement, file: File) { + // Assert one file. + const files = testProxy.element.$.carousel.files; + assertEquals(files.length, 1); + + assertEquals(files[0]!.type, file.type); + assertEquals(files[0]!.name, file.name); + + // Assert file is uploaded. + assertEquals(testProxy.searchboxHandler.getCallCount(ADD_FILE_CONTEXT_FN), 1); + + const fileBuffer = await file.arrayBuffer(); + const fileArray = Array.from(new Uint8Array(fileBuffer)); + + const [[fileInfo, fileData]] = + testProxy.searchboxHandler.getArgs(ADD_FILE_CONTEXT_FN); + assertEquals(fileInfo.fileName, file.name); + assertDeepEquals(fileData.bytes, fileArray); +}
diff --git a/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.cc b/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.cc index 9ae33637..c21ca35 100644 --- a/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.cc +++ b/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.cc
@@ -94,16 +94,24 @@ RunTest("new_tab_page/transparency_test.js", "mocha.run()"); } -// TODO(crbug.com/483519387): Flaky on Linux and ChromeOS Debug bots. -#if (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) && !defined(NDEBUG) -#define MAYBE_Composebox DISABLED_Composebox -#else -#define MAYBE_Composebox Composebox -#endif -IN_PROC_BROWSER_TEST_F(NewTabPageTest, MAYBE_Composebox) { +IN_PROC_BROWSER_TEST_F(NewTabPageTest, Composebox) { RunTest("new_tab_page/composebox/composebox_test.js", "mocha.run()"); } +IN_PROC_BROWSER_TEST_F(NewTabPageTest, ComposeboxAutocomplete) { + RunTest("new_tab_page/composebox/composebox_autocomplete_test.js", + "mocha.run()"); +} + +IN_PROC_BROWSER_TEST_F(NewTabPageTest, ComposeboxContextMenu) { + RunTest("new_tab_page/composebox/composebox_context_menu_test.js", + "mocha.run()"); +} + +IN_PROC_BROWSER_TEST_F(NewTabPageTest, ComposeboxUpload) { + RunTest("new_tab_page/composebox/composebox_upload_test.js", "mocha.run()"); +} + IN_PROC_BROWSER_TEST_F(NewTabPageTest, ComposeboxFileCarousel) { RunTest("new_tab_page/composebox/file_carousel_test.js", "mocha.run()"); }
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsScopes.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsScopes.java index 0458a20..46dd081 100644 --- a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsScopes.java +++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsScopes.java
@@ -39,7 +39,7 @@ activity, layout, () -> { - return new ActivityWindowAndroid( + return ActivityWindowAndroid.create( activity, /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(activity),
diff --git a/clank b/clank index 9313600..0f6bcbf 160000 --- a/clank +++ b/clank
@@ -1 +1 @@ -Subproject commit 93136002dccd088d38584d5a14cd03c645b6f3e1 +Subproject commit 0f6bcbff8115cd83bf904013b823dbbff9b9736d
diff --git a/components/autofill_strings.grdp b/components/autofill_strings.grdp index e81205a..916ee95 100644 --- a/components/autofill_strings.grdp +++ b/components/autofill_strings.grdp
@@ -1183,6 +1183,9 @@ <message name="IDS_AUTOFILL_AI_SAVE_ENTITY_TO_WALLET_DIALOG_SUBTITLE_NEW" desc="A description that appears beneath the Save title prompt asking users whether they want to save their data to Google Wallet. This sentence essentially defines Autofill in Chrome. We prompt the user to save their information with Google Wallet with the promise that Chrome can then help the user fill out forms across devices more quickly using their saved info."> Save your info and get things done faster, like filling forms across Google products. You can <ph name="MANAGE_INFO_LINK">$1<ex>manage your info</ex></ph> in <ph name="IDS_AUTOFILL_GOOGLE_WALLET_TITLE">$2<ex>Google Wallet</ex></ph> for <ph name="ACCOUNT">$3<ex>alexpark@gmail.com</ex></ph>. </message> + <message name="IDS_AUTOFILL_AI_SAVE_ENTITY_TO_WALLET_SETTINGS_SUBTITLE" desc="A description that appears beneath the Save title prompt asking users whether they want to save their data to Google Wallet from Settings."> + Your info is saved to <ph name="IDS_AUTOFILL_GOOGLE_WALLET_TITLE">$1<ex>Google Wallet</ex></ph> for <ph name="ACCOUNT">$2<ex>alexpark@gmail.com</ex></ph>. You can use it across Google products to fill forms faster. + </message> <message name="IDS_AUTOFILL_AI_UPDATE_ENTITY_DIALOG_UPDATED_ATTRIBUTE_ACCESSIBLE_NAME" desc="Used in entity update dialogs, as the accessible names for rows with updated attributes"> <ph name="ATTRIBUTE_NAME">$1<ex>Passport number</ex></ph> updated </message>
diff --git a/components/autofill_strings_grdp/IDS_AUTOFILL_AI_SAVE_ENTITY_TO_WALLET_SETTINGS_SUBTITLE.png.sha1 b/components/autofill_strings_grdp/IDS_AUTOFILL_AI_SAVE_ENTITY_TO_WALLET_SETTINGS_SUBTITLE.png.sha1 new file mode 100644 index 0000000..9ff04730 --- /dev/null +++ b/components/autofill_strings_grdp/IDS_AUTOFILL_AI_SAVE_ENTITY_TO_WALLET_SETTINGS_SUBTITLE.png.sha1
@@ -0,0 +1 @@ +9fdf4dc3140fc756ac75c77ea36da4f47729a369 \ No newline at end of file
diff --git a/components/bookmarks/browser/bookmark_storage.cc b/components/bookmarks/browser/bookmark_storage.cc index d1d69f7a..6663a89 100644 --- a/components/bookmarks/browser/bookmark_storage.cc +++ b/components/bookmarks/browser/bookmark_storage.cc
@@ -39,14 +39,13 @@ "BookmarkStorageEncrypted"; void BackupCallback(const base::FilePath& path, - const std::optional<base::FilePath> encrypted_file_path) { + const base::FilePath& encrypted_file_path) { base::FilePath backup_path = path.ReplaceExtension(kBackupExtension); base::CopyFile(path, backup_path); if (ShouldWriteEncryptedBookmarksToDisk()) { - CHECK(encrypted_file_path); base::FilePath encrypted_backup_path = - encrypted_file_path->ReplaceExtension(kBackupExtension); - base::CopyFile(encrypted_file_path.value(), encrypted_backup_path); + encrypted_file_path.ReplaceExtension(kBackupExtension); + base::CopyFile(encrypted_file_path, encrypted_backup_path); } } @@ -103,7 +102,7 @@ const scoped_refptr<base::RefCountedData<const os_crypt_async::Encryptor>> encryptor, const base::FilePath& file_path, - const std::optional<base::FilePath> encrypted_file_path) + const base::FilePath& encrypted_file_path) : model_(model), backend_task_runner_(base::ThreadPool::CreateSequencedTaskRunner( {base::MayBlock(), base::TaskPriority::BEST_EFFORT, @@ -117,6 +116,7 @@ kBookmarkStorageHistogramSuffix), last_scheduled_save_(base::TimeTicks::Now()) { CHECK(!file_path.empty()); + CHECK(!encrypted_file_path.empty()); } BookmarkStorage::~BookmarkStorage() {
diff --git a/components/bookmarks/browser/bookmark_storage.h b/components/bookmarks/browser/bookmark_storage.h index 4d02ffa..a86c114 100644 --- a/components/bookmarks/browser/bookmark_storage.h +++ b/components/bookmarks/browser/bookmark_storage.h
@@ -7,8 +7,6 @@ #include <stdint.h> -#include <optional> - #include "base/files/file_path.h" #include "base/files/important_file_writer.h" #include "base/functional/callback_forward.h" @@ -66,7 +64,7 @@ const scoped_refptr<base::RefCountedData<const os_crypt_async::Encryptor>> encryptor, const base::FilePath& file_path, - const std::optional<base::FilePath> encrypted_file_path); + const base::FilePath& encrypted_file_path); BookmarkStorage(const BookmarkStorage&) = delete; BookmarkStorage& operator=(const BookmarkStorage&) = delete; @@ -116,7 +114,7 @@ const scoped_refptr<base::RefCountedData<const os_crypt_async::Encryptor>> encryptor_; - const std::optional<base::FilePath> encrypted_file_path_; + const base::FilePath encrypted_file_path_; // Helper to write bookmark data safely. base::ImportantFileWriter writer_;
diff --git a/components/bookmarks/browser/bookmark_storage_unittest.cc b/components/bookmarks/browser/bookmark_storage_unittest.cc index 43a63c0..459d96c 100644 --- a/components/bookmarks/browser/bookmark_storage_unittest.cc +++ b/components/bookmarks/browser/bookmark_storage_unittest.cc
@@ -34,6 +34,11 @@ return temp_dir.Append(FILE_PATH_LITERAL("TestBookmarks")); } +base::FilePath GetTestEncryptedBookmarksFileNameInNewTempDir() { + const base::FilePath temp_dir = base::CreateUniqueTempDirectoryScopedToTest(); + return temp_dir.Append(FILE_PATH_LITERAL("TestEncryptedBookmarks")); +} + std::unique_ptr<BookmarkModel> CreateModelWithOneBookmark() { std::unique_ptr<BookmarkModel> model(TestBookmarkClient::CreateModel()); const BookmarkNode* bookmark_bar = model->bookmark_bar_node(); @@ -79,10 +84,10 @@ base::test::TaskEnvironment task_environment{ base::test::TaskEnvironment::TimeSource::MOCK_TIME}; - BookmarkStorage storage(model.get(), - BookmarkStorage::kSelectLocalOrSyncableNodes, - /*encryptor=*/nullptr, bookmarks_file_path, - /*encrypted_file_path=*/std::nullopt); + BookmarkStorage storage( + model.get(), BookmarkStorage::kSelectLocalOrSyncableNodes, + /*encryptor=*/nullptr, bookmarks_file_path, + /*encrypted_file_path=*/GetTestEncryptedBookmarksFileNameInNewTempDir()); ASSERT_FALSE(storage.HasScheduledSaveForTesting()); ASSERT_FALSE(base::PathExists(bookmarks_file_path)); @@ -119,7 +124,8 @@ BookmarkStorage storage(model.get(), BookmarkStorage::kSelectLocalOrSyncableNodes, /*encryptor=*/nullptr, bookmarks_file_path, - /*encrypted_file_path=*/std::nullopt); + /*encrypted_file_path=*/ + GetTestEncryptedBookmarksFileNameInNewTempDir()); storage.ScheduleSave(); ASSERT_TRUE(storage.HasScheduledSaveForTesting()); @@ -144,10 +150,10 @@ base::test::TaskEnvironment task_environment{ base::test::TaskEnvironment::TimeSource::MOCK_TIME}; - BookmarkStorage storage(model.get(), - BookmarkStorage::kSelectLocalOrSyncableNodes, - /*encryptor=*/nullptr, bookmarks_file_path, - /*encrypted_file_path=*/std::nullopt); + BookmarkStorage storage( + model.get(), BookmarkStorage::kSelectLocalOrSyncableNodes, + /*encryptor=*/nullptr, bookmarks_file_path, + /*encrypted_file_path=*/GetTestEncryptedBookmarksFileNameInNewTempDir()); // The backup file should be created upon first save, not earlier. task_environment.RunUntilIdle(); @@ -178,10 +184,10 @@ base::test::TaskEnvironment task_environment{ base::test::TaskEnvironment::TimeSource::MOCK_TIME}; - BookmarkStorage storage(model.get(), - BookmarkStorage::kSelectLocalOrSyncableNodes, - /*encryptor=*/nullptr, bookmarks_file_path, - /*encrypted_file_path=*/std::nullopt); + BookmarkStorage storage( + model.get(), BookmarkStorage::kSelectLocalOrSyncableNodes, + /*encryptor=*/nullptr, bookmarks_file_path, + /*encrypted_file_path=*/GetTestEncryptedBookmarksFileNameInNewTempDir()); ASSERT_FALSE(storage.HasScheduledSaveForTesting()); ASSERT_FALSE(base::PathExists(bookmarks_file_path)); @@ -220,9 +226,10 @@ base::test::TaskEnvironment task_environment{ base::test::TaskEnvironment::TimeSource::MOCK_TIME}; - BookmarkStorage storage(model.get(), BookmarkStorage::kSelectAccountNodes, - /*encryptor=*/nullptr, bookmarks_file_path, - /*encrypted_file_path=*/std::nullopt); + BookmarkStorage storage( + model.get(), BookmarkStorage::kSelectAccountNodes, + /*encryptor=*/nullptr, bookmarks_file_path, + /*encrypted_file_path=*/GetTestEncryptedBookmarksFileNameInNewTempDir()); ASSERT_FALSE(base::PathExists(bookmarks_file_path)); ASSERT_FALSE(base::PathExists(backup_file_path)); @@ -251,9 +258,10 @@ base::test::TaskEnvironment task_environment{ base::test::TaskEnvironment::TimeSource::MOCK_TIME}; - BookmarkStorage storage(model.get(), BookmarkStorage::kSelectAccountNodes, - /*encryptor=*/nullptr, bookmarks_file_path, - /*encrypted_file_path=*/std::nullopt); + BookmarkStorage storage( + model.get(), BookmarkStorage::kSelectAccountNodes, + /*encryptor=*/nullptr, bookmarks_file_path, + /*encrypted_file_path=*/GetTestEncryptedBookmarksFileNameInNewTempDir()); ASSERT_EQ(ReadFileToDict(bookmarks_file_path), std::nullopt); @@ -273,11 +281,10 @@ base::HistogramTester histogram_tester; std::unique_ptr<BookmarkModel> model = CreateModelWithOneBookmark(); - const base::FilePath temp_dir = base::CreateUniqueTempDirectoryScopedToTest(); const base::FilePath bookmarks_file_path = - temp_dir.Append(FILE_PATH_LITERAL("TestBookmarks")); + GetTestBookmarksFileNameInNewTempDir(); const base::FilePath encrypted_bookmarks_file_path = - temp_dir.Append(FILE_PATH_LITERAL("TestEncryptedBookmarks")); + GetTestEncryptedBookmarksFileNameInNewTempDir(); scoped_refptr<base::RefCountedData<const os_crypt_async::Encryptor>> encryptor = base::MakeRefCounted< @@ -316,13 +323,12 @@ features, BookmarkEncryptionStage::kWriteBothReadOnlyClear); std::unique_ptr<BookmarkModel> model = CreateModelWithOneBookmark(); - const base::FilePath temp_dir = base::CreateUniqueTempDirectoryScopedToTest(); const base::FilePath bookmarks_file_path = - temp_dir.Append(FILE_PATH_LITERAL("TestBookmarks")); + GetTestBookmarksFileNameInNewTempDir(); const base::FilePath backup_file_path = bookmarks_file_path.ReplaceExtension(FILE_PATH_LITERAL("bak")); const base::FilePath encrypted_bookmarks_file_path = - temp_dir.Append(FILE_PATH_LITERAL("TestEncryptedBookmarks")); + GetTestEncryptedBookmarksFileNameInNewTempDir(); const base::FilePath encrypted_backup_file_path = encrypted_bookmarks_file_path.ReplaceExtension(FILE_PATH_LITERAL("bak"));
diff --git a/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java b/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java index d4134caf..99ac3f0 100644 --- a/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java +++ b/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java
@@ -145,7 +145,7 @@ ThreadUtils.runOnUiThreadBlocking( () -> { mActivity = activityTestRule.getActivity(); - return new ActivityWindowAndroid( + return ActivityWindowAndroid.create( mActivity, /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(mActivity),
diff --git a/components/browser_ui/photo_picker/android/java/src/org/chromium/components/browser_ui/photo_picker/PhotoPickerDialogTest.java b/components/browser_ui/photo_picker/android/java/src/org/chromium/components/browser_ui/photo_picker/PhotoPickerDialogTest.java index 62022dd..a83dbaf 100644 --- a/components/browser_ui/photo_picker/android/java/src/org/chromium/components/browser_ui/photo_picker/PhotoPickerDialogTest.java +++ b/components/browser_ui/photo_picker/android/java/src/org/chromium/components/browser_ui/photo_picker/PhotoPickerDialogTest.java
@@ -142,7 +142,7 @@ mWindowAndroid = ThreadUtils.runOnUiThreadBlocking( () -> { - return new ActivityWindowAndroid( + return ActivityWindowAndroid.create( mActivityTestRule.getActivity(), /* listenToActivityState= */ true, IntentRequestTracker.createFromActivity(
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/containment/ContainmentItem.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/containment/ContainmentItem.java index 352c390..0dd4b58 100644 --- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/containment/ContainmentItem.java +++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/containment/ContainmentItem.java
@@ -35,8 +35,7 @@ * Returns the custom background style for the container. By default, the standard background * will be used. */ - @BackgroundStyle - default int getCustomBackgroundStyle() { + default @BackgroundStyle int getCustomBackgroundStyle() { return BackgroundStyle.STANDARD; }
diff --git a/components/contextual_search/input_state_model.cc b/components/contextual_search/input_state_model.cc index a182c50..865d7328 100644 --- a/components/contextual_search/input_state_model.cc +++ b/components/contextual_search/input_state_model.cc
@@ -526,4 +526,8 @@ return additional_params; } +const InputState& InputStateModel::GetInputState() const { + return state_; +} + } // namespace contextual_search
diff --git a/components/contextual_search/input_state_model.h b/components/contextual_search/input_state_model.h index d32dbf2..572e6ee 100644 --- a/components/contextual_search/input_state_model.h +++ b/components/contextual_search/input_state_model.h
@@ -62,6 +62,9 @@ // Gets additional query params for the current state. std::map<std::string, std::string> GetAdditionalQueryParams(); + // Returns the current state. + const InputState& GetInputState() const; + // Methods for testing. void set_state_for_testing(const InputState& state) { state_ = state; } const InputState& get_state_for_testing() { return state_; }
diff --git a/components/contextual_search/internal/composebox_query_controller.cc b/components/contextual_search/internal/composebox_query_controller.cc index df6a0fed..6b7bc95 100644 --- a/components/contextual_search/internal/composebox_query_controller.cc +++ b/components/contextual_search/internal/composebox_query_controller.cc
@@ -1364,7 +1364,7 @@ void ComposeboxQueryController::CreateImageUploadRequest( lens::LensOverlayRequestId request_id, - const std::vector<uint8_t>& image_data, + std::vector<uint8_t> image_data, std::optional<lens::ImageEncodingOptions> image_options, std::optional<std::string> file_name, RequestBodyProtoCreatedCallback callback) {
diff --git a/components/contextual_search/internal/composebox_query_controller.h b/components/contextual_search/internal/composebox_query_controller.h index 55ea2c72..1ef4721 100644 --- a/components/contextual_search/internal/composebox_query_controller.h +++ b/components/contextual_search/internal/composebox_query_controller.h
@@ -214,7 +214,7 @@ // request. virtual void CreateImageUploadRequest( lens::LensOverlayRequestId request_id, - const std::vector<uint8_t>& image_data, + std::vector<uint8_t> image_data, std::optional<lens::ImageEncodingOptions> options, std::optional<std::string> file_name, RequestBodyProtoCreatedCallback callback);
diff --git a/components/omnibox/browser/on_device_tail_model_service.cc b/components/omnibox/browser/on_device_tail_model_service.cc index a75ec3e..95c3e7d4 100644 --- a/components/omnibox/browser/on_device_tail_model_service.cc +++ b/components/omnibox/browser/on_device_tail_model_service.cc
@@ -13,6 +13,7 @@ #include "base/functional/bind.h" #include "base/functional/callback.h" #include "base/logging.h" +#include "base/memory_coordinator/utils.h" #include "base/metrics/field_trial_params.h" #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" @@ -196,7 +197,7 @@ ResultCallback result_callback) { if (model_task_runner_) { // Do not call the model if memory pressure level is too high. - if (memory_pressure_level() != base::MEMORY_PRESSURE_LEVEL_CRITICAL) { + if (GetMemoryLimit() > base::kCriticalMemoryPressureThreshold) { model_task_runner_->PostTaskAndReplyWithResult( FROM_HERE, base::BindOnce(&RunTailModelExecutor, tail_model_executor_.get(),
diff --git a/components/omnibox/composebox/ios/composebox_query_controller_ios.h b/components/omnibox/composebox/ios/composebox_query_controller_ios.h index cab1a8c..8a89bd1 100644 --- a/components/omnibox/composebox/ios/composebox_query_controller_ios.h +++ b/components/omnibox/composebox/ios/composebox_query_controller_ios.h
@@ -19,7 +19,7 @@ // ComposeboxQueryController overrides: void CreateImageUploadRequest( lens::LensOverlayRequestId request_id, - const std::vector<uint8_t>& image_data, + std::vector<uint8_t> image_data, std::optional<lens::ImageEncodingOptions> options, std::optional<std::string> file_name, RequestBodyProtoCreatedCallback callback) override;
diff --git a/components/omnibox/composebox/ios/composebox_query_controller_ios.mm b/components/omnibox/composebox/ios/composebox_query_controller_ios.mm index 5ae00054f98..406ac51 100644 --- a/components/omnibox/composebox/ios/composebox_query_controller_ios.mm +++ b/components/omnibox/composebox/ios/composebox_query_controller_ios.mm
@@ -18,7 +18,7 @@ void ComposeboxQueryControllerIOS::CreateImageUploadRequest( lens::LensOverlayRequestId request_id, - const std::vector<uint8_t>& image_data, + std::vector<uint8_t> image_data, std::optional<lens::ImageEncodingOptions> image_options, std::optional<std::string> file_name, RequestBodyProtoCreatedCallback callback) {
diff --git a/components/open_from_clipboard/clipboard_recent_content_generic.cc b/components/open_from_clipboard/clipboard_recent_content_generic.cc index c40b7d4c41..0cb4bdd 100644 --- a/components/open_from_clipboard/clipboard_recent_content_generic.cc +++ b/components/open_from_clipboard/clipboard_recent_content_generic.cc
@@ -61,71 +61,95 @@ ClipboardRecentContentGeneric::ClipboardRecentContentGeneric() = default; ClipboardRecentContentGeneric::~ClipboardRecentContentGeneric() = default; -std::optional<GURL> ClipboardRecentContentGeneric::GetRecentURLFromClipboard() { - if (GetClipboardContentAge() > MaximumAgeOfClipboard()) - return std::nullopt; +void ClipboardRecentContentGeneric::GetRecentURLFromClipboard( + GetRecentURLCallback callback) { + if (GetClipboardContentAge() > MaximumAgeOfClipboard()) { + std::move(callback).Run(std::nullopt); + return; + } - // Get and clean up the clipboard before processing. - std::string gurl_string; - ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); ui::DataTransferEndpoint data_dst = ui::DataTransferEndpoint( ui::EndpointType::kDefault, {.notify_if_restricted = false}); + #if BUILDFLAG(IS_ANDROID) - clipboard->ReadBookmark(&data_dst, nullptr, &gurl_string); + std::string gurl_string; + ui::Clipboard::GetForCurrentThread()->ReadBookmark(&data_dst, nullptr, + &gurl_string); + OnReadURLAsAsciiText(std::move(callback), std::move(gurl_string)); #else - clipboard->ReadAsciiText(ui::ClipboardBuffer::kCopyPaste, &data_dst, - &gurl_string); + ui::Clipboard::GetForCurrentThread()->ReadAsciiText( + ui::ClipboardBuffer::kCopyPaste, std::move(data_dst), + base::BindOnce(&ClipboardRecentContentGeneric::OnReadURLAsAsciiText, + weak_factory_.GetWeakPtr(), std::move(callback))); #endif // BUILDFLAG(IS_ANDROID) +} + +void ClipboardRecentContentGeneric::OnReadURLAsAsciiText( + GetRecentURLCallback callback, + std::string gurl_string) { base::TrimWhitespaceASCII(gurl_string, base::TrimPositions::TRIM_ALL, &gurl_string); - // Interpret the clipboard as a URL if possible. - GURL url; // If there is mid-string whitespace, don't attempt to interpret the string // as a URL. (Otherwise gurl will happily try to convert // "http://example.com extra words" into "http://example.com%20extra%20words", // which is not likely to be a useful or intended destination.) - if (gurl_string.find_first_of(base::kWhitespaceASCII) != std::string::npos) - return std::nullopt; - if (!gurl_string.empty()) { - url = GURL(gurl_string); - } else { - // Fall back to unicode / UTF16, as some URLs may use international domain - // names, not punycode. - std::u16string gurl_string16; - clipboard->ReadText(ui::ClipboardBuffer::kCopyPaste, &data_dst, - &gurl_string16); - base::TrimWhitespace(gurl_string16, base::TrimPositions::TRIM_ALL, - &gurl_string16); - if (gurl_string16.find_first_of(base::kWhitespaceUTF16) != - std::string::npos) - return std::nullopt; - if (!gurl_string16.empty()) - url = GURL(gurl_string16); + if (!gurl_string.empty() && + gurl_string.find_first_of(base::kWhitespaceASCII) == std::string::npos) { + GURL url(gurl_string); + if (url.is_valid() && IsAppropriateSuggestion(url)) { + std::move(callback).Run(std::move(url)); + return; + } } - if (!url.is_valid() || !IsAppropriateSuggestion(url)) { - return std::nullopt; - } - return url; -} -std::optional<std::u16string> -ClipboardRecentContentGeneric::GetRecentTextFromClipboard() { - if (GetClipboardContentAge() > MaximumAgeOfClipboard()) - return std::nullopt; - - std::u16string text_from_clipboard; + // Fall back to unicode / UTF16, as some URLs may use international domain + // names, not punycode. ui::DataTransferEndpoint data_dst = ui::DataTransferEndpoint( ui::EndpointType::kDefault, {.notify_if_restricted = false}); ui::Clipboard::GetForCurrentThread()->ReadText( - ui::ClipboardBuffer::kCopyPaste, &data_dst, &text_from_clipboard); - base::TrimWhitespace(text_from_clipboard, base::TrimPositions::TRIM_ALL, - &text_from_clipboard); - if (text_from_clipboard.empty()) { - return std::nullopt; + ui::ClipboardBuffer::kCopyPaste, std::move(data_dst), + base::BindOnce(&ClipboardRecentContentGeneric::OnReadText, + weak_factory_.GetWeakPtr(), std::move(callback))); +} + +void ClipboardRecentContentGeneric::OnReadText(GetRecentURLCallback callback, + std::u16string gurl_string16) { + base::TrimWhitespace(gurl_string16, base::TrimPositions::TRIM_ALL, + &gurl_string16); + if (!gurl_string16.empty() && + gurl_string16.find_first_of(base::kWhitespaceUTF16) == + std::string::npos) { + GURL url(gurl_string16); + if (url.is_valid() && IsAppropriateSuggestion(url)) { + std::move(callback).Run(std::move(url)); + return; + } + } + std::move(callback).Run(std::nullopt); +} + +void ClipboardRecentContentGeneric::GetRecentTextFromClipboard( + GetRecentTextCallback callback) { + if (GetClipboardContentAge() > MaximumAgeOfClipboard()) { + std::move(callback).Run(std::nullopt); + return; } - return text_from_clipboard; + ui::DataTransferEndpoint data_dst = ui::DataTransferEndpoint( + ui::EndpointType::kDefault, {.notify_if_restricted = false}); + ui::Clipboard::GetForCurrentThread()->ReadText( + ui::ClipboardBuffer::kCopyPaste, std::move(data_dst), + base::BindOnce( + [](GetRecentTextCallback callback, std::u16string text) { + base::TrimWhitespace(text, base::TrimPositions::TRIM_ALL, &text); + if (text.empty()) { + std::move(callback).Run(std::nullopt); + } else { + std::move(callback).Run(std::move(text)); + } + }, + std::move(callback))); } void ClipboardRecentContentGeneric::GetRecentImageFromClipboard( @@ -203,16 +227,6 @@ std::move(callback).Run(matching_types); } -void ClipboardRecentContentGeneric::GetRecentURLFromClipboard( - GetRecentURLCallback callback) { - std::move(callback).Run(GetRecentURLFromClipboard()); -} - -void ClipboardRecentContentGeneric::GetRecentTextFromClipboard( - GetRecentTextCallback callback) { - std::move(callback).Run(GetRecentTextFromClipboard()); -} - base::TimeDelta ClipboardRecentContentGeneric::GetClipboardContentAge() const { const base::Time last_modified_time = ui::Clipboard::GetForCurrentThread()->GetLastModifiedTime();
diff --git a/components/open_from_clipboard/clipboard_recent_content_generic.h b/components/open_from_clipboard/clipboard_recent_content_generic.h index 27ba4ef..3b58e7a 100644 --- a/components/open_from_clipboard/clipboard_recent_content_generic.h +++ b/components/open_from_clipboard/clipboard_recent_content_generic.h
@@ -5,6 +5,7 @@ #ifndef COMPONENTS_OPEN_FROM_CLIPBOARD_CLIPBOARD_RECENT_CONTENT_GENERIC_H_ #define COMPONENTS_OPEN_FROM_CLIPBOARD_CLIPBOARD_RECENT_CONTENT_GENERIC_H_ +#include "base/memory/weak_ptr.h" #include "base/time/time.h" #include "components/open_from_clipboard/clipboard_recent_content.h" #include "url/gurl.h" @@ -26,16 +27,6 @@ ~ClipboardRecentContentGeneric() override; - // Returns clipboard content as URL, if it has a compatible type, - // is recent enough, has not been suppressed and will not trigger a system - // notification that the clipboard has been accessed. - std::optional<GURL> GetRecentURLFromClipboard(); - - // Returns clipboard content as text, if it has a compatible type, - // is recent enough, has not been suppressed and will not trigger a system - // notification that the clipboard has been accessed. - std::optional<std::u16string> GetRecentTextFromClipboard(); - // Return if system's clipboard contains an image that will not trigger a // system notification that the clipboard has been accessed. bool HasRecentImageFromClipboard(); @@ -55,6 +46,12 @@ private: // Returns true if the URL is appropriate to be suggested. static bool IsAppropriateSuggestion(const GURL& url); + + void OnReadURLAsAsciiText(GetRecentURLCallback callback, + std::string gurl_string); + void OnReadText(GetRecentURLCallback callback, std::u16string gurl_string16); + + base::WeakPtrFactory<ClipboardRecentContentGeneric> weak_factory_{this}; }; #endif // COMPONENTS_OPEN_FROM_CLIPBOARD_CLIPBOARD_RECENT_CONTENT_GENERIC_H_
diff --git a/components/open_from_clipboard/clipboard_recent_content_generic_unittest.cc b/components/open_from_clipboard/clipboard_recent_content_generic_unittest.cc index 6913b89d..0ede76dc 100644 --- a/components/open_from_clipboard/clipboard_recent_content_generic_unittest.cc +++ b/components/open_from_clipboard/clipboard_recent_content_generic_unittest.cc
@@ -16,6 +16,8 @@ #include "base/strings/utf_string_conversions.h" #include "base/test/bind.h" #include "base/test/scoped_feature_list.h" +#include "base/test/task_environment.h" +#include "base/test/test_future.h" #include "base/time/time.h" #include "build/build_config.h" #include "components/open_from_clipboard/clipboard_recent_content_features.h" @@ -73,6 +75,8 @@ class ClipboardRecentContentGenericTest : public testing::Test { protected: + base::test::TaskEnvironment task_environment_; + void SetUp() override { // Make sure "chrome" as standard scheme for non chrome embedder. std::vector<std::string> standard_schemes = url::GetStandardSchemes(); @@ -130,8 +134,11 @@ for (size_t i = 0; i < std::size(test_data); ++i) { test_clipboard_->WriteText(test_data[i].clipboard); test_clipboard_->SetLastModifiedTime(now - base::Seconds(10)); - EXPECT_EQ(test_data[i].expected_get_recent_url_value, - recent_content.GetRecentURLFromClipboard().has_value()) + + base::test::TestFuture<std::optional<GURL>> future; + recent_content.GetRecentURLFromClipboard(future.GetCallback()); + std::optional<GURL> url = future.Take(); + EXPECT_EQ(test_data[i].expected_get_recent_url_value, url.has_value()) << "for input " << test_data[i].clipboard; } } @@ -146,11 +153,21 @@ std::string text = "http://example.com/"; test_clipboard_->WriteText(text); test_clipboard_->SetLastModifiedTime(now - base::Minutes(9)); - EXPECT_TRUE(recent_content.GetRecentURLFromClipboard().has_value()); + + { + base::test::TestFuture<std::optional<GURL>> future; + recent_content.GetRecentURLFromClipboard(future.GetCallback()); + EXPECT_TRUE(future.Take().has_value()); + } + // If the last modified time is 10 minutes ago, the URL shouldn't be // suggested. test_clipboard_->SetLastModifiedTime(now - base::Minutes(11)); - EXPECT_FALSE(recent_content.GetRecentURLFromClipboard().has_value()); + { + base::test::TestFuture<std::optional<GURL>> future; + recent_content.GetRecentURLFromClipboard(future.GetCallback()); + EXPECT_FALSE(future.Take().has_value()); + } } TEST_F(ClipboardRecentContentGenericTest, OlderContentNotSuggestedLowerLimit) { @@ -162,7 +179,11 @@ std::string text = "http://example.com/"; test_clipboard_->WriteText(text); test_clipboard_->SetLastModifiedTime(now - base::Minutes(2)); - EXPECT_FALSE(recent_content.GetRecentURLFromClipboard().has_value()); + { + base::test::TestFuture<std::optional<GURL>> future; + recent_content.GetRecentURLFromClipboard(future.GetCallback()); + EXPECT_FALSE(future.Take().has_value()); + } } TEST_F(ClipboardRecentContentGenericTest, GetClipboardContentAge) { @@ -184,20 +205,41 @@ std::string text = "http://example.com/"; test_clipboard_->WriteText(text); test_clipboard_->SetLastModifiedTime(now - base::Seconds(10)); - EXPECT_TRUE(recent_content.GetRecentURLFromClipboard().has_value()); - EXPECT_TRUE(recent_content.GetRecentTextFromClipboard().has_value()); + + { + base::test::TestFuture<std::optional<GURL>> future; + recent_content.GetRecentURLFromClipboard(future.GetCallback()); + EXPECT_TRUE(future.Take().has_value()); + } + { + base::test::TestFuture<std::optional<std::u16string>> future; + recent_content.GetRecentTextFromClipboard(future.GetCallback()); + EXPECT_TRUE(future.Take().has_value()); + } EXPECT_FALSE(recent_content.HasRecentImageFromClipboard()); // After suppressing it, it shouldn't be suggested. recent_content.SuppressClipboardContent(); - EXPECT_FALSE(recent_content.GetRecentURLFromClipboard().has_value()); + { + base::test::TestFuture<std::optional<GURL>> future; + recent_content.GetRecentURLFromClipboard(future.GetCallback()); + EXPECT_FALSE(future.Take().has_value()); + } // If the clipboard changes, even if to the same thing again, the content // should be suggested again. test_clipboard_->WriteText(text); test_clipboard_->SetLastModifiedTime(now); - EXPECT_TRUE(recent_content.GetRecentURLFromClipboard().has_value()); - EXPECT_TRUE(recent_content.GetRecentTextFromClipboard().has_value()); + { + base::test::TestFuture<std::optional<GURL>> future; + recent_content.GetRecentURLFromClipboard(future.GetCallback()); + EXPECT_TRUE(future.Take().has_value()); + } + { + base::test::TestFuture<std::optional<std::u16string>> future; + recent_content.GetRecentTextFromClipboard(future.GetCallback()); + EXPECT_TRUE(future.Take().has_value()); + } EXPECT_FALSE(recent_content.HasRecentImageFromClipboard()); } @@ -208,13 +250,20 @@ std::string text = " Foo Bar "; test_clipboard_->WriteText(text); test_clipboard_->SetLastModifiedTime(now - base::Seconds(10)); - EXPECT_TRUE(recent_content.GetRecentTextFromClipboard().has_value()); - EXPECT_FALSE(recent_content.GetRecentURLFromClipboard().has_value()); + + { + base::test::TestFuture<std::optional<std::u16string>> future; + recent_content.GetRecentTextFromClipboard(future.GetCallback()); + std::optional<std::u16string> text_result = future.Take(); + EXPECT_TRUE(text_result.has_value()); + EXPECT_STREQ("Foo Bar", base::UTF16ToUTF8(text_result.value()).c_str()); + } + { + base::test::TestFuture<std::optional<GURL>> future; + recent_content.GetRecentURLFromClipboard(future.GetCallback()); + EXPECT_FALSE(future.Take().has_value()); + } EXPECT_FALSE(recent_content.HasRecentImageFromClipboard()); - EXPECT_STREQ( - "Foo Bar", - base::UTF16ToUTF8(recent_content.GetRecentTextFromClipboard().value()) - .c_str()); } TEST_F(ClipboardRecentContentGenericTest, ClearClipboardContent) { @@ -224,17 +273,29 @@ std::string text = "http://example.com/"; test_clipboard_->WriteText(text); test_clipboard_->SetLastModifiedTime(now - base::Seconds(10)); - EXPECT_TRUE(recent_content.GetRecentURLFromClipboard().has_value()); + { + base::test::TestFuture<std::optional<GURL>> future; + recent_content.GetRecentURLFromClipboard(future.GetCallback()); + EXPECT_TRUE(future.Take().has_value()); + } // After clear it, it shouldn't be suggested. recent_content.ClearClipboardContent(); - EXPECT_FALSE(recent_content.GetRecentURLFromClipboard().has_value()); + { + base::test::TestFuture<std::optional<GURL>> future; + recent_content.GetRecentURLFromClipboard(future.GetCallback()); + EXPECT_FALSE(future.Take().has_value()); + } // If the clipboard changes, even if to the same thing again, the content // should be suggested again. test_clipboard_->WriteText(text); test_clipboard_->SetLastModifiedTime(now); - EXPECT_TRUE(recent_content.GetRecentURLFromClipboard().has_value()); + { + base::test::TestFuture<std::optional<GURL>> future; + recent_content.GetRecentURLFromClipboard(future.GetCallback()); + EXPECT_TRUE(future.Take().has_value()); + } } TEST_F(ClipboardRecentContentGenericTest, HasRecentImageFromClipboard) { @@ -246,12 +307,21 @@ test_clipboard_->WriteBitmap(bitmap); test_clipboard_->SetLastModifiedTime(now - base::Seconds(10)); EXPECT_TRUE(recent_content.HasRecentImageFromClipboard()); - EXPECT_FALSE(recent_content.GetRecentURLFromClipboard().has_value()); - EXPECT_FALSE(recent_content.GetRecentTextFromClipboard().has_value()); - recent_content.GetRecentImageFromClipboard( - base::BindLambdaForTesting([&bitmap](std::optional<gfx::Image> image) { - EXPECT_TRUE(gfx::BitmapsAreEqual(image->AsBitmap(), bitmap)); - })); + { + base::test::TestFuture<std::optional<GURL>> future; + recent_content.GetRecentURLFromClipboard(future.GetCallback()); + EXPECT_FALSE(future.Take().has_value()); + } + { + base::test::TestFuture<std::optional<std::u16string>> future; + recent_content.GetRecentTextFromClipboard(future.GetCallback()); + EXPECT_FALSE(future.Take().has_value()); + } + base::test::TestFuture<std::optional<gfx::Image>> future; + recent_content.GetRecentImageFromClipboard(future.GetCallback()); + std::optional<gfx::Image> image = future.Take(); + EXPECT_TRUE(image.has_value()); + EXPECT_TRUE(gfx::BitmapsAreEqual(image->AsBitmap(), bitmap)); } TEST_F(ClipboardRecentContentGenericTest, HasRecentContentFromClipboard_URL) {
diff --git a/components/page_content_annotations/content/annotate_page_content_request.cc b/components/page_content_annotations/content/annotate_page_content_request.cc index 94b584e..0aac2a6ce 100644 --- a/components/page_content_annotations/content/annotate_page_content_request.cc +++ b/components/page_content_annotations/content/annotate_page_content_request.cc
@@ -233,7 +233,12 @@ FetchPageContextOptions options; options.annotated_page_content_options = request_->Clone(); if (features::kPageContentCacheEnableScreenshot.Get()) { - options.screenshot_options = ScreenshotOptions::ViewportOnly(std::nullopt); + ScreenshotOptions::ScreenshotCollectionOptions + screenshot_collection_options; + screenshot_collection_options.screenshot_image_format = + ScreenshotOptions::ScreenshotImageFormat::kPng; + options.screenshot_options = + ScreenshotOptions::ViewportOnly(std::nullopt, std::nullopt); } fetch_page_context_callback_.Run( *web_contents_, options, /*progress_listener=*/nullptr,
diff --git a/components/page_content_annotations/content/page_context_fetcher.cc b/components/page_content_annotations/content/page_context_fetcher.cc index ba8ad01..3ba9932 100644 --- a/components/page_content_annotations/content/page_context_fetcher.cc +++ b/components/page_content_annotations/content/page_context_fetcher.cc
@@ -51,16 +51,30 @@ namespace { -gfx::Size GetScreenshotSize(const gfx::Size& original_size) { +gfx::Size GetScreenshotSize( + const gfx::Size& original_size, + const std::optional<ScreenshotOptions::ScreenshotCollectionOptions>& + screenshot_collection_options) { + if (original_size.IsEmpty()) { + return gfx::Size(); + } + // By default, no scaling. - if (!base::FeatureList::IsEnabled(kGlicTabScreenshotExperiment)) { + if (!base::FeatureList::IsEnabled(kGlicTabScreenshotExperiment) && + !screenshot_collection_options) { return gfx::Size(); } // If either width or height is 0, or the view is empty, no scaling. - int max_width = kMaxScreenshotWidthParam.Get(); - int max_height = kMaxScreenshotHeightParam.Get(); - if (max_width == 0 || max_height == 0 || original_size.IsEmpty()) { + int max_width = (screenshot_collection_options && + screenshot_collection_options->max_width) + ? screenshot_collection_options->max_width.value() + : kMaxScreenshotWidthParam.Get(); + int max_height = (screenshot_collection_options && + screenshot_collection_options->max_height) + ? screenshot_collection_options->max_height.value() + : kMaxScreenshotHeightParam.Get(); + if (max_width == 0 || max_height == 0) { return gfx::Size(); } @@ -96,7 +110,23 @@ return new_size.width() / original_size.width(); } -int GetScreenshotJpegQuality() { +int GetScreenshotJpegQuality( + const std::optional<ScreenshotOptions::ScreenshotCollectionOptions>& + screenshot_collection_options) { + if (screenshot_collection_options && + screenshot_collection_options->screenshot_compression_quality) { + switch ( + screenshot_collection_options->screenshot_compression_quality.value()) { + case ScreenshotOptions::ScreenshotCompressionQuality::kLow: + return 20; + case ScreenshotOptions::ScreenshotCompressionQuality::kMedium: + return 40; + case ScreenshotOptions::ScreenshotCompressionQuality::kHigh: + return 60; + case ScreenshotOptions::ScreenshotCompressionQuality::kNone: + return 100; + } + } if (!base::FeatureList::IsEnabled(kGlicTabScreenshotExperiment)) { return 40; } @@ -104,16 +134,28 @@ return std::max(0, std::min(100, kScreenshotQuality.Get())); } -int GetScreenshotWebPQuality() { - return GetScreenshotJpegQuality(); +int GetScreenshotWebPQuality( + const std::optional<ScreenshotOptions::ScreenshotCollectionOptions>& + screenshot_collection_options) { + return GetScreenshotJpegQuality(screenshot_collection_options); } -// Png only has two modes exposed, so we use the quality to determine if it is -// low quality or not by checking if it is 50 or lower. -bool ShouldPngScreenshotBeLowQuality() { +// Png only has two modes exposed. +bool ShouldPngScreenshotBeLowQuality( + const std::optional<ScreenshotOptions::ScreenshotCollectionOptions>& + screenshot_collection_options) { + // If low is configured, then we should use low quality for png screenshots. + if (screenshot_collection_options && + screenshot_collection_options->screenshot_compression_quality) { + return screenshot_collection_options->screenshot_compression_quality + .value() == + ScreenshotOptions::ScreenshotCompressionQuality::kLow; + } if (!base::FeatureList::IsEnabled(kGlicTabScreenshotExperiment)) { return false; } + // We use the quality to determine if it is low quality or not by checking if + // it is 50 or lower. return kScreenshotQuality.Get() < 50; } @@ -131,7 +173,21 @@ constexpr base::TimeDelta kScreenshotTimeoutBrowserAllowance = base::Milliseconds(500); -ScreenshotImageType GetScreenshotImageType() { +ScreenshotImageType GetScreenshotImageType( + const std::optional<ScreenshotOptions::ScreenshotCollectionOptions>& + screenshot_collection_options) { + if (screenshot_collection_options && + screenshot_collection_options->screenshot_image_format) { + switch (screenshot_collection_options->screenshot_image_format.value()) { + case ScreenshotOptions::ScreenshotImageFormat::kJpeg: + return ScreenshotImageType::kJpeg; + case ScreenshotOptions::ScreenshotImageFormat::kPng: + return ScreenshotImageType::kPng; + case ScreenshotOptions::ScreenshotImageFormat::kWebp: + return ScreenshotImageType::kWebp; + } + } + if (!base::FeatureList::IsEnabled(kGlicTabScreenshotExperiment)) { return ScreenshotImageType::kJpeg; } @@ -240,14 +296,18 @@ } // namespace // static -std::optional<std::vector<uint8_t>> EncodeScreenshot(const SkBitmap& bitmap) { +std::optional<std::vector<uint8_t>> EncodeScreenshot( + const SkBitmap& bitmap, + const std::optional<ScreenshotOptions::ScreenshotCollectionOptions>& + screenshot_collection_options) { std::optional<std::vector<uint8_t>> encoded; - switch (GetScreenshotImageType()) { + switch (GetScreenshotImageType(screenshot_collection_options)) { case ScreenshotImageType::kJpeg: - encoded = gfx::JPEGCodec::Encode(bitmap, GetScreenshotJpegQuality()); + encoded = gfx::JPEGCodec::Encode( + bitmap, GetScreenshotJpegQuality(screenshot_collection_options)); break; case ScreenshotImageType::kPng: - if (ShouldPngScreenshotBeLowQuality()) { + if (ShouldPngScreenshotBeLowQuality(screenshot_collection_options)) { encoded = gfx::PNGCodec::FastEncodeBGRASkBitmap( bitmap, /*discard_transparency=*/true); } else { @@ -256,7 +316,8 @@ } break; case ScreenshotImageType::kWebp: - encoded = gfx::WebpCodec::Encode(bitmap, GetScreenshotWebPQuality()); + encoded = gfx::WebpCodec::Encode( + bitmap, GetScreenshotWebPQuality(screenshot_collection_options)); break; default: break; @@ -396,6 +457,8 @@ } screenshot_redaction_color_ = screenshot_options.redaction_color(); + screenshot_collection_options_ = + screenshot_options.screenshot_collection_options(); gfx::Size view_size = view->GetViewBounds().size(); @@ -433,8 +496,9 @@ } PageContentScreenshotService::RequestParams request_params = { .clip_rect = clip_rect, - .scale_factor = - GetScreenshotScaleFactor(view_size, GetScreenshotSize(view_size)), + .scale_factor = GetScreenshotScaleFactor( + view_size, + GetScreenshotSize(view_size, screenshot_collection_options_)), .clip_x_coord_override = clip_coord_override, .clip_y_coord_override = clip_coord_override, .redaction_params = std::move(redaction_params), @@ -451,7 +515,8 @@ view->CopyFromSurface( gfx::Rect(), // Copy entire surface area. - GetScreenshotSize(view_size), kScreenshotTimeout.Get(), + GetScreenshotSize(view_size, screenshot_collection_options_), + kScreenshotTimeout.Get(), base::BindOnce(&PageContextFetcher::ReceivedViewportBitmap, GetWeakPtr())); } @@ -518,11 +583,13 @@ base::BindOnce( [](const SkBitmap& bitmap, std::vector<gfx::Rect> visible_bounding_boxes_for_redaction, - SkColor4f redaction_color) { + SkColor4f redaction_color, + std::optional<ScreenshotOptions::ScreenshotCollectionOptions> + screenshot_collection_options) { SkBitmap redacted_bitmap = RedactScreenshotOnWorkerThread( bitmap, visible_bounding_boxes_for_redaction, redaction_color); std::optional<std::vector<uint8_t>> encoded = - EncodeScreenshot(redacted_bitmap); + EncodeScreenshot(redacted_bitmap, screenshot_collection_options); base::expected<std::pair<std::vector<uint8_t>, SkBitmap>, std::string> reply; @@ -535,7 +602,7 @@ return reply; }, *screenshot_bitmap_, std::move(visible_bounding_boxes_for_redaction), - screenshot_redaction_color_), + screenshot_redaction_color_, screenshot_collection_options_), base::BindOnce(&PageContextFetcher::ReceivedEncodedScreenshot, GetWeakPtr())); screenshot_bitmap_.reset(); @@ -615,7 +682,7 @@ if (screenshot_data.has_value()) { pending_result_->screenshot_result.value().screenshot_data = std::move(screenshot_data.value().first); - switch (GetScreenshotImageType()) { + switch (GetScreenshotImageType(screenshot_collection_options_)) { case ScreenshotImageType::kJpeg: pending_result_->screenshot_result.value().mime_type = "image/jpeg"; break;
diff --git a/components/page_content_annotations/content/page_context_fetcher.h b/components/page_content_annotations/content/page_context_fetcher.h index 2a36001..05e0bbe 100644 --- a/components/page_content_annotations/content/page_context_fetcher.h +++ b/components/page_content_annotations/content/page_context_fetcher.h
@@ -63,17 +63,63 @@ class ScreenshotOptions { public: + // The format of the screenshot. + // The default value is JPEG. + enum class ScreenshotImageFormat { + kJpeg, + kPng, + kWebp, + }; + + // The compression quality of the screenshot. + // Depending on screenshot format, the compression quality may not be + // respected or may mean something different. + // The default value is MEDIUM. + enum class ScreenshotCompressionQuality { + kLow, + kMedium, + kHigh, + kNone, + }; + + struct ScreenshotCollectionOptions { + public: + // The maximum width of the screenshot. If 0, the screenshot will be + // returned at the natural width of the screen. Screenshot will be scaled to + // fit the max width and height while maintaining the aspect ratio. + std::optional<int32_t> max_width; + // The maximum height of the screenshot. If 0, the screenshot will be + // returned at the natural height of the screen. + // Screenshot will be scaled to fit the max width and height while + // maintaining the aspect ratio. + std::optional<int32_t> max_height; + // The format of the screenshot. + // The default value is JPEG. + std::optional<ScreenshotImageFormat> screenshot_image_format; + // The compression quality of the screenshot. + // Depending on screenshot format, the compression quality may not be + // respected or may mean something different. + // The default value is MEDIUM. + std::optional<ScreenshotCompressionQuality> screenshot_compression_quality; + }; + // Creates options for a full-page screenshot. // Full-page screenshots always use the paint preview backend. - static ScreenshotOptions FullPage(PaintPreviewOptions paint_preview_options) { - return ScreenshotOptions(/*capture_full_page=*/true, paint_preview_options); + static ScreenshotOptions FullPage(PaintPreviewOptions paint_preview_options, + std::optional<ScreenshotCollectionOptions> + screenshot_collection_options) { + return ScreenshotOptions(/*capture_full_page=*/true, paint_preview_options, + std::move(screenshot_collection_options)); } // Creates options for a viewport-only screenshot. static ScreenshotOptions ViewportOnly( - std::optional<PaintPreviewOptions> paint_preview_options) { + std::optional<PaintPreviewOptions> paint_preview_options, + std::optional<ScreenshotCollectionOptions> + screenshot_collection_options) { return ScreenshotOptions(/*capture_full_page=*/false, - std::move(paint_preview_options)); + std::move(paint_preview_options), + std::move(screenshot_collection_options)); } bool capture_full_page() const { return capture_full_page_; } @@ -86,13 +132,21 @@ void set_redaction_color_for_testing(SkColor4f redaction_color) { redaction_color_ = redaction_color; } + const std::optional<ScreenshotCollectionOptions>& + screenshot_collection_options() const { + return screenshot_collection_options_; + } private: // Private constructor to force object creation through static methods. - ScreenshotOptions(bool capture_full_page, - std::optional<PaintPreviewOptions> paint_preview_options) + ScreenshotOptions( + bool capture_full_page, + std::optional<PaintPreviewOptions> paint_preview_options, + std::optional<ScreenshotCollectionOptions> screenshot_collection_options) : capture_full_page_(capture_full_page), - paint_preview_options_(paint_preview_options) {} + paint_preview_options_(paint_preview_options), + screenshot_collection_options_( + std::move(screenshot_collection_options)) {} // Whether to capture a full-page screenshot. If false, only the viewport will // be captured. @@ -101,6 +155,9 @@ std::optional<PaintPreviewOptions> paint_preview_options_ = std::nullopt; // The color to paint for redaction. SkColor4f redaction_color_ = SkColors::kBlack; + // Options for screenshot collection. If not set, the screenshot will be + // captured with the default options. + std::optional<ScreenshotCollectionOptions> screenshot_collection_options_; }; struct FetchPageContextOptions { @@ -186,6 +243,8 @@ FetchPageContextErrorDetails>; // Controls scaling and quality of tab screenshots. +// Does not override screenshot_collection_options if they are set, only +// modifies the default values. BASE_DECLARE_FEATURE(kGlicTabScreenshotExperiment); // Controls whether password fields are redacted from screenshots. @@ -224,7 +283,9 @@ content::BrowserContext*)>; // Encodes a screenshot according to the enabled feature flags. -std::optional<std::vector<uint8_t>> EncodeScreenshot(const SkBitmap& bitmap); +std::optional<std::vector<uint8_t>> EncodeScreenshot(const SkBitmap& bitmap, + const std::optional<ScreenshotOptions::ScreenshotCollectionOptions>& + screenshot_collection_options); // Coordinates fetching multiple types of page context. class PageContextFetcher : public content::WebContentsObserver { @@ -303,6 +364,8 @@ bool pdf_done_ = false; bool annotated_page_content_done_ = false; SkColor4f screenshot_redaction_color_ = SkColors::kBlack; + std::optional<ScreenshotOptions::ScreenshotCollectionOptions> + screenshot_collection_options_; // Whether the primary page has changed since context fetching began. bool primary_page_changed_ = false; std::unique_ptr<FetchPageContextResult> pending_result_;
diff --git a/components/password_manager/core/browser/actor_login/actor_login_types.h b/components/password_manager/core/browser/actor_login/actor_login_types.h index 14837e3..ab16a6e 100644 --- a/components/password_manager/core/browser/actor_login/actor_login_types.h +++ b/components/password_manager/core/browser/actor_login/actor_login_types.h
@@ -57,15 +57,17 @@ // This could be an email address or a username used to identify the user // during the login process. It is unique for this `source_site_or_app`. // It may be an empty string if the credential has no associated username. + // For federated credentials, this is the user's email, if used by the + // identity provider, otherwise the account display identifier (not the + // display name). // This field may be presented to the user. // Callers are responsible for formatting strings for display. std::u16string username; // The original website or application for which this credential was saved in // GPM. This filed may be presented to the user. - // TODO(crbug.com/441231531): Clarify the format. - // We should probably provide display and non-display values, or let the - // caller format strings to display. + // For federated credentials, this is the site of the identity provider + // formatted for display. std::u16string source_site_or_app; // The origin for which this credential was requested.
diff --git a/components/payments/content/secure_payment_confirmation_service.cc b/components/payments/content/secure_payment_confirmation_service.cc index 1b6fc04c..f133a34 100644 --- a/components/payments/content/secure_payment_confirmation_service.cc +++ b/components/payments/content/secure_payment_confirmation_service.cc
@@ -6,10 +6,12 @@ #include <optional> +#include "base/barrier_callback.h" #include "base/compiler_specific.h" #include "base/feature_list.h" #include "base/functional/bind.h" #include "base/memory/ref_counted_memory.h" +#include "base/task/thread_pool.h" #include "components/payments/content/browser_binding/browser_bound_key.h" #include "components/payments/content/browser_binding/browser_bound_key_store.h" #include "components/payments/content/web_payments_web_data_service.h" @@ -28,6 +30,7 @@ namespace payments { namespace { + void OnIsUserVerifyingPlatformAuthenticatorAvailable( SecurePaymentConfirmationService:: SecurePaymentConfirmationAvailabilityCallback callback, @@ -38,6 +41,13 @@ : mojom::SecurePaymentConfirmationAvailabilityEnum:: kUnavailableNoUserVerifyingPlatformAuthenticator); } + +mojom::SecurePaymentConfirmationCapabilityPtr MakeCapability(std::string name, + bool available) { + return mojom::SecurePaymentConfirmationCapability::New(std::move(name), + available); +} + } // namespace SecurePaymentConfirmationService::SecurePaymentConfirmationService( @@ -104,6 +114,22 @@ &OnIsUserVerifyingPlatformAuthenticatorAvailable, std::move(callback))); } +void SecurePaymentConfirmationService::GetSecurePaymentConfirmationCapabilities( + GetSecurePaymentConfirmationCapabilitiesCallback callback) { + const size_t kNumberOfCapabilities = 1; + // Currently we only support 1 capability, but using a barrier callback + // converts the output to a std::vector for us and will allow for easy + // expansion in the future. + auto barrier_callback = + base::BarrierCallback<mojom::SecurePaymentConfirmationCapabilityPtr>( + kNumberOfCapabilities, std::move(callback)); + + IsBrowserBoundKeyHardwareSupported( + base::BindOnce(&MakeCapability, + spc_capabilities::kBrowserBoundKeyHardware) + .Then(barrier_callback)); +} + void SecurePaymentConfirmationService::StorePaymentCredential( const std::vector<uint8_t>& credential_id, const std::string& rp_id, @@ -197,6 +223,11 @@ passkey_browser_binder_ = std::move(passkey_browser_binder); } +void SecurePaymentConfirmationService::SetBrowserBoundKeyStoreForTesting( + scoped_refptr<BrowserBoundKeyStore> browser_bound_key_store) { + test_browser_bound_key_store_ = browser_bound_key_store; +} + void SecurePaymentConfirmationService::OnStorePaymentCredential( WebDataServiceBase::Handle h, std::unique_ptr<WDTypedResult> result) { @@ -274,6 +305,25 @@ std::move(relying_party_id), std::move(unbound_key))); } +void SecurePaymentConfirmationService::IsBrowserBoundKeyHardwareSupported( + base::OnceCallback<void(bool)> callback) { + scoped_refptr<BrowserBoundKeyStore> bbk_store = + test_browser_bound_key_store_ + ? test_browser_bound_key_store_ + : GetBrowserBoundKeyStoreInstance(BrowserBoundKeyStore::Config{ +#if BUILDFLAG(IS_MAC) + .keychain_access_group = + browser_bound_key_store_keychain_access_group_ +#endif // BUILDFLAG(IS_MAC) + }); + + base::ThreadPool::PostTaskAndReplyWithResult( + FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()}, + base::BindOnce(&BrowserBoundKeyStore::GetDeviceSupportsHardwareKeys, + bbk_store), + std::move(callback)); +} + bool SecurePaymentConfirmationService::IsCurrentStateValid() const { if (!content::IsFrameAllowedToUseSecurePaymentConfirmation( &render_frame_host()) ||
diff --git a/components/payments/content/secure_payment_confirmation_service.h b/components/payments/content/secure_payment_confirmation_service.h index 0d07558..38409f38 100644 --- a/components/payments/content/secure_payment_confirmation_service.h +++ b/components/payments/content/secure_payment_confirmation_service.h
@@ -32,6 +32,15 @@ class WebPaymentsWebDataService; +// https://w3c.github.io/secure-payment-confirmation/#enumdef-securepaymentconfirmationavailability +namespace spc_capabilities { + +// This is the set of SPC capabilities (currently only 1) computed by the +// browser. +inline constexpr char kBrowserBoundKeyHardware[] = "browserBoundKeyHardware"; + +} // namespace spc_capabilities + // Implementation of the mojom::SecurePaymentConfirmationService interface, // which provides SPC-related functionality that is not tied to a specific // PaymentRequest invocation. @@ -56,6 +65,10 @@ SecurePaymentConfirmationAvailabilityCallback callback) override; // mojom::SecurePaymentConfirmationService: + void GetSecurePaymentConfirmationCapabilities( + GetSecurePaymentConfirmationCapabilitiesCallback callback) override; + + // mojom::SecurePaymentConfirmationService: void StorePaymentCredential(const std::vector<uint8_t>& credential_id, const std::string& rp_id, const std::vector<uint8_t>& user_id, @@ -69,6 +82,9 @@ void SetPasskeyBrowserBinderForTesting( std::unique_ptr<PasskeyBrowserBinder> passkey_browser_binder); + void SetBrowserBoundKeyStoreForTesting( + scoped_refptr<BrowserBoundKeyStore> browser_bound_key_store); + private: // States of the enrollment flow, necessary to ensure correctness with // round-trips to the renderer process. Methods that perform async @@ -110,6 +126,14 @@ blink::mojom::PublicKeyCredentialCreationOptionsPtr options, MakePaymentCredentialCallback callback, std::optional<PasskeyBrowserBinder::UnboundKey> unbound_key); + + // Checks if hardware backed browser bound keys are supported on this device, + // i.e. if it has a hardware security chip that can be used to generate and + // store key pairs. If so, runs |callback| with `true`. Otherwise, runs + // |callback| with `false`. + void IsBrowserBoundKeyHardwareSupported( + base::OnceCallback<void(bool)> callback); + bool IsCurrentStateValid() const; void RecordFirstSystemPromptResult( SecurePaymentConfirmationEnrollSystemPromptResult result); @@ -124,6 +148,7 @@ set_browser_bound_key_request_handle_; bool is_system_prompt_result_recorded_ = false; std::unique_ptr<PasskeyBrowserBinder> passkey_browser_binder_; + scoped_refptr<BrowserBoundKeyStore> test_browser_bound_key_store_; std::string browser_bound_key_store_keychain_access_group_; base::WeakPtrFactory<SecurePaymentConfirmationService> weak_ptr_factory_{
diff --git a/components/payments/content/secure_payment_confirmation_service_unittest.cc b/components/payments/content/secure_payment_confirmation_service_unittest.cc index 3b8e82b1..7310e3a 100644 --- a/components/payments/content/secure_payment_confirmation_service_unittest.cc +++ b/components/payments/content/secure_payment_confirmation_service_unittest.cc
@@ -14,6 +14,7 @@ #include "components/payments/content/browser_binding/browser_bound_key_store.h" #include "components/payments/content/browser_binding/fake_browser_bound_key.h" #include "components/payments/content/browser_binding/fake_browser_bound_key_store.h" +#include "components/payments/content/browser_binding/mock_browser_bound_key_store.h" #include "components/payments/content/mock_web_payments_web_data_service.h" #include "components/payments/content/web_payments_web_data_service.h" #include "components/payments/core/features.h" @@ -243,6 +244,52 @@ mock_secure_payment_confirmation_availability_callback_.Get()); } +TEST_F( + SecurePaymentConfirmationServiceTest, + GetSecurePaymentConfirmationCapabilities_BrowserBoundKeyHardwareSupported) { + base::RunLoop run_loop; + + // Set up the SPC service in the test as otherwise, the RenderFrameHost + // prematurely closes during run_loop.Run() when there are multiple threads. + context_.set_is_off_the_record(false); + web_contents_ = web_contents_factory_.CreateWebContents(&context_); + + mojo::PendingRemote<mojom::SecurePaymentConfirmationService> remote; + mojo::PendingReceiver<mojom::SecurePaymentConfirmationService> receiver = + remote.InitWithNewPipeAndPassReceiver(); + spc_service_ = std::unique_ptr<SecurePaymentConfirmationService, + SecurePaymentConfirmationServiceDeleter>( + new SecurePaymentConfirmationService( + *web_contents_->GetPrimaryMainFrame(), + /*receiver=*/std::move(receiver), mock_web_data_service_, + CreateMockInternalAuthenticator(), + /*browser_bound_key_store_keychain_access_group=*/"")); + + auto browser_bound_key_store = + base::MakeRefCounted<MockBrowserBoundKeyStore>(); + EXPECT_CALL(*browser_bound_key_store, GetDeviceSupportsHardwareKeys()) + .WillOnce(testing::Return(true)); + spc_service_->SetBrowserBoundKeyStoreForTesting(browser_bound_key_store); + + std::vector<mojom::SecurePaymentConfirmationCapabilityPtr> capabilities; + spc_service_->GetSecurePaymentConfirmationCapabilities( + base::BindLambdaForTesting( + [&capabilities, &run_loop]( + std::vector<mojom::SecurePaymentConfirmationCapabilityPtr> c) { + capabilities = std::move(c); + run_loop.Quit(); + })); + run_loop.Run(); + + EXPECT_THAT( + capabilities, + testing::Contains(Pointee(testing::AllOf( + testing::Field(&mojom::SecurePaymentConfirmationCapability::name, + spc_capabilities::kBrowserBoundKeyHardware), + testing::Field(&mojom::SecurePaymentConfirmationCapability::supported, + true))))); +} + #if !BUILDFLAG(IS_IOS) struct CredentialTestParams {
diff --git a/components/privacy_sandbox_chrome_strings.grdp b/components/privacy_sandbox_chrome_strings.grdp index fdfe6f0..ef23dc2a 100644 --- a/components/privacy_sandbox_chrome_strings.grdp +++ b/components/privacy_sandbox_chrome_strings.grdp
@@ -8,42 +8,42 @@ <!-- LINT.IfChange --> <grit-part> <!-- Privacy Sandbox v4 - Consent EEA. --> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_TITLE" desc="This string is a page title. It needs to serve 2 purposes: 1) it needs to encompass 3 settings: one of which appears on this screen and two others on a second screen. 2) it also tries to give a sense for a bigger project, Privacy Sandbox described at www.privacysandbox.com. * 'ad privacy' is also the name of a new page in Chrome settings called 'Ad settings'. We mean privacy associated with ads, and not privacy from ads. That new page includes 3 settings: Ad topics, Site-suggested ads, and Ad measurement. Those are also the same 3 settings associated with this 2-screen moment. * The new ad settings appear in the privacy section of Chrome settings: chrome://settings/adSettings **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA CONSENT **** 1) This screen is a Consent moment for Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is 1 of 2 screens. This first screen describes the “Ad topics” setting. The second screen describes 2 other settings: “Site-suggested ads” and “Ad measurement”. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_TITLE" desc="This string is a page title. It needs to serve 2 purposes: 1) it needs to encompass 3 settings: one of which appears on this screen and two others on a second screen. 2) it also tries to give a sense for a bigger project, Privacy Sandbox described at www.privacysandbox.com. * 'ad privacy' is also the name of a new page in Chrome settings called 'Ad settings'. We mean privacy associated with ads, and not privacy from ads. That new page includes 3 settings: Ad topics, Site-suggested ads, and Ad measurement. Those are also the same 3 settings associated with this 2-screen moment. * The new ad settings appear in the privacy section of Chrome settings: chrome://settings/adSettings **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA CONSENT **** 1) This screen is a Consent moment for Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is 1 of 2 screens. This first screen describes the “Ad topics” setting. The second screen describes 2 other settings: “Site-suggested ads” and “Ad measurement”. "> Turn on an ad privacy feature </message> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_DESCRIPTION_1" desc="* 'we' refers to Chrome. We're using a personal pronoun to improve tone and make the moment more conversational, but that's not required and you can replace this with 'Chrome is launching new privacy features...' * 'new privacy features': This screen is the first encounter users have with Privacy Sandbox (www.privacysandbox.com), Chrome's effort to deprecate third-party cookies. This paragraph speaks broadly to the effort and so references multiple settings, even though the user, in this moment, is consenting (or not) to one single setting: Ad topics. * 'more choice over the ads you see': This phrase suggests a comparison. We're comparing the new features with third-party cookies, the old manner advertisers had to track users and show personalized ads. The 'more choice' we're providing is the possibility to block certain ad topics. So if the user sets Chrome settings to allow personalized ads, the user can block an ad topic like 'Sports'. This is one input among others, so it doesn't necessarily mean the user won't see ads related to sports, but it's more control than they have today and it can be a meaningful control. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA CONSENT **** 1) This screen is a Consent moment for Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is 1 of 2 screens. This first screen describes the “Ad topics” setting. The second screen describes 2 other settings: “Site-suggested ads” and “Ad measurement”. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_DESCRIPTION_1" desc="* 'we' refers to Chrome. We're using a personal pronoun to improve tone and make the moment more conversational, but that's not required and you can replace this with 'Chrome is launching new privacy features...' * 'new privacy features': This screen is the first encounter users have with Privacy Sandbox (www.privacysandbox.com), Chrome's effort to deprecate third-party cookies. This paragraph speaks broadly to the effort and so references multiple settings, even though the user, in this moment, is consenting (or not) to one single setting: Ad topics. * 'more choice over the ads you see': This phrase suggests a comparison. We're comparing the new features with third-party cookies, the old manner advertisers had to track users and show personalized ads. The 'more choice' we're providing is the possibility to block certain ad topics. So if the user sets Chrome settings to allow personalized ads, the user can block an ad topic like 'Sports'. This is one input among others, so it doesn't necessarily mean the user won't see ads related to sports, but it's more control than they have today and it can be a meaningful control. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA CONSENT **** 1) This screen is a Consent moment for Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is 1 of 2 screens. This first screen describes the “Ad topics” setting. The second screen describes 2 other settings: “Site-suggested ads” and “Ad measurement”. "> We’re launching new privacy features that give you more choice over the ads you see. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_DESCRIPTION_2" desc="* 'Ad topics' is both the name of a new Chrome setting and the concept of topics of interest used to influence ads. For legal reasons, it's important that we convey the name of the setting to users so that they can find the setting of the same name in Chrome settings. * 'help sites': The setting allows users to decide whether or not a site they visit can ask Chrome for ad topics (topics of interests) that Chrome estimates based on the user's browsing history. It's simply a 'help' and not an 'allow' because a site the user visits might have many means of showing the user personalized ads—Chrome's new Ad topics setting is just one of them. * 'relevant ads' is another way to say 'personalized ads' * 'while protecting your browsing history and identity': Today, with third-party cookies, an ad company can track a user from site to site, and often know who the user is by name. The new settings help sites maintain personalized ads while limiting sites from tracking users from site to site, another way to say 'protecting your browsing history'. * 'Chrome can note...': This explains how the new setting works. When the Ad topics setting is on, a user gives Chrome permission to consider the user's browsing history to determine topics of interest that Chrome can then share with sites so that a site can show personalized ads based on the user's topics of interest. * 'recent browsing history': At the time of launch, this is a 4 week window. For example, imagine you spend time looking at web sites related to model airplanes. With this setting on, Chrome can use this info to choose topics of interest. But then for the next 4 weeks, you don't look at any sites related to model airplanes. Topics of interest are deleted after 4 weeks on a rolling basis, so after 4 weeks the fact that you looked at sites related to model airplanes won't have any influence on the topics of interests chosen by Chrome. * 'Later' helps the user understand that there are 2 stages relative to this setting. In the first stage, Chrome can estimate the user's interests based on their browsing history. Later, as the user continues browsing, a site can ask Chrome for topics of interest used to personalize ads. It's not instantaneous. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA CONSENT **** 1) This screen is a Consent moment for Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is 1 of 2 screens. This first screen describes the “Ad topics” setting. The second screen describes 2 other settings: “Site-suggested ads” and “Ad measurement”." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_DESCRIPTION_2" desc="* 'Ad topics' is both the name of a new Chrome setting and the concept of topics of interest used to influence ads. For legal reasons, it's important that we convey the name of the setting to users so that they can find the setting of the same name in Chrome settings. * 'help sites': The setting allows users to decide whether or not a site they visit can ask Chrome for ad topics (topics of interests) that Chrome estimates based on the user's browsing history. It's simply a 'help' and not an 'allow' because a site the user visits might have many means of showing the user personalized ads—Chrome's new Ad topics setting is just one of them. * 'relevant ads' is another way to say 'personalized ads' * 'while protecting your browsing history and identity': Today, with third-party cookies, an ad company can track a user from site to site, and often know who the user is by name. The new settings help sites maintain personalized ads while limiting sites from tracking users from site to site, another way to say 'protecting your browsing history'. * 'Chrome can note...': This explains how the new setting works. When the Ad topics setting is on, a user gives Chrome permission to consider the user's browsing history to determine topics of interest that Chrome can then share with sites so that a site can show personalized ads based on the user's topics of interest. * 'recent browsing history': At the time of launch, this is a 4 week window. For example, imagine you spend time looking at web sites related to model airplanes. With this setting on, Chrome can use this info to choose topics of interest. But then for the next 4 weeks, you don't look at any sites related to model airplanes. Topics of interest are deleted after 4 weeks on a rolling basis, so after 4 weeks the fact that you looked at sites related to model airplanes won't have any influence on the topics of interests chosen by Chrome. * 'Later' helps the user understand that there are 2 stages relative to this setting. In the first stage, Chrome can estimate the user's interests based on their browsing history. Later, as the user continues browsing, a site can ask Chrome for topics of interest used to personalize ads. It's not instantaneous. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA CONSENT **** 1) This screen is a Consent moment for Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is 1 of 2 screens. This first screen describes the “Ad topics” setting. The second screen describes 2 other settings: “Site-suggested ads” and “Ad measurement”."> Ad topics help sites show you relevant ads while protecting your browsing history and identity. Chrome can note topics of interest based on your recent browsing history. Later, a site you visit can ask Chrome for relevant topics to personalize the ads you see. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_DESCRIPTION_3" desc="* A reassuring paragraph that emphasizes the element of control. Today, with third-party cookies, advertisers can learn much about users and this information is largely hidden from the user. With this new setting, the user can see the topics of interests estimated by Chrome and block ones they're not comfortable with. * 'auto-deletes': this could also read 'Chrome also deletes...' We included the 'auto' to reinforce that this is part of a system and the deletion is done regularly. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA CONSENT **** 1) This screen is a Consent moment for Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is 1 of 2 screens. This first screen describes the “Ad topics” setting. The second screen describes 2 other settings: “Site-suggested ads” and “Ad measurement”. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_DESCRIPTION_3" desc="* A reassuring paragraph that emphasizes the element of control. Today, with third-party cookies, advertisers can learn much about users and this information is largely hidden from the user. With this new setting, the user can see the topics of interests estimated by Chrome and block ones they're not comfortable with. * 'auto-deletes': this could also read 'Chrome also deletes...' We included the 'auto' to reinforce that this is part of a system and the deletion is done regularly. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA CONSENT **** 1) This screen is a Consent moment for Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is 1 of 2 screens. This first screen describes the “Ad topics” setting. The second screen describes 2 other settings: “Site-suggested ads” and “Ad measurement”. "> You can see ad topics in settings and block the ones you don’t want shared with sites. Chrome also auto-deletes ad topics that are older than 4 weeks. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_EXPAND_LABEL" desc="This is an actionable string, allowing the user to learn more about the Ad topics setting. The name of the setting is 'Ad topics', with a capital 'A'. So here in this string, we use 'ad topics' to help the user find the setting later in Settings, but we're more referring to the concept of topics of interest than the setting. Both are important." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_EXPAND_LABEL" desc="This is an actionable string, allowing the user to learn more about the Ad topics setting. The name of the setting is 'Ad topics', with a capital 'A'. So here in this string, we use 'ad topics' to help the user find the setting later in Settings, but we're more referring to the concept of topics of interest than the setting. Both are important."> More about ad topics </message> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_DESCRIPTION_4" desc="We inform the user that regardless of their choice in this moment, they can always change their mind in Chrome settings—in this case by finding the 'Ad topics' setting and changing the toggle. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA CONSENT **** 1) This screen is a Consent moment for Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is 1 of 2 screens. This first screen describes the “Ad topics” setting. The second screen describes 2 other settings: “Site-suggested ads” and “Ad measurement”. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_DESCRIPTION_4" desc="We inform the user that regardless of their choice in this moment, they can always change their mind in Chrome settings—in this case by finding the 'Ad topics' setting and changing the toggle. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA CONSENT **** 1) This screen is a Consent moment for Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is 1 of 2 screens. This first screen describes the “Ad topics” setting. The second screen describes 2 other settings: “Site-suggested ads” and “Ad measurement”. "> You can change your mind any time in Chrome settings. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_DECLINE_BUTTON" desc="A button label that enables a user to refuse turning on the Ad topics setting. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA CONSENT **** 1) This screen is a Consent moment for Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is 1 of 2 screens. This first screen describes the “Ad topics” setting. The second screen describes 2 other settings: “Site-suggested ads” and “Ad measurement”. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_DECLINE_BUTTON" desc="A button label that enables a user to refuse turning on the Ad topics setting. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA CONSENT **** 1) This screen is a Consent moment for Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is 1 of 2 screens. This first screen describes the “Ad topics” setting. The second screen describes 2 other settings: “Site-suggested ads” and “Ad measurement”. "> No thanks </message> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_ACCEPT_BUTTON" desc="It's rare that a Google interface speaks from the user's perspective, but we do it here to reinforce the importance of the choice. This could also read something like 'Yes, I accept'. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA CONSENT **** 1) This screen is a Consent moment for Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is 1 of 2 screens. This first screen describes the “Ad topics” setting. The second screen describes 2 other settings: “Site-suggested ads” and “Ad measurement”. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_ACCEPT_BUTTON" desc="It's rare that a Google interface speaks from the user's perspective, but we do it here to reinforce the importance of the choice. This could also read something like 'Yes, I accept'. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA CONSENT **** 1) This screen is a Consent moment for Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is 1 of 2 screens. This first screen describes the “Ad topics” setting. The second screen describes 2 other settings: “Site-suggested ads” and “Ad measurement”. "> Turn it on </message> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_SAVING_LABEL" desc="This is a third screen that appears momentarily between the 2 primary screens of this consent/notice moment. Technically, we don't need this screen and we don't need time to save the user's choice on the first screen. We include this screen, and this string, to: 1) reinforce that the user has made a choice and that we're saved that choice 2) separate the 1st from the second screen. The 2 screens are related, because they're both about 'Ad privacy' settings, but they're distinct moments because legally, we need the user's consent from Ad topics. For Site-suggested ads and Ad measurement, it's sufficient to provide the user notice about these settings." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_SAVING_LABEL" desc="This is a third screen that appears momentarily between the 2 primary screens of this consent/notice moment. Technically, we don't need this screen and we don't need time to save the user's choice on the first screen. We include this screen, and this string, to: 1) reinforce that the user has made a choice and that we're saved that choice 2) separate the 1st from the second screen. The 2 screens are related, because they're both about 'Ad privacy' settings, but they're distinct moments because legally, we need the user's consent from Ad topics. For Site-suggested ads and Ad measurement, it's sufficient to provide the user notice about these settings."> Saving... </message> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_1" desc="* This is 1 of 3 bullets. The label above the bullets is 'More about ad topics'. That string appears on the Consent page as an actionable label that leads the user to more information (this page) about the Ad topics setting. * 'What data is used:': This string appears in bold and serves as a label for this bullet. * 'Your ad topics': 'Ad topics' is the name of the setting. Here, it's referring to a list of topics that are generated by Chrome when the user turns this setting on. It's useful to use the same string, 'ad topics', to reinforce that the list of topics is associated with the setting of that name, but it's not strictly necessary. * 'recent browsing history': Chrome estimates the user's interests based on the URLs of the sites they visit. Every 4 weeks, Chrome deletes ad topics so that the list of topics is always 'recent'. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_1" desc="* This is 1 of 3 bullets. The label above the bullets is 'More about ad topics'. That string appears on the Consent page as an actionable label that leads the user to more information (this page) about the Ad topics setting. * 'What data is used:': This string appears in bold and serves as a label for this bullet. * 'Your ad topics': 'Ad topics' is the name of the setting. Here, it's referring to a list of topics that are generated by Chrome when the user turns this setting on. It's useful to use the same string, 'ad topics', to reinforce that the list of topics is associated with the setting of that name, but it's not strictly necessary. * 'recent browsing history': Chrome estimates the user's interests based on the URLs of the sites they visit. Every 4 weeks, Chrome deletes ad topics so that the list of topics is always 'recent'. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com."> <ph name="BEGIN_BOLD"><b></ph>What data is used:<ph name="END_BOLD"></b></ph> Your ad topics are based on your recent browsing history, a list of sites you’ve visited using Chrome on this device. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_2" desc="* This is 2 of 3 bullets. The label above the bullets is 'More about ad topics'. That string appears on the Consent page as an actionable label that leads the user to more information (this page) about the Ad topics setting. * 'How we use this data:': This string appears in bold and serves as a label for this bullet. * 'Chrome notes...': This user uses Chrome to browse the web, so Chrome already has a notion of their browsing history. With this setting on, Chrome considers the recent browsing history to make a list of topics of interest. * 'Topic labels are predefined': Right now, the publicly-availabel list of topics is here: https://github.com/patcg-individual-drafts/topics/blob/main/taxonomy_v1.md. * 'Later' helps the user understand that there are 2 stages relative to this setting. In the first stage, Chrome can estimate the user's interests based on their browsing history. Later, as the user continues browsing, a site can ask Chrome for topics of interest used to personalize ads. It's not instantaneous. * 'a few of your topics': The actual number is 3, so 'a few' is referring to 3. We don't want to be precise about the number, even though it's a small number and should feel reassuring to the user, because concrete numbers often invite questions. * 'but not your browsing history': we include this to reassure the user. If a site could access the user's browsing history, that site could track the user across the Web. This setting uses info from the user's browsing history, but it doesn't share the actual browsing history. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_2" desc="* This is 2 of 3 bullets. The label above the bullets is 'More about ad topics'. That string appears on the Consent page as an actionable label that leads the user to more information (this page) about the Ad topics setting. * 'How we use this data:': This string appears in bold and serves as a label for this bullet. * 'Chrome notes...': This user uses Chrome to browse the web, so Chrome already has a notion of their browsing history. With this setting on, Chrome considers the recent browsing history to make a list of topics of interest. * 'Topic labels are predefined': Right now, the publicly-availabel list of topics is here: https://github.com/patcg-individual-drafts/topics/blob/main/taxonomy_v1.md. * 'Later' helps the user understand that there are 2 stages relative to this setting. In the first stage, Chrome can estimate the user's interests based on their browsing history. Later, as the user continues browsing, a site can ask Chrome for topics of interest used to personalize ads. It's not instantaneous. * 'a few of your topics': The actual number is 3, so 'a few' is referring to 3. We don't want to be precise about the number, even though it's a small number and should feel reassuring to the user, because concrete numbers often invite questions. * 'but not your browsing history': we include this to reassure the user. If a site could access the user's browsing history, that site could track the user across the Web. This setting uses info from the user's browsing history, but it doesn't share the actual browsing history. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com."> <ph name="BEGIN_BOLD"><b></ph>How we use this data:<ph name="END_BOLD"></b></ph> Chrome notes topics of interest as you browse. Topic labels are predefined and include things like, Arts & Entertainment, Shopping, and Sports. Later, a site you visit can ask Chrome for a few of your topics (but not your browsing history) to personalize the ads you see. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_3" desc="* This is 3 of 3 bullets. The label above the bullets is 'More about ad topics'. That string appears on the Consent page as an actionable label that leads the user to more information (this page) about the Ad topics setting. * 'How you can manage your data:': This string appears in bold and serves as a label for this bullet. * 'auto-deletes': this could also read 'Chrome also deletes...' We included the 'auto' to reinforce that this is part of a system and the deletion is done regularly. * 'As you keep browsing...': If a user visits similar sites every week for months on end, the same topic may appear on their list of topics and appear 'permanent'. This sentence explains why a topic might persist despite having just said that topics are deleted every 4 weeks. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_3" desc="* This is 3 of 3 bullets. The label above the bullets is 'More about ad topics'. That string appears on the Consent page as an actionable label that leads the user to more information (this page) about the Ad topics setting. * 'How you can manage your data:': This string appears in bold and serves as a label for this bullet. * 'auto-deletes': this could also read 'Chrome also deletes...' We included the 'auto' to reinforce that this is part of a system and the deletion is done regularly. * 'As you keep browsing...': If a user visits similar sites every week for months on end, the same topic may appear on their list of topics and appear 'permanent'. This sentence explains why a topic might persist despite having just said that topics are deleted every 4 weeks. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. "> <ph name="BEGIN_BOLD"><b></ph>How you can manage your data:<ph name="END_BOLD"></b></ph> Chrome auto-deletes topics that are older than 4 weeks. As you keep browsing, a topic might reappear on the list. You can also block topics you don’t want Chrome to share with sites and turn ad topics off at any time in Chrome settings. </message> <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_LINK" desc="A sentence that appears alone at the bottom of the learn more page. It offers the user a path towards additional information about how Google protects their data."> @@ -51,47 +51,47 @@ </message> <!-- Privacy Sandbox v4 - Notice EEA. --> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_TITLE" desc="* A page title for the second of 2 screens * 'Other ad privacy features': There are 3 settings we're launching that all appear on Chrome's 'Ad privacy' page. The first screen of this Consent/Notice moment speaks to the Ad topics setting. This second screen speaks to the 2 other Ad privacy settings: Site-suggested ads and Ad measurement. So this page title refers to features other than the Ad topics setting that Chrome is launching. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA NOTICE **** 1) This screen provides notice to Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is the second of 2 screens. This second screen describes 2 settings: “Site-suggested ads” and “Ad measurement”. The first screen describes the “Ad topics” setting. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_TITLE" desc="* A page title for the second of 2 screens * 'Other ad privacy features': There are 3 settings we're launching that all appear on Chrome's 'Ad privacy' page. The first screen of this Consent/Notice moment speaks to the Ad topics setting. This second screen speaks to the 2 other Ad privacy settings: Site-suggested ads and Ad measurement. So this page title refers to features other than the Ad topics setting that Chrome is launching. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA NOTICE **** 1) This screen provides notice to Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is the second of 2 screens. This second screen describes 2 settings: “Site-suggested ads” and “Ad measurement”. The first screen describes the “Ad topics” setting. "> Other ad privacy features now available </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_DESCRIPTION_1" desc="* 'we' refers to Chrome. We're using a personal pronoun to improve tone and make the moment more conversational, but that's not required and you can replace this with 'Chrome is launching new ways...' * 'limit': Don't use a word like 'prevent', because we can't promise that. A site can still learn something about a user when they use these new settings, but it's relatively little compared to what a site can learn using third-party cookies. * 'when they show you personalized ads': We're referring to the ad-personalization system broadly. Arguably, a site doesn't learn anything about a user when that user is shown a single ad (except if the user clicks on the ad), but in the process of learning about their users and showing them ads, a site learns something about specific users. * 'for example:': 2 bullets follow these words. We're suggesting that the bullets describe ways we limit what sites can learn but that these 2 things aren't the only ways. In fact, the Ad topics setting described on the first screen of this Consent/Notice moment also helps limit what sites can learn about you as part of the ad personalization system. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA NOTICE **** 1) This screen provides notice to Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is the second of 2 screens. This second screen describes 2 settings: “Site-suggested ads” and “Ad measurement”. The first screen describes the “Ad topics” setting. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA NOTICE **** 1) This screen provides notice to Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is the second of 2 screens. This second screen describes 2 settings: “Site-suggested ads” and “Ad measurement”. The first screen describes the “Ad topics” setting. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_DESCRIPTION_1" desc="* 'we' refers to Chrome. We're using a personal pronoun to improve tone and make the moment more conversational, but that's not required and you can replace this with 'Chrome is launching new ways...' * 'limit': Don't use a word like 'prevent', because we can't promise that. A site can still learn something about a user when they use these new settings, but it's relatively little compared to what a site can learn using third-party cookies. * 'when they show you personalized ads': We're referring to the ad-personalization system broadly. Arguably, a site doesn't learn anything about a user when that user is shown a single ad (except if the user clicks on the ad), but in the process of learning about their users and showing them ads, a site learns something about specific users. * 'for example:': 2 bullets follow these words. We're suggesting that the bullets describe ways we limit what sites can learn but that these 2 things aren't the only ways. In fact, the Ad topics setting described on the first screen of this Consent/Notice moment also helps limit what sites can learn about you as part of the ad personalization system. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA NOTICE **** 1) This screen provides notice to Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is the second of 2 screens. This second screen describes 2 settings: “Site-suggested ads” and “Ad measurement”. The first screen describes the “Ad topics” setting. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA NOTICE **** 1) This screen provides notice to Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is the second of 2 screens. This second screen describes 2 settings: “Site-suggested ads” and “Ad measurement”. The first screen describes the “Ad topics” setting. "> We’re launching new ways to limit what sites can learn about you when they show you personalized ads, for example: </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_BULLET_1" desc="* This is 1 of 2 bullets that site beneath the sentence: 'We’re launching new ways to limit what sites can learn about you when they show you personalized ads, for example:' * 'Site-suggested ads' is the name of a new setting we're launching and that appears on the Ad privacy page of Chrome settings. * 'help protect your browsing history and identity': Today, with third-party cookies, an ad company can track a user from site to site, and often know who the user is by name. The new settings help sites maintain personalized ads while limiting sites from tracking users from site to site, another way to say 'protecting your browsing history'. * 'while enabling sites': When this new setting is on, a site can ask Chrome for information that helps the site show the user personalized ads. Chrome is helping the site show personalized ads (and thus helping sites earn money and keep the Internet free to people browsing). * 'Based on your activity' this sentence explains how this setting works. 'your activity' refers to your activity on a specific site, not to your browsing history. * 'a site you visited': This setting is about the user's interaction with a single site; not the user's browsing history. This string is intentionally in the past tense to help the reader understand that there are 2 stages associated with this setting: 1) a user interacts with a site and the site (or an advertiser associated with the site), gets a sense for the user based on that interaction. And then 2) later, as the user engages with a different site, the user might see ads suggested by the first site. * 'can suggest related ads': Say you visit a site that sells shoes. That site can store info with Chrome, such as the fact that you nearly purchased running shoes on the first site. Then as you keep browsing, other sites can ask Chrome for this info saved by the first site and show you ads related to running shows suggested by the first site. * 'You can see...' this sentence gives the user a sense of control. The user can always turn this setting off and not receive 'site-suggested ads'. Or, if this setting is on, a user can block a specific site from suggesting ads. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA NOTICE **** 1) This screen provides notice to Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is the second of 2 screens. This second screen describes 2 settings: “Site-suggested ads” and “Ad measurement”. The first screen describes the “Ad topics” setting. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_BULLET_1" desc="* This is 1 of 2 bullets that site beneath the sentence: 'We’re launching new ways to limit what sites can learn about you when they show you personalized ads, for example:' * 'Site-suggested ads' is the name of a new setting we're launching and that appears on the Ad privacy page of Chrome settings. * 'help protect your browsing history and identity': Today, with third-party cookies, an ad company can track a user from site to site, and often know who the user is by name. The new settings help sites maintain personalized ads while limiting sites from tracking users from site to site, another way to say 'protecting your browsing history'. * 'while enabling sites': When this new setting is on, a site can ask Chrome for information that helps the site show the user personalized ads. Chrome is helping the site show personalized ads (and thus helping sites earn money and keep the Internet free to people browsing). * 'Based on your activity' this sentence explains how this setting works. 'your activity' refers to your activity on a specific site, not to your browsing history. * 'a site you visited': This setting is about the user's interaction with a single site; not the user's browsing history. This string is intentionally in the past tense to help the reader understand that there are 2 stages associated with this setting: 1) a user interacts with a site and the site (or an advertiser associated with the site), gets a sense for the user based on that interaction. And then 2) later, as the user engages with a different site, the user might see ads suggested by the first site. * 'can suggest related ads': Say you visit a site that sells shoes. That site can store info with Chrome, such as the fact that you nearly purchased running shoes on the first site. Then as you keep browsing, other sites can ask Chrome for this info saved by the first site and show you ads related to running shows suggested by the first site. * 'You can see...' this sentence gives the user a sense of control. The user can always turn this setting off and not receive 'site-suggested ads'. Or, if this setting is on, a user can block a specific site from suggesting ads. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA NOTICE **** 1) This screen provides notice to Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is the second of 2 screens. This second screen describes 2 settings: “Site-suggested ads” and “Ad measurement”. The first screen describes the “Ad topics” setting. "> Site-suggested ads help protect your browsing history and identity while enabling sites to show you relevant ads. Based on your activity, a site you visited can suggest related ads as you continue browsing. You can see a list of these sites and block the ones you don’t want in settings. </message> <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_BULLET_2" desc="* This is 2 of 2 bullets that site beneath the sentence: 'We’re launching new ways to limit what sites can learn about you when they show you personalized ads, for example:' * 'ad measurement' is the name of a new setting we're launching and that appears on the Ad privacy page of Chrome settings. * 'limited types of data': This setting helps an advertiser associate a user's actions on one site with their actions on another. For example, a user sees an ad on one site and then later buys that product from the company that sells the product. The ad measurement setting allows Chrome to help a company make the association between the two sites so that the first site can be fairly compensated for showing an ad. Compared with third-party cookies, very little info is shared between sites to support this functionality. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA NOTICE **** 1) This screen provides notice to Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is the second of 2 screens. This second screen describes 2 settings: “Site-suggested ads” and “Ad measurement”. The first screen describes the “Ad topics” setting. "> With ad measurement, limited types of data are shared between sites to measure the performance of their ads, such as whether you made a purchase after visiting a site. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LEARN_MORE_EXPAND_LABEL" desc="This is an actionable string, allowing the user to learn more about these two new settings. The names of the settings are 'Site-suggested ads' and 'Ad measurement'. We use the names of the settings to help the user find these settings later in Chrome settings. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA NOTICE **** 1) This screen provides notice to Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is the second of 2 screens. This second screen describes 2 settings: “Site-suggested ads” and “Ad measurement”. The first screen describes the “Ad topics” setting. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LEARN_MORE_EXPAND_LABEL" desc="This is an actionable string, allowing the user to learn more about these two new settings. The names of the settings are 'Site-suggested ads' and 'Ad measurement'. We use the names of the settings to help the user find these settings later in Chrome settings. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA NOTICE **** 1) This screen provides notice to Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is the second of 2 screens. This second screen describes 2 settings: “Site-suggested ads” and “Ad measurement”. The first screen describes the “Ad topics” setting. "> More about site-suggested ads and ad measurement </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_DESCRIPTION_2" desc="We inform the user that they can always change these new settings in Chrome settings. This is the second of two screens that comprise this Consent/Notice moment. On the first screen, for the Ad topics settings, we must ask the user for their consent to turn on the setting. On this page, we only need to provide notice to the user that we're launching two new settings. By default, these new settings will be on. This string reminds the user that they can find and change the state of these settings in Chrome settings. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA NOTICE **** 1) This screen provides notice to Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is the second of 2 screens. This second screen describes 2 settings: “Site-suggested ads” and “Ad measurement”. The first screen describes the “Ad topics” setting. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_DESCRIPTION_2" desc="We inform the user that they can always change these new settings in Chrome settings. This is the second of two screens that comprise this Consent/Notice moment. On the first screen, for the Ad topics settings, we must ask the user for their consent to turn on the setting. On this page, we only need to provide notice to the user that we're launching two new settings. By default, these new settings will be on. This string reminds the user that they can find and change the state of these settings in Chrome settings. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA NOTICE **** 1) This screen provides notice to Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is the second of 2 screens. This second screen describes 2 settings: “Site-suggested ads” and “Ad measurement”. The first screen describes the “Ad topics” setting. "> You can make changes in Chrome settings. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_ACK_BUTTON" desc="Because this isn't a moment of Consent, the button label reflects acknowledgement of this setting, and not explicit consent with something like 'Yes, I'm in'. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA NOTICE **** 1) This screen provides notice to Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is the second of 2 screens. This second screen describes 2 settings: “Site-suggested ads” and “Ad measurement”. The first screen describes the “Ad topics” setting. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_ACK_BUTTON" desc="Because this isn't a moment of Consent, the button label reflects acknowledgement of this setting, and not explicit consent with something like 'Yes, I'm in'. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA NOTICE **** 1) This screen provides notice to Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is the second of 2 screens. This second screen describes 2 settings: “Site-suggested ads” and “Ad measurement”. The first screen describes the “Ad topics” setting. "> Got it </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_SETTINGS_BUTTON" desc="Because this isn't a moment of Consent, and the new settings are turned on by default, we don't provide a 'No thanks' button. Instead, we provide a simple means to find these new settings in Chrome settings. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA NOTICE **** 1) This screen provides notice to Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is the second of 2 screens. This second screen describes 2 settings: “Site-suggested ads” and “Ad measurement”. The first screen describes the “Ad topics” setting." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_SETTINGS_BUTTON" desc="Because this isn't a moment of Consent, and the new settings are turned on by default, we don't provide a 'No thanks' button. Instead, we provide a simple means to find these new settings in Chrome settings. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT EEA NOTICE **** 1) This screen provides notice to Chrome users in the European Economic Area (EEA). It follows guidelines established by the GDPR. 2) This screen is the second of 2 screens. This second screen describes 2 settings: “Site-suggested ads” and “Ad measurement”. The first screen describes the “Ad topics” setting."> Settings </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LEARN_MORE_HEADING_1" desc="This is the name of a new setting. In this context, it appears as a self-standing label (1 of 2 on the page), in bold, above 3 bullets that describe this setting. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LEARN_MORE_HEADING_1" desc="This is the name of a new setting. In this context, it appears as a self-standing label (1 of 2 on the page), in bold, above 3 bullets that describe this setting. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. "> Site-suggested ads </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LEARN_MORE_BULLET_1" desc="* This is 1 of 3 bullets. The label above the bullets is 'More about site-suggested ads and ad measurement'. That string appears on the Notice page (page 2 of 2 in the Consent / Notice moment) as an actionable label that leads the user to more information (this page) about the Site-suggested ads and Ad measurement settings. * 'What data is used:': This string appears in bold and serves as a label for this bullet. * 'Your activity on a site': Don't confuse this with the user's browsing history (a list of URLs the user visits as they browse the web). In this case, we mean actions a user takes on a specific site. * 'On this device' is important because it suggests to the user that they might have different results (different ad personalization) based on the device they're using. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LEARN_MORE_BULLET_1" desc="* This is 1 of 3 bullets. The label above the bullets is 'More about site-suggested ads and ad measurement'. That string appears on the Notice page (page 2 of 2 in the Consent / Notice moment) as an actionable label that leads the user to more information (this page) about the Site-suggested ads and Ad measurement settings. * 'What data is used:': This string appears in bold and serves as a label for this bullet. * 'Your activity on a site': Don't confuse this with the user's browsing history (a list of URLs the user visits as they browse the web). In this case, we mean actions a user takes on a specific site. * 'On this device' is important because it suggests to the user that they might have different results (different ad personalization) based on the device they're using. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. "> <ph name="BEGIN_BOLD"><b></ph>What data is used:<ph name="END_BOLD"></b></ph> Your activity on a site you visit using Chrome on this device. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LEARN_MORE_BULLET_2" desc="* This is 2 of 3 bullets. The label above the bullets is 'More about site-suggested ads and ad measurement'. That string appears on the Notice page (page 2 of 2 in the Consent / Notice moment) as an actionable label that leads the user to more information (this page) about the Site-suggested ads and Ad measurement settings. * 'How we use this data:': This string appears in bold and serves as a label for this bullet. * 'Sites can store...': This explains to the user how this setting works. In this case, the info sites can store refers to short strings of text that reference specific ads. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LEARN_MORE_BULLET_2" desc="* This is 2 of 3 bullets. The label above the bullets is 'More about site-suggested ads and ad measurement'. That string appears on the Notice page (page 2 of 2 in the Consent / Notice moment) as an actionable label that leads the user to more information (this page) about the Site-suggested ads and Ad measurement settings. * 'How we use this data:': This string appears in bold and serves as a label for this bullet. * 'Sites can store...': This explains to the user how this setting works. In this case, the info sites can store refers to short strings of text that reference specific ads. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. "> <ph name="BEGIN_BOLD"><b></ph>How sites use this data:<ph name="END_BOLD"></b></ph> Sites can store information with Chrome about things you like. For example, if you visit a site about marathon training, the site might decide that you’re interested in running shoes. Later, if you visit a different site, that site can show you an ad for running shoes suggested by the first site. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LEARN_MORE_BULLET_3" desc="* This is 3 of 3 bullets. The label above the bullets is 'More about site-suggested ads and ad measurement'. That string appears on the Notice page (page 2 of 2 in the Consent / Notice moment) as an actionable label that leads the user to more information (this page) about the Site-suggested ads and Ad measurement settings. * 'How you can manage your data:': This string appears in bold and serves as a label for this bullet. * 'auto-deletes': this could also read 'Chrome deletes...' We included the 'auto' to reinforce that this is part of a system and the deletion is done regularly. * 'reappear on the list': If a user visits similar sites every week for months on end, the same sites may appear on their list of sites and appear 'permanent'. This sentence explains why a site might persist despite having just said that sites are deleted every 30 days. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LEARN_MORE_BULLET_3" desc="* This is 3 of 3 bullets. The label above the bullets is 'More about site-suggested ads and ad measurement'. That string appears on the Notice page (page 2 of 2 in the Consent / Notice moment) as an actionable label that leads the user to more information (this page) about the Site-suggested ads and Ad measurement settings. * 'How you can manage your data:': This string appears in bold and serves as a label for this bullet. * 'auto-deletes': this could also read 'Chrome deletes...' We included the 'auto' to reinforce that this is part of a system and the deletion is done regularly. * 'reappear on the list': If a user visits similar sites every week for months on end, the same sites may appear on their list of sites and appear 'permanent'. This sentence explains why a site might persist despite having just said that sites are deleted every 30 days. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. "> <ph name="BEGIN_BOLD"><b></ph>How you can manage your data:<ph name="END_BOLD"></b></ph> Chrome auto-deletes sites that are older than 30 days. A site you visit again might reappear on the list. You can also block a site from suggesting ads for you and turn site-suggested ads off at any time in Chrome settings. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LEARN_MORE_HEADING_2" desc="* This is the name of a new setting. In this context, it appears as a self-standing label (2 of 2 on the page), in bold, above a paragraph that describes this setting. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LEARN_MORE_HEADING_2" desc="* This is the name of a new setting. In this context, it appears as a self-standing label (2 of 2 on the page), in bold, above a paragraph that describes this setting. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. "> Ad measurement </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LEARN_MORE_DESCRIPTION" desc="A paragraph on a learn more page **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LEARN_MORE_DESCRIPTION" desc="A paragraph on a learn more page **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com."> Sites you visit can ask Chrome for information that helps them measure the performance of their ads. Chrome protects your privacy by limiting the information sites can share with one another. </message> @@ -117,56 +117,56 @@ </message> <!-- Privacy Sandbox v4 - Notice ROW. --> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_TITLE" desc="* A page title for this Notice screen for users outside of the EEA. It needs to serve 2 purposes: 1) it needs to encompass 3 settings: Ad topics, Site-suggested ads, and Ad measurement 2) it also tries to give a sense for a bigger project, Privacy Sandbox described at www.privacysandbox.com. * 'ad privacy' is also the name of a new page in Chrome settings called 'Ad settings'. That new page includes 3 settings: Ad topics, Site-suggested ads, and Ad measurement. * The new ad settings appear in the privacy section of Chrome settings: chrome://settings/adSettings **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_TITLE" desc="* A page title for this Notice screen for users outside of the EEA. It needs to serve 2 purposes: 1) it needs to encompass 3 settings: Ad topics, Site-suggested ads, and Ad measurement 2) it also tries to give a sense for a bigger project, Privacy Sandbox described at www.privacysandbox.com. * 'ad privacy' is also the name of a new page in Chrome settings called 'Ad settings'. That new page includes 3 settings: Ad topics, Site-suggested ads, and Ad measurement. * The new ad settings appear in the privacy section of Chrome settings: chrome://settings/adSettings **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen. "> Enhanced ad privacy in Chrome </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_DESCRIPTION_1" desc="* ''we'' refers to Chrome. We're using a personal pronoun to improve tone and make the moment more conversational, but that's not required and you can replace this with ''Chrome is launching new privacy features...'' * ''new privacy features'': This screen is the first encounter users have with Privacy Sandbox (www.privacysandbox.com), Chrome's effort to deprecate third-party cookies. This paragraph speaks broadly to the effort and so references multiple settings, even though the user, in this moment, is consenting (or not) to one single setting: Ad topics. * ''more choice over the ads you see'': This phrase suggests a comparison. We're comparing the new features with third-party cookies, the old manner advertisers had to track users and show personalized ads. The ''more choice'' we're providing is the possibility to block certain ad topics. So if the user sets Chrome settings to allow personalized ads, the user can block an ad topic like ''Sports''. This is one input among others, so it doesn't necessarily mean the user won't see ads related to sports, but it's more control than they have today and it can be a meaningful control.' **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_DESCRIPTION_1" desc="* ''we'' refers to Chrome. We're using a personal pronoun to improve tone and make the moment more conversational, but that's not required and you can replace this with ''Chrome is launching new privacy features...'' * ''new privacy features'': This screen is the first encounter users have with Privacy Sandbox (www.privacysandbox.com), Chrome's effort to deprecate third-party cookies. This paragraph speaks broadly to the effort and so references multiple settings, even though the user, in this moment, is consenting (or not) to one single setting: Ad topics. * ''more choice over the ads you see'': This phrase suggests a comparison. We're comparing the new features with third-party cookies, the old manner advertisers had to track users and show personalized ads. The ''more choice'' we're providing is the possibility to block certain ad topics. So if the user sets Chrome settings to allow personalized ads, the user can block an ad topic like ''Sports''. This is one input among others, so it doesn't necessarily mean the user won't see ads related to sports, but it's more control than they have today and it can be a meaningful control.' **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen. "> We’re launching new privacy features that give you more choice over the ads you see. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_DESCRIPTION_2" desc="* A paragraph on the 'Enhanced ad privacy in Chrome' page that provides notice to Chrome users outside of the EEA. PARAGRAPH STRUCTURE * The first sentence describes the Ad topics setting. * The second sentence describes the Site-suggested ads setting. * The third sentence refers to both settings, 'topics and sites'. * 'Chrome notes...': This user uses Chrome to browse the web, so Chrome already has a notion of their browsing history. With this setting on, Chrome considers the recent browsing history to make a list of topics of interest. * 'recent browsing history': At the time of launch, this is a 4 week window. For example, imagine you spend time looking at web sites related to model airplanes. With this setting on, Chrome can use this info to choose topics of interest. But then for the next 4 weeks, you don't look at any sites related to model airplanes. Topics of interest are deleted after 4 weeks on a rolling basis, so after 4 weeks the fact that you looked at sites related to model airplanes won't have any influence on the topics of interests chosen by Chrome. * 'Also...': Imagine a user visits a site that sells shoes and they place a pair of red sneakers in a shopping cart. That site can decide that the user is interested in red sneakers and then suggest ads to other sites as the user continues browsing. * 'this information' refers to both topics of interest and to the list of sites that suggest ads to other sites. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_DESCRIPTION_2" desc="* A paragraph on the 'Enhanced ad privacy in Chrome' page that provides notice to Chrome users outside of the EEA. PARAGRAPH STRUCTURE * The first sentence describes the Ad topics setting. * The second sentence describes the Site-suggested ads setting. * The third sentence refers to both settings, 'topics and sites'. * 'Chrome notes...': This user uses Chrome to browse the web, so Chrome already has a notion of their browsing history. With this setting on, Chrome considers the recent browsing history to make a list of topics of interest. * 'recent browsing history': At the time of launch, this is a 4 week window. For example, imagine you spend time looking at web sites related to model airplanes. With this setting on, Chrome can use this info to choose topics of interest. But then for the next 4 weeks, you don't look at any sites related to model airplanes. Topics of interest are deleted after 4 weeks on a rolling basis, so after 4 weeks the fact that you looked at sites related to model airplanes won't have any influence on the topics of interests chosen by Chrome. * 'Also...': Imagine a user visits a site that sells shoes and they place a pair of red sneakers in a shopping cart. That site can decide that the user is interested in red sneakers and then suggest ads to other sites as the user continues browsing. * 'this information' refers to both topics of interest and to the list of sites that suggest ads to other sites. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen. "> Chrome notes topics of interest based on your recent browsing history. Also, sites you visit can determine what you like. Later, sites can ask for this information to show you personalized ads. You can choose which topics and sites are used to show you ads. </message> <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_DESCRIPTION_3" desc="A paragraph on the 'Enhanced ad privacy in Chrome' page that provides notice to Chrome users outside of the EEA. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen. "> To measure the performance of an ad, limited types of data are shared between sites, such as whether you made a purchase after visiting a site. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_EXPAND_LABEL" desc="This is an actionable string, allowing the user to learn more about ads in Chrome. 'ads in Chrome' refers to all 3 ad settings we're launching: Ad topics, Site-suggested ads, and Ad measurement. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_EXPAND_LABEL" desc="This is an actionable string, allowing the user to learn more about ads in Chrome. 'ads in Chrome' refers to all 3 ad settings we're launching: Ad topics, Site-suggested ads, and Ad measurement. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen."> More about ads in Chrome </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_DESCRIPTION_4" desc="We inform the user that they can always change these new settings in Chrome settings. By default, these new settings will be on. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_DESCRIPTION_4" desc="We inform the user that they can always change these new settings in Chrome settings. By default, these new settings will be on. "> You can make changes in Chrome settings. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_ACK_BUTTON" desc="Because this isn't a moment of Consent, the button label reflects acknowledgement of this setting, and not explicit consent with something like 'Yes, I'm in'. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_ACK_BUTTON" desc="Because this isn't a moment of Consent, the button label reflects acknowledgement of this setting, and not explicit consent with something like 'Yes, I'm in'. "> Got it </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_SETTINGS_BUTTON" desc="Because this isn't a moment of Consent, and the new settings are turned on by default, we don't provide a 'No thanks' button. Instead, we provide a simple means to find these new settings in Chrome settings." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_SETTINGS_BUTTON" desc="Because this isn't a moment of Consent, and the new settings are turned on by default, we don't provide a 'No thanks' button. Instead, we provide a simple means to find these new settings in Chrome settings."> Settings </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_HEADING_1" desc="This string is a label. It's essentially another name for 'personalized ads'. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_HEADING_1" desc="This string is a label. It's essentially another name for 'personalized ads'. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen."> More useful ads </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_DESCRIPTION_1" desc="A paragraph that broadly describes 2 new settings that help sites show personalized ads to users. This paragraph is followed by two bullets, the first of which describes Ad topics and a second that describes Site-suggested ads. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_DESCRIPTION_1" desc="A paragraph that broadly describes 2 new settings that help sites show personalized ads to users. This paragraph is followed by two bullets, the first of which describes Ad topics and a second that describes Site-suggested ads. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen."> Sites can ask Chrome for information to help personalize the ads you see. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_BULLET_1" desc="This bullet, 1 of 2, describes the Ad topics setting. * 'Chrome can note...': This explains how the new setting works. When the Ad topics setting is on, a user gives Chrome permission to consider the user's browsing history to determine topics of interest that Chrome can then share with sites so that a site can show personalized ads based on the user's topics of interest. * 'recent browsing history': At the time of launch, this is a 4 week window. For example, imagine you spend time looking at web sites related to model airplanes. With this setting on, Chrome can use this info to choose topics of interest. But then for the next 4 weeks, you don't look at any sites related to model airplanes. Topics of interest are deleted after 4 weeks on a rolling basis, so after 4 weeks the fact that you looked at sites related to model airplanes won't have any influence on the topics of interests chosen by Chrome. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_BULLET_1" desc="This bullet, 1 of 2, describes the Ad topics setting. * 'Chrome can note...': This explains how the new setting works. When the Ad topics setting is on, a user gives Chrome permission to consider the user's browsing history to determine topics of interest that Chrome can then share with sites so that a site can show personalized ads based on the user's topics of interest. * 'recent browsing history': At the time of launch, this is a 4 week window. For example, imagine you spend time looking at web sites related to model airplanes. With this setting on, Chrome can use this info to choose topics of interest. But then for the next 4 weeks, you don't look at any sites related to model airplanes. Topics of interest are deleted after 4 weeks on a rolling basis, so after 4 weeks the fact that you looked at sites related to model airplanes won't have any influence on the topics of interests chosen by Chrome. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen."> Chrome notes topics of interest based on your recent browsing history. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_BULLET_2" desc="* This bullet, 2 of 2, describes Site-suggested ads. * 'Sites you visit' is referring to a user interacting with a specific site as a one off. We're not referencing 'browsing history' right now, (though each visit to each site becomes part of the user's browsing history). * 'determine what you like': Imagine a user visits a site that sells shoes and they place a pair of red sneakers in a shopping cart. That site can decide that the user is interested in red sneakers and then suggest ads to other sites as the user continues browsing. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_BULLET_2" desc="* This bullet, 2 of 2, describes Site-suggested ads. * 'Sites you visit' is referring to a user interacting with a specific site as a one off. We're not referencing 'browsing history' right now, (though each visit to each site becomes part of the user's browsing history). * 'determine what you like': Imagine a user visits a site that sells shoes and they place a pair of red sneakers in a shopping cart. That site can decide that the user is interested in red sneakers and then suggest ads to other sites as the user continues browsing. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen."> Sites you visit can also determine what you like based on your activity on the site. For example, if you visit a site that sells long-distance running shoes, the site might decide that you’re interested in running marathons. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_DESCRIPTION_2" desc="* This paragraph appears beneath 2 bullets. The 1st bullet describes the Ad topics setting. The second bullet describes the Site-suggested ads setting. This paragraph refers to both settings (and, therefore, both bullets). * 'Later' helps the user understand that there are 2 stages relative to these settings. In the first stage, Chrome or a site establishes a sense for the user's interests. Later, as the user continues browsing, a site can ask Chrome for information used to personalize ads. It's not instantaneous. * 'this information': Just to repeat, 'this information' refers to both 1) topics established by Chrome based on the user's browsing history (the Ad topics setting), and 2) sites defining the user's interests based on activity on a specific site (Site-suggested ads settings). **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_DESCRIPTION_2" desc="* This paragraph appears beneath 2 bullets. The 1st bullet describes the Ad topics setting. The second bullet describes the Site-suggested ads setting. This paragraph refers to both settings (and, therefore, both bullets). * 'Later' helps the user understand that there are 2 stages relative to these settings. In the first stage, Chrome or a site establishes a sense for the user's interests. Later, as the user continues browsing, a site can ask Chrome for information used to personalize ads. It's not instantaneous. * 'this information': Just to repeat, 'this information' refers to both 1) topics established by Chrome based on the user's browsing history (the Ad topics setting), and 2) sites defining the user's interests based on activity on a specific site (Site-suggested ads settings). **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen."> Later, a site you visit can ask for this information — either your ad topics or ads suggested by sites you’ve visited. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_DESCRIPTION_3" desc="* 'auto-deletes': We included the 'auto' to reinforce that this is part of a system and the deletion is done regularly. * 'topics and sites that suggest ads': What can the user block? ** For the Ad topics setting, the user will find a list of up to 15 ad topics in Chrome settings. The full list of potential topics is published at https://github.com/patcg-individual-drafts/topics/blob/main/taxonomy_v1.md. The user can block any topic at any time. ** For the Site-suggested ads setting, it's slightly more complicated. When the user visits a site, that site can save a string of text with Chrome that refers to a set of ads that the user might see as they continue browsing. So, for example, a site might store either of these strings with Chrome: 'red_shoes_size_36_long_distance_running' or '2389jKKFSD' (a unique ID used by a database to refer to a set of ads). To simplify and give the user more control, we show a list of sites that have defined interests, such as www.this-exciting-site.com, but NOT the strings of text saved by that site. The list of sites for a user can be long. A user can block any site at any time and Chrome automatically deletes sites from the list that are older than 30 days. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_DESCRIPTION_3" desc="* 'auto-deletes': We included the 'auto' to reinforce that this is part of a system and the deletion is done regularly. * 'topics and sites that suggest ads': What can the user block? ** For the Ad topics setting, the user will find a list of up to 15 ad topics in Chrome settings. The full list of potential topics is published at https://github.com/patcg-individual-drafts/topics/blob/main/taxonomy_v1.md. The user can block any topic at any time. ** For the Site-suggested ads setting, it's slightly more complicated. When the user visits a site, that site can save a string of text with Chrome that refers to a set of ads that the user might see as they continue browsing. So, for example, a site might store either of these strings with Chrome: 'red_shoes_size_36_long_distance_running' or '2389jKKFSD' (a unique ID used by a database to refer to a set of ads). To simplify and give the user more control, we show a list of sites that have defined interests, such as www.this-exciting-site.com, but NOT the strings of text saved by that site. The list of sites for a user can be long. A user can block any site at any time and Chrome automatically deletes sites from the list that are older than 30 days. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen."> Chrome auto-deletes topics and sites that suggest ads within 30 days. Or you can block specific topics and sites you don’t like. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_HEADING_2" desc="This string is a label that represents the Ad measurement setting. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_HEADING_2" desc="This string is a label that represents the Ad measurement setting. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen."> Measuring how well an ad performs </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_DESCRIPTION_4" desc="A paragraph that describes the Ad measurement setting. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_DESCRIPTION_4" desc="A paragraph that describes the Ad measurement setting. **** CONTEXT PRIVACY SANDBOX **** Chrome’s Privacy Sandbox initiative 1) deprecates third-party cookies in Chrome, 2) supports free and open content on the web (by finding better ways to support ads online), 3) while providing stronger privacy protections for users. You can see a high-level description of this public project at www.privacysanbox.com. **** CONTEXT ROW NOTICE **** 1) This screen provides notice to Chrome users outside of the European Economic Area (EEA) (we typically refer to this screen as “Rest of World” or “ROW”). It follows guidelines established by the GDPR. 2) We’re using similar but distinct content for EEA / ROW because legal requirements differ. For ROW, we can provide notice for all 3 settings, and so all 3 settings can appear on a single screen. "> Sites you visit can ask Chrome for information to help them measure the performance of their ads. Chrome lets sites collect limited types of data, such as whether you made a purchase after visiting a site. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_DESCRIPTION_5" desc="A paragraph that helps users learn more about how Google protects their data by pointing them to Google's Privacy Policy." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_DESCRIPTION_5" desc="A paragraph that helps users learn more about how Google protects their data by pointing them to Google's Privacy Policy."> Learn more about how Google protects your data in our Privacy Policy. </message>
diff --git a/components/privacy_sandbox_strings.grd b/components/privacy_sandbox_strings.grd index 14f41bf..4f0d97c 100644 --- a/components/privacy_sandbox_strings.grd +++ b/components/privacy_sandbox_strings.grd
@@ -321,7 +321,7 @@ </message> <!-- Privacy UX WebUI --> <!-- Privacy Sandbox Ads Notice --> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_LEARN_MORE_V2_CLANK" desc="A sentence that appears alone at the bottom of the learn more page. It offers the user a path towards additional information about how Google protects their data." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_LEARN_MORE_V2_CLANK" desc="A sentence that appears alone at the bottom of the learn more page. It offers the user a path towards additional information about how Google protects their data."> Learn more about how Google protects your data in our <ph name="BEGIN_LINK"><link></ph>Privacy Policy<ph name="END_LINK"></link></ph>. </message> <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_LEARN_MORE_V2_DESKTOP" desc="A sentence that appears alone at the bottom of the learn more page. It offers the user a path towards additional information about how Google protects their data."> @@ -331,85 +331,85 @@ Learn more about how Google protects your data in our Privacy Policy. </message> <!-- Ad Topics EEA consent - Ads API UX Enhancements --> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_DESCRIPTION_2_V2" desc="This string gives an overview of “ad topics”, an ad privacy feature. “Topics of interest” refers to topics that are relevant or interesting to the user based on their browsing activity online." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_DESCRIPTION_2_V2" desc="This string gives an overview of “ad topics”, an ad privacy feature. “Topics of interest” refers to topics that are relevant or interesting to the user based on their browsing activity online."> Ad topics help websites show you relevant ads while protecting your browsing history and identity. Chrome can note topics of interest based on your recent browsing history. Later, a site you visit can ask Chrome for relevant topics to personalize the ads you see. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_DESCRIPTION_4_V2" desc="This string notifies users that they can make changes to their ad topics in the “ad privacy” section of settings." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_DESCRIPTION_4_V2" desc="This string notifies users that they can make changes to their ad topics in the “ad privacy” section of settings."> You can make changes in ad privacy settings </message> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_1_V2" desc="This string appears when the user expands on the “More about ad topics” label. It explains to the user in second person that their browsing history is used to generate ad topics. “Site” refers to a website a user may visit." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_1_V2" desc="This string appears when the user expands on the “More about ad topics” label. It explains to the user in second person that their browsing history is used to generate ad topics. “Site” refers to a website a user may visit."> <ph name="BEGIN_BOLD"><b></ph>What data is used?<ph name="END_BOLD"></b></ph> Your ad topics are based on your recent browsing history, a list of sites you've visited using Chrome on this device. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_2_V2" desc="This string appears when the user expands on the “More about ad topics” label. It explains to the user in second person that Chrome uses their browsing activity to note topics of interest. When a user visits a site, that site can ask Chrome for these topics of interest to personalize ads for the user. “Site” refers to a website a user may visit." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_2_V2" desc="This string appears when the user expands on the “More about ad topics” label. It explains to the user in second person that Chrome uses their browsing activity to note topics of interest. When a user visits a site, that site can ask Chrome for these topics of interest to personalize ads for the user. “Site” refers to a website a user may visit."> <ph name="BEGIN_BOLD"><b></ph>How do sites use this data?<ph name="END_BOLD"></b></ph> Chrome notes topics of interest as you browse. Topic labels are predefined and include things like, Arts & Entertainment, Shopping, and Sports. Later, a site you visit can ask Chrome for a few of your topics to personalize the ads you see. </message> <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_2_DESCRIPTION" desc="This string appears when the user expands on the “More about ad topics” label. “Sites” refers to websites that the user visits. The tone is informative. “Companies” refers to businesses or organizations that these websites belong to. This is intended to explain the possible user risks currently available with our evolving privacy-preserving technologies, in neutral language."> Google requires companies to state publicly that they won't use this data to track you across sites. Some sites may use your activity to personalize your experience for more than just ads. They may also combine it with other information they already know about you. Companies are responsible for letting you know how they use your data. <ph name="BEGIN_LINK1"><a href="#" id="$1" aria-description="$2" on-click="$3"></ph>Learn more in our Privacy Policy<ph name="LINK_END1"></a></ph> </message> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_2_DESCRIPTION_CLANK" desc="This string appears when the user expands on the “More about ad topics” label. “Sites” refers to websites that the user visits. The tone is informative. “Companies” refers to businesses or organizations that these websites belong to. This is intended to explain the possible user risks currently available with our evolving privacy-preserving technologies, in neutral language." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_2_DESCRIPTION_CLANK" desc="This string appears when the user expands on the “More about ad topics” label. “Sites” refers to websites that the user visits. The tone is informative. “Companies” refers to businesses or organizations that these websites belong to. This is intended to explain the possible user risks currently available with our evolving privacy-preserving technologies, in neutral language."> Google requires companies to state publicly that they won't use this data to track you across sites. Some sites may use your activity to personalize your experience for more than just ads. They may also combine it with other information they already know about you. Companies are responsible for letting you know how they use your data. <ph name="BEGIN_LINK"><link></ph>Learn more in our Privacy Policy<ph name="END_LINK"></link></ph> </message> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_3_V2" desc="This string appears when the user expands on the “More about ad topics” label. “Auto-deletes” is short for “automatically deletes”. “A topic” refers to one of the ad topics generated by Chrome. “Block topics” means a user can stop Chrome from sharing an ad topic with sites. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_3_V2" desc="This string appears when the user expands on the “More about ad topics” label. “Auto-deletes” is short for “automatically deletes”. “A topic” refers to one of the ad topics generated by Chrome. “Block topics” means a user can stop Chrome from sharing an ad topic with sites. "> <ph name="BEGIN_BOLD"><b></ph>How can you manage this data?<ph name="END_BOLD"></b></ph> Chrome auto-deletes topics that are older than 4 weeks. As you keep browsing, a topic might reappear on the list. You can also block topics you don't want Chrome to share with sites and turn ad topics off at any time in Chrome settings. </message> <!--Ad Topics EEA Notice - Ads API UX Enhancements --> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_DESCRIPTION_1_V2" desc="This string explains the function of “ad privacy features”. “Ad” is short for “advertisement”." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_DESCRIPTION_1_V2" desc="This string explains the function of “ad privacy features”. “Ad” is short for “advertisement”."> Ad privacy features help limit what websites and their advertising partners can learn about you when they show you personalized ads. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_SITE_SUGGESTED_ADS_TITLE" desc="This is a header for more information about site-suggested, an ad privacy feature." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_SITE_SUGGESTED_ADS_TITLE" desc="This is a header for more information about site-suggested, an ad privacy feature."> Site-suggested ads </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_SITE_SUGGESTED_ADS_DESCRIPTION" desc="This string explains the function of “site-suggested ads” using second person. “Sites” refers to websites the user visits." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_SITE_SUGGESTED_ADS_DESCRIPTION" desc="This string explains the function of “site-suggested ads” using second person. “Sites” refers to websites the user visits."> Site-suggested ads help protect your browsing history and identity while allowing sites to show you relevant ads. Using your activity, such as how you spend your time on sites you visit, other sites can suggest related ads as you continue browsing. You can see a list of these sites and block the ones you don't want in settings. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_SITE_SUGGESTED_ADS_LEARN_MORE_LABEL" desc="This is an actionable string. When a user clicks on it, a drop down menu opens to give additional information about how the “site-suggested ads” feature works." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_SITE_SUGGESTED_ADS_LEARN_MORE_LABEL" desc="This is an actionable string. When a user clicks on it, a drop down menu opens to give additional information about how the “site-suggested ads” feature works."> More about site-suggested ads </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_SITE_SUGGESTED_ADS_LEARN_MORE_BULLET_1" desc="This string appears when the user expands the “More about site-suggested ads” card. It explains to the user, in second person, how a website they visit may use their activity to recommend ads that are personal to the user. The user is also provided with an example of how a website may do so." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_SITE_SUGGESTED_ADS_LEARN_MORE_BULLET_1" desc="This string appears when the user expands the “More about site-suggested ads” card. It explains to the user, in second person, how a website they visit may use their activity to recommend ads that are personal to the user. The user is also provided with an example of how a website may do so."> <ph name="BEGIN_BOLD"><b></ph>How do sites use this data?<ph name="END_BOLD"></b></ph> Sites and their advertising partners can use your activity to personalize ads on other sites. For example, if you visit a site to find recipes for dinner, the site might decide that you're interested in cooking. Later, another site may show you a related ad for a grocery delivery service suggested by the first site. </message> <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_SITE_SUGGESTED_ADS_LEARN_MORE_BULLET_1_DESCRIPTION" desc="This string appears when the user expands on the “More about ad topics” label. “Sites” refers to websites that the user visits. The tone is informative. “Companies” refers to businesses or organizations that these websites belong to. This string is intended to explain the possible user risks currently available with our evolving privacy-preserving technologies, in neutral language."> Google requires companies to state publicly that they won't use this data to track you across sites. Some sites may use your activity to personalize your experience for more than just ads. They may also combine it with other information they already know about you. Companies are responsible for letting you know how they use your data. <ph name="BEGIN_LINK1"><a href="#" id="$1" aria-description="$2" on-click="$3"></ph>Learn more in our Privacy Policy<ph name="LINK_END1"></a></ph> </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_SITE_SUGGESTED_ADS_LEARN_MORE_BULLET_1_DESCRIPTION_CLANK" desc="This string appears when the user expands on the “More about ad topics” label. “Sites” refers to websites that the user visits. The tone is informative. “Companies” refers to businesses or organizations that these websites belong to. This string is intended to explain the possible user risks currently available with our evolving privacy-preserving technologies, in neutral language." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_SITE_SUGGESTED_ADS_LEARN_MORE_BULLET_1_DESCRIPTION_CLANK" desc="This string appears when the user expands on the “More about ad topics” label. “Sites” refers to websites that the user visits. The tone is informative. “Companies” refers to businesses or organizations that these websites belong to. This string is intended to explain the possible user risks currently available with our evolving privacy-preserving technologies, in neutral language."> Google requires companies to state publicly that they won't use this data to track you across sites. Some sites may use your activity to personalize your experience for more than just ads. They may also combine it with other information they already know about you. Companies are responsible for letting you know how they use your data. <ph name="BEGIN_LINK"><link></ph>Learn more in our Privacy Policy<ph name="END_LINK"></link></ph> </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_SITE_SUGGESTED_ADS_LEARN_MORE_BULLET_2" desc="This string appears when the user expands on the “More about ad topics” label and explains to the user how they can manage what data is used by websites. “Auto-deletes” is short for “automatically deletes”." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_SITE_SUGGESTED_ADS_LEARN_MORE_BULLET_2" desc="This string appears when the user expands on the “More about ad topics” label and explains to the user how they can manage what data is used by websites. “Auto-deletes” is short for “automatically deletes”."> <ph name="BEGIN_BOLD"><b></ph>How can you manage this data?<ph name="END_BOLD"></b></ph> Chrome auto-deletes sites that are older than 30 days. A site you visit again might reappear on the list. You can also block a site from suggesting ads for you and turn site-suggested ads off at any time in Chrome settings. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_AD_MEASUREMENT_TITLE" desc="This is a header for more information about ad measurement, an ad privacy feature." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_AD_MEASUREMENT_TITLE" desc="This is a header for more information about ad measurement, an ad privacy feature."> Ad measurement </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_AD_MEASUREMENT_DESCRIPTION" desc="This string explains how the “ad measurement” feature, explaining that data is shared between websites to measure how their ads are doing. “Performance” refers to how well or poorly an ad is working. In addition, it gives a real life example for the user to understand the feature." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_AD_MEASUREMENT_DESCRIPTION" desc="This string explains how the “ad measurement” feature, explaining that data is shared between websites to measure how their ads are doing. “Performance” refers to how well or poorly an ad is working. In addition, it gives a real life example for the user to understand the feature."> With ad measurement, limited types of data are shared between sites to measure the performance of their ads, such as whether you made a purchase after visiting a site. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_AD_MEASUREMENT_LEARN_MORE_LABEL" desc="This is an actionable string. When a user clicks on it, a drop down menu opens to give additional information about how the “ad measurement” feature works." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_AD_MEASUREMENT_LEARN_MORE_LABEL" desc="This is an actionable string. When a user clicks on it, a drop down menu opens to give additional information about how the “ad measurement” feature works."> More about ad measurement </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_AD_MEASUREMENT_LEARN_MORE_BULLET_1" desc="This string appears when the user expands the “More about ad measurement” card. It explains how sites visited by the user may ask Chrome for information to measure the performance of their advertisements." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_AD_MEASUREMENT_LEARN_MORE_BULLET_1" desc="This string appears when the user expands the “More about ad measurement” card. It explains how sites visited by the user may ask Chrome for information to measure the performance of their advertisements."> <ph name="BEGIN_BOLD"><b></ph>How do sites use this data?<ph name="END_BOLD"></b></ph> Sites you visit can ask Chrome for information that helps them measure the performance of their ads. Chrome protects your privacy by limiting the information sites can share with one another. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LAST_TEXT" desc="This string notifies users that they can make changes to their ad topics in the “ad privacy” section of settings." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_EEA_LAST_TEXT" desc="This string notifies users that they can make changes to their ad topics in the “ad privacy” section of settings."> You can make changes in ad privacy settings </message> <!--Ad Topics ROW Notice - Ads API UX Enhancements --> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_BULLET_2_V2" desc="This string appears when the user clicks on the “More about ads in Chrome” under the “More useful ads” header. It explains to the user, in second person, how a website they visit may use their activity to recommend ads that are personal to the user. The user is also provided with an example of how a website may do so." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_BULLET_2_V2" desc="This string appears when the user clicks on the “More about ads in Chrome” under the “More useful ads” header. It explains to the user, in second person, how a website they visit may use their activity to recommend ads that are personal to the user. The user is also provided with an example of how a website may do so."> Sites and their advertising partners can use your activity, such as how you spend your time on sites you visit, to personalize ads on other sites. For example, if you visit a site to find recipes for dinner, the site might decide that you're interested in cooking. Later, another site may show you a related ad for a grocery delivery service suggested by the first site. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_DESCRIPTION_2_V2" desc="This string appears when the user clicks on the “More about ads in Chrome” under the “More useful ads” header. It explains that a website can ask Chrome for this information. “Ads suggested by sites” is the same as “site-suggested ads”." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_DESCRIPTION_2_V2" desc="This string appears when the user clicks on the “More about ads in Chrome” under the “More useful ads” header. It explains that a website can ask Chrome for this information. “Ads suggested by sites” is the same as “site-suggested ads”."> A site you visit can ask for this information — either your ad topics or ads suggested by sites you've visited. </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_HEADING_3" desc="This string appears when the user clicks on the “More about ads in Chrome” under the “Measuring how well an ad performs” header. " formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_HEADING_3" desc="This string appears when the user clicks on the “More about ads in Chrome” under the “Measuring how well an ad performs” header. "> How sites use your data </message> <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_DESCRIPTION_5_V2" desc="This string appears when the user expands on the “More about ads in Chrome” label. “Sites” refers to websites that the user visits. The tone is informative. “Companies” refers to businesses or organizations that these websites belong to. This is intended to explain the possible user risks currently available with our evolving privacy-preserving technologies, in neutral language."> Google requires companies to state publicly that they won't use this data to track you across sites. Some sites may use your activity to personalize your experience for more than just ads. They may also combine it with other information they already know about you. Companies are responsible for letting you know how they use your data. <ph name="BEGIN_LINK1"><a href="#" id="$1" aria-description="$2" on-click="$3"></ph>Learn more in our Privacy Policy<ph name="LINK_END1"></a></ph> </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_DESCRIPTION_5_V2_CLANK" desc="This string appears when the user expands on the “More about ads in Chrome” label. “Sites” refers to websites that the user visits. The tone is informative. “Companies” refers to businesses or organizations that these websites belong to. This is intended to explain the possible user risks currently available with our evolving privacy-preserving technologies, in neutral language." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LEARN_MORE_DESCRIPTION_5_V2_CLANK" desc="This string appears when the user expands on the “More about ads in Chrome” label. “Sites” refers to websites that the user visits. The tone is informative. “Companies” refers to businesses or organizations that these websites belong to. This is intended to explain the possible user risks currently available with our evolving privacy-preserving technologies, in neutral language."> Google requires companies to state publicly that they won't use this data to track you across sites. Some sites may use your activity to personalize your experience for more than just ads. They may also combine it with other information they already know about you. Companies are responsible for letting you know how they use your data. <ph name="BEGIN_LINK"><link></ph>Learn more in our Privacy Policy<ph name="END_LINK"></link></ph> </message> - <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LAST_TEXT" desc="This string notifies users that they can make changes to their ad topics in the “ad privacy” section of settings." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_NOTICE_ROW_LAST_TEXT" desc="This string notifies users that they can make changes to their ad topics in the “ad privacy” section of settings."> You can make changes in ad privacy settings </message> @@ -563,20 +563,20 @@ Google requires companies to state publicly that they won't use this data to track you across sites. Some sites may use your activity to personalize your experience for more than just ads. They may also store ad topics for longer than 4 weeks and combine it with other information they already know about you. Companies are responsible for letting you know how they use your data. Learn more in our <ph name="BEGIN_LINK"><link></ph>Privacy Policy<ph name="END_LINK"></link></ph>. </message> <!--Ad Topics Content Parity - Topics EEA Consent Dialog --> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_DESCRIPTION_1_CONTENT_PARITY" desc="This string gives an overview of “ad topics”, an ad privacy feature. “Topics of interest” refers to topics that are relevant or interesting to the user based on their browsing activity online." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_DESCRIPTION_1_CONTENT_PARITY" desc="This string gives an overview of “ad topics”, an ad privacy feature. “Topics of interest” refers to topics that are relevant or interesting to the user based on their browsing activity online."> Ad topics limit what sites and their advertising partners can learn about you to show you personalized ads. Chrome can note topics of interest based on your recent browsing history. Later, a site you visit can ask Chrome for relevant topics to personalize the ads you see. </message> <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_2_DESCRIPTION_CONTENT_PARITY" desc="This string appears at the bottom of the “Ad topics” settings page. “Sites” refers to websites that the user visits. The tone is informative. “Companies” refers to businesses or organizations that these websites belong to. This is intended to explain the possible user risks currently available with our evolving privacy-preserving technologies, in neutral language."> Google requires companies to state publicly that they won't use this data to track you across sites. Some sites may use your activity to personalize your experience for more than just ads. They may also store ad topics for longer than 4 weeks and combine it with other information they already know about you. Companies are responsible for letting you know how they use your data. <ph name="BEGIN_LINK1"><a href="#" id="$1" aria-description="$2" on-click="$3"></ph>Learn more in our Privacy Policy<ph name="LINK_END1"></a></ph> </message> - <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_2_DESCRIPTION_CONTENT_PARITY_CLANK" desc="This string appears at the bottom of the “Ad topics” settings page. “Sites” refers to websites that the user visits. The tone is informative. “Companies” refers to businesses or organizations that these websites belong to. This is intended to explain the possible user risks currently available with our evolving privacy-preserving technologies, in neutral language." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_M1_CONSENT_LEARN_MORE_BULLET_2_DESCRIPTION_CONTENT_PARITY_CLANK" desc="This string appears at the bottom of the “Ad topics” settings page. “Sites” refers to websites that the user visits. The tone is informative. “Companies” refers to businesses or organizations that these websites belong to. This is intended to explain the possible user risks currently available with our evolving privacy-preserving technologies, in neutral language."> Google requires companies to state publicly that they won't use this data to track you across sites. Some sites may use your activity to personalize your experience for more than just ads. They may also store ad topics for longer than 4 weeks and combine it with other information they already know about you. Companies are responsible for letting you know how they use your data. <ph name="BEGIN_LINK"><link></ph>Learn more in our Privacy Policy<ph name="END_LINK"></link></ph>. </message> <!--Privacy Sandbox Privacy Policy--> - <message name="IDS_PRIVACY_SANDBOX_PRIVACY_POLICY_PAGE_TITLE" desc="Title for Google's Privacy Policy page." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_PRIVACY_POLICY_PAGE_TITLE" desc="Title for Google's Privacy Policy page."> Google Privacy Policy </message> - <message name="IDS_PRIVACY_SANDBOX_PRIVACY_POLICY_BACK_BUTTON" desc="Content description for the Privacy Policy page back button." formatter_data="android_java"> + <message name="IDS_PRIVACY_SANDBOX_PRIVACY_POLICY_BACK_BUTTON" desc="Content description for the Privacy Policy page back button."> Back </message>
diff --git a/components/reading_list/core/reading_list_model_impl.h b/components/reading_list/core/reading_list_model_impl.h index 4384bb6..1c03ea0 100644 --- a/components/reading_list/core/reading_list_model_impl.h +++ b/components/reading_list/core/reading_list_model_impl.h
@@ -204,7 +204,12 @@ ReadingListSyncBridge sync_bridge_; - base::ObserverList<ReadingListModelObserver>::Unchecked observers_; + // TODO(crbug.com/484371187): Investigate if reentrancy can be removed. + base::ObserverList< + ReadingListModelObserver, + /*check_empty=*/false, + base::ObserverListReentrancyPolicy::kAllowReentrancyUntriaged>::Unchecked + observers_; bool loaded_ = false;
diff --git a/components/safe_browsing/content/browser/client_side_detection_host.cc b/components/safe_browsing/content/browser/client_side_detection_host.cc index d680a5a5..c311eb2 100644 --- a/components/safe_browsing/content/browser/client_side_detection_host.cc +++ b/components/safe_browsing/content/browser/client_side_detection_host.cc
@@ -946,10 +946,35 @@ // ping back but only cancel the showing of the interstitial. weak_factory_.InvalidateWeakPtrs(); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + did_first_visually_non_empty_paint_ = false; + on_first_contentful_paint_ = false; + trigger_model_request_sent_as_force_request_ = false; + return; + } + trigger_model_request_sent_as_force_request_ = false; MaybeStartPreClassification(ClientSideDetectionType::TRIGGER_MODELS); } +void ClientSideDetectionHost::DidFirstVisuallyNonEmptyPaint() { + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + did_first_visually_non_empty_paint_ = true; + if (on_first_contentful_paint_) { + MaybeStartPreClassification(ClientSideDetectionType::TRIGGER_MODELS); + } + } +} + +void ClientSideDetectionHost::OnFirstContentfulPaintInPrimaryMainFrame() { + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + on_first_contentful_paint_ = true; + if (did_first_visually_non_empty_paint_) { + MaybeStartPreClassification(ClientSideDetectionType::TRIGGER_MODELS); + } + } +} + void ClientSideDetectionHost::OnPromptAdded() { if (!IsEnhancedProtectionEnabled(*delegate_->GetPrefs())) { return; @@ -2202,6 +2227,11 @@ ? "ConditionalImageResize.Enabled" : "ConditionalImageResize.Control"); + verdict->mutable_population()->add_finch_active_groups( + base::FeatureList::IsEnabled(kClientSideDetectionNewObservers) + ? "ClientSideDetectionNewObservers.Enabled" + : "ClientSideDetectionNewObservers.Control"); + raw_ptr<VerdictCacheManager> cache_manager = delegate_->GetCacheManager(); if (cache_manager) { ChromeUserPopulation::PageLoadToken token =
diff --git a/components/safe_browsing/content/browser/client_side_detection_host.h b/components/safe_browsing/content/browser/client_side_detection_host.h index 7a20e8f..0cd1906f 100644 --- a/components/safe_browsing/content/browser/client_side_detection_host.h +++ b/components/safe_browsing/content/browser/client_side_detection_host.h
@@ -153,6 +153,8 @@ void VibrationRequested() override; void OnTextCopiedToClipboard(content::RenderFrameHost* render_frame_host, const std::u16string& copied_text) override; + void DidFirstVisuallyNonEmptyPaint() override; + void OnFirstContentfulPaintInPrimaryMainFrame() override; // permissions::PermissionRequestManager::Observer methods: void OnPromptAdded() override; @@ -547,6 +549,18 @@ // fullscreen. GURL last_fullscreen_url_; + // `did_first_visually_non_empty_paint_` becomes true after the first paint + // that is not the background color. `on_first_contentful_paint_` becomes + // true after the browser renders the first content from the DOM (e.g., + // text or an image). + // + // Client-side detection for TRIGGER_MODELS will only start after both events + // have occurred. This ensures that classification doesn't begin before the + // page has meaningfully rendered. These flags are reset on each new main + // frame navigation. + bool did_first_visually_non_empty_paint_ = false; + bool on_first_contentful_paint_ = false; + // Records the start time of when image embedding started. base::TimeTicks image_embedding_start_time_; raw_ptr<const base::TickClock> tick_clock_;
diff --git a/components/safe_browsing/content/browser/web_ui/safe_browsing_content_ui_handler.cc b/components/safe_browsing/content/browser/web_ui/safe_browsing_content_ui_handler.cc index de20512..0c78aa6 100644 --- a/components/safe_browsing/content/browser/web_ui/safe_browsing_content_ui_handler.cc +++ b/components/safe_browsing/content/browser/web_ui/safe_browsing_content_ui_handler.cc
@@ -132,116 +132,22 @@ } void SafeBrowsingContentUIHandler::RegisterMessages() { - web_ui()->RegisterMessageCallback( - "getExperiments", - base::BindRepeating(&SafeBrowsingUIHandler::GetExperiments, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getPolicies", base::BindRepeating(&SafeBrowsingUIHandler::GetPolicies, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getPrefs", base::BindRepeating(&SafeBrowsingUIHandler::GetPrefs, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getCookie", base::BindRepeating(&SafeBrowsingUIHandler::GetCookie, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getSavedPasswords", - base::BindRepeating(&SafeBrowsingUIHandler::GetSavedPasswords, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getDatabaseManagerInfo", - base::BindRepeating(&SafeBrowsingUIHandler::GetDatabaseManagerInfo, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getDownloadUrlsChecked", - base::BindRepeating(&SafeBrowsingUIHandler::GetDownloadUrlsChecked, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getSentClientDownloadRequests", - base::BindRepeating(&SafeBrowsingUIHandler::GetSentClientDownloadRequests, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getReceivedClientDownloadResponses", - base::BindRepeating( - &SafeBrowsingUIHandler::GetReceivedClientDownloadResponses, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getSentClientPhishingRequests", - base::BindRepeating(&SafeBrowsingUIHandler::GetSentClientPhishingRequests, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getReceivedClientPhishingResponses", - base::BindRepeating( - &SafeBrowsingUIHandler::GetReceivedClientPhishingResponses, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getSentCSBRRs", - base::BindRepeating(&SafeBrowsingUIHandler::GetSentCSBRRs, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getPGEvents", base::BindRepeating(&SafeBrowsingUIHandler::GetPGEvents, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getSecurityEvents", - base::BindRepeating(&SafeBrowsingUIHandler::GetSecurityEvents, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getPGPings", base::BindRepeating(&SafeBrowsingUIHandler::GetPGPings, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getPGResponses", - base::BindRepeating(&SafeBrowsingUIHandler::GetPGResponses, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getURTLookupPings", - base::BindRepeating(&SafeBrowsingUIHandler::GetURTLookupPings, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getURTLookupResponses", - base::BindRepeating(&SafeBrowsingUIHandler::GetURTLookupResponses, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getHPRTLookupPings", - base::BindRepeating(&SafeBrowsingUIHandler::GetHPRTLookupPings, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getHPRTLookupResponses", - base::BindRepeating(&SafeBrowsingUIHandler::GetHPRTLookupResponses, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getLogMessages", - base::BindRepeating(&SafeBrowsingUIHandler::GetLogMessages, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( + SafeBrowsingUIHandler::RegisterMessages(); + RegisterMessage( "getReferrerChain", base::BindRepeating(&SafeBrowsingContentUIHandler::GetReferrerChain, base::Unretained(this))); #if BUILDFLAG(IS_ANDROID) - web_ui()->RegisterMessageCallback( + RegisterMessage( "getReferringAppInfo", base::BindRepeating(&SafeBrowsingContentUIHandler::GetReferringAppInfo, base::Unretained(this))); #endif - web_ui()->RegisterMessageCallback( - "getReportingEvents", - base::BindRepeating(&SafeBrowsingUIHandler::GetReportingEvents, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getDeepScans", base::BindRepeating(&SafeBrowsingUIHandler::GetDeepScans, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "getTailoredVerdictOverride", - base::BindRepeating(&SafeBrowsingUIHandler::GetTailoredVerdictOverride, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "setTailoredVerdictOverride", - base::BindRepeating(&SafeBrowsingUIHandler::SetTailoredVerdictOverride, - base::Unretained(this))); - web_ui()->RegisterMessageCallback( - "clearTailoredVerdictOverride", - base::BindRepeating(&SafeBrowsingUIHandler::ClearTailoredVerdictOverride, - base::Unretained(this))); +} + +void SafeBrowsingContentUIHandler::RegisterMessage(std::string_view name, + MessageCallback callback) { + web_ui()->RegisterMessageCallback(name, std::move(callback)); } void SafeBrowsingContentUIHandler::ResolveCallback(
diff --git a/components/safe_browsing/content/browser/web_ui/safe_browsing_content_ui_handler.h b/components/safe_browsing/content/browser/web_ui/safe_browsing_content_ui_handler.h index 2bab2e6..e9bf107 100644 --- a/components/safe_browsing/content/browser/web_ui/safe_browsing_content_ui_handler.h +++ b/components/safe_browsing/content/browser/web_ui/safe_browsing_content_ui_handler.h
@@ -82,6 +82,8 @@ void RegisterMessages() override; // SafeBrowsingUIHandler:: + void RegisterMessage(std::string_view name, + MessageCallback callback) override; void ResolveCallback(const base::ValueView callback_id, const base::ValueView response) override; PrefService* user_prefs() override;
diff --git a/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.cc b/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.cc index 16d0392..de5f83b 100644 --- a/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.cc +++ b/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.cc
@@ -139,9 +139,29 @@ classifier_->SetClientSideDetectionType(request_type); RecordEvent(SBPhishingClassifierEvent::kPhishingDetectionRequested); - // Start classifying the current page if all conditions are met. - // See MaybeStartClassification() for details. - MaybeStartClassification(); + if (base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + // Browser request has come in, but renderer has not fully loaded, so leave + // it up for renderer load to start the classification. + if (!renderer_layout_finished_) { + return; + } + + if (request_type_ == mojom::ClientSideDetectionType::kImageEmbeddingMatch || + request_type_ == mojom::ClientSideDetectionType::kTriggerModels) { + base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( + FROM_HERE, + base::BindOnce(&PhishingClassifierDelegate::MaybeStartClassification, + weak_factory_.GetWeakPtr()), + base::Seconds(kCsdClassificationDelay.Get())); + } else { + MaybeStartClassification(); + } + + } else { + // Start classifying the current page if all conditions are met. + // See MaybeStartClassification() for details. + MaybeStartClassification(); + } } void PhishingClassifierDelegate::DidCommitProvisionalLoad( @@ -149,6 +169,7 @@ blink::WebLocalFrame* frame = render_frame()->GetWebFrame(); // A new page is starting to load, so cancel classificaiton. CancelPendingClassification(CancelClassificationReason::kNavigateAway); + renderer_layout_finished_ = false; if (!frame->Parent()) last_main_frame_transition_ = transition; } @@ -160,24 +181,70 @@ void PhishingClassifierDelegate::PageCaptured( scoped_refptr<const base::RefCountedString16> page_text, bool preliminary_capture) { - RecordEvent(SBPhishingClassifierEvent::kPageTextCaptured); + if (!base::FeatureList::IsEnabled(kClientSideDetectionNewObservers)) { + RecordEvent(SBPhishingClassifierEvent::kPageTextCaptured); - if (preliminary_capture) { - return; + if (preliminary_capture) { + return; + } + + // Note: Currently, if the url hasn't changed, we won't restart + // classification in this case. We may want to adjust this. + + last_finished_load_url_ = + render_frame()->GetWebFrame()->GetDocument().Url(); + + GURL stripped_last_load_url(StripRef(last_finished_load_url_)); + // Check if toplevel URL has changed. + if (stripped_last_load_url == StripRef(last_url_sent_to_classifier_)) { + return; + } + + MaybeStartClassification(); + } else { + // This is true if layout_type == kWebMeaningfulLayout::kFinishedParsing. + // We are looking for kWebMeaningfulLayout::kFinishedLoading only. + // PageCaptured is not called for any other cases of kWebMeaningfulLayout. + if (preliminary_capture) { + return; + } + renderer_layout_finished_ = true; + RecordEvent( + SBPhishingClassifierEvent::kPhishingClassifierPageFinishedLoading); + // Note: Currently, if the url hasn't changed, we won't restart + // classification in this case. We may want to adjust this. + last_finished_load_url_ = + render_frame()->GetWebFrame()->GetDocument().Url(); + + // Browser side has not made a request yet, so no need to try to start the + // classification. + if (!is_phishing_detection_running_) { + return; + } + + GURL stripped_last_load_url(StripRef(last_finished_load_url_)); + // If we're classifying at the moment and there's a new finished load on the + // page, do not attempt to start a new classification. + if (is_classifying_ && + stripped_last_load_url == StripRef(last_url_sent_to_classifier_)) { + RecordEvent( + SBPhishingClassifierEvent:: + kPhishingClassifierPageFinishedLoadingAgainDuringClassification); + return; + } + + if (request_type_ == mojom::ClientSideDetectionType::kTriggerModels || + request_type_ == mojom::ClientSideDetectionType::kImageEmbeddingMatch) { + base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( + FROM_HERE, + base::BindOnce(&PhishingClassifierDelegate::MaybeStartClassification, + weak_factory_.GetWeakPtr()), + base::Seconds(kCsdClassificationDelay.Get())); + + } else { + MaybeStartClassification(); + } } - - // Note: Currently, if the url hasn't changed, we won't restart - // classification in this case. We may want to adjust this. - - last_finished_load_url_ = render_frame()->GetWebFrame()->GetDocument().Url(); - - GURL stripped_last_load_url(StripRef(last_finished_load_url_)); - // Check if toplevel URL has changed. - if (stripped_last_load_url == StripRef(last_url_sent_to_classifier_)) { - return; - } - - MaybeStartClassification(); } void PhishingClassifierDelegate::CancelPendingClassification(
diff --git a/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.h b/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.h index d45fedab..d690cfb 100644 --- a/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.h +++ b/components/safe_browsing/content/renderer/phishing_classifier/phishing_classifier_delegate.h
@@ -51,7 +51,12 @@ kPhishingClasifierCallbackEmptyOnCompletion = 8, // Phishing classification request responded. kPhishingClassifierRequestResponded = 9, - kMaxValue = kPhishingClassifierRequestResponded, + // Renderer frame layout has finished loading as + // WebMeaningfulLayout::kFinishedLoading. + kPhishingClassifierPageFinishedLoading = 10, + // Renderer frame layout has finished loading during classification. + kPhishingClassifierPageFinishedLoadingAgainDuringClassification = 11, + kMaxValue = kPhishingClassifierPageFinishedLoadingAgainDuringClassification, }; class PhishingClassifierDelegate : public content::RenderFrameObserver, @@ -174,6 +179,10 @@ // set to false whenever phishing detection has finished. bool is_phishing_detection_running_ = false; + // Set to true when PageText is captured. Set to false when there is a new + // frame loading. This is used as a signal to start the classification. + bool renderer_layout_finished_ = false; + // Set to true when we want to classify for the page, but classifier was not // ready. It is set to false whenever |is_phishing_detection_running_| is set // to true, classification is happening, completed, or cancelled.
diff --git a/components/safe_browsing/core/browser/web_ui/safe_browsing_ui_handler.cc b/components/safe_browsing/core/browser/web_ui/safe_browsing_ui_handler.cc index 98e75635..1d43086 100644 --- a/components/safe_browsing/core/browser/web_ui/safe_browsing_ui_handler.cc +++ b/components/safe_browsing/core/browser/web_ui/safe_browsing_ui_handler.cc
@@ -487,6 +487,102 @@ ResolveTailoredVerdictOverrideCallback(args[0].GetString()); } +void SafeBrowsingUIHandler::RegisterMessages() { + RegisterMessage("getExperiments", + base::BindRepeating(&SafeBrowsingUIHandler::GetExperiments, + base::Unretained(this))); + RegisterMessage("getPolicies", + base::BindRepeating(&SafeBrowsingUIHandler::GetPolicies, + base::Unretained(this))); + RegisterMessage("getPrefs", + base::BindRepeating(&SafeBrowsingUIHandler::GetPrefs, + base::Unretained(this))); + RegisterMessage("getCookie", + base::BindRepeating(&SafeBrowsingUIHandler::GetCookie, + base::Unretained(this))); + RegisterMessage("getSavedPasswords", + base::BindRepeating(&SafeBrowsingUIHandler::GetSavedPasswords, + base::Unretained(this))); + RegisterMessage( + "getDatabaseManagerInfo", + base::BindRepeating(&SafeBrowsingUIHandler::GetDatabaseManagerInfo, + base::Unretained(this))); + RegisterMessage( + "getDownloadUrlsChecked", + base::BindRepeating(&SafeBrowsingUIHandler::GetDownloadUrlsChecked, + base::Unretained(this))); + RegisterMessage( + "getSentClientDownloadRequests", + base::BindRepeating(&SafeBrowsingUIHandler::GetSentClientDownloadRequests, + base::Unretained(this))); + RegisterMessage( + "getReceivedClientDownloadResponses", + base::BindRepeating( + &SafeBrowsingUIHandler::GetReceivedClientDownloadResponses, + base::Unretained(this))); + RegisterMessage( + "getSentClientPhishingRequests", + base::BindRepeating(&SafeBrowsingUIHandler::GetSentClientPhishingRequests, + base::Unretained(this))); + RegisterMessage( + "getReceivedClientPhishingResponses", + base::BindRepeating( + &SafeBrowsingUIHandler::GetReceivedClientPhishingResponses, + base::Unretained(this))); + RegisterMessage("getSentCSBRRs", + base::BindRepeating(&SafeBrowsingUIHandler::GetSentCSBRRs, + base::Unretained(this))); + RegisterMessage("getPGEvents", + base::BindRepeating(&SafeBrowsingUIHandler::GetPGEvents, + base::Unretained(this))); + RegisterMessage("getSecurityEvents", + base::BindRepeating(&SafeBrowsingUIHandler::GetSecurityEvents, + base::Unretained(this))); + RegisterMessage("getPGPings", + base::BindRepeating(&SafeBrowsingUIHandler::GetPGPings, + base::Unretained(this))); + RegisterMessage("getPGResponses", + base::BindRepeating(&SafeBrowsingUIHandler::GetPGResponses, + base::Unretained(this))); + RegisterMessage("getURTLookupPings", + base::BindRepeating(&SafeBrowsingUIHandler::GetURTLookupPings, + base::Unretained(this))); + RegisterMessage( + "getURTLookupResponses", + base::BindRepeating(&SafeBrowsingUIHandler::GetURTLookupResponses, + base::Unretained(this))); + RegisterMessage( + "getHPRTLookupPings", + base::BindRepeating(&SafeBrowsingUIHandler::GetHPRTLookupPings, + base::Unretained(this))); + RegisterMessage( + "getHPRTLookupResponses", + base::BindRepeating(&SafeBrowsingUIHandler::GetHPRTLookupResponses, + base::Unretained(this))); + RegisterMessage("getLogMessages", + base::BindRepeating(&SafeBrowsingUIHandler::GetLogMessages, + base::Unretained(this))); + RegisterMessage( + "getReportingEvents", + base::BindRepeating(&SafeBrowsingUIHandler::GetReportingEvents, + base::Unretained(this))); + RegisterMessage("getDeepScans", + base::BindRepeating(&SafeBrowsingUIHandler::GetDeepScans, + base::Unretained(this))); + RegisterMessage( + "getTailoredVerdictOverride", + base::BindRepeating(&SafeBrowsingUIHandler::GetTailoredVerdictOverride, + base::Unretained(this))); + RegisterMessage( + "setTailoredVerdictOverride", + base::BindRepeating(&SafeBrowsingUIHandler::SetTailoredVerdictOverride, + base::Unretained(this))); + RegisterMessage( + "clearTailoredVerdictOverride", + base::BindRepeating(&SafeBrowsingUIHandler::ClearTailoredVerdictOverride, + base::Unretained(this))); +} + void SafeBrowsingUIHandler::ResolveTailoredVerdictOverrideCallback( const std::string& callback_id) { ResolveCallback(callback_id, GetFormattedTailoredVerdictOverride());
diff --git a/components/safe_browsing/core/browser/web_ui/safe_browsing_ui_handler.h b/components/safe_browsing/core/browser/web_ui/safe_browsing_ui_handler.h index 48095e8..715cc65 100644 --- a/components/safe_browsing/core/browser/web_ui/safe_browsing_ui_handler.h +++ b/components/safe_browsing/core/browser/web_ui/safe_browsing_ui_handler.h
@@ -23,6 +23,8 @@ class WebUIInfoSingleton; class WebUIInfoSingletonEventObserver; +using MessageCallback = base::RepeatingCallback<void(const base::ListValue&)>; + class SafeBrowsingUIHandler { public: SafeBrowsingUIHandler( @@ -129,6 +131,14 @@ // Clears the current tailored verdict override. void ClearTailoredVerdictOverride(const base::ListValue& args); + // Registers all the messages from the DOM to which the handler class intends + // to listen. + void RegisterMessages(); + + // Registers the message and callback with the WebUI. + virtual void RegisterMessage(std::string_view name, + MessageCallback callback) = 0; + // Resolves Javascript requests initiated with returned promises. virtual void ResolveCallback(const base::ValueView callback_id, const base::ValueView response) = 0;
diff --git a/components/safe_browsing/core/common/features.cc b/components/safe_browsing/core/common/features.cc index 1156893..e2fb332 100644 --- a/components/safe_browsing/core/common/features.cc +++ b/components/safe_browsing/core/common/features.cc
@@ -133,6 +133,11 @@ BASE_FEATURE(kClientSideDetectionLlamaForcedTriggerInfoForScamDetection, base::FEATURE_ENABLED_BY_DEFAULT); +BASE_FEATURE(kClientSideDetectionNewObservers, + base::FEATURE_DISABLED_BY_DEFAULT); +constexpr base::FeatureParam<double> kCsdClassificationDelay{ + &kClientSideDetectionNewObservers, "ClassificationDelay", 0.0}; + #if BUILDFLAG(IS_ANDROID) BASE_FEATURE(kClientSideDetectionOnDeviceModelLazyDownloadAndroid, base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/components/safe_browsing/core/common/features.h b/components/safe_browsing/core/common/features.h index 061b070..454249330 100644 --- a/components/safe_browsing/core/common/features.h +++ b/components/safe_browsing/core/common/features.h
@@ -120,6 +120,11 @@ BASE_DECLARE_FEATURE( kClientSideDetectionLlamaForcedTriggerInfoForScamDetection); +// The observers that trigger the image classification have been tweaked with a +// more defined page loading state check. +BASE_DECLARE_FEATURE(kClientSideDetectionNewObservers); +extern const base::FeatureParam<double> kCsdClassificationDelay; + #if BUILDFLAG(IS_ANDROID) // Instead of starting model download on startup, do it lazily during inference. BASE_DECLARE_FEATURE(kClientSideDetectionOnDeviceModelLazyDownloadAndroid);
diff --git a/components/search_engines/template_url_prepopulate_data_unittest.cc b/components/search_engines/template_url_prepopulate_data_unittest.cc index a83152f..4361f73dc 100644 --- a/components/search_engines/template_url_prepopulate_data_unittest.cc +++ b/components/search_engines/template_url_prepopulate_data_unittest.cc
@@ -604,15 +604,13 @@ std::unique_ptr<TemplateURLData> data = TemplateURLDataFromPrepopulatedEngine(*engine); - if (engine == &TemplateURLPrepopulateData::yahoo_jp && - engine->migrate_to_id != 0) { + if (engine == &TemplateURLPrepopulateData::yahoo_jp) { // This is checking the deprecated version of Yahoo, for which we would be // using the post-migration SearchEngineType. - // TODO(crbug.com/446637115): Remove the redundant `migrate_to_id` check - // when the updated data rolls out. + ASSERT_EQ(engine->type, SEARCH_ENGINE_YAHOO); EXPECT_EQ(SEARCH_ENGINE_YAHOO_JP, TemplateURL(*data).GetEngineType(SearchTermsData())); - return; + continue; } EXPECT_EQ(engine->type, @@ -631,15 +629,13 @@ std::unique_ptr<TemplateURLData> data = TemplateURLDataFromPrepopulatedEngine(*engine); - if (engine->type == SEARCH_ENGINE_YAHOO_JP && - engine != &TemplateURLPrepopulateData::yahoo_jp) { + if (engine == &TemplateURLPrepopulateData::yahoo_jp_next) { // This is checking the post-migration version of Yahoo, but as migration // is disabled, the returned SearchEngineType would be the old one. - // TODO(crbug.com/446637115): Update the test with the exact engine when - // the updated data rolls out. + ASSERT_EQ(engine->type, SEARCH_ENGINE_YAHOO_JP); EXPECT_EQ(SEARCH_ENGINE_YAHOO, TemplateURL(*data).GetEngineType(SearchTermsData())); - return; + continue; } EXPECT_EQ(engine->type,
diff --git a/components/segmentation_platform/embedder/home_modules/BUILD.gn b/components/segmentation_platform/embedder/home_modules/BUILD.gn index 3c4806d..d6a726a 100644 --- a/components/segmentation_platform/embedder/home_modules/BUILD.gn +++ b/components/segmentation_platform/embedder/home_modules/BUILD.gn
@@ -36,6 +36,8 @@ "default_browser_promo_ephemeral_module.h", "enhanced_safe_browsing_ephemeral_module.cc", "enhanced_safe_browsing_ephemeral_module.h", + "home_modules_card_registry_ios.cc", + "home_modules_card_registry_ios.h", "lens_ephemeral_module.cc", "lens_ephemeral_module.h", "save_passwords_ephemeral_module.cc", @@ -51,6 +53,8 @@ "default_browser_promo.h", "history_sync_promo.cc", "history_sync_promo.h", + "home_modules_card_registry_android.cc", + "home_modules_card_registry_android.h", "quick_delete_promo.cc", "quick_delete_promo.h", "tab_group_promo.cc", @@ -70,6 +74,7 @@ "//components/segmentation_platform/embedder/home_modules/tips_manager:constants", "//components/segmentation_platform/embedder/home_modules/tips_manager:signal_constants", "//components/segmentation_platform/internal", + "//components/segmentation_platform/public", "//components/send_tab_to_self", "//components/signin/public/base:signin_switches", ]
diff --git a/components/segmentation_platform/embedder/home_modules/auxiliary_search_promo_unittest.cc b/components/segmentation_platform/embedder/home_modules/auxiliary_search_promo_unittest.cc index 4fc06b3..bd94275 100644 --- a/components/segmentation_platform/embedder/home_modules/auxiliary_search_promo_unittest.cc +++ b/components/segmentation_platform/embedder/home_modules/auxiliary_search_promo_unittest.cc
@@ -5,7 +5,7 @@ #include "components/segmentation_platform/embedder/home_modules/auxiliary_search_promo.h" #include "components/segmentation_platform/embedder/home_modules/constants.h" -#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h" +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.h" #include "components/segmentation_platform/embedder/home_modules/test_utils.h" #include "components/segmentation_platform/public/constants.h" #include "testing/gtest/include/gtest/gtest.h"
diff --git a/components/segmentation_platform/embedder/home_modules/default_browser_promo.cc b/components/segmentation_platform/embedder/home_modules/default_browser_promo.cc index 35b335b8..3e8debf 100644 --- a/components/segmentation_platform/embedder/home_modules/default_browser_promo.cc +++ b/components/segmentation_platform/embedder/home_modules/default_browser_promo.cc
@@ -8,7 +8,7 @@ #include "components/commerce/core/commerce_feature_list.h" #include "components/segmentation_platform/embedder/home_modules/constants.h" #include "components/segmentation_platform/embedder/home_modules/ephemeral_module_utils.h" -#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h" +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.h" #include "components/segmentation_platform/public/features.h" #include "components/segmentation_platform/public/proto/model_metadata.pb.h"
diff --git a/components/segmentation_platform/embedder/home_modules/default_browser_promo_unittest.cc b/components/segmentation_platform/embedder/home_modules/default_browser_promo_unittest.cc index 176801a..963c08f 100644 --- a/components/segmentation_platform/embedder/home_modules/default_browser_promo_unittest.cc +++ b/components/segmentation_platform/embedder/home_modules/default_browser_promo_unittest.cc
@@ -7,7 +7,7 @@ #include "components/prefs/testing_pref_service.h" #include "components/segmentation_platform/embedder/home_modules/card_selection_signals.h" #include "components/segmentation_platform/embedder/home_modules/constants.h" -#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h" +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.h" #include "components/segmentation_platform/embedder/home_modules/test_utils.h" #include "testing/gtest/include/gtest/gtest.h"
diff --git a/components/segmentation_platform/embedder/home_modules/ephemeral_home_module_backend_unittest.cc b/components/segmentation_platform/embedder/home_modules/ephemeral_home_module_backend_unittest.cc index 5abc165..39b2235a 100644 --- a/components/segmentation_platform/embedder/home_modules/ephemeral_home_module_backend_unittest.cc +++ b/components/segmentation_platform/embedder/home_modules/ephemeral_home_module_backend_unittest.cc
@@ -74,8 +74,12 @@ profile_pref_service_.registry()); HomeModulesCardRegistry::RegisterLocalStatePrefs( local_state_pref_service_.registry()); - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); + if (!registry_) { + registry_ = HomeModulesCardRegistry::CreateForTesting( + &profile_pref_service_, &local_state_pref_service_, {}); + } static_cast<EphemeralHomeModuleBackend*>(model_.get()) ->set_home_modules_card_registry_for_testing(registry_.get()); } @@ -124,7 +128,7 @@ std::make_unique<EphemeralHomeModuleBackend>(nullptr)) { std::vector<std::unique_ptr<CardSelectionInfo>> cards; cards.emplace_back(std::make_unique<TestCardInfo>()); - registry_ = std::make_unique<HomeModulesCardRegistry>( + registry_ = HomeModulesCardRegistry::CreateForTesting( &profile_pref_service_, &local_state_pref_service_, std::move(cards)); static_cast<EphemeralHomeModuleBackend*>(model_.get()) ->set_home_modules_card_registry_for_testing(registry_.get());
diff --git a/components/segmentation_platform/embedder/home_modules/history_sync_promo.cc b/components/segmentation_platform/embedder/home_modules/history_sync_promo.cc index 5653010..6f223783 100644 --- a/components/segmentation_platform/embedder/home_modules/history_sync_promo.cc +++ b/components/segmentation_platform/embedder/home_modules/history_sync_promo.cc
@@ -8,7 +8,7 @@ #include "components/commerce/core/commerce_feature_list.h" #include "components/segmentation_platform/embedder/home_modules/constants.h" #include "components/segmentation_platform/embedder/home_modules/ephemeral_module_utils.h" -#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h" +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.h" #include "components/segmentation_platform/public/features.h" #include "components/segmentation_platform/public/proto/model_metadata.pb.h" #include "components/signin/public/base/signin_switches.h"
diff --git a/components/segmentation_platform/embedder/home_modules/history_sync_promo_unittest.cc b/components/segmentation_platform/embedder/home_modules/history_sync_promo_unittest.cc index e3d2cc15..f8a2eb47 100644 --- a/components/segmentation_platform/embedder/home_modules/history_sync_promo_unittest.cc +++ b/components/segmentation_platform/embedder/home_modules/history_sync_promo_unittest.cc
@@ -7,7 +7,7 @@ #include "components/prefs/testing_pref_service.h" #include "components/segmentation_platform/embedder/home_modules/card_selection_signals.h" #include "components/segmentation_platform/embedder/home_modules/constants.h" -#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h" +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.h" #include "components/segmentation_platform/embedder/home_modules/test_utils.h" #include "testing/gtest/include/gtest/gtest.h"
diff --git a/components/segmentation_platform/embedder/home_modules/home_modules_card_registry.cc b/components/segmentation_platform/embedder/home_modules/home_modules_card_registry.cc index f0c23b3..d0b43ad2 100644 --- a/components/segmentation_platform/embedder/home_modules/home_modules_card_registry.cc +++ b/components/segmentation_platform/embedder/home_modules/home_modules_card_registry.cc
@@ -2,562 +2,102 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifdef UNSAFE_BUFFERS_BUILD -// TODO(crbug.com/390223051): Remove C-library calls to fix the errors. -#pragma allow_unsafe_libc_calls -#endif - #include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h" #include <string> -#include <string_view> +#include <vector> -#include "base/metrics/field_trial_params.h" -#include "base/strings/strcat.h" -#include "base/strings/string_split.h" -#include "components/commerce/core/commerce_feature_list.h" -#include "components/segmentation_platform/embedder/home_modules/auxiliary_search_promo.h" -#include "components/segmentation_platform/embedder/home_modules/card_selection_info.h" +#include "base/check.h" +#include "build/build_config.h" +#include "components/prefs/pref_registry_simple.h" #include "components/segmentation_platform/embedder/home_modules/constants.h" -#include "components/segmentation_platform/embedder/home_modules/default_browser_promo.h" -#include "components/segmentation_platform/embedder/home_modules/ephemeral_module_utils.h" -#include "components/segmentation_platform/embedder/home_modules/history_sync_promo.h" -#include "components/segmentation_platform/embedder/home_modules/price_tracking_notification_promo.h" -#include "components/segmentation_platform/embedder/home_modules/quick_delete_promo.h" -#include "components/segmentation_platform/embedder/home_modules/send_tab_notification_promo.h" -#include "components/segmentation_platform/embedder/home_modules/tab_group_promo.h" -#include "components/segmentation_platform/embedder/home_modules/tab_group_sync_promo.h" -#include "components/segmentation_platform/embedder/home_modules/tips_manager/constants.h" -#include "components/segmentation_platform/embedder/home_modules/tips_notifications_promo.h" -#include "components/segmentation_platform/public/constants.h" -#include "components/segmentation_platform/public/features.h" + #if BUILDFLAG(IS_IOS) -#include "components/segmentation_platform/embedder/home_modules/address_bar_position_ephemeral_module.h" -#include "components/segmentation_platform/embedder/home_modules/app_bundle_promo_ephemeral_module.h" -#include "components/segmentation_platform/embedder/home_modules/autofill_passwords_ephemeral_module.h" -#include "components/segmentation_platform/embedder/home_modules/default_browser_promo_ephemeral_module.h" -#include "components/segmentation_platform/embedder/home_modules/enhanced_safe_browsing_ephemeral_module.h" -#include "components/segmentation_platform/embedder/home_modules/ephemeral_module_utils.h" -#include "components/segmentation_platform/embedder/home_modules/lens_ephemeral_module.h" -#include "components/segmentation_platform/embedder/home_modules/save_passwords_ephemeral_module.h" +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_ios.h" +#elif BUILDFLAG(IS_ANDROID) +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.h" #endif namespace segmentation_platform::home_modules { -#if BUILDFLAG(IS_ANDROID) -const char kAuxiliarySearchPromoImpressionCounterPref[] = - "ephemeral_pref_counter.auxiliary_search_promo_counter"; -const char kAuxiliarySearchPromoInteractedPref[] = - "ephemeral_pref_interacted.auxiliary_search_promo_interacted"; -const char kDefaultBrowserPromoImpressionCounterPref[] = - "ephemeral_pref_counter.default_browser_promo_counter"; -const char kDefaultBrowserPromoInteractedPref[] = - "ephemeral_pref_interacted.default_browser_promo_interacted"; -const char kTabGroupPromoImpressionCounterPref[] = - "ephemeral_pref_counter.tab_group_promo_counter"; -const char kTabGroupPromoInteractedPref[] = - "ephemeral_pref_interacted.tab_group_promo_interacted"; -const char kTabGroupSyncPromoImpressionCounterPref[] = - "ephemeral_pref_counter.tab_group_sync_promo_counter"; -const char kTabGroupSyncPromoInteractedPref[] = - "ephemeral_pref_interacted.tab_group_sync_promo_interacted"; -const char kQuickDeletePromoImpressionCounterPref[] = - "ephemeral_pref_counter.quick_delete_promo_counter"; -const char kQuickDeletePromoInteractedPref[] = - "ephemeral_pref_interacted.quick_delete_promo_interacted"; -const char kHistorySyncPromoImpressionCounterPref[] = - "ephemeral_pref_counter.history_sync_promo_counter"; -const char kHistorySyncPromoInteractedPref[] = - "ephemeral_pref_interacted.history_sync_promo_interacted"; -const char kTipsNotificationsPromoImpressionCounterPref[] = - "ephemeral_pref_counter.tips_notifications_promo_counter"; -const char kTipsNotificationsPromoInteractedPref[] = - "ephemeral_pref_interacted.tips_notifications_promo_interacted"; -#endif - namespace { -#if BUILDFLAG(IS_IOS) -// Impression counter for the Price Tracking notification promo card. -const char kPriceTrackingPromoImpressionCounterPref[] = - "ephemeral_pref_counter.price_tracking_promo_counter"; -// Impression counter for the Address Bar Position ephemeral module. -const char kAddressBarPositionEphemeralModuleImpressionCounterPref[] = - "ephemeral_pref_counter.address_bar_position_ephemeral_module_counter"; -// Impression counter for the Autofill Passwords ephemeral module. -const char kAutofillPasswordsEphemeralModuleImpressionCounterPref[] = - "ephemeral_pref_counter.autofill_passwords_ephemeral_module_counter"; -// Impression counter for the Enhanced Safe Browsing ephemeral module. -const char kEnhancedSafeBrowsingEphemeralModuleImpressionCounterPref[] = - "ephemeral_pref_counter.enhanced_safe_browsing_ephemeral_module_counter"; -// Impression counter for the Save Passwords ephemeral module. -const char kSavePasswordsEphemeralModuleImpressionCounterPref[] = - "ephemeral_pref_counter.save_passwords_ephemeral_module_counter"; -// Impression counter for the Lens ephemeral module. -const char kLensEphemeralModuleImpressionCounterPref[] = - "ephemeral_pref_counter.lens_ephemeral_module_counter"; -// Impression counter for the Send Tab ephemeral module. -const char kSendTabPromoImpressionCounterPref[] = - "ephemeral_pref_counter.send_tab_promo_counter"; -// Impression counter for the App Bundle promo ephemeral module. -const char kAppBundlePromoEphemeralModuleImpressionCounterPref[] = - "ephemeral_pref_counter.app_bundle_promo_ephemeral_module_counter"; -// Impression counter for the Default Browser promo ephemeral module. -const char kDefaultBrowserPromoEphemeralModuleImpressionCounterPref[] = - "ephemeral_pref_counter.default_browser_promo_ephemeral_module_counter"; - -// Creates a card corresponding to the given ephemeral `tip` module and adds -// it to the `cards` list if the module is enabled. -void AddCardForTip(TipIdentifier tip, - std::vector<std::unique_ptr<CardSelectionInfo>>& cards, - PrefService* prefs) { - switch (tip) { - case TipIdentifier::kUnknown: - return; // Do nothing for unknown tips - case TipIdentifier::kLensSearch: - case TipIdentifier::kLensShop: - case TipIdentifier::kLensTranslate: { - int impression_count = - prefs->GetInteger(kLensEphemeralModuleImpressionCounterPref); - if (LensEphemeralModule::IsEnabled(impression_count)) { - cards.push_back(std::make_unique<LensEphemeralModule>(prefs)); - } - break; - } - case TipIdentifier::kAddressBarPosition: { - int impression_count = prefs->GetInteger( - kAddressBarPositionEphemeralModuleImpressionCounterPref); - if (AddressBarPositionEphemeralModule::IsEnabled(impression_count)) { - cards.push_back( - std::make_unique<AddressBarPositionEphemeralModule>(prefs)); - } - break; - } - case TipIdentifier::kSavePasswords: { - int impression_count = - prefs->GetInteger(kSavePasswordsEphemeralModuleImpressionCounterPref); - if (SavePasswordsEphemeralModule::IsEnabled(impression_count)) { - cards.push_back(std::make_unique<SavePasswordsEphemeralModule>(prefs)); - } - break; - } - case TipIdentifier::kAutofillPasswords: { - int impression_count = prefs->GetInteger( - kAutofillPasswordsEphemeralModuleImpressionCounterPref); - if (AutofillPasswordsEphemeralModule::IsEnabled(impression_count)) { - cards.push_back( - std::make_unique<AutofillPasswordsEphemeralModule>(prefs)); - } - break; - } - case TipIdentifier::kEnhancedSafeBrowsing: { - int impression_count = prefs->GetInteger( - kEnhancedSafeBrowsingEphemeralModuleImpressionCounterPref); - if (EnhancedSafeBrowsingEphemeralModule::IsEnabled(impression_count)) { - cards.push_back( - std::make_unique<EnhancedSafeBrowsingEphemeralModule>(prefs)); - } - break; - } +// A concrete implementation for testing that accepts a custom list of cards +// and provides empty implementations for the pure virtual tracking methods. +// +// TODO(crbug.com/489045573): Refactor shared test logic into reusable test +// utils. +class TestHomeModulesCardRegistry : public HomeModulesCardRegistry { + public: + TestHomeModulesCardRegistry( + PrefService* profile_prefs, + PrefService* local_state_prefs, + std::vector<std::unique_ptr<CardSelectionInfo>> test_cards) + : HomeModulesCardRegistry(profile_prefs, local_state_prefs) { + all_cards_by_priority_.swap(test_cards); + InitializeAfterAddingCards(); } -} -// Returns the default sequence of tips variations for iOS. -std::string GetDefaultTipsExperimentTrain() { - return base::StrCat({kLensEphemeralModuleSearchVariation, ",", - kEnhancedSafeBrowsingEphemeralModule}); -} - -#endif + void NotifyCardShown(const char* card_name) override {} + void NotifyCardInteracted(const char* card_name) override {} +}; } // namespace -HomeModulesCardRegistry::HomeModulesCardRegistry(PrefService* profile_prefs, - PrefService* local_state_prefs) - : profile_prefs_(profile_prefs), local_state_prefs_(local_state_prefs) { - CHECK(profile_prefs); - CHECK(local_state_prefs); - CreateAllCards(); +// static +std::unique_ptr<HomeModulesCardRegistry> HomeModulesCardRegistry::Create( + PrefService* profile_prefs, + PrefService* local_state_prefs) { +#if BUILDFLAG(IS_IOS) + return std::make_unique<HomeModulesCardRegistryIOS>(profile_prefs, + local_state_prefs); +#elif BUILDFLAG(IS_ANDROID) + return std::make_unique<HomeModulesCardRegistryAndroid>(profile_prefs, + local_state_prefs); +#else + // Fallback for platforms that don't support home modules (e.g., Desktop). + return nullptr; +#endif } -HomeModulesCardRegistry::HomeModulesCardRegistry( +// static +std::unique_ptr<HomeModulesCardRegistry> +HomeModulesCardRegistry::CreateForTesting( PrefService* profile_prefs, PrefService* local_state_prefs, - std::vector<std::unique_ptr<CardSelectionInfo>> cards) - : profile_prefs_(profile_prefs), local_state_prefs_(local_state_prefs) { - CHECK(profile_prefs); - CHECK(local_state_prefs); - all_cards_by_priority_.swap(cards); - InitializeAfterAddingCards(); + std::vector<std::unique_ptr<CardSelectionInfo>> test_cards) { + return std::make_unique<TestHomeModulesCardRegistry>( + profile_prefs, local_state_prefs, std::move(test_cards)); } -HomeModulesCardRegistry::~HomeModulesCardRegistry() = default; +// static +void HomeModulesCardRegistry::RegisterProfilePrefs( + PrefRegistrySimple* registry) { +#if BUILDFLAG(IS_IOS) + HomeModulesCardRegistryIOS::RegisterProfilePrefs(registry); +#elif BUILDFLAG(IS_ANDROID) + HomeModulesCardRegistryAndroid::RegisterProfilePrefs(registry); +#endif +} // static void HomeModulesCardRegistry::RegisterLocalStatePrefs( PrefRegistrySimple* registry) { #if BUILDFLAG(IS_IOS) - // Local state prefs are used for the `AppBundleEphemeralModule` because this - // promo relates to app installations on the device level, meaning impressions - // should be tracked per-device rather than per profile. - registry->RegisterIntegerPref( - kAppBundlePromoEphemeralModuleImpressionCounterPref, 0); + HomeModulesCardRegistryIOS::RegisterLocalStatePrefs(registry); +#elif BUILDFLAG(IS_ANDROID) + HomeModulesCardRegistryAndroid::RegisterLocalStatePrefs(registry); #endif } -// static -void HomeModulesCardRegistry::RegisterProfilePrefs( - PrefRegistrySimple* registry) { -#if BUILDFLAG(IS_IOS) - registry->RegisterIntegerPref(kPriceTrackingPromoImpressionCounterPref, 0); - registry->RegisterIntegerPref(kSendTabPromoImpressionCounterPref, 0); - registry->RegisterIntegerPref( - kAddressBarPositionEphemeralModuleImpressionCounterPref, 0); - registry->RegisterIntegerPref( - kAutofillPasswordsEphemeralModuleImpressionCounterPref, 0); - registry->RegisterIntegerPref( - kEnhancedSafeBrowsingEphemeralModuleImpressionCounterPref, 0); - registry->RegisterIntegerPref( - kSavePasswordsEphemeralModuleImpressionCounterPref, 0); - registry->RegisterIntegerPref(kLensEphemeralModuleImpressionCounterPref, 0); - registry->RegisterBooleanPref( - kAddressBarPositionEphemeralModuleInteractedPref, false); - registry->RegisterBooleanPref(kAutofillPasswordsEphemeralModuleInteractedPref, - false); - registry->RegisterBooleanPref( - kEnhancedSafeBrowsingEphemeralModuleInteractedPref, false); - registry->RegisterBooleanPref(kSavePasswordsEphemeralModuleInteractedPref, - false); - registry->RegisterBooleanPref(kLensEphemeralModuleInteractedPref, false); - registry->RegisterBooleanPref( - kLensEphemeralModuleSearchVariationInteractedPref, false); - registry->RegisterBooleanPref(kLensEphemeralModuleShopVariationInteractedPref, - false); - registry->RegisterBooleanPref( - kLensEphemeralModuleTranslateVariationInteractedPref, false); - registry->RegisterIntegerPref( - kDefaultBrowserPromoEphemeralModuleImpressionCounterPref, 0); -#endif - -#if BUILDFLAG(IS_ANDROID) - registry->RegisterIntegerPref(kAuxiliarySearchPromoImpressionCounterPref, 0); - registry->RegisterBooleanPref(kAuxiliarySearchPromoInteractedPref, false); - registry->RegisterIntegerPref(kDefaultBrowserPromoImpressionCounterPref, 0); - registry->RegisterBooleanPref(kDefaultBrowserPromoInteractedPref, false); - registry->RegisterIntegerPref(kTabGroupPromoImpressionCounterPref, 0); - registry->RegisterBooleanPref(kTabGroupPromoInteractedPref, false); - registry->RegisterIntegerPref(kTabGroupSyncPromoImpressionCounterPref, 0); - registry->RegisterBooleanPref(kTabGroupSyncPromoInteractedPref, false); - registry->RegisterIntegerPref(kQuickDeletePromoImpressionCounterPref, 0); - registry->RegisterBooleanPref(kQuickDeletePromoInteractedPref, false); - registry->RegisterIntegerPref(kHistorySyncPromoImpressionCounterPref, 0); - registry->RegisterBooleanPref(kHistorySyncPromoInteractedPref, false); - registry->RegisterIntegerPref(kTipsNotificationsPromoImpressionCounterPref, - 0); - registry->RegisterBooleanPref(kTipsNotificationsPromoInteractedPref, false); -#endif +HomeModulesCardRegistry::HomeModulesCardRegistry(PrefService* profile_prefs, + PrefService* local_state_prefs) + : profile_prefs_(profile_prefs), local_state_prefs_(local_state_prefs) { + CHECK(profile_prefs_); + CHECK(local_state_prefs_); } -// static -bool HomeModulesCardRegistry::IsEphemeralTipsModuleLabel( - std::string_view label) { -#if BUILDFLAG(IS_IOS) - return AddressBarPositionEphemeralModule::IsModuleLabel(label) || - AutofillPasswordsEphemeralModule::IsModuleLabel(label) || - EnhancedSafeBrowsingEphemeralModule::IsModuleLabel(label) || - SavePasswordsEphemeralModule::IsModuleLabel(label) || - LensEphemeralModule::IsModuleLabel(label); -#else - return false; -#endif -} - -void HomeModulesCardRegistry::NotifyCardShown(const char* card_name) { -#if BUILDFLAG(IS_IOS) - if (strcmp(card_name, kPriceTrackingNotificationPromo) == 0) { - int freshness_impression_count = - profile_prefs_->GetInteger(kPriceTrackingPromoImpressionCounterPref); - profile_prefs_->SetInteger(kPriceTrackingPromoImpressionCounterPref, - freshness_impression_count + 1); - } else if (strcmp(card_name, kAddressBarPositionEphemeralModule) == 0) { - int freshness_impression_count = profile_prefs_->GetInteger( - kAddressBarPositionEphemeralModuleImpressionCounterPref); - profile_prefs_->SetInteger( - kAddressBarPositionEphemeralModuleImpressionCounterPref, - freshness_impression_count + 1); - } else if (strcmp(card_name, kAutofillPasswordsEphemeralModule) == 0) { - int freshness_impression_count = profile_prefs_->GetInteger( - kAutofillPasswordsEphemeralModuleImpressionCounterPref); - profile_prefs_->SetInteger( - kAutofillPasswordsEphemeralModuleImpressionCounterPref, - freshness_impression_count + 1); - } else if (strcmp(card_name, kEnhancedSafeBrowsingEphemeralModule) == 0) { - int freshness_impression_count = profile_prefs_->GetInteger( - kEnhancedSafeBrowsingEphemeralModuleImpressionCounterPref); - profile_prefs_->SetInteger( - kEnhancedSafeBrowsingEphemeralModuleImpressionCounterPref, - freshness_impression_count + 1); - } else if (strcmp(card_name, kSavePasswordsEphemeralModule) == 0) { - int freshness_impression_count = profile_prefs_->GetInteger( - kSavePasswordsEphemeralModuleImpressionCounterPref); - profile_prefs_->SetInteger( - kSavePasswordsEphemeralModuleImpressionCounterPref, - freshness_impression_count + 1); - } else if (strcmp(card_name, kLensEphemeralModule) == 0 || - strcmp(card_name, kLensEphemeralModuleSearchVariation) == 0 || - strcmp(card_name, kLensEphemeralModuleShopVariation) == 0 || - strcmp(card_name, kLensEphemeralModuleTranslateVariation) == 0) { - int freshness_impression_count = - profile_prefs_->GetInteger(kLensEphemeralModuleImpressionCounterPref); - profile_prefs_->SetInteger(kLensEphemeralModuleImpressionCounterPref, - freshness_impression_count + 1); - } else if (strcmp(card_name, kSendTabNotificationPromo) == 0) { - int impression_count = - profile_prefs_->GetInteger(kSendTabPromoImpressionCounterPref); - profile_prefs_->SetInteger(kSendTabPromoImpressionCounterPref, - impression_count + 1); - } else if (strcmp(card_name, kAppBundlePromoEphemeralModule) == 0) { - int local_impression_count = local_state_prefs_->GetInteger( - kAppBundlePromoEphemeralModuleImpressionCounterPref); - local_state_prefs_->SetInteger( - kAppBundlePromoEphemeralModuleImpressionCounterPref, - local_impression_count + 1); - } else if (strcmp(card_name, kDefaultBrowserPromoEphemeralModule) == 0) { - int impression_count = profile_prefs_->GetInteger( - kDefaultBrowserPromoEphemeralModuleImpressionCounterPref); - profile_prefs_->SetInteger( - kDefaultBrowserPromoEphemeralModuleImpressionCounterPref, - impression_count + 1); - } -#endif - -#if BUILDFLAG(IS_ANDROID) - if (strcmp(card_name, kDefaultBrowserPromo) == 0) { - int freshness_impression_count = - profile_prefs_->GetInteger(kDefaultBrowserPromoImpressionCounterPref); - profile_prefs_->SetInteger(kDefaultBrowserPromoImpressionCounterPref, - freshness_impression_count + 1); - } else if (ShouldNotifyCardShownPerSession(card_name)) { - // Educational tip cards, except for the default browser promo card, will - // send a notification when the card is shown once per session, rather than - // every time it is displayed. - if (strcmp(card_name, kTabGroupPromo) == 0) { - int freshness_impression_count = - profile_prefs_->GetInteger(kTabGroupPromoImpressionCounterPref); - profile_prefs_->SetInteger(kTabGroupPromoImpressionCounterPref, - freshness_impression_count + 1); - } else if (strcmp(card_name, kTabGroupSyncPromo) == 0) { - int freshness_impression_count = - profile_prefs_->GetInteger(kTabGroupSyncPromoImpressionCounterPref); - profile_prefs_->SetInteger(kTabGroupSyncPromoImpressionCounterPref, - freshness_impression_count + 1); - } else if (strcmp(card_name, kQuickDeletePromo) == 0) { - int freshness_impression_count = - profile_prefs_->GetInteger(kQuickDeletePromoImpressionCounterPref); - profile_prefs_->SetInteger(kQuickDeletePromoImpressionCounterPref, - freshness_impression_count + 1); - } else if (strcmp(card_name, kAuxiliarySearch) == 0) { - int freshness_impression_count = profile_prefs_->GetInteger( - kAuxiliarySearchPromoImpressionCounterPref); - profile_prefs_->SetInteger(kAuxiliarySearchPromoImpressionCounterPref, - freshness_impression_count + 1); - } else if (strcmp(card_name, kHistorySyncPromo) == 0) { - int freshness_impression_count = - profile_prefs_->GetInteger(kHistorySyncPromoImpressionCounterPref); - profile_prefs_->SetInteger(kHistorySyncPromoImpressionCounterPref, - freshness_impression_count + 1); - } else if (strcmp(card_name, kTipsNotificationsPromo) == 0) { - int freshness_impression_count = profile_prefs_->GetInteger( - kTipsNotificationsPromoImpressionCounterPref); - profile_prefs_->SetInteger(kTipsNotificationsPromoImpressionCounterPref, - freshness_impression_count + 1); - } - } -#endif -} - -#if BUILDFLAG(IS_ANDROID) -bool HomeModulesCardRegistry::ShouldNotifyCardShownPerSession( - const std::string& card_name) { - if (shown_in_current_session_.find(card_name) != - shown_in_current_session_.end()) { - return false; - } - - shown_in_current_session_.insert(card_name); - return true; -} -#endif - -void HomeModulesCardRegistry::NotifyCardInteracted(const char* card_name) { -#if BUILDFLAG(IS_IOS) - if (strcmp(card_name, kAddressBarPositionEphemeralModule) == 0) { - profile_prefs_->SetBoolean(kAddressBarPositionEphemeralModuleInteractedPref, - true); - } else if (strcmp(card_name, kAutofillPasswordsEphemeralModule) == 0) { - profile_prefs_->SetBoolean(kAutofillPasswordsEphemeralModuleInteractedPref, - true); - } else if (strcmp(card_name, kEnhancedSafeBrowsingEphemeralModule) == 0) { - profile_prefs_->SetBoolean( - kEnhancedSafeBrowsingEphemeralModuleInteractedPref, true); - } else if (strcmp(card_name, kSavePasswordsEphemeralModule) == 0) { - profile_prefs_->SetBoolean(kSavePasswordsEphemeralModuleInteractedPref, - true); - } else if (strcmp(card_name, kLensEphemeralModule) == 0) { - profile_prefs_->SetBoolean(kLensEphemeralModuleInteractedPref, true); - } else if (strcmp(card_name, kLensEphemeralModuleSearchVariation) == 0) { - profile_prefs_->SetBoolean( - kLensEphemeralModuleSearchVariationInteractedPref, true); - } else if (strcmp(card_name, kLensEphemeralModuleShopVariation) == 0) { - profile_prefs_->SetBoolean(kLensEphemeralModuleShopVariationInteractedPref, - true); - } else if (strcmp(card_name, kLensEphemeralModuleTranslateVariation) == 0) { - profile_prefs_->SetBoolean( - kLensEphemeralModuleTranslateVariationInteractedPref, true); - } -#endif - -#if BUILDFLAG(IS_ANDROID) - if (strcmp(card_name, kDefaultBrowserPromo) == 0) { - profile_prefs_->SetBoolean(kDefaultBrowserPromoInteractedPref, true); - } else if (strcmp(card_name, kTabGroupPromo) == 0) { - profile_prefs_->SetBoolean(kTabGroupPromoInteractedPref, true); - } else if (strcmp(card_name, kTabGroupSyncPromo) == 0) { - profile_prefs_->SetBoolean(kTabGroupSyncPromoInteractedPref, true); - } else if (strcmp(card_name, kQuickDeletePromo) == 0) { - profile_prefs_->SetBoolean(kQuickDeletePromoInteractedPref, true); - } else if (strcmp(card_name, kAuxiliarySearch) == 0) { - profile_prefs_->SetBoolean(kAuxiliarySearchPromoInteractedPref, true); - } else if (strcmp(card_name, kHistorySyncPromo) == 0) { - profile_prefs_->SetBoolean(kHistorySyncPromoInteractedPref, true); - } else if (strcmp(card_name, kTipsNotificationsPromo) == 0) { - profile_prefs_->SetBoolean(kTipsNotificationsPromoInteractedPref, true); - } -#endif -} - -void HomeModulesCardRegistry::CreateAllCards() { -#if BUILDFLAG(IS_IOS) - int price_tracking_promo_count = - profile_prefs_->GetInteger(kPriceTrackingPromoImpressionCounterPref); - int send_tab_promo_count = - profile_prefs_->GetInteger(kSendTabPromoImpressionCounterPref); - int app_bundle_promo_count = local_state_prefs_->GetInteger( - kAppBundlePromoEphemeralModuleImpressionCounterPref); - if (PriceTrackingNotificationPromo::IsEnabled(price_tracking_promo_count)) { - all_cards_by_priority_.push_back( - std::make_unique<PriceTrackingNotificationPromo>( - price_tracking_promo_count)); - } - int default_browser_promo_count = profile_prefs_->GetInteger( - kDefaultBrowserPromoEphemeralModuleImpressionCounterPref); - - std::optional<CardSelectionInfo::ShowResult> forced_result = - GetForcedEphemeralModuleShowResult(); - - // Determine the forced card identifier and label, if any. - TipIdentifier forced_identifier = TipIdentifier::kUnknown; - std::string_view forced_label; - - if (forced_result.has_value() && - forced_result.value().position == EphemeralHomeModuleRank::kTop) { - forced_label = forced_result.value().result_label.value(); - forced_identifier = TipIdentifierForOutputLabel(forced_label); - - if (forced_identifier != TipIdentifier::kUnknown) { - AddCardForTip(forced_identifier, all_cards_by_priority_, profile_prefs_); - } - } - - // Iterate through variation labels and add unique cards. - for (std::string_view variation_label : - base::SplitString(GetDefaultTipsExperimentTrain(), ",", - base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) { - TipIdentifier identifier = TipIdentifierForOutputLabel(variation_label); - - // Skip adding if: - // 1. The identifier is unknown. - // 2. It matches the forced identifier. - // 3. Both belong to the same "family" of Lens cards. - if (identifier == TipIdentifier::kUnknown || - identifier == forced_identifier || - (LensEphemeralModule::IsModuleLabel(variation_label) && - LensEphemeralModule::IsModuleLabel(forced_label))) { - continue; - } - - AddCardForTip(identifier, all_cards_by_priority_, profile_prefs_); - } - - if (SendTabNotificationPromo::IsEnabled(send_tab_promo_count)) { - all_cards_by_priority_.push_back( - std::make_unique<SendTabNotificationPromo>(send_tab_promo_count)); - } - - if (AppBundlePromoEphemeralModule::IsEnabled(app_bundle_promo_count)) { - all_cards_by_priority_.push_back( - std::make_unique<AppBundlePromoEphemeralModule>()); - } - - if (DefaultBrowserPromoEphemeralModule::IsEnabled( - default_browser_promo_count)) { - all_cards_by_priority_.push_back( - std::make_unique<DefaultBrowserPromoEphemeralModule>()); - } -#endif - -#if BUILDFLAG(IS_ANDROID) - int auxiliary_search_promo_count = - profile_prefs_->GetInteger(kAuxiliarySearchPromoImpressionCounterPref); - if (AuxiliarySearchPromo::IsEnabled(auxiliary_search_promo_count)) { - all_cards_by_priority_.push_back(std::make_unique<AuxiliarySearchPromo>()); - } - - // TODO(crbug.com/420897397): Move the forced card check out from each card. - int default_browser_promo_count = - profile_prefs_->GetInteger(kDefaultBrowserPromoImpressionCounterPref); - if (DefaultBrowserPromo::IsEnabled( - default_browser_promo_count)) { - all_cards_by_priority_.push_back( - std::make_unique<DefaultBrowserPromo>(profile_prefs_)); - } - int history_sync_educational_promo_show_count = - profile_prefs_->GetInteger(kHistorySyncPromoImpressionCounterPref); - if (HistorySyncPromo::IsEnabled(history_sync_educational_promo_show_count)) { - all_cards_by_priority_.push_back( - std::make_unique<HistorySyncPromo>(profile_prefs_)); - } - int tab_group_promo_count = - profile_prefs_->GetInteger(kTabGroupPromoImpressionCounterPref); - if (TabGroupPromo::IsEnabled(tab_group_promo_count)) { - all_cards_by_priority_.push_back( - std::make_unique<TabGroupPromo>(profile_prefs_)); - } - - int tab_group_sync_promo_count = - profile_prefs_->GetInteger(kTabGroupSyncPromoImpressionCounterPref); - if (TabGroupSyncPromo::IsEnabled(tab_group_sync_promo_count)) { - all_cards_by_priority_.push_back( - std::make_unique<TabGroupSyncPromo>(profile_prefs_)); - } - - int quick_delete_promo_count = - profile_prefs_->GetInteger(kQuickDeletePromoImpressionCounterPref); - if (QuickDeletePromo::IsEnabled(quick_delete_promo_count)) { - all_cards_by_priority_.push_back( - std::make_unique<QuickDeletePromo>(profile_prefs_)); - } - - int tips_notifications_promo_show_count = - profile_prefs_->GetInteger(kTipsNotificationsPromoImpressionCounterPref); - if (TipsNotificationsPromo::IsEnabled(tips_notifications_promo_show_count)) { - all_cards_by_priority_.push_back( - std::make_unique<TipsNotificationsPromo>(profile_prefs_)); - } - -#endif - InitializeAfterAddingCards(); -} +HomeModulesCardRegistry::~HomeModulesCardRegistry() = default; void HomeModulesCardRegistry::InitializeAfterAddingCards() { size_t input_counter = 0;
diff --git a/components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h b/components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h index 0a4c8a3..12a7ec6 100644 --- a/components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h +++ b/components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h
@@ -5,138 +5,129 @@ #ifndef COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_HOME_MODULES_HOME_MODULES_CARD_REGISTRY_H_ #define COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_HOME_MODULES_HOME_MODULES_CARD_REGISTRY_H_ +#include <map> #include <memory> +#include <string> #include <vector> +#include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/supports_user_data.h" -#include "components/prefs/pref_registry_simple.h" #include "components/prefs/pref_service.h" #include "components/segmentation_platform/embedder/home_modules/card_selection_info.h" #include "components/segmentation_platform/embedder/home_modules/card_selection_signals.h" #include "components/segmentation_platform/embedder/home_modules/rank_fetcher_helper.h" +class PrefRegistrySimple; + namespace segmentation_platform::home_modules { -#if BUILDFLAG(IS_ANDROID) -// Impression counter for each card. -extern const char kDefaultBrowserPromoImpressionCounterPref[]; -extern const char kTabGroupPromoImpressionCounterPref[]; -extern const char kTabGroupSyncPromoImpressionCounterPref[]; -extern const char kQuickDeletePromoImpressionCounterPref[]; -extern const char kAuxiliarySearchPromoImpressionCounterPref[]; -extern const char kHistorySyncPromoImpressionCounterPref[]; -extern const char kTipsNotificationsPromoImpressionCounterPref[]; - -// Interaction flag for each card. -extern const char kDefaultBrowserPromoInteractedPref[]; -extern const char kTabGroupPromoInteractedPref[]; -extern const char kTabGroupSyncPromoInteractedPref[]; -extern const char kQuickDeletePromoInteractedPref[]; -extern const char kAuxiliarySearchPromoInteractedPref[]; -extern const char kHistorySyncPromoInteractedPref[]; -extern const char kTipsNotificationsPromoInteractedPref[]; -#endif - // Registry that manages all ephemeral cards in mobile home modules. class HomeModulesCardRegistry : public base::SupportsUserData::Data { public: - explicit HomeModulesCardRegistry(PrefService* profile_prefs, - PrefService* local_state_prefs); - // For testing. - HomeModulesCardRegistry( + // Creates and returns specific subclass (iOS or Android) based on build + // flags. + static std::unique_ptr<HomeModulesCardRegistry> Create( + PrefService* profile_prefs, + PrefService* local_state_prefs); + + // Creates a test-only registry with mocked or injected cards. + static std::unique_ptr<HomeModulesCardRegistry> CreateForTesting( PrefService* profile_prefs, PrefService* local_state_prefs, - std::vector<std::unique_ptr<CardSelectionInfo>> cards); + std::vector<std::unique_ptr<CardSelectionInfo>> test_cards); + ~HomeModulesCardRegistry() override; HomeModulesCardRegistry(const HomeModulesCardRegistry&) = delete; HomeModulesCardRegistry& operator=(const HomeModulesCardRegistry&) = delete; - // Registers all the profile prefs needed for the ephemeral cards system. + // Registers profile prefs in `registry`. static void RegisterProfilePrefs(PrefRegistrySimple* registry); - // Registers all the local state prefs needed for the ephemeral cards system. + // Registers local-state prefs in `registry`. static void RegisterLocalStatePrefs(PrefRegistrySimple* registry); - // Returns `true` if the given `label` corresponds to any of the Tips - // ephemeral module classes. - static bool IsEphemeralTipsModuleLabel(std::string_view label); - // Indicates that `card_name` was shown to the user. - void NotifyCardShown(const char* card_name); + virtual void NotifyCardShown(const char* card_name) = 0; // Indicates that the user interacted with `card_name`. This could be // through clicking, tapping, or engaging with the card in some way. - void NotifyCardInteracted(const char* card_name); + virtual void NotifyCardInteracted(const char* card_name) = 0; - RankFetcherHelper* get_rank_fecther_helper() { return &rank_fecther_helper_; } + // Returns the helper responsible for fetching card ranks. + RankFetcherHelper* get_rank_fetcher_helper() { return &rank_fetcher_helper_; } + // Returns a list of all output labels for the registered cards. const std::vector<std::string>& all_output_labels() const { return all_output_labels_; } + // Returns the total count of input signals across all cards. size_t all_cards_input_size() const { return all_cards_input_size_; } + // Returns all registered cards ordered by their default priority. const std::vector<std::unique_ptr<CardSelectionInfo>>& get_all_cards_by_priority() const { return all_cards_by_priority_; } + // Returns the mapping of each card to its input signals and output indices. const CardSignalMap& get_card_signal_map() const { return card_signal_map_; } + // Returns the specific output index for a given card `label`. size_t get_label_index(const std::string& label) const { return label_to_output_index_.at(label); } base::WeakPtr<HomeModulesCardRegistry> GetWeakPtr(); -#if BUILDFLAG(IS_ANDROID) - // Returns true if this is the first time the card is displayed to the user in - // the current session and the event should be recorded. - bool ShouldNotifyCardShownPerSession(const std::string& card_name); -#endif + protected: + // Protected constructor for platform-specific subclasses to initialize pref + // services. + HomeModulesCardRegistry(PrefService* profile_prefs, + PrefService* local_state_prefs); - private: - // Populats `all_cards_by_priority_`. - void CreateAllCards(); - - // Initializes the registry after all cards are added. + // Initializes state, signal maps, and label indices after subclasses populate + // cards. void InitializeAfterAddingCards(); - // Adds `card_labels` to the registry. + // Registers a list of output labels and maps them to their respective output + // indices. void AddCardLabels(const std::vector<std::string>& card_labels); - RankFetcherHelper rank_fecther_helper_; + // Helper responsible for fetching and managing ranking signals for the cards. + RankFetcherHelper rank_fetcher_helper_; + // PrefService tied to the user profile, used for tracking profile-level + // impressions and interactions. const raw_ptr<PrefService> profile_prefs_; - // `PrefService` for the local state. + // PrefService tied to the local device state, used for tracking device-level + // metrics. const raw_ptr<PrefService> local_state_prefs_; - // Maps a card label to its output index order. + // Maps each card's specific output label to its index position in the final + // results. std::map<std::string, size_t> label_to_output_index_; - // List of cards by their default priority. + // Holds all instantiated ephemeral cards, ordered by their default fallback + // priority. std::vector<std::unique_ptr<CardSelectionInfo>> all_cards_by_priority_; - // Holds a map of each card to a map of its input signals to their overall - // index order of the returned fetch. + // Maps each card's name to its input signals and their corresponding index + // orders. CardSignalMap card_signal_map_; - // All the output labels for all the cards registered by this class. + // A comprehensive, ordered list of all output labels for every registered + // card. std::vector<std::string> all_output_labels_; - // The total count of the inputs of all cards. + // The total combined count of input signals required across all registered + // cards. size_t all_cards_input_size_{0}; -#if BUILDFLAG(IS_ANDROID) - // A list includes all educational tip card types (excluding the default - // browser promo card) that have been displayed to the user during the current - // session. - std::unordered_set<std::string> shown_in_current_session_; -#endif - + private: base::WeakPtrFactory<HomeModulesCardRegistry> weak_ptr_factory_{this}; };
diff --git a/components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.cc b/components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.cc new file mode 100644 index 0000000..2543ecd --- /dev/null +++ b/components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.cc
@@ -0,0 +1,219 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifdef UNSAFE_BUFFERS_BUILD +// TODO(crbug.com/390223051): Remove C-library calls to fix the errors. +#pragma allow_unsafe_libc_calls +#endif + +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.h" + +#include <memory> +#include <string> +#include <vector> + +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "components/segmentation_platform/embedder/home_modules/auxiliary_search_promo.h" +#include "components/segmentation_platform/embedder/home_modules/card_selection_info.h" +#include "components/segmentation_platform/embedder/home_modules/constants.h" +#include "components/segmentation_platform/embedder/home_modules/default_browser_promo.h" +#include "components/segmentation_platform/embedder/home_modules/history_sync_promo.h" +#include "components/segmentation_platform/embedder/home_modules/quick_delete_promo.h" +#include "components/segmentation_platform/embedder/home_modules/tab_group_promo.h" +#include "components/segmentation_platform/embedder/home_modules/tab_group_sync_promo.h" +#include "components/segmentation_platform/embedder/home_modules/tips_notifications_promo.h" +#include "components/segmentation_platform/public/constants.h" +#include "components/segmentation_platform/public/features.h" + +namespace segmentation_platform::home_modules { + +const char kAuxiliarySearchPromoImpressionCounterPref[] = + "ephemeral_pref_counter.auxiliary_search_promo_counter"; +const char kAuxiliarySearchPromoInteractedPref[] = + "ephemeral_pref_interacted.auxiliary_search_promo_interacted"; +const char kDefaultBrowserPromoImpressionCounterPref[] = + "ephemeral_pref_counter.default_browser_promo_counter"; +const char kDefaultBrowserPromoInteractedPref[] = + "ephemeral_pref_interacted.default_browser_promo_interacted"; +const char kTabGroupPromoImpressionCounterPref[] = + "ephemeral_pref_counter.tab_group_promo_counter"; +const char kTabGroupPromoInteractedPref[] = + "ephemeral_pref_interacted.tab_group_promo_interacted"; +const char kTabGroupSyncPromoImpressionCounterPref[] = + "ephemeral_pref_counter.tab_group_sync_promo_counter"; +const char kTabGroupSyncPromoInteractedPref[] = + "ephemeral_pref_interacted.tab_group_sync_promo_interacted"; +const char kQuickDeletePromoImpressionCounterPref[] = + "ephemeral_pref_counter.quick_delete_promo_counter"; +const char kQuickDeletePromoInteractedPref[] = + "ephemeral_pref_interacted.quick_delete_promo_interacted"; +const char kHistorySyncPromoImpressionCounterPref[] = + "ephemeral_pref_counter.history_sync_promo_counter"; +const char kHistorySyncPromoInteractedPref[] = + "ephemeral_pref_interacted.history_sync_promo_interacted"; +const char kTipsNotificationsPromoImpressionCounterPref[] = + "ephemeral_pref_counter.tips_notifications_promo_counter"; +const char kTipsNotificationsPromoInteractedPref[] = + "ephemeral_pref_interacted.tips_notifications_promo_interacted"; + +HomeModulesCardRegistryAndroid::HomeModulesCardRegistryAndroid( + PrefService* profile_prefs, + PrefService* local_state_prefs) + : HomeModulesCardRegistry(profile_prefs, local_state_prefs) { + int auxiliary_search_promo_count = + profile_prefs_->GetInteger(kAuxiliarySearchPromoImpressionCounterPref); + if (AuxiliarySearchPromo::IsEnabled(auxiliary_search_promo_count)) { + all_cards_by_priority_.push_back(std::make_unique<AuxiliarySearchPromo>()); + } + + // TODO(crbug.com/420897397): Move the forced card check out from each card. + int default_browser_promo_count = + profile_prefs_->GetInteger(kDefaultBrowserPromoImpressionCounterPref); + if (DefaultBrowserPromo::IsEnabled(default_browser_promo_count)) { + all_cards_by_priority_.push_back( + std::make_unique<DefaultBrowserPromo>(profile_prefs_)); + } + + int history_sync_educational_promo_show_count = + profile_prefs_->GetInteger(kHistorySyncPromoImpressionCounterPref); + if (HistorySyncPromo::IsEnabled(history_sync_educational_promo_show_count)) { + all_cards_by_priority_.push_back( + std::make_unique<HistorySyncPromo>(profile_prefs_)); + } + + int tab_group_promo_count = + profile_prefs_->GetInteger(kTabGroupPromoImpressionCounterPref); + if (TabGroupPromo::IsEnabled(tab_group_promo_count)) { + all_cards_by_priority_.push_back( + std::make_unique<TabGroupPromo>(profile_prefs_)); + } + + int tab_group_sync_promo_count = + profile_prefs_->GetInteger(kTabGroupSyncPromoImpressionCounterPref); + if (TabGroupSyncPromo::IsEnabled(tab_group_sync_promo_count)) { + all_cards_by_priority_.push_back( + std::make_unique<TabGroupSyncPromo>(profile_prefs_)); + } + + int quick_delete_promo_count = + profile_prefs_->GetInteger(kQuickDeletePromoImpressionCounterPref); + if (QuickDeletePromo::IsEnabled(quick_delete_promo_count)) { + all_cards_by_priority_.push_back( + std::make_unique<QuickDeletePromo>(profile_prefs_)); + } + + int tips_notifications_promo_show_count = + profile_prefs_->GetInteger(kTipsNotificationsPromoImpressionCounterPref); + if (TipsNotificationsPromo::IsEnabled(tips_notifications_promo_show_count)) { + all_cards_by_priority_.push_back( + std::make_unique<TipsNotificationsPromo>(profile_prefs_)); + } + + InitializeAfterAddingCards(); +} + +HomeModulesCardRegistryAndroid::~HomeModulesCardRegistryAndroid() = default; + +// static +void HomeModulesCardRegistryAndroid::RegisterLocalStatePrefs( + PrefRegistrySimple* registry) { + // Currently no local-state prefs are required for Android ephemeral modules. +} + +// static +void HomeModulesCardRegistryAndroid::RegisterProfilePrefs( + PrefRegistrySimple* registry) { + registry->RegisterIntegerPref(kAuxiliarySearchPromoImpressionCounterPref, 0); + registry->RegisterBooleanPref(kAuxiliarySearchPromoInteractedPref, false); + registry->RegisterIntegerPref(kDefaultBrowserPromoImpressionCounterPref, 0); + registry->RegisterBooleanPref(kDefaultBrowserPromoInteractedPref, false); + registry->RegisterIntegerPref(kTabGroupPromoImpressionCounterPref, 0); + registry->RegisterBooleanPref(kTabGroupPromoInteractedPref, false); + registry->RegisterIntegerPref(kTabGroupSyncPromoImpressionCounterPref, 0); + registry->RegisterBooleanPref(kTabGroupSyncPromoInteractedPref, false); + registry->RegisterIntegerPref(kQuickDeletePromoImpressionCounterPref, 0); + registry->RegisterBooleanPref(kQuickDeletePromoInteractedPref, false); + registry->RegisterIntegerPref(kHistorySyncPromoImpressionCounterPref, 0); + registry->RegisterBooleanPref(kHistorySyncPromoInteractedPref, false); + registry->RegisterIntegerPref(kTipsNotificationsPromoImpressionCounterPref, + 0); + registry->RegisterBooleanPref(kTipsNotificationsPromoInteractedPref, false); +} + +void HomeModulesCardRegistryAndroid::NotifyCardShown(const char* card_name) { + if (strcmp(card_name, kDefaultBrowserPromo) == 0) { + int freshness_impression_count = + profile_prefs_->GetInteger(kDefaultBrowserPromoImpressionCounterPref); + profile_prefs_->SetInteger(kDefaultBrowserPromoImpressionCounterPref, + freshness_impression_count + 1); + } else if (ShouldNotifyCardShownPerSession(card_name)) { + // Educational tip cards, except for the default browser promo card, will + // send a notification when the card is shown once per session, rather than + // every time it is displayed. + if (strcmp(card_name, kTabGroupPromo) == 0) { + int freshness_impression_count = + profile_prefs_->GetInteger(kTabGroupPromoImpressionCounterPref); + profile_prefs_->SetInteger(kTabGroupPromoImpressionCounterPref, + freshness_impression_count + 1); + } else if (strcmp(card_name, kTabGroupSyncPromo) == 0) { + int freshness_impression_count = + profile_prefs_->GetInteger(kTabGroupSyncPromoImpressionCounterPref); + profile_prefs_->SetInteger(kTabGroupSyncPromoImpressionCounterPref, + freshness_impression_count + 1); + } else if (strcmp(card_name, kQuickDeletePromo) == 0) { + int freshness_impression_count = + profile_prefs_->GetInteger(kQuickDeletePromoImpressionCounterPref); + profile_prefs_->SetInteger(kQuickDeletePromoImpressionCounterPref, + freshness_impression_count + 1); + } else if (strcmp(card_name, kAuxiliarySearch) == 0) { + int freshness_impression_count = profile_prefs_->GetInteger( + kAuxiliarySearchPromoImpressionCounterPref); + profile_prefs_->SetInteger(kAuxiliarySearchPromoImpressionCounterPref, + freshness_impression_count + 1); + } else if (strcmp(card_name, kHistorySyncPromo) == 0) { + int freshness_impression_count = + profile_prefs_->GetInteger(kHistorySyncPromoImpressionCounterPref); + profile_prefs_->SetInteger(kHistorySyncPromoImpressionCounterPref, + freshness_impression_count + 1); + } else if (strcmp(card_name, kTipsNotificationsPromo) == 0) { + int freshness_impression_count = profile_prefs_->GetInteger( + kTipsNotificationsPromoImpressionCounterPref); + profile_prefs_->SetInteger(kTipsNotificationsPromoImpressionCounterPref, + freshness_impression_count + 1); + } + } +} + +void HomeModulesCardRegistryAndroid::NotifyCardInteracted( + const char* card_name) { + if (strcmp(card_name, kDefaultBrowserPromo) == 0) { + profile_prefs_->SetBoolean(kDefaultBrowserPromoInteractedPref, true); + } else if (strcmp(card_name, kTabGroupPromo) == 0) { + profile_prefs_->SetBoolean(kTabGroupPromoInteractedPref, true); + } else if (strcmp(card_name, kTabGroupSyncPromo) == 0) { + profile_prefs_->SetBoolean(kTabGroupSyncPromoInteractedPref, true); + } else if (strcmp(card_name, kQuickDeletePromo) == 0) { + profile_prefs_->SetBoolean(kQuickDeletePromoInteractedPref, true); + } else if (strcmp(card_name, kAuxiliarySearch) == 0) { + profile_prefs_->SetBoolean(kAuxiliarySearchPromoInteractedPref, true); + } else if (strcmp(card_name, kHistorySyncPromo) == 0) { + profile_prefs_->SetBoolean(kHistorySyncPromoInteractedPref, true); + } else if (strcmp(card_name, kTipsNotificationsPromo) == 0) { + profile_prefs_->SetBoolean(kTipsNotificationsPromoInteractedPref, true); + } +} + +bool HomeModulesCardRegistryAndroid::ShouldNotifyCardShownPerSession( + const std::string& card_name) { + if (shown_in_current_session_.find(card_name) != + shown_in_current_session_.end()) { + return false; + } + + shown_in_current_session_.insert(card_name); + return true; +} + +} // namespace segmentation_platform::home_modules
diff --git a/components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.h b/components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.h new file mode 100644 index 0000000..52d5730 --- /dev/null +++ b/components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.h
@@ -0,0 +1,71 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_HOME_MODULES_HOME_MODULES_CARD_REGISTRY_ANDROID_H_ +#define COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_HOME_MODULES_HOME_MODULES_CARD_REGISTRY_ANDROID_H_ + +#include <string> +#include <unordered_set> + +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h" + +class PrefRegistrySimple; +class PrefService; + +namespace segmentation_platform::home_modules { + +// Impression counters for Android cards. +extern const char kDefaultBrowserPromoImpressionCounterPref[]; +extern const char kTabGroupPromoImpressionCounterPref[]; +extern const char kTabGroupSyncPromoImpressionCounterPref[]; +extern const char kQuickDeletePromoImpressionCounterPref[]; +extern const char kAuxiliarySearchPromoImpressionCounterPref[]; +extern const char kHistorySyncPromoImpressionCounterPref[]; +extern const char kTipsNotificationsPromoImpressionCounterPref[]; + +// Interaction flags for Android cards. +extern const char kDefaultBrowserPromoInteractedPref[]; +extern const char kTabGroupPromoInteractedPref[]; +extern const char kTabGroupSyncPromoInteractedPref[]; +extern const char kQuickDeletePromoInteractedPref[]; +extern const char kAuxiliarySearchPromoInteractedPref[]; +extern const char kHistorySyncPromoInteractedPref[]; +extern const char kTipsNotificationsPromoInteractedPref[]; + +// The Android-specific implementation of the HomeModulesCardRegistry. +class HomeModulesCardRegistryAndroid : public HomeModulesCardRegistry { + public: + HomeModulesCardRegistryAndroid(PrefService* profile_prefs, + PrefService* local_state_prefs); + ~HomeModulesCardRegistryAndroid() override; + + HomeModulesCardRegistryAndroid(const HomeModulesCardRegistryAndroid&) = + delete; + HomeModulesCardRegistryAndroid& operator=( + const HomeModulesCardRegistryAndroid&) = delete; + + // Registers all the profile prefs needed for the Android ephemeral cards. + static void RegisterProfilePrefs(PrefRegistrySimple* registry); + + // Registers all the local state prefs needed for the Android ephemeral cards. + static void RegisterLocalStatePrefs(PrefRegistrySimple* registry); + + // `HomeModulesCardRegistry` overrides: + void NotifyCardShown(const char* card_name) override; + void NotifyCardInteracted(const char* card_name) override; + + private: + // Returns true if this is the first time the card is displayed to the user in + // the current session and the event should be recorded. + bool ShouldNotifyCardShownPerSession(const std::string& card_name); + + // A list that includes all educational tip card types (excluding the default + // browser promo card) that have been displayed to the user during the current + // session. + std::unordered_set<std::string> shown_in_current_session_; +}; + +} // namespace segmentation_platform::home_modules + +#endif // COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_HOME_MODULES_HOME_MODULES_CARD_REGISTRY_ANDROID_H_
diff --git a/components/segmentation_platform/embedder/home_modules/home_modules_card_registry_ios.cc b/components/segmentation_platform/embedder/home_modules/home_modules_card_registry_ios.cc new file mode 100644 index 0000000..126f05e5 --- /dev/null +++ b/components/segmentation_platform/embedder/home_modules/home_modules_card_registry_ios.cc
@@ -0,0 +1,346 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifdef UNSAFE_BUFFERS_BUILD +// TODO(crbug.com/390223051): Remove C-library calls to fix the errors. +#pragma allow_unsafe_libc_calls +#endif + +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_ios.h" + +#include <memory> +#include <optional> +#include <vector> + +#include "base/strings/strcat.h" +#include "base/strings/string_split.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "components/segmentation_platform/embedder/home_modules/address_bar_position_ephemeral_module.h" +#include "components/segmentation_platform/embedder/home_modules/app_bundle_promo_ephemeral_module.h" +#include "components/segmentation_platform/embedder/home_modules/autofill_passwords_ephemeral_module.h" +#include "components/segmentation_platform/embedder/home_modules/card_selection_info.h" +#include "components/segmentation_platform/embedder/home_modules/constants.h" +#include "components/segmentation_platform/embedder/home_modules/default_browser_promo_ephemeral_module.h" +#include "components/segmentation_platform/embedder/home_modules/enhanced_safe_browsing_ephemeral_module.h" +#include "components/segmentation_platform/embedder/home_modules/ephemeral_module_utils.h" +#include "components/segmentation_platform/embedder/home_modules/lens_ephemeral_module.h" +#include "components/segmentation_platform/embedder/home_modules/price_tracking_notification_promo.h" +#include "components/segmentation_platform/embedder/home_modules/save_passwords_ephemeral_module.h" +#include "components/segmentation_platform/embedder/home_modules/send_tab_notification_promo.h" +#include "components/segmentation_platform/embedder/home_modules/tips_manager/constants.h" + +namespace segmentation_platform::home_modules { + +namespace { + +// Impression counter for the Price Tracking notification promo card. +const char kPriceTrackingPromoImpressionCounterPref[] = + "ephemeral_pref_counter.price_tracking_promo_counter"; +// Impression counter for the Address Bar Position ephemeral module. +const char kAddressBarPositionEphemeralModuleImpressionCounterPref[] = + "ephemeral_pref_counter.address_bar_position_ephemeral_module_counter"; +// Impression counter for the Autofill Passwords ephemeral module. +const char kAutofillPasswordsEphemeralModuleImpressionCounterPref[] = + "ephemeral_pref_counter.autofill_passwords_ephemeral_module_counter"; +// Impression counter for the Enhanced Safe Browsing ephemeral module. +const char kEnhancedSafeBrowsingEphemeralModuleImpressionCounterPref[] = + "ephemeral_pref_counter.enhanced_safe_browsing_ephemeral_module_counter"; +// Impression counter for the Save Passwords ephemeral module. +const char kSavePasswordsEphemeralModuleImpressionCounterPref[] = + "ephemeral_pref_counter.save_passwords_ephemeral_module_counter"; +// Impression counter for the Lens ephemeral module. +const char kLensEphemeralModuleImpressionCounterPref[] = + "ephemeral_pref_counter.lens_ephemeral_module_counter"; +// Impression counter for the Send Tab ephemeral module. +const char kSendTabPromoImpressionCounterPref[] = + "ephemeral_pref_counter.send_tab_promo_counter"; +// Impression counter for the App Bundle promo ephemeral module. +const char kAppBundlePromoEphemeralModuleImpressionCounterPref[] = + "ephemeral_pref_counter.app_bundle_promo_ephemeral_module_counter"; +// Impression counter for the Default Browser promo ephemeral module. +const char kDefaultBrowserPromoEphemeralModuleImpressionCounterPref[] = + "ephemeral_pref_counter.default_browser_promo_ephemeral_module_counter"; + +// Creates a card corresponding to the given ephemeral `tip` module and adds +// it to the `cards` list if the module is enabled. +void AddCardForTip(TipIdentifier tip, + std::vector<std::unique_ptr<CardSelectionInfo>>& cards, + PrefService* prefs) { + switch (tip) { + case TipIdentifier::kUnknown: + return; // Do nothing for unknown tips + case TipIdentifier::kLensSearch: + case TipIdentifier::kLensShop: + case TipIdentifier::kLensTranslate: { + int impression_count = + prefs->GetInteger(kLensEphemeralModuleImpressionCounterPref); + if (LensEphemeralModule::IsEnabled(impression_count)) { + cards.push_back(std::make_unique<LensEphemeralModule>(prefs)); + } + break; + } + case TipIdentifier::kAddressBarPosition: { + int impression_count = prefs->GetInteger( + kAddressBarPositionEphemeralModuleImpressionCounterPref); + if (AddressBarPositionEphemeralModule::IsEnabled(impression_count)) { + cards.push_back( + std::make_unique<AddressBarPositionEphemeralModule>(prefs)); + } + break; + } + case TipIdentifier::kSavePasswords: { + int impression_count = + prefs->GetInteger(kSavePasswordsEphemeralModuleImpressionCounterPref); + if (SavePasswordsEphemeralModule::IsEnabled(impression_count)) { + cards.push_back(std::make_unique<SavePasswordsEphemeralModule>(prefs)); + } + break; + } + case TipIdentifier::kAutofillPasswords: { + int impression_count = prefs->GetInteger( + kAutofillPasswordsEphemeralModuleImpressionCounterPref); + if (AutofillPasswordsEphemeralModule::IsEnabled(impression_count)) { + cards.push_back( + std::make_unique<AutofillPasswordsEphemeralModule>(prefs)); + } + break; + } + case TipIdentifier::kEnhancedSafeBrowsing: { + int impression_count = prefs->GetInteger( + kEnhancedSafeBrowsingEphemeralModuleImpressionCounterPref); + if (EnhancedSafeBrowsingEphemeralModule::IsEnabled(impression_count)) { + cards.push_back( + std::make_unique<EnhancedSafeBrowsingEphemeralModule>(prefs)); + } + break; + } + } +} + +// Returns the default sequence of tips variations for iOS. +std::string GetDefaultTipsExperimentTrain() { + return base::StrCat({kLensEphemeralModuleSearchVariation, ",", + kEnhancedSafeBrowsingEphemeralModule}); +} + +} // namespace + +HomeModulesCardRegistryIOS::HomeModulesCardRegistryIOS( + PrefService* profile_prefs, + PrefService* local_state_prefs) + : HomeModulesCardRegistry(profile_prefs, local_state_prefs) { + int price_tracking_promo_count = + profile_prefs_->GetInteger(kPriceTrackingPromoImpressionCounterPref); + int send_tab_promo_count = + profile_prefs_->GetInteger(kSendTabPromoImpressionCounterPref); + int app_bundle_promo_count = local_state_prefs_->GetInteger( + kAppBundlePromoEphemeralModuleImpressionCounterPref); + + if (PriceTrackingNotificationPromo::IsEnabled(price_tracking_promo_count)) { + all_cards_by_priority_.push_back( + std::make_unique<PriceTrackingNotificationPromo>( + price_tracking_promo_count)); + } + + int default_browser_promo_count = profile_prefs_->GetInteger( + kDefaultBrowserPromoEphemeralModuleImpressionCounterPref); + + std::optional<CardSelectionInfo::ShowResult> forced_result = + GetForcedEphemeralModuleShowResult(); + + // Determine the forced card identifier and label, if any. + TipIdentifier forced_identifier = TipIdentifier::kUnknown; + std::string_view forced_label; + + if (forced_result.has_value() && + forced_result.value().position == EphemeralHomeModuleRank::kTop) { + forced_label = forced_result.value().result_label.value(); + forced_identifier = TipIdentifierForOutputLabel(forced_label); + + if (forced_identifier != TipIdentifier::kUnknown) { + AddCardForTip(forced_identifier, all_cards_by_priority_, profile_prefs_); + } + } + + // Iterate through variation labels and add unique cards. + for (std::string_view variation_label : + base::SplitString(GetDefaultTipsExperimentTrain(), ",", + base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) { + TipIdentifier identifier = TipIdentifierForOutputLabel(variation_label); + + // Skip adding if: + // 1. The identifier is unknown. + // 2. It matches the forced identifier. + // 3. Both belong to the same "family" of Lens cards. + if (identifier == TipIdentifier::kUnknown || + identifier == forced_identifier || + (LensEphemeralModule::IsModuleLabel(variation_label) && + LensEphemeralModule::IsModuleLabel(forced_label))) { + continue; + } + + AddCardForTip(identifier, all_cards_by_priority_, profile_prefs_); + } + + if (SendTabNotificationPromo::IsEnabled(send_tab_promo_count)) { + all_cards_by_priority_.push_back( + std::make_unique<SendTabNotificationPromo>(send_tab_promo_count)); + } + + if (AppBundlePromoEphemeralModule::IsEnabled(app_bundle_promo_count)) { + all_cards_by_priority_.push_back( + std::make_unique<AppBundlePromoEphemeralModule>()); + } + + if (DefaultBrowserPromoEphemeralModule::IsEnabled( + default_browser_promo_count)) { + all_cards_by_priority_.push_back( + std::make_unique<DefaultBrowserPromoEphemeralModule>()); + } + + InitializeAfterAddingCards(); +} + +HomeModulesCardRegistryIOS::~HomeModulesCardRegistryIOS() = default; + +// static +void HomeModulesCardRegistryIOS::RegisterLocalStatePrefs( + PrefRegistrySimple* registry) { + // Local state prefs are used for the `AppBundleEphemeralModule` because this + // promo relates to app installations on the device level, meaning impressions + // should be tracked per-device rather than per profile. + registry->RegisterIntegerPref( + kAppBundlePromoEphemeralModuleImpressionCounterPref, 0); +} + +// static +void HomeModulesCardRegistryIOS::RegisterProfilePrefs( + PrefRegistrySimple* registry) { + registry->RegisterIntegerPref(kPriceTrackingPromoImpressionCounterPref, 0); + registry->RegisterIntegerPref(kSendTabPromoImpressionCounterPref, 0); + registry->RegisterIntegerPref( + kAddressBarPositionEphemeralModuleImpressionCounterPref, 0); + registry->RegisterIntegerPref( + kAutofillPasswordsEphemeralModuleImpressionCounterPref, 0); + registry->RegisterIntegerPref( + kEnhancedSafeBrowsingEphemeralModuleImpressionCounterPref, 0); + registry->RegisterIntegerPref( + kSavePasswordsEphemeralModuleImpressionCounterPref, 0); + registry->RegisterIntegerPref(kLensEphemeralModuleImpressionCounterPref, 0); + registry->RegisterBooleanPref( + kAddressBarPositionEphemeralModuleInteractedPref, false); + registry->RegisterBooleanPref(kAutofillPasswordsEphemeralModuleInteractedPref, + false); + registry->RegisterBooleanPref( + kEnhancedSafeBrowsingEphemeralModuleInteractedPref, false); + registry->RegisterBooleanPref(kSavePasswordsEphemeralModuleInteractedPref, + false); + registry->RegisterBooleanPref(kLensEphemeralModuleInteractedPref, false); + registry->RegisterBooleanPref( + kLensEphemeralModuleSearchVariationInteractedPref, false); + registry->RegisterBooleanPref(kLensEphemeralModuleShopVariationInteractedPref, + false); + registry->RegisterBooleanPref( + kLensEphemeralModuleTranslateVariationInteractedPref, false); + registry->RegisterIntegerPref( + kDefaultBrowserPromoEphemeralModuleImpressionCounterPref, 0); +} + +// static +bool HomeModulesCardRegistryIOS::IsEphemeralTipsModuleLabel( + std::string_view label) { + return AddressBarPositionEphemeralModule::IsModuleLabel(label) || + AutofillPasswordsEphemeralModule::IsModuleLabel(label) || + EnhancedSafeBrowsingEphemeralModule::IsModuleLabel(label) || + SavePasswordsEphemeralModule::IsModuleLabel(label) || + LensEphemeralModule::IsModuleLabel(label); +} + +void HomeModulesCardRegistryIOS::NotifyCardShown(const char* card_name) { + if (strcmp(card_name, kPriceTrackingNotificationPromo) == 0) { + int freshness_impression_count = + profile_prefs_->GetInteger(kPriceTrackingPromoImpressionCounterPref); + profile_prefs_->SetInteger(kPriceTrackingPromoImpressionCounterPref, + freshness_impression_count + 1); + } else if (strcmp(card_name, kAddressBarPositionEphemeralModule) == 0) { + int freshness_impression_count = profile_prefs_->GetInteger( + kAddressBarPositionEphemeralModuleImpressionCounterPref); + profile_prefs_->SetInteger( + kAddressBarPositionEphemeralModuleImpressionCounterPref, + freshness_impression_count + 1); + } else if (strcmp(card_name, kAutofillPasswordsEphemeralModule) == 0) { + int freshness_impression_count = profile_prefs_->GetInteger( + kAutofillPasswordsEphemeralModuleImpressionCounterPref); + profile_prefs_->SetInteger( + kAutofillPasswordsEphemeralModuleImpressionCounterPref, + freshness_impression_count + 1); + } else if (strcmp(card_name, kEnhancedSafeBrowsingEphemeralModule) == 0) { + int freshness_impression_count = profile_prefs_->GetInteger( + kEnhancedSafeBrowsingEphemeralModuleImpressionCounterPref); + profile_prefs_->SetInteger( + kEnhancedSafeBrowsingEphemeralModuleImpressionCounterPref, + freshness_impression_count + 1); + } else if (strcmp(card_name, kSavePasswordsEphemeralModule) == 0) { + int freshness_impression_count = profile_prefs_->GetInteger( + kSavePasswordsEphemeralModuleImpressionCounterPref); + profile_prefs_->SetInteger( + kSavePasswordsEphemeralModuleImpressionCounterPref, + freshness_impression_count + 1); + } else if (strcmp(card_name, kLensEphemeralModule) == 0 || + strcmp(card_name, kLensEphemeralModuleSearchVariation) == 0 || + strcmp(card_name, kLensEphemeralModuleShopVariation) == 0 || + strcmp(card_name, kLensEphemeralModuleTranslateVariation) == 0) { + int freshness_impression_count = + profile_prefs_->GetInteger(kLensEphemeralModuleImpressionCounterPref); + profile_prefs_->SetInteger(kLensEphemeralModuleImpressionCounterPref, + freshness_impression_count + 1); + } else if (strcmp(card_name, kSendTabNotificationPromo) == 0) { + int impression_count = + profile_prefs_->GetInteger(kSendTabPromoImpressionCounterPref); + profile_prefs_->SetInteger(kSendTabPromoImpressionCounterPref, + impression_count + 1); + } else if (strcmp(card_name, kAppBundlePromoEphemeralModule) == 0) { + int local_impression_count = local_state_prefs_->GetInteger( + kAppBundlePromoEphemeralModuleImpressionCounterPref); + local_state_prefs_->SetInteger( + kAppBundlePromoEphemeralModuleImpressionCounterPref, + local_impression_count + 1); + } else if (strcmp(card_name, kDefaultBrowserPromoEphemeralModule) == 0) { + int impression_count = profile_prefs_->GetInteger( + kDefaultBrowserPromoEphemeralModuleImpressionCounterPref); + profile_prefs_->SetInteger( + kDefaultBrowserPromoEphemeralModuleImpressionCounterPref, + impression_count + 1); + } +} + +void HomeModulesCardRegistryIOS::NotifyCardInteracted(const char* card_name) { + if (strcmp(card_name, kAddressBarPositionEphemeralModule) == 0) { + profile_prefs_->SetBoolean(kAddressBarPositionEphemeralModuleInteractedPref, + true); + } else if (strcmp(card_name, kAutofillPasswordsEphemeralModule) == 0) { + profile_prefs_->SetBoolean(kAutofillPasswordsEphemeralModuleInteractedPref, + true); + } else if (strcmp(card_name, kEnhancedSafeBrowsingEphemeralModule) == 0) { + profile_prefs_->SetBoolean( + kEnhancedSafeBrowsingEphemeralModuleInteractedPref, true); + } else if (strcmp(card_name, kSavePasswordsEphemeralModule) == 0) { + profile_prefs_->SetBoolean(kSavePasswordsEphemeralModuleInteractedPref, + true); + } else if (strcmp(card_name, kLensEphemeralModule) == 0) { + profile_prefs_->SetBoolean(kLensEphemeralModuleInteractedPref, true); + } else if (strcmp(card_name, kLensEphemeralModuleSearchVariation) == 0) { + profile_prefs_->SetBoolean( + kLensEphemeralModuleSearchVariationInteractedPref, true); + } else if (strcmp(card_name, kLensEphemeralModuleShopVariation) == 0) { + profile_prefs_->SetBoolean(kLensEphemeralModuleShopVariationInteractedPref, + true); + } else if (strcmp(card_name, kLensEphemeralModuleTranslateVariation) == 0) { + profile_prefs_->SetBoolean( + kLensEphemeralModuleTranslateVariationInteractedPref, true); + } +} + +} // namespace segmentation_platform::home_modules
diff --git a/components/segmentation_platform/embedder/home_modules/home_modules_card_registry_ios.h b/components/segmentation_platform/embedder/home_modules/home_modules_card_registry_ios.h new file mode 100644 index 0000000..8ced0d8 --- /dev/null +++ b/components/segmentation_platform/embedder/home_modules/home_modules_card_registry_ios.h
@@ -0,0 +1,45 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_HOME_MODULES_HOME_MODULES_CARD_REGISTRY_IOS_H_ +#define COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_HOME_MODULES_HOME_MODULES_CARD_REGISTRY_IOS_H_ + +#include <string_view> + +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h" + +class PrefRegistrySimple; +class PrefService; + +namespace segmentation_platform::home_modules { + +// The iOS-specific implementation of the HomeModulesCardRegistry. +class HomeModulesCardRegistryIOS : public HomeModulesCardRegistry { + public: + HomeModulesCardRegistryIOS(PrefService* profile_prefs, + PrefService* local_state_prefs); + ~HomeModulesCardRegistryIOS() override; + + HomeModulesCardRegistryIOS(const HomeModulesCardRegistryIOS&) = delete; + HomeModulesCardRegistryIOS& operator=(const HomeModulesCardRegistryIOS&) = + delete; + + // Registers all the profile prefs needed for the iOS ephemeral cards. + static void RegisterProfilePrefs(PrefRegistrySimple* registry); + + // Registers all the local state prefs needed for the iOS ephemeral cards. + static void RegisterLocalStatePrefs(PrefRegistrySimple* registry); + + // Returns `true` if the given `label` corresponds to any of the Tips + // ephemeral module classes on iOS. + static bool IsEphemeralTipsModuleLabel(std::string_view label); + + // `HomeModulesCardRegistry` overrides: + void NotifyCardShown(const char* card_name) override; + void NotifyCardInteracted(const char* card_name) override; +}; + +} // namespace segmentation_platform::home_modules + +#endif // COMPONENTS_SEGMENTATION_PLATFORM_EMBEDDER_HOME_MODULES_HOME_MODULES_CARD_REGISTRY_IOS_H_
diff --git a/components/segmentation_platform/embedder/home_modules/home_modules_card_registry_unittest.cc b/components/segmentation_platform/embedder/home_modules/home_modules_card_registry_unittest.cc index 6833282..b2a8b16b 100644 --- a/components/segmentation_platform/embedder/home_modules/home_modules_card_registry_unittest.cc +++ b/components/segmentation_platform/embedder/home_modules/home_modules_card_registry_unittest.cc
@@ -7,11 +7,10 @@ #include <algorithm> #include "base/test/scoped_feature_list.h" +#include "build/build_config.h" #include "components/commerce/core/commerce_feature_list.h" #include "components/prefs/testing_pref_service.h" -#include "components/segmentation_platform/embedder/home_modules/app_bundle_promo_ephemeral_module.h" #include "components/segmentation_platform/embedder/home_modules/constants.h" -#include "components/segmentation_platform/embedder/home_modules/default_browser_promo_ephemeral_module.h" #include "components/segmentation_platform/embedder/home_modules/test_utils.h" #include "components/segmentation_platform/embedder/home_modules/tips_manager/constants.h" #include "components/segmentation_platform/embedder/home_modules/tips_manager/signal_constants.h" @@ -21,11 +20,21 @@ #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +#if BUILDFLAG(IS_IOS) +#include "components/segmentation_platform/embedder/home_modules/app_bundle_promo_ephemeral_module.h" +#include "components/segmentation_platform/embedder/home_modules/default_browser_promo_ephemeral_module.h" +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_ios.h" +#elif BUILDFLAG(IS_ANDROID) +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.h" +#endif + namespace segmentation_platform::home_modules { using ::testing::Contains; using ::testing::Not; +// TODO(crbug.com/489045573): Split `HomeModulesCardRegistryTest` into +// platform-specific test suites. class HomeModulesCardRegistryTest : public testing::Test { public: HomeModulesCardRegistryTest() = default; @@ -52,8 +61,8 @@ // when its feature is enabled. #if BUILDFLAG(IS_IOS) TEST_F(HomeModulesCardRegistryTest, TestPriceTrackingNotificationPromoCard) { - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); ASSERT_EQ(9u, registry_->all_output_labels().size()); ASSERT_EQ(0u, registry_->get_label_index(kPlaceholderEphemeralModuleLabel)); @@ -73,8 +82,8 @@ // Tests that the Registry registers the TipsEphemeralModule cards when the // Tips (Magic Stack) is enabled. TEST_F(HomeModulesCardRegistryTest, TestTipsEphemeralModuleCards) { - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); ASSERT_EQ(9u, registry_->all_output_labels().size()); ASSERT_EQ(0u, registry_->get_label_index(kPlaceholderEphemeralModuleLabel)); @@ -106,8 +115,8 @@ {{send_tab_to_self::kSendTabIOSPushNotificationsWithMagicStackCardParam, "true"}}}}, {}); - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); ASSERT_EQ(9u, registry_->all_output_labels().size()); ASSERT_EQ(0u, registry_->get_label_index(kPlaceholderEphemeralModuleLabel)); @@ -132,8 +141,8 @@ // when its feature is enabled. TEST_F(HomeModulesCardRegistryTest, TestAppBundlePromoCard) { feature_list_.InitAndEnableFeature(features::kAppBundlePromoEphemeralCard); - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); // Check that the `kAppBundlePromoEphemeralModule` label is included in the // registry's card labels. EXPECT_THAT(registry_->all_output_labels(), @@ -158,8 +167,8 @@ // card when its feature is enabled. TEST_F(HomeModulesCardRegistryTest, TestDefaultBrowserPromoCard) { feature_list_.InitAndEnableFeature(features::kDefaultBrowserMagicStackIos); - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); // Check that the `kDefaultBrowserPromoEphemeralModule` label is included in // the registry's card labels. EXPECT_THAT(registry_->all_output_labels(), @@ -185,8 +194,8 @@ // Tests that the Registry registers the DefaultBrowserPromo card when its // feature is enabled. TEST_F(HomeModulesCardRegistryTest, TestDefaultBrowserPromoCardEnabled) { - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); EXPECT_THAT(registry_->all_output_labels(), Contains(kDefaultBrowserPromo)); EXPECT_GE(registry_->all_cards_input_size(), 3u); @@ -210,8 +219,8 @@ TEST_F(HomeModulesCardRegistryTest, TestDefaultBrowserPromoCardDisabled) { profile_pref_service_.SetUserPref(kDefaultBrowserPromoImpressionCounterPref, std::make_unique<base::Value>(4)); - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); EXPECT_THAT(registry_->all_output_labels(), Not(Contains(kDefaultBrowserPromo))); @@ -236,8 +245,8 @@ // Tests that the Registry registers the TabGroupPromo card when its feature is // enabled. TEST_F(HomeModulesCardRegistryTest, TestTabGroupPromoCardEnabled) { - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); EXPECT_THAT(registry_->all_output_labels(), Contains(kTabGroupPromo)); EXPECT_GE(registry_->all_cards_input_size(), 5u); @@ -261,8 +270,8 @@ TEST_F(HomeModulesCardRegistryTest, TestTabGroupPromoCardDisabled) { profile_pref_service_.SetUserPref(kTabGroupPromoImpressionCounterPref, std::make_unique<base::Value>(11)); - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); EXPECT_THAT(registry_->all_output_labels(), Not(Contains(kTabGroupPromo))); EXPECT_GE(registry_->all_cards_input_size(), 0u); @@ -285,21 +294,45 @@ // card, could send a notification when the card is shown once per session, // rather than every time it is displayed. TEST_F(HomeModulesCardRegistryTest, TestShouldNotifyCardShownPerSession) { - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); - const char* card_name_1 = "TabGroupPromo"; - const char* card_name_2 = "TabGroupSyncPromo"; - EXPECT_TRUE(registry_->ShouldNotifyCardShownPerSession(card_name_1)); - EXPECT_FALSE(registry_->ShouldNotifyCardShownPerSession(card_name_1)); - EXPECT_TRUE(registry_->ShouldNotifyCardShownPerSession(card_name_2)); - EXPECT_FALSE(registry_->ShouldNotifyCardShownPerSession(card_name_2)); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); + + const char* card_name_1 = kTabGroupPromo; + const char* card_name_2 = kTabGroupSyncPromo; + + // Verify initial pref values are 0 + EXPECT_EQ( + 0, profile_pref_service_.GetInteger(kTabGroupPromoImpressionCounterPref)); + EXPECT_EQ(0, profile_pref_service_.GetInteger( + kTabGroupSyncPromoImpressionCounterPref)); + + // First impression in the session should increment the counter. + registry_->NotifyCardShown(card_name_1); + EXPECT_EQ( + 1, profile_pref_service_.GetInteger(kTabGroupPromoImpressionCounterPref)); + + // Second impression in the same session should NOT increment the counter. + registry_->NotifyCardShown(card_name_1); + EXPECT_EQ( + 1, profile_pref_service_.GetInteger(kTabGroupPromoImpressionCounterPref)); + + // Test with a different card to ensure it's tracked independently. + registry_->NotifyCardShown(card_name_2); + EXPECT_EQ(1, profile_pref_service_.GetInteger( + kTabGroupSyncPromoImpressionCounterPref)); + + // Second impression for the second card in the same session should NOT + // increment. + registry_->NotifyCardShown(card_name_2); + EXPECT_EQ(1, profile_pref_service_.GetInteger( + kTabGroupSyncPromoImpressionCounterPref)); } // Tests that the Registry registers the TabGroupSyncPromo card when its feature // is enabled. TEST_F(HomeModulesCardRegistryTest, TestTabGroupSyncPromoCardEnabled) { - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); EXPECT_THAT(registry_->all_output_labels(), Contains(kTabGroupSyncPromo)); EXPECT_GE(registry_->all_cards_input_size(), 3u); @@ -321,8 +354,8 @@ TEST_F(HomeModulesCardRegistryTest, TestTabGroupSyncPromoCardDisabled) { profile_pref_service_.SetUserPref(kTabGroupSyncPromoImpressionCounterPref, std::make_unique<base::Value>(11)); - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); EXPECT_THAT(registry_->all_output_labels(), Not(Contains(kTabGroupSyncPromo))); @@ -343,8 +376,8 @@ // Tests that the Registry registers the QuickDeletePromo card when its feature // is enabled. TEST_F(HomeModulesCardRegistryTest, TestQuickDeletePromoCardEnabled) { - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); EXPECT_THAT(registry_->all_output_labels(), Contains(kQuickDeletePromo)); EXPECT_GE(registry_->all_cards_input_size(), 5u); @@ -369,8 +402,8 @@ TEST_F(HomeModulesCardRegistryTest, TestQuickDeletePromoCardDisabled) { profile_pref_service_.SetUserPref(kQuickDeletePromoImpressionCounterPref, std::make_unique<base::Value>(11)); - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); EXPECT_THAT(registry_->all_output_labels(), Not(Contains(kQuickDeletePromo))); EXPECT_GE(registry_->all_cards_input_size(), 0u); @@ -395,8 +428,8 @@ // feature is enabled. TEST_F(HomeModulesCardRegistryTest, TestAuxiliarySearchPromoCardEnabled) { feature_list_.InitWithFeatures({features::kAndroidAppIntegrationModule}, {}); - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); EXPECT_THAT(registry_->all_output_labels(), Contains(kAuxiliarySearch)); EXPECT_GE(registry_->all_cards_input_size(), 1u); @@ -417,8 +450,8 @@ feature_list_.InitWithFeatures({features::kAndroidAppIntegrationModule}, {}); profile_pref_service_.SetUserPref(kAuxiliarySearchPromoImpressionCounterPref, std::make_unique<base::Value>(4)); - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); EXPECT_THAT(registry_->all_output_labels(), Not(Contains(kAuxiliarySearch))); EXPECT_GE(registry_->all_cards_input_size(), 0u); @@ -436,8 +469,8 @@ // Tests that the Registry registers the HistorySyncPromo card when its feature // is enabled. TEST_F(HomeModulesCardRegistryTest, TestHistorySyncPromoCardEnabled) { - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); EXPECT_THAT(registry_->all_output_labels(), Contains(kHistorySyncPromo)); EXPECT_GE(registry_->all_cards_input_size(), 3u); @@ -459,8 +492,8 @@ TEST_F(HomeModulesCardRegistryTest, TestHistorySyncPromoCardDisabled) { profile_pref_service_.SetUserPref(kHistorySyncPromoImpressionCounterPref, std::make_unique<base::Value>(11)); - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); EXPECT_THAT(registry_->all_output_labels(), Not(Contains(kHistorySyncPromo))); EXPECT_GE(registry_->all_cards_input_size(), 0u); @@ -481,8 +514,8 @@ // feature is enabled. TEST_F(HomeModulesCardRegistryTest, TestTipsNotificationsPromoCardEnabled) { feature_list_.InitWithFeatures({features::kAndroidTipsNotifications}, {}); - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); EXPECT_THAT(registry_->all_output_labels(), Contains(kTipsNotificationsPromo)); @@ -507,8 +540,8 @@ profile_pref_service_.SetUserPref( kTipsNotificationsPromoImpressionCounterPref, std::make_unique<base::Value>(11)); - registry_ = std::make_unique<HomeModulesCardRegistry>( - &profile_pref_service_, &local_state_pref_service_); + registry_ = HomeModulesCardRegistry::Create(&profile_pref_service_, + &local_state_pref_service_); EXPECT_THAT(registry_->all_output_labels(), Not(Contains(kTipsNotificationsPromo)));
diff --git a/components/segmentation_platform/embedder/home_modules/quick_delete_promo.cc b/components/segmentation_platform/embedder/home_modules/quick_delete_promo.cc index ce6c787..235ccc9a 100644 --- a/components/segmentation_platform/embedder/home_modules/quick_delete_promo.cc +++ b/components/segmentation_platform/embedder/home_modules/quick_delete_promo.cc
@@ -8,7 +8,7 @@ #include "components/commerce/core/commerce_feature_list.h" #include "components/segmentation_platform/embedder/home_modules/constants.h" #include "components/segmentation_platform/embedder/home_modules/ephemeral_module_utils.h" -#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h" +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.h" #include "components/segmentation_platform/public/features.h" #include "components/segmentation_platform/public/proto/model_metadata.pb.h"
diff --git a/components/segmentation_platform/embedder/home_modules/quick_delete_promo_unittest.cc b/components/segmentation_platform/embedder/home_modules/quick_delete_promo_unittest.cc index d3127a7..605f38c1 100644 --- a/components/segmentation_platform/embedder/home_modules/quick_delete_promo_unittest.cc +++ b/components/segmentation_platform/embedder/home_modules/quick_delete_promo_unittest.cc
@@ -7,7 +7,7 @@ #include "components/prefs/testing_pref_service.h" #include "components/segmentation_platform/embedder/home_modules/card_selection_signals.h" #include "components/segmentation_platform/embedder/home_modules/constants.h" -#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h" +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.h" #include "components/segmentation_platform/embedder/home_modules/test_utils.h" #include "testing/gtest/include/gtest/gtest.h"
diff --git a/components/segmentation_platform/embedder/home_modules/tab_group_promo.cc b/components/segmentation_platform/embedder/home_modules/tab_group_promo.cc index e8b9f12..b49a920 100644 --- a/components/segmentation_platform/embedder/home_modules/tab_group_promo.cc +++ b/components/segmentation_platform/embedder/home_modules/tab_group_promo.cc
@@ -8,7 +8,7 @@ #include "components/commerce/core/commerce_feature_list.h" #include "components/segmentation_platform/embedder/home_modules/constants.h" #include "components/segmentation_platform/embedder/home_modules/ephemeral_module_utils.h" -#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h" +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.h" #include "components/segmentation_platform/public/features.h" #include "components/segmentation_platform/public/proto/model_metadata.pb.h"
diff --git a/components/segmentation_platform/embedder/home_modules/tab_group_promo_unittest.cc b/components/segmentation_platform/embedder/home_modules/tab_group_promo_unittest.cc index bb9e63f..76585a6 100644 --- a/components/segmentation_platform/embedder/home_modules/tab_group_promo_unittest.cc +++ b/components/segmentation_platform/embedder/home_modules/tab_group_promo_unittest.cc
@@ -7,7 +7,7 @@ #include "components/prefs/testing_pref_service.h" #include "components/segmentation_platform/embedder/home_modules/card_selection_signals.h" #include "components/segmentation_platform/embedder/home_modules/constants.h" -#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h" +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.h" #include "components/segmentation_platform/embedder/home_modules/test_utils.h" #include "testing/gtest/include/gtest/gtest.h"
diff --git a/components/segmentation_platform/embedder/home_modules/tab_group_sync_promo.cc b/components/segmentation_platform/embedder/home_modules/tab_group_sync_promo.cc index b05767d7..2d32d35f 100644 --- a/components/segmentation_platform/embedder/home_modules/tab_group_sync_promo.cc +++ b/components/segmentation_platform/embedder/home_modules/tab_group_sync_promo.cc
@@ -8,7 +8,7 @@ #include "components/commerce/core/commerce_feature_list.h" #include "components/segmentation_platform/embedder/home_modules/constants.h" #include "components/segmentation_platform/embedder/home_modules/ephemeral_module_utils.h" -#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h" +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.h" #include "components/segmentation_platform/public/features.h" #include "components/segmentation_platform/public/proto/model_metadata.pb.h"
diff --git a/components/segmentation_platform/embedder/home_modules/tab_group_sync_promo_unittest.cc b/components/segmentation_platform/embedder/home_modules/tab_group_sync_promo_unittest.cc index 6a61277..f89e449 100644 --- a/components/segmentation_platform/embedder/home_modules/tab_group_sync_promo_unittest.cc +++ b/components/segmentation_platform/embedder/home_modules/tab_group_sync_promo_unittest.cc
@@ -7,7 +7,7 @@ #include "components/prefs/testing_pref_service.h" #include "components/segmentation_platform/embedder/home_modules/card_selection_signals.h" #include "components/segmentation_platform/embedder/home_modules/constants.h" -#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h" +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.h" #include "components/segmentation_platform/embedder/home_modules/test_utils.h" #include "testing/gtest/include/gtest/gtest.h"
diff --git a/components/segmentation_platform/embedder/home_modules/tips_notifications_promo.cc b/components/segmentation_platform/embedder/home_modules/tips_notifications_promo.cc index 3774025c..0b9fbe77 100644 --- a/components/segmentation_platform/embedder/home_modules/tips_notifications_promo.cc +++ b/components/segmentation_platform/embedder/home_modules/tips_notifications_promo.cc
@@ -8,7 +8,7 @@ #include "components/commerce/core/commerce_feature_list.h" #include "components/segmentation_platform/embedder/home_modules/constants.h" #include "components/segmentation_platform/embedder/home_modules/ephemeral_module_utils.h" -#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h" +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.h" #include "components/segmentation_platform/public/features.h" #include "components/segmentation_platform/public/proto/model_metadata.pb.h"
diff --git a/components/segmentation_platform/embedder/home_modules/tips_notifications_promo_unittest.cc b/components/segmentation_platform/embedder/home_modules/tips_notifications_promo_unittest.cc index dc54642..5999ced7 100644 --- a/components/segmentation_platform/embedder/home_modules/tips_notifications_promo_unittest.cc +++ b/components/segmentation_platform/embedder/home_modules/tips_notifications_promo_unittest.cc
@@ -7,7 +7,7 @@ #include "components/prefs/testing_pref_service.h" #include "components/segmentation_platform/embedder/home_modules/card_selection_signals.h" #include "components/segmentation_platform/embedder/home_modules/constants.h" -#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h" +#include "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_android.h" #include "components/segmentation_platform/embedder/home_modules/test_utils.h" #include "testing/gtest/include/gtest/gtest.h"
diff --git a/components/sessions/core/tab_restore_service_helper.h b/components/sessions/core/tab_restore_service_helper.h index e160fb75..e1661f46 100644 --- a/components/sessions/core/tab_restore_service_helper.h +++ b/components/sessions/core/tab_restore_service_helper.h
@@ -243,7 +243,12 @@ // historical tab. bool restoring_; - base::ObserverList<TabRestoreServiceObserver>::Unchecked observer_list_; + // TODO(crbug.com/484371187): Investigate if reentrancy can be removed. + base::ObserverList< + TabRestoreServiceObserver, + /*check_empty=*/false, + base::ObserverListReentrancyPolicy::kAllowReentrancyUntriaged>::Unchecked + observer_list_; // Set of contexts that we've received a BrowserClosing method for but no // corresponding BrowserClosed. We cache the set of contexts closing to
diff --git a/components/sync_preferences/synced_set_up/OWNERS b/components/sync_preferences/synced_set_up/OWNERS index 4c158f6e..23d058a 100644 --- a/components/sync_preferences/synced_set_up/OWNERS +++ b/components/sync_preferences/synced_set_up/OWNERS
@@ -1,2 +1,3 @@ bwwilliams@google.com +ericekey@google.com jhimawan@google.com
diff --git a/components/thin_webview/internal/java/src/org/chromium/components/thinwebview/internal/ThinWebViewImpl.java b/components/thin_webview/internal/java/src/org/chromium/components/thinwebview/internal/ThinWebViewImpl.java index 73af936..61ec6d5d 100644 --- a/components/thin_webview/internal/java/src/org/chromium/components/thinwebview/internal/ThinWebViewImpl.java +++ b/components/thin_webview/internal/java/src/org/chromium/components/thinwebview/internal/ThinWebViewImpl.java
@@ -4,6 +4,7 @@ package org.chromium.components.thinwebview.internal; +import android.app.Activity; import android.content.Context; import android.view.View; import android.view.ViewGroup; @@ -47,6 +48,7 @@ /** * Creates a {@link ThinWebViewImpl} backed by a {@link Surface}. + * * @param context The Context to create this view. * @param constraints A set of constraints associated with this view. * @param intentRequestTracker The {@link IntentRequestTracker} of the current activity. @@ -56,10 +58,11 @@ ThinWebViewConstraints constraints, IntentRequestTracker intentRequestTracker) { super(context); - if (ContextUtils.activityFromContext(context) != null) { + Activity activity = ContextUtils.activityFromContext(context); + if (activity != null) { mWindowAndroid = - new ActivityWindowAndroid( - context, + ActivityWindowAndroid.create( + activity, /* listenToActivityState= */ true, intentRequestTracker, /* insetObserver= */ null,
diff --git a/components/viz/service/frame_sinks/video_capture/video_capture_overlay.cc b/components/viz/service/frame_sinks/video_capture/video_capture_overlay.cc index a45d32a2..63b08a8 100644 --- a/components/viz/service/frame_sinks/video_capture/video_capture_overlay.cc +++ b/components/viz/service/frame_sinks/video_capture/video_capture_overlay.cc
@@ -567,9 +567,11 @@ if (color_space_.GetMatrixID() != gfx::ColorSpace::MatrixID::RGB) { color_space_.ToSkYUVColorSpace(&colors_yuv_cs); } - skia::ConvertRGBAToOrFromYUVA(scaled_image.pixmap(), - kIdentity_SkYUVColorSpace, colors_pm, - colors_yuv_cs); + skia::ConvertRGBAToYUVA( + scaled_image.pixmap(), + SkYUVAInfo(colors_info.dimensions(), SkYUVAInfo::PlaneConfig::kYUVA, + SkYUVAInfo::Subsampling::k444, colors_yuv_cs), + {colors_pm}); } switch (format_) {
diff --git a/components/viz/test/gl_scaler_test_util.cc b/components/viz/test/gl_scaler_test_util.cc index b28cd8b1..81b89c3 100644 --- a/components/viz/test/gl_scaler_test_util.cc +++ b/components/viz/test/gl_scaler_test_util.cc
@@ -189,8 +189,11 @@ // static void GLScalerTestUtil::ConvertRGBABitmapToYUV(SkBitmap* image) { - skia::ConvertRGBAToOrFromYUVA(image->pixmap(), kIdentity_SkYUVColorSpace, - image->pixmap(), kRec709_SkYUVColorSpace); + skia::ConvertRGBAToYUVA( + image->pixmap(), + SkYUVAInfo(image->dimensions(), SkYUVAInfo::PlaneConfig::kYUVA, + SkYUVAInfo::Subsampling::k444, kRec709_SkYUVColorSpace), + {image->pixmap()}); } // static
diff --git a/components/webapps/browser/android/app_banner_manager_android.cc b/components/webapps/browser/android/app_banner_manager_android.cc index 558d0b1c..ccecca7 100644 --- a/components/webapps/browser/android/app_banner_manager_android.cc +++ b/components/webapps/browser/android/app_banner_manager_android.cc
@@ -95,8 +95,8 @@ AppBannerManagerAndroid::AppBannerManagerAndroid( content::WebContents* web_contents, std::unique_ptr<ChromeDelegate> delegate) - : AppBannerManager(this, web_contents), - content::WebContentsUserData<AppBannerManagerAndroid>(*web_contents), + : content::WebContentsUserData<AppBannerManagerAndroid>(*web_contents), + app_banner_manager_(AppBannerManager::Create(this, web_contents)), delegate_(std::move(delegate)) { CreateJavaBannerManager(web_contents); } @@ -124,11 +124,11 @@ } bool AppBannerManagerAndroid::IsRunningForTesting(JNIEnv* env) { - return IsRunning(); + return app_banner_manager_->IsRunningForTesting(); } int AppBannerManagerAndroid::GetPipelineStatusForTesting(JNIEnv* env) { - return static_cast<int>(state()); + return static_cast<int>(app_banner_manager_->state()); } int AppBannerManagerAndroid::GetBadgeStatusForTesting(JNIEnv* env) { @@ -147,7 +147,8 @@ const JavaRef<jstring>& jicon_url) { // If the state isn't fetching native data, that means the page must have // navigated or reset in some way. - if (state() != State::FETCHING_NATIVE_DATA) { + if (app_banner_manager_->state() != + AppBannerManager::State::FETCHING_NATIVE_DATA) { return; } if (request_id != current_native_request_id_) { @@ -185,14 +186,15 @@ void AppBannerManagerAndroid::ShowBannerFromBadge( const InstallBannerConfig& config) { - ShowBannerUi(InstallableMetrics::GetInstallSource( - web_contents(), InstallTrigger::AMBIENT_BADGE), - config); + ShowBannerUi( + InstallableMetrics::GetInstallSource(app_banner_manager_->web_contents(), + InstallTrigger::AMBIENT_BADGE), + config); // Close our bindings to ensure that any existing beforeinstallprompt events // cannot trigger add to home screen (which would cause a crash). If the // banner is dismissed, the event will be resent. - ResetBindings(); + app_banner_manager_->ResetBindings(); } // static @@ -339,7 +341,8 @@ if (!current_config || install_config.web_app_data.manifest_id != current_config->web_app_data.manifest_id) { // TODO(https://crbug.com/324322110): remove once crash understood. - DUMP_WILL_BE_CHECK(false) << "Pipeline state:" << static_cast<int>(state()); + DUMP_WILL_BE_CHECK(false) + << "Pipeline state:" << static_cast<int>(app_banner_manager_->state()); return; } @@ -369,8 +372,9 @@ base::BindOnce(&AppBannerManagerAndroid::CreateAddToHomescreenParams, install_config, global_native_java_app_data) .Then(base::BindOnce( - &PwaBottomSheetController::MaybeShow, web_contents(), - install_config.web_app_data, /*expand_sheet=*/false, + &PwaBottomSheetController::MaybeShow, + app_banner_manager_->web_contents(), install_config.web_app_data, + /*expand_sheet=*/false, base::BindRepeating(&AppBannerManagerAndroid::OnInstallEvent, GetAndroidWeakPtr(), install_config.validated_url)))); @@ -378,7 +382,7 @@ void AppBannerManagerAndroid::ShowBannerUi(WebappInstallSource install_source, const InstallBannerConfig& config) { - content::WebContents* contents = web_contents(); + content::WebContents* contents = app_banner_manager_->web_contents(); DCHECK(contents); JNIEnv* env = AttachCurrentThread(); @@ -392,7 +396,7 @@ config, ScopedJavaGlobalRef<jobject>(env, native_java_app_data), install_source); was_shown = AddToHomescreenCoordinator::ShowForAppBanner( - weak_factory_.GetWeakPtr(), std::move(a2hs_params), + app_banner_manager_->GetWeakPtr(), std::move(a2hs_params), base::BindRepeating(&AppBannerManagerAndroid::OnInstallEvent, weak_factory_.GetWeakPtr(), config.validated_url)); } @@ -408,12 +412,15 @@ if (was_shown) { if (native_java_app_data.is_null()) { - ReportStatus(InstallableStatusCode::SHOWING_WEB_APP_BANNER); + app_banner_manager_->ReportStatus( + InstallableStatusCode::SHOWING_WEB_APP_BANNER); } else { - ReportStatus(InstallableStatusCode::SHOWING_NATIVE_APP_BANNER); + app_banner_manager_->ReportStatus( + InstallableStatusCode::SHOWING_NATIVE_APP_BANNER); } } else { - ReportStatus(InstallableStatusCode::FAILED_TO_CREATE_BANNER); + app_banner_manager_->ReportStatus( + InstallableStatusCode::FAILED_TO_CREATE_BANNER); } } @@ -446,17 +453,19 @@ a2hs_params.install_source == WebappInstallSource::MENU_CUSTOM_TAB)) { switch (event) { case AddToHomescreenEvent::INSTALL_REQUEST_FINISHED: - SendBannerAccepted(); - OnInstall(a2hs_params.shortcut_info->display, - /*set_current_web_app_not_installable=*/false); + app_banner_manager_->SendBannerAccepted(); + app_banner_manager_->OnInstall( + a2hs_params.shortcut_info->display, + /*set_current_web_app_not_installable=*/false); break; case AddToHomescreenEvent::UI_CANCELLED: // Collapsing the bottom sheet installer UI does not count as // UI_CANCELLED as it is still visible to the user and they can expand // it later. - SendBannerDismissed(); + app_banner_manager_->SendBannerDismissed(); AppBannerSettingsHelper::RecordBannerDismissEvent( - web_contents(), a2hs_params.shortcut_info->url.spec()); + app_banner_manager_->web_contents(), + a2hs_params.shortcut_info->url.spec()); break; default: break; @@ -495,7 +504,8 @@ case AddToHomescreenParams::AppType::TWA: TrackUserResponse(USER_RESPONSE_WEB_APP_ACCEPTED); AppBannerSettingsHelper::RecordBannerInstallEvent( - web_contents(), a2hs_params.shortcut_info->url.spec()); + app_banner_manager_->web_contents(), + a2hs_params.shortcut_info->url.spec()); break; default: // a2hs_params should be the one created by @@ -522,10 +532,11 @@ break; case AddToHomescreenEvent::INSTALL_REQUEST_FINISHED: - SendBannerAccepted(); + app_banner_manager_->SendBannerAccepted(); if (a2hs_params.app_type != AddToHomescreenParams::AppType::NATIVE) { - OnInstall(a2hs_params.shortcut_info->display, - /*set_current_web_app_not_installable=*/false); + app_banner_manager_->OnInstall( + a2hs_params.shortcut_info->display, + /*set_current_web_app_not_installable=*/false); } break; @@ -535,8 +546,9 @@ case AddToHomescreenEvent::UI_SHOWN: AppBannerSettingsHelper::RecordBannerEvent( - web_contents(), validated_url, identifier, - AppBannerSettingsHelper::APP_BANNER_EVENT_DID_SHOW, GetCurrentTime()); + app_banner_manager_->web_contents(), validated_url, identifier, + AppBannerSettingsHelper::APP_BANNER_EVENT_DID_SHOW, + app_banner_manager_->GetCurrentTime()); TrackDisplayEvent(a2hs_params.app_type == AddToHomescreenParams::AppType::NATIVE ? DISPLAY_EVENT_NATIVE_APP_BANNER_CREATED @@ -546,16 +558,18 @@ case AddToHomescreenEvent::UI_CANCELLED: TrackDismissEvent(DISMISS_EVENT_DISMISSED); - SendBannerDismissed(); + app_banner_manager_->SendBannerDismissed(); if (a2hs_params.app_type == AddToHomescreenParams::AppType::NATIVE) { DCHECK(!a2hs_params.native_app_package_name.empty()); TrackUserResponse(USER_RESPONSE_NATIVE_APP_DISMISSED); AppBannerSettingsHelper::RecordBannerDismissEvent( - web_contents(), a2hs_params.native_app_package_name); + app_banner_manager_->web_contents(), + a2hs_params.native_app_package_name); } else { TrackUserResponse(USER_RESPONSE_WEB_APP_DISMISSED); AppBannerSettingsHelper::RecordBannerDismissEvent( - web_contents(), a2hs_params.shortcut_info->url.spec()); + app_banner_manager_->web_contents(), + a2hs_params.shortcut_info->url.spec()); } break; } @@ -680,7 +694,7 @@ install_source); return PwaBottomSheetController::MaybeShow( - web_contents(), web_app_data, expand_sheet, + app_banner_manager_->web_contents(), web_app_data, expand_sheet, base::BindRepeating(&AppBannerManagerAndroid::OnInstallEvent, AppBannerManagerAndroid::GetAndroidWeakPtr(), config.validated_url), @@ -719,9 +733,8 @@ JNI_AppBannerManager_GetJavaBannerManagerForWebContents( JNIEnv* env, const JavaRef<jobject>& java_web_contents) { - auto* manager = - static_cast<AppBannerManagerAndroid*>(AppBannerManager::FromWebContents( - content::WebContents::FromJavaWebContents(java_web_contents))); + auto* manager = AppBannerManagerAndroid::FromWebContents( + content::WebContents::FromJavaWebContents(java_web_contents)); return manager ? manager->GetJavaBannerManager() : base::android::ScopedJavaLocalRef<jobject>(); } @@ -740,9 +753,8 @@ static bool JNI_AppBannerManager_IsProbablyPromotable( JNIEnv* env, const base::android::JavaRef<jobject>& java_web_contents) { - auto* manager = - static_cast<AppBannerManagerAndroid*>(AppBannerManager::FromWebContents( - content::WebContents::FromJavaWebContents(java_web_contents))); + auto* manager = AppBannerManager::FromWebContents( + content::WebContents::FromJavaWebContents(java_web_contents)); if (!manager) { return false; }
diff --git a/components/webapps/browser/android/app_banner_manager_android.h b/components/webapps/browser/android/app_banner_manager_android.h index 99423be2..13a0d3f1 100644 --- a/components/webapps/browser/android/app_banner_manager_android.h +++ b/components/webapps/browser/android/app_banner_manager_android.h
@@ -61,8 +61,7 @@ // TODO(crbug.com/40730613): remove remaining Chrome-specific functionality and // move to //components/webapps. class AppBannerManagerAndroid - : public AppBannerManager, - public AppBannerManager::Delegate, + : public AppBannerManager::Delegate, public content::WebContentsUserData<AppBannerManagerAndroid> { public: class ChromeDelegate { @@ -137,9 +136,17 @@ WebappInstallSource install_source, const InstallBannerConfig& data); + std::optional<InstallBannerConfig> GetCurrentBannerConfig() const { + return app_banner_manager_->GetCurrentBannerConfig(); + } + // AppBannerManager::Delegate override: void OnMlInstallPrediction(std::string result_label) override; + AppBannerManager* app_banner_manager() const { + return app_banner_manager_.get(); + } + protected: friend class content::WebContentsUserData<AppBannerManagerAndroid>; @@ -231,6 +238,9 @@ base::android::ScopedJavaLocalRef<jobject> GetJavaBannerManager( JNIEnv* env) const; + + std::unique_ptr<AppBannerManager> app_banner_manager_; + const std::unique_ptr<ChromeDelegate> delegate_; // A weak reference to the Java object. The Java object will be kept alive by
diff --git a/components/webapps/browser/android/bottomsheet/pwa_bottom_sheet_controller.cc b/components/webapps/browser/android/bottomsheet/pwa_bottom_sheet_controller.cc index ea63ce1..7a7bc3dd 100644 --- a/components/webapps/browser/android/bottomsheet/pwa_bottom_sheet_controller.cc +++ b/components/webapps/browser/android/bottomsheet/pwa_bottom_sheet_controller.cc
@@ -61,8 +61,8 @@ int install_trigger) { content::WebContents* web_contents = content::WebContents::FromJavaWebContents(jweb_contents); - auto* app_banner_manager = static_cast<AppBannerManagerAndroid*>( - WebappsClient::Get()->GetAppBannerManager(web_contents)); + auto* app_banner_manager = + AppBannerManagerAndroid::FromWebContents(web_contents); std::optional<InstallBannerConfig> install_config = app_banner_manager->GetCurrentBannerConfig();
diff --git a/components/webapps/browser/banners/app_banner_manager.cc b/components/webapps/browser/banners/app_banner_manager.cc index dcd37d7e3..14e0978 100644 --- a/components/webapps/browser/banners/app_banner_manager.cc +++ b/components/webapps/browser/banners/app_banner_manager.cc
@@ -284,6 +284,13 @@ return receiver_.is_bound(); } +// static +std::unique_ptr<AppBannerManager> AppBannerManager::Create( + Delegate* delegate, + content::WebContents* web_contents) { + return base::WrapUnique(new AppBannerManager(delegate, web_contents)); +} + AppBannerManager::AppBannerManager(Delegate* delegate, content::WebContents* web_contents) : content::WebContentsObserver(web_contents), @@ -798,8 +805,7 @@ manager_ = nullptr; } - -bool AppBannerManager::IsRunning() const { +bool AppBannerManager::IsRunningForTesting() const { switch (state_) { case State::INACTIVE: case State::PENDING_PROMPT_CANCELED:
diff --git a/components/webapps/browser/banners/app_banner_manager.h b/components/webapps/browser/banners/app_banner_manager.h index e537a8d8..260b5c54 100644 --- a/components/webapps/browser/banners/app_banner_manager.h +++ b/components/webapps/browser/banners/app_banner_manager.h
@@ -59,10 +59,9 @@ // native app banner if requested). The second call completes the checking for a // web app banner (checking manifest validity, service worker, and icon). // -// TODO(crbug.com/41440485): Refactor this into several simpler classes, -// and remove all inheritance. -class AppBannerManager : public content::WebContentsObserver, - public blink::mojom::AppBannerService { +// TODO(crbug.com/41440485): Refactor this into several simpler classes. +class AppBannerManager final : public content::WebContentsObserver, + public blink::mojom::AppBannerService { public: class Observer : public base::CheckedObserver { public: @@ -209,6 +208,10 @@ // |web_contents|. static AppBannerManager* FromWebContents(content::WebContents* web_contents); + static std::unique_ptr<AppBannerManager> Create( + AppBannerManager::Delegate* delegate, + content::WebContents* web_contents); + ~AppBannerManager() override; AppBannerManager(const AppBannerManager&) = delete; AppBannerManager& operator=(const AppBannerManager&) = delete; @@ -304,14 +307,25 @@ void RecheckInstallabilityForLoadedPage(); - protected: - AppBannerManager(AppBannerManager::Delegate* delegate, - content::WebContents* web_contents); - ~AppBannerManager() override; - - void PostInstallableWebAppCheckValidation(const bool does_conflict); + bool IsRunningForTesting() const; // TODO(http://crbug.com/322342499): Make this private. + State state() const { return state_; } + + // Voids all outstanding service pointers. + // TODO(http://crbug.com/322342499): Make this private. + void ResetBindings(); + + // Reports |code| via a UMA histogram or logs it to the console. + // TODO(http://crbug.com/322342499): Figure out if this should be private, + // currently AppBannerManagerAndroid use it. + void ReportStatus(InstallableStatusCode code); + + private: + AppBannerManager(AppBannerManager::Delegate* delegate, + content::WebContents* web_contents); + void PostInstallableWebAppCheckValidation(const bool does_conflict); + enum class UrlType { // This url & page should be considered for installability & promotability. kValidForBanner, @@ -322,29 +336,6 @@ kInvalidPrimaryFrameUrl, }; - // content::WebContentsObserver override: - // TODO(http://crbug.com/322342499): Make this private with the rest of the - // web contents observer overrides. - void DidFinishLoad(content::RenderFrameHost* render_frame_host, - const GURL& validated_url) override; - void DidFailLoad(content::RenderFrameHost* render_frame_host, - const GURL& validated_url, - int error_code) override; - - // TODO(http://crbug.com/322342499): Make this private. - State state() const { return state_; } - - // Voids all outstanding service pointers. - // TODO(http://crbug.com/322342499): Make this private. - void ResetBindings(); - - // TODO(http://crbug.com/322342499): Make this private. - bool IsRunning() const; - - // Reports |code| via a UMA histogram or logs it to the console. - void ReportStatus(InstallableStatusCode code); - - private: void RequestAppBanner(); void UpdateState(State state); @@ -416,6 +407,11 @@ const content::MediaPlayerId& id, WebContentsObserver::MediaStoppedReason reason) override; void WebContentsDestroyed() override; + void DidFinishLoad(content::RenderFrameHost* render_frame_host, + const GURL& validated_url) override; + void DidFailLoad(content::RenderFrameHost* render_frame_host, + const GURL& validated_url, + int error_code) override; // Subclass accessors for private fields which should not be changed outside // this class.
diff --git a/content/browser/code_cache/generated_code_cache_context.cc b/content/browser/code_cache/generated_code_cache_context.cc index 46f052c..29ecc0f 100644 --- a/content/browser/code_cache/generated_code_cache_context.cc +++ b/content/browser/code_cache/generated_code_cache_context.cc
@@ -8,7 +8,6 @@ #include <memory> #include <optional> #include <string> -#include <string_view> #include "base/containers/span.h" #include "base/feature_list.h" @@ -237,7 +236,7 @@ void GeneratedCodeCacheContext::InsertIntoPersistentCacheCollection( const std::string& context_key, - std::string_view url, + base::span<const uint8_t> resource_key, base::span<const uint8_t> content, persistent_cache::EntryMetadata metadata) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); @@ -246,15 +245,8 @@ return; } - // Since `content` is coming in through mojo it's important to make sure that - // it's copied so it cannot be modified racily. This happens implicitly - // because of the way the SQLite backend (the only backend available - // currently) of PersistentCache stores data through the BLOB type. - // - // TODO(crbug.com/377475540): Make an explicit copy here once PersistentCache - // handles taking ownership of the memory passed in. RETURN_IF_ERROR(persistent_cache_collection_->Insert( - context_key, base::as_byte_span(url), content, metadata), + context_key, resource_key, content, metadata), [](persistent_cache::TransactionError error) { // TODO(crbug.com/374930286): Handle or at least address // permanent errors. @@ -265,7 +257,7 @@ std::optional<GeneratedCodeCacheContext::MetadataAndContent> GeneratedCodeCacheContext::FindInPersistentCacheCollection( const std::string& context_key, - std::string_view url) { + base::span<const uint8_t> resource_key) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (!persistent_cache_collection_) { @@ -281,18 +273,17 @@ return base::span(content_buffer); }; - ASSIGN_OR_RETURN( - std::optional<persistent_cache::EntryMetadata> metadata, - persistent_cache_collection_->Find(context_key, base::as_byte_span(url), - std::move(buffer_provider)), - // An adapter that is invoked on error. Its return value - // percolates up out of this function. - [](persistent_cache::TransactionError error) - -> std::optional<MetadataAndContent> { - // TODO(crbug.com/374930286): Handle or at least address - // permanent errors. - return std::nullopt; - }); + ASSIGN_OR_RETURN(std::optional<persistent_cache::EntryMetadata> metadata, + persistent_cache_collection_->Find( + context_key, resource_key, std::move(buffer_provider)), + // An adapter that is invoked on error. Its return value + // percolates up out of this function. + [](persistent_cache::TransactionError error) + -> std::optional<MetadataAndContent> { + // TODO(crbug.com/374930286): Handle or at least address + // permanent errors. + return std::nullopt; + }); if (!metadata.has_value()) { return std::nullopt; // Cache miss.
diff --git a/content/browser/code_cache/generated_code_cache_context.h b/content/browser/code_cache/generated_code_cache_context.h index 8b3e9bb..06f4528b 100644 --- a/content/browser/code_cache/generated_code_cache_context.h +++ b/content/browser/code_cache/generated_code_cache_context.h
@@ -10,7 +10,6 @@ #include <memory> #include <optional> #include <string> -#include <string_view> #include "base/containers/span.h" #include "base/files/file_path.h" @@ -87,12 +86,11 @@ std::optional<persistent_cache::PendingBackend> ShareReadOnlyConnection( const std::string& context_key); - // Using a persistent cache collection with `context_key` as the cache_id - // makes sure that there are seperate files for separate process locks. This - // will eventually allow the sharing of the files with the renderers. + // Inserts `content` and `metadata` for `resource_key` in the cache identified + // by `context_key`. void InsertIntoPersistentCacheCollection( const std::string& context_key, - std::string_view url, + base::span<const uint8_t> resource_key, base::span<const uint8_t> content, persistent_cache::EntryMetadata metadata); @@ -105,11 +103,11 @@ mojo_base::BigBuffer content; }; - // TODO(crbug.com/377475540): Use types that are not interchangeable for - // `context_key` and `url` so that they cannot be mixed up by mistake. + // Returns the entry for `resource_key` in the cache identified by + // `context_key`, or no value in case of a cache miss or retrieval error. std::optional<MetadataAndContent> FindInPersistentCacheCollection( const std::string& context_key, - std::string_view url); + base::span<const uint8_t> resource_key); #endif // !BUILDFLAG(IS_FUCHSIA) private:
diff --git a/content/browser/digital_credentials/cross_device_request_dispatcher.cc b/content/browser/digital_credentials/cross_device_request_dispatcher.cc index b25105b..b709801 100644 --- a/content/browser/digital_credentials/cross_device_request_dispatcher.cc +++ b/content/browser/digital_credentials/cross_device_request_dispatcher.cc
@@ -57,18 +57,14 @@ } // namespace RequestDispatcher::RequestDispatcher( - std::unique_ptr<device::FidoDiscoveryBase> v1_discovery, std::unique_ptr<device::FidoDiscoveryBase> v2_discovery, RequestInfo request_info, CompletionCallback callback) - : v1_discovery_(std::move(v1_discovery)), - v2_discovery_(std::move(v2_discovery)), + : v2_discovery_(std::move(v2_discovery)), request_info_(std::move(request_info)), callback_(std::move(callback)) { FIDO_LOG(EVENT) << "Starting digital identity flow"; - v1_discovery_->set_observer(this); v2_discovery_->set_observer(this); - v1_discovery_->Start(); v2_discovery_->Start(); }
diff --git a/content/browser/digital_credentials/cross_device_request_dispatcher.h b/content/browser/digital_credentials/cross_device_request_dispatcher.h index 187591d4..59e23346 100644 --- a/content/browser/digital_credentials/cross_device_request_dispatcher.h +++ b/content/browser/digital_credentials/cross_device_request_dispatcher.h
@@ -34,8 +34,7 @@ using CompletionCallback = base::OnceCallback<void(base::expected<Response, Error>)>; - RequestDispatcher(std::unique_ptr<device::FidoDiscoveryBase> v1_discovery, - std::unique_ptr<device::FidoDiscoveryBase> v2_discovery, + RequestDispatcher(std::unique_ptr<device::FidoDiscoveryBase> v2_discovery, RequestInfo request_info, CompletionCallback callback); @@ -54,7 +53,6 @@ void OnAuthenticatorReady(device::FidoAuthenticator* authenticator); void OnComplete(std::optional<std::vector<uint8_t>> response); - const std::unique_ptr<device::FidoDiscoveryBase> v1_discovery_; const std::unique_ptr<device::FidoDiscoveryBase> v2_discovery_; RequestInfo request_info_; CompletionCallback callback_;
diff --git a/content/browser/digital_credentials/cross_device_request_dispatcher_unittest.cc b/content/browser/digital_credentials/cross_device_request_dispatcher_unittest.cc index 9ed3732..d1e1db8 100644 --- a/content/browser/digital_credentials/cross_device_request_dispatcher_unittest.cc +++ b/content/browser/digital_credentials/cross_device_request_dispatcher_unittest.cc
@@ -8,12 +8,12 @@ #include "base/test/bind.h" #include "base/test/task_environment.h" #include "base/test/test_future.h" +#include "build/build_config.h" #include "content/browser/digital_credentials/cross_device_request_dispatcher.h" #include "content/public/browser/cross_device_request_info.h" #include "content/public/browser/digital_credentials_cross_device.h" #include "device/bluetooth/bluetooth_adapter_factory.h" -#include "device/bluetooth/test/mock_bluetooth_adapter.h" -#include "device/fido/cable/fido_cable_discovery.h" +#include "device/fido/cable/cable_mock_bluetooth_adapter.h" #include "device/fido/cable/pairing.h" #include "device/fido/cable/v2_authenticator.h" #include "device/fido/cable/v2_constants.h" @@ -29,6 +29,10 @@ #include "url/gurl.h" #include "url/origin.h" +#if BUILDFLAG(IS_CHROMEOS) +#include "device/bluetooth/floss/floss_features.h" +#endif // BUILDFLAG(IS_CHROMEOS) + using base::JSONReader; using testing::NiceMock; @@ -40,8 +44,6 @@ public: void SetUp() override { network_context_ = device::cablev2::NewMockTunnelServer(std::nullopt); - std::tie(ble_advert_callback_, ble_advert_events_) = - device::cablev2::Discovery::AdvertEventStream::New(); bssl::UniquePtr<EC_GROUP> p256( EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); @@ -53,8 +55,7 @@ POINT_CONVERSION_UNCOMPRESSED, peer_identity_x962_, sizeof(peer_identity_x962_), /*ctx=*/nullptr)); - mock_adapter_ = - base::MakeRefCounted<NiceMock<device::MockBluetoothAdapter>>(); + mock_adapter_ = device::cablev2::CableMockBluetoothAdapter::MakePoweredOn(); device::BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_); } @@ -69,8 +70,7 @@ // This value isn't used since it's a QR-based transaction. device::FidoRequestType::kGetAssertion, base::BindLambdaForTesting([&]() { return network_context_.get(); }), - qr_generator_key_, std::move(ble_advert_events_), - std::move(callback_and_event_stream.second), + qr_generator_key_, std::move(callback_and_event_stream.second), /*extension_contents=*/std::vector<device::CableDiscoveryData>(), GetPairingCallback(), GetInvalidatedPairingCallback(), GetEventCallback(), @@ -83,18 +83,22 @@ base::Value(std::move(request_value))}; base::test::TestFuture<base::expected<Response, RequestDispatcher::Error>> callback; +#if BUILDFLAG(IS_CHROMEOS) + if (!floss::features::IsFlossEnabled()) { + mock_adapter_->ExpectDiscoveryWithScanCallback(); + } +#else + mock_adapter_->ExpectDiscoveryWithScanCallback(); +#endif auto request_handler = std::make_unique<RequestDispatcher>( - std::make_unique<device::FidoCableDiscovery>( - std::vector<device::CableDiscoveryData>()), std::move(discovery), std::move(request_info), callback.GetCallback()); std::unique_ptr<device::cablev2::authenticator::Transaction> transaction = device::cablev2::authenticator:: TransactDigitalIdentityFromQRCodeForTesting( device::cablev2::authenticator::NewMockPlatform( - std::move(ble_advert_callback_), - /*ctap2_device=*/nullptr, + /*ctap2_device=*/nullptr, mock_adapter_, /*observer=*/nullptr), base::BindLambdaForTesting( [&]() { return network_context_.get(); }), @@ -121,9 +125,6 @@ std::unique_ptr<network::mojom::NetworkContext> network_context_; const std::array<uint8_t, device::cablev2::kQRKeySize> qr_generator_key_ = { 0}; - std::unique_ptr<device::cablev2::Discovery::AdvertEventStream> - ble_advert_events_; - device::cablev2::Discovery::AdvertEventStream::Callback ble_advert_callback_; uint8_t peer_identity_x962_[device::kP256X962Length] = {}; const std::array<uint8_t, device::cablev2::kQRSecretSize> zero_qr_secret_ = { 0}; @@ -131,7 +132,7 @@ 0}; const std::array<uint8_t, device::cablev2::kQRSeedSize> zero_seed_ = {0}; - scoped_refptr<NiceMock<device::MockBluetoothAdapter>> mock_adapter_; + scoped_refptr<device::cablev2::CableMockBluetoothAdapter> mock_adapter_; base::test::TaskEnvironment task_environment; };
diff --git a/content/browser/digital_credentials/cross_device_transaction_impl.cc b/content/browser/digital_credentials/cross_device_transaction_impl.cc index 780532e..7d9e81aa 100644 --- a/content/browser/digital_credentials/cross_device_transaction_impl.cc +++ b/content/browser/digital_credentials/cross_device_transaction_impl.cc
@@ -17,7 +17,6 @@ #include "device/bluetooth/bluetooth_adapter.h" #include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/fido/ble_adapter_manager.h" -#include "device/fido/cable/fido_cable_discovery.h" #include "device/fido/fido_discovery_base.h" #include "device/fido/public/cable_discovery_data.h" #include "device/fido/public/fido_constants.h" @@ -84,14 +83,12 @@ return; } - auto v1_discovery = std::make_unique<device::FidoCableDiscovery>( - std::vector<device::CableDiscoveryData>()); auto v2_discovery = std::make_unique<device::cablev2::Discovery>( // This request type argument is unused. It only applies to the payload // sent for state-assisted transactions, but those aren't supported for // digital credentials. device::FidoRequestType::kGetAssertion, network_context_factory, - qr_generator_key, v1_discovery->GetV2AdvertStream(), + qr_generator_key, /*contact_device_stream=*/nullptr, std::vector<device::CableDiscoveryData>(), /*pairing_callback=*/std::nullopt, @@ -100,8 +97,7 @@ weak_ptr_factory_.GetWeakPtr()), /*must_support_ctap=*/false); dispatcher_ = std::make_unique<RequestDispatcher>( - std::move(v1_discovery), std::move(v2_discovery), - std::move(request_info_), + std::move(v2_discovery), std::move(request_info_), base::BindOnce(&TransactionImpl::OnHaveResponse, weak_ptr_factory_.GetWeakPtr()));
diff --git a/content/browser/digital_credentials/cross_device_transaction_impl.h b/content/browser/digital_credentials/cross_device_transaction_impl.h index bec7827..8b343f5 100644 --- a/content/browser/digital_credentials/cross_device_transaction_impl.h +++ b/content/browser/digital_credentials/cross_device_transaction_impl.h
@@ -16,7 +16,6 @@ #include "content/public/browser/cross_device_request_info.h" #include "content/public/browser/digital_credentials_cross_device.h" #include "device/bluetooth/bluetooth_adapter.h" -#include "device/fido/cable/fido_cable_discovery.h" #include "device/fido/cable/v2_constants.h" #include "device/fido/cable/v2_discovery.h" #include "device/fido/network_context_factory.h"
diff --git a/content/browser/file_system_access/file_system_access_safe_move_helper.cc b/content/browser/file_system_access/file_system_access_safe_move_helper.cc index 000b82c0..1710b3b 100644 --- a/content/browser/file_system_access/file_system_access_safe_move_helper.cc +++ b/content/browser/file_system_access/file_system_access_safe_move_helper.cc
@@ -13,6 +13,7 @@ #include "base/task/sequenced_task_runner.h" #include "base/task/thread_pool.h" #include "base/thread_annotations.h" +#include "base/types/expected.h" #include "build/build_config.h" #include "components/services/quarantine/quarantine.h" #include "content/browser/file_system_access/features.h" @@ -72,20 +73,25 @@ source_url, 0, storage::kMaximumLength, base::Time()); int64_t length = reader_->GetLength(base::BindOnce(&HashCalculator::GotLength, this)); - if (length == net::ERR_IO_PENDING) + if (length == net::ERR_IO_PENDING) { return; - GotLength(length); + } + if (length < 0) { + GotLength(base::unexpected(static_cast<net::Error>(length))); + } else { + GotLength(length); + } } - void GotLength(int64_t length) { + void GotLength(base::expected<int64_t, net::Error> length) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (length < 0) { - std::move(callback_).Run(storage::NetErrorToFileError(length), + if (!length.has_value()) { + std::move(callback_).Run(storage::NetErrorToFileError(length.error()), std::string(), -1); return; } - file_size_ = length; + file_size_ = length.value(); ReadMore(); }
diff --git a/content/browser/payments/stub_secure_payment_confirmation_service.cc b/content/browser/payments/stub_secure_payment_confirmation_service.cc index 1b006ef..6c3971e 100644 --- a/content/browser/payments/stub_secure_payment_confirmation_service.cc +++ b/content/browser/payments/stub_secure_payment_confirmation_service.cc
@@ -48,6 +48,12 @@ kUnavailableFeatureNotEnabled); } +void StubSecurePaymentConfirmationService:: + GetSecurePaymentConfirmationCapabilities( + GetSecurePaymentConfirmationCapabilitiesCallback callback) { + std::move(callback).Run(/*capabilities=*/{}); +} + void StubSecurePaymentConfirmationService::StorePaymentCredential( const std::vector<uint8_t>& credential_id, const std::string& rp_id,
diff --git a/content/browser/payments/stub_secure_payment_confirmation_service.h b/content/browser/payments/stub_secure_payment_confirmation_service.h index 05c2e85..d500051 100644 --- a/content/browser/payments/stub_secure_payment_confirmation_service.h +++ b/content/browser/payments/stub_secure_payment_confirmation_service.h
@@ -44,6 +44,10 @@ SecurePaymentConfirmationAvailabilityCallback callback) override; // mojom::SecurePaymentConfirmationService: + void GetSecurePaymentConfirmationCapabilities( + GetSecurePaymentConfirmationCapabilitiesCallback callback) override; + + // mojom::SecurePaymentConfirmationService: void StorePaymentCredential(const std::vector<uint8_t>& credential_id, const std::string& rp_id, const std::vector<uint8_t>& user_id,
diff --git a/content/browser/preloading/prerender/prerender_host_registry.cc b/content/browser/preloading/prerender/prerender_host_registry.cc index 0d7b69e..2a5fe547 100644 --- a/content/browser/preloading/prerender/prerender_host_registry.cc +++ b/content/browser/preloading/prerender/prerender_host_registry.cc
@@ -11,6 +11,7 @@ #include "base/feature_list.h" #include "base/functional/bind.h" #include "base/memory/memory_pressure_monitor.h" +#include "base/memory_coordinator/utils.h" #include "base/metrics/field_trial_params.h" #include "base/metrics/histogram_functions.h" #include "base/notreached.h" @@ -629,14 +630,10 @@ } // Don't prerender under critical memory pressure. - switch (GetCurrentMemoryPressureLevel()) { - case base::MEMORY_PRESSURE_LEVEL_NONE: - case base::MEMORY_PRESSURE_LEVEL_MODERATE: - break; - case base::MEMORY_PRESSURE_LEVEL_CRITICAL: - builder.RejectAsNotEligible( - attributes, PrerenderFinalStatus::kMemoryPressureOnTrigger); - return PrerenderHostId(); + if (GetCurrentMemoryLimit() <= base::kCriticalMemoryPressureThreshold) { + builder.RejectAsNotEligible( + attributes, PrerenderFinalStatus::kMemoryPressureOnTrigger); + return PrerenderHostId(); } // Disable prerendering on slow network. @@ -1949,13 +1946,8 @@ return; } - switch (memory_pressure_level) { - case base::MEMORY_PRESSURE_LEVEL_NONE: - case base::MEMORY_PRESSURE_LEVEL_MODERATE: - break; - case base::MEMORY_PRESSURE_LEVEL_CRITICAL: - CancelAllHosts(PrerenderFinalStatus::kMemoryPressureAfterTriggered); - break; + if (GetMemoryLimit() <= base::kCriticalMemoryPressureThreshold) { + CancelAllHosts(PrerenderFinalStatus::kMemoryPressureAfterTriggered); } } @@ -1966,15 +1958,14 @@ : base::SingleThreadTaskRunner::GetCurrentDefault(); } -base::MemoryPressureLevel -PrerenderHostRegistry::GetCurrentMemoryPressureLevel() { +int PrerenderHostRegistry::GetCurrentMemoryLimit() { // Ignore the memory pressure event if the memory control is disabled. if (!base::FeatureList::IsEnabled( blink::features::kPrerender2MemoryControls)) { - return base::MEMORY_PRESSURE_LEVEL_NONE; + return base::kNoMemoryPressureThreshold; } - return memory_pressure_level(); + return GetMemoryLimit(); } void PrerenderHostRegistry::SetTaskRunnerForTesting(
diff --git a/content/browser/preloading/prerender/prerender_host_registry.h b/content/browser/preloading/prerender/prerender_host_registry.h index 4564a498..3fc878b2 100644 --- a/content/browser/preloading/prerender/prerender_host_registry.h +++ b/content/browser/preloading/prerender/prerender_host_registry.h
@@ -369,7 +369,7 @@ scoped_refptr<base::SingleThreadTaskRunner> GetTimerTaskRunner(); - base::MemoryPressureLevel GetCurrentMemoryPressureLevel(); + int GetCurrentMemoryLimit(); // Holds the PrerenderHostId of running PrerenderHost. Reset to an invalid // value when there's no running PrerenderHost. Tracks only the host id of
diff --git a/content/browser/renderer_host/code_cache_host_impl.cc b/content/browser/renderer_host/code_cache_host_impl.cc index ffc684a..a255629 100644 --- a/content/browser/renderer_host/code_cache_host_impl.cc +++ b/content/browser/renderer_host/code_cache_host_impl.cc
@@ -521,7 +521,7 @@ url, MojoCacheTypeToCodeCacheType(cache_type)); generated_code_cache_context()->InsertIntoPersistentCacheCollection( - cache_id, resource_key, std::move(data), + cache_id, base::as_byte_span(resource_key), std::move(data), persistent_cache::EntryMetadata{ .input_signature = expected_response_time.ToDeltaSinceWindowsEpoch() .InMicroseconds()}); @@ -550,7 +550,7 @@ if (auto metadata_and_content = generated_code_cache_context()->FindInPersistentCacheCollection( - cache_id, resource_key); + cache_id, base::as_byte_span(resource_key)); metadata_and_content.has_value() && metadata_and_content->content.size() > 0) { // Cache hit with content.
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc index b5cd09d..25efdf5e 100644 --- a/content/browser/renderer_host/render_frame_host_impl.cc +++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -29,6 +29,7 @@ #include "base/lazy_instance.h" #include "base/memory/ptr_util.h" #include "base/memory/raw_ptr.h" +#include "base/memory_coordinator/utils.h" #include "base/metrics/field_trial_params.h" #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" @@ -839,9 +840,8 @@ // Returns the amount of time to keep subframe processes alive in case they can // be reused. Returns zero if under memory pressure, as memory should be freed // up as soon as possible if it's limited. -base::TimeDelta GetSubframeProcessShutdownDelay( - BrowserContext* browser_context, - base::MemoryPressureLevel memory_pressure_level) { +base::TimeDelta GetSubframeProcessShutdownDelay(BrowserContext* browser_context, + int memory_limit) { static constexpr base::TimeDelta kZeroDelay; if (!RenderProcessHostImpl::ShouldDelayProcessShutdown()) { return kZeroDelay; @@ -849,7 +849,7 @@ // Don't delay process shutdown under memory pressure. Does not cancel // existing shutdown delays for processes already in delayed-shutdown state. - if (memory_pressure_level >= base::MEMORY_PRESSURE_LEVEL_MODERATE) { + if (memory_limit <= base::kModerateMemoryPressureThreshold) { return kZeroDelay; } @@ -4649,8 +4649,7 @@ frame_tree_->IsBeingDestroyed() ? base::TimeDelta() : GetSubframeProcessShutdownDelay( - GetSiteInstance()->GetBrowserContext(), - memory_pressure_level()); + GetSiteInstance()->GetBrowserContext(), GetMemoryLimit()); // If this document has unload handlers (and is active), ensure that they // have a chance to execute by delaying process cleanup. This will prevent // the process from shutting down immediately in the case where this is
diff --git a/content/browser/renderer_host/spare_render_process_host_manager_impl.cc b/content/browser/renderer_host/spare_render_process_host_manager_impl.cc index a9ddae0..50896f70 100644 --- a/content/browser/renderer_host/spare_render_process_host_manager_impl.cc +++ b/content/browser/renderer_host/spare_render_process_host_manager_impl.cc
@@ -9,6 +9,7 @@ #include "base/check.h" #include "base/debug/dump_without_crashing.h" +#include "base/memory_coordinator/utils.h" #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" #include "base/no_destructor.h" @@ -313,13 +314,13 @@ } } -// Returns the MemoryPressureLevel threshold that determines when a spare RPH -// can be created or killed. -base::MemoryPressureLevel GetMemoryPressureLevelThreshold() { +// Returns the memory limit threshold (expressed as a percentage) that +// determines when a spare RPH can be created or killed. +int GetMemoryLimitThreshold() { if (base::FeatureList::IsEnabled(kSpareRPHUseCriticalMemoryPressure)) { - return base::MEMORY_PRESSURE_LEVEL_CRITICAL; + return base::kCriticalMemoryPressureThreshold; } - return base::MEMORY_PRESSURE_LEVEL_MODERATE; + return base::kModerateMemoryPressureThreshold; } } // namespace @@ -501,7 +502,7 @@ // Don't create a spare renderer when the system is under load. This is // currently approximated by only looking at the memory pressure. See also // https://crbug.com/852905. - if (memory_pressure_level() >= GetMemoryPressureLevelThreshold()) { + if (GetMemoryLimit() <= GetMemoryLimitThreshold()) { no_spare_renderer_reason_ = NoSpareRendererReason::kMemoryPressure; return nullptr; } @@ -960,7 +961,7 @@ return; } - if (memory_pressure_level < GetMemoryPressureLevelThreshold()) { + if (GetMemoryLimit() > GetMemoryLimitThreshold()) { return; } @@ -1010,7 +1011,7 @@ } // Don't create spares when under memory pressure. - if (memory_pressure_level() != base::MEMORY_PRESSURE_LEVEL_NONE) { + if (GetMemoryLimit() < base::kNoMemoryPressureThreshold) { return false; }
diff --git a/content/browser/webauth/authenticator_impl_unittest.cc b/content/browser/webauth/authenticator_impl_unittest.cc index a6df0ec..8a5feab5 100644 --- a/content/browser/webauth/authenticator_impl_unittest.cc +++ b/content/browser/webauth/authenticator_impl_unittest.cc
@@ -84,6 +84,7 @@ #include "device/fido/attested_credential_data.h" #include "device/fido/authenticator_data.h" #include "device/fido/authenticator_get_assertion_response.h" +#include "device/fido/cable/cable_mock_bluetooth_adapter.h" #include "device/fido/cable/fido_tunnel_device.h" #include "device/fido/cable/pairing.h" #include "device/fido/cable/v2_authenticator.h" @@ -159,6 +160,7 @@ #if BUILDFLAG(IS_CHROMEOS) #include "chromeos/dbus/tpm_manager/tpm_manager_client.h" #include "chromeos/dbus/u2f/u2f_client.h" +#include "device/bluetooth/floss/floss_features.h" #endif namespace content { @@ -9473,8 +9475,12 @@ POINT_CONVERSION_UNCOMPRESSED, peer_identity_x962_, sizeof(peer_identity_x962_), /*ctx=*/nullptr)); - std::tie(ble_advert_callback_, ble_advert_events_) = - device::cablev2::Discovery::AdvertEventStream::New(); + // These tests use a more specialized adapter than is used in the base + // class. + mock_bluetooth_adapter_ = + device::cablev2::CableMockBluetoothAdapter::MakePoweredOn(); + device::BluetoothAdapterFactory::SetAdapterForTesting( + mock_bluetooth_adapter_); } void TearDown() override { @@ -9613,12 +9619,22 @@ void OnCableEvent(Event event) { events_.push_back(event); } + void MaybeExpectDiscoveryWithScanCallback() { +#if BUILDFLAG(IS_CHROMEOS) + if (!floss::features::IsFlossEnabled()) { + mock_bluetooth_adapter_->ExpectDiscoveryWithScanCallback(); + } +#else + mock_bluetooth_adapter_->ExpectDiscoveryWithScanCallback(); +#endif + } + void DoPairingConnection() { // First do unpaired exchange to get pairing data. auto discovery = std::make_unique<device::cablev2::Discovery>( device::FidoRequestType::kGetAssertion, base::BindLambdaForTesting([&]() { return network_context_.get(); }), - qr_generator_key_, std::move(ble_advert_events_), + qr_generator_key_, /*contact_device_stream=*/nullptr, /*extension_contents=*/std::vector<device::CableDiscoveryData>(), GetPairingCallback(), GetInvalidatedPairingCallback(), @@ -9626,12 +9642,13 @@ ReplaceDiscoveryFactory( std::make_unique<DiscoveryFactory>(std::move(discovery))); + MaybeExpectDiscoveryWithScanCallback(); const std::vector<uint8_t> contact_id(/*count=*/200, /*value=*/1); std::unique_ptr<device::cablev2::authenticator::Transaction> transaction = device::cablev2::authenticator::TransactFromQRCode( device::cablev2::authenticator::NewMockPlatform( - std::move(ble_advert_callback_), &virtual_device_, + &virtual_device_, mock_bluetooth_adapter_, /*observer=*/nullptr), base::BindLambdaForTesting( [&]() { return network_context_.get(); }), @@ -9652,16 +9669,12 @@ expected_request_type_string = "ga"; } - std::tie(ble_advert_callback_, ble_advert_events_) = - device::cablev2::Discovery::EventStream< - base::span<const uint8_t, device::cablev2::kAdvertSize>>::New(); auto callback_and_event_stream = device::cablev2::Discovery::EventStream< std::unique_ptr<device::cablev2::Pairing>>::New(); discovery = std::make_unique<device::cablev2::Discovery>( request_type, base::BindLambdaForTesting([&]() { return network_context_.get(); }), - qr_generator_key_, std::move(ble_advert_events_), - std::move(callback_and_event_stream.second), + qr_generator_key_, std::move(callback_and_event_stream.second), /*extension_contents=*/std::vector<device::CableDiscoveryData>(), GetPairingCallback(), GetInvalidatedPairingCallback(), GetEventCallback(), /*must_support_ctap=*/true); @@ -9692,7 +9705,7 @@ CHECK_EQ(request_type_hint, expected_request_type_string); transaction = device::cablev2::authenticator::TransactFromFCM( device::cablev2::authenticator::NewMockPlatform( - std::move(ble_advert_callback_), &virtual_device_, + &virtual_device_, mock_bluetooth_adapter_, /*observer=*/nullptr), base::BindLambdaForTesting( [&]() { return network_context_.get(); }), @@ -9702,6 +9715,7 @@ ReplaceDiscoveryFactory( std::make_unique<DiscoveryFactory>(std::move(discovery))); + MaybeExpectDiscoveryWithScanCallback(); EXPECT_EQ(AuthenticatorMakeCredential().status, AuthenticatorStatus::SUCCESS); @@ -9731,9 +9745,6 @@ base::span<const uint8_t, device::cablev2::kClientNonceSize> client_nonce, const std::string& request_type_hint)> contact_callback_; - std::unique_ptr<device::cablev2::Discovery::AdvertEventStream> - ble_advert_events_; - device::cablev2::Discovery::AdvertEventStream::Callback ble_advert_callback_; ContactWhenReadyContentBrowserClient browser_client_{ base::BindRepeating(&AuthenticatorCableV2Test::MaybeContactPhones, base::Unretained(this))}; @@ -9741,6 +9752,9 @@ base::OnceClosure maybe_contact_phones_callback_; std::vector<Event> events_; + scoped_refptr<device::cablev2::CableMockBluetoothAdapter> + mock_bluetooth_adapter_; + private: static VirtualCtap2Device::State* DeviceState() { VirtualCtap2Device::State* state = new VirtualCtap2Device::State; @@ -9767,7 +9781,7 @@ auto discovery = std::make_unique<device::cablev2::Discovery>( device::FidoRequestType::kGetAssertion, base::BindLambdaForTesting([&]() { return network_context_.get(); }), - qr_generator_key_, std::move(ble_advert_events_), + qr_generator_key_, /*contact_device_stream=*/nullptr, /*extension_contents=*/std::vector<device::CableDiscoveryData>(), GetPairingCallback(), GetInvalidatedPairingCallback(), GetEventCallback(), @@ -9775,11 +9789,12 @@ ReplaceDiscoveryFactory( std::make_unique<DiscoveryFactory>(std::move(discovery))); + MaybeExpectDiscoveryWithScanCallback(); std::unique_ptr<device::cablev2::authenticator::Transaction> transaction = device::cablev2::authenticator::TransactFromQRCode( device::cablev2::authenticator::NewMockPlatform( - std::move(ble_advert_callback_), &virtual_device_, + &virtual_device_, mock_bluetooth_adapter_, /*observer=*/nullptr), base::BindLambdaForTesting([&]() { return network_context_.get(); }), root_secret_, "Test Authenticator", zero_qr_secret_, @@ -9797,7 +9812,7 @@ base::BindLambdaForTesting([&]() { return network_context_.get(); }); auto discovery = std::make_unique<device::cablev2::Discovery>( device::FidoRequestType::kGetAssertion, network_context_factory, - qr_generator_key_, std::move(ble_advert_events_), + qr_generator_key_, /*contact_device_stream=*/nullptr, /*extension_contents=*/std::vector<device::CableDiscoveryData>(), GetPairingCallback(), GetInvalidatedPairingCallback(), GetEventCallback(), @@ -9805,11 +9820,12 @@ ReplaceDiscoveryFactory( std::make_unique<DiscoveryFactory>(std::move(discovery))); + MaybeExpectDiscoveryWithScanCallback(); std::unique_ptr<device::cablev2::authenticator::Transaction> transaction = device::cablev2::authenticator::NewHandshakeErrorDevice( device::cablev2::authenticator::NewMockPlatform( - std::move(ble_advert_callback_), &virtual_device_, + &virtual_device_, mock_bluetooth_adapter_, /*observer=*/nullptr), network_context_factory, zero_qr_secret_); @@ -9838,7 +9854,7 @@ auto discovery = std::make_unique<device::cablev2::Discovery>( device::FidoRequestType::kGetAssertion, base::BindLambdaForTesting([&]() { return network_context_.get(); }), - qr_generator_key_, std::move(ble_advert_events_), + qr_generator_key_, /*contact_device_stream=*/nullptr, /*extension_contents=*/std::vector<device::CableDiscoveryData>(), GetPairingCallback(), GetInvalidatedPairingCallback(), GetEventCallback(), @@ -9846,6 +9862,7 @@ ReplaceDiscoveryFactory( std::make_unique<DiscoveryFactory>(std::move(discovery))); + MaybeExpectDiscoveryWithScanCallback(); // Simulate the network service restarting. ResetNetworkService(); @@ -9853,7 +9870,7 @@ std::unique_ptr<device::cablev2::authenticator::Transaction> transaction = device::cablev2::authenticator::TransactFromQRCode( device::cablev2::authenticator::NewMockPlatform( - std::move(ble_advert_callback_), &virtual_device_, + &virtual_device_, mock_bluetooth_adapter_, /*observer=*/nullptr), base::BindLambdaForTesting([&]() { return network_context_.get(); }), root_secret_, "Test Authenticator", zero_qr_secret_, @@ -9916,14 +9933,14 @@ auto discovery = std::make_unique<device::cablev2::Discovery>( device::FidoRequestType::kGetAssertion, base::BindLambdaForTesting([&]() { return network_context_.get(); }), - qr_generator_key_, std::move(ble_advert_events_), - std::move(callback_and_event_stream.second), + qr_generator_key_, std::move(callback_and_event_stream.second), /*extension_contents=*/std::vector<device::CableDiscoveryData>(), GetPairingCallback(), GetInvalidatedPairingCallback(), GetEventCallback(), /*must_support_ctap=*/true); ReplaceDiscoveryFactory( std::make_unique<DiscoveryFactory>(std::move(discovery))); + MaybeExpectDiscoveryWithScanCallback(); maybe_contact_phones_callback_ = base::BindLambdaForTesting([&callback_and_event_stream]() { @@ -9991,24 +10008,25 @@ auto discovery = std::make_unique<device::cablev2::Discovery>( device::FidoRequestType::kGetAssertion, base::BindLambdaForTesting([&]() { return network_context_.get(); }), - qr_generator_key_, std::move(ble_advert_events_), + qr_generator_key_, /*contact_device_stream=*/nullptr, extension_values, GetPairingCallback(), GetInvalidatedPairingCallback(), GetEventCallback(), /*must_support_ctap=*/true); ReplaceDiscoveryFactory( std::make_unique<DiscoveryFactory>(std::move(discovery))); + MaybeExpectDiscoveryWithScanCallback(); - // Both extension values should work, but we can only do a single transaction - // per test because a lot of state is setup for a test. Therefore pick one of - // the two to check, at random. + // Both extension values should work, but we can only do a single + // transaction per test because a lot of state is setup for a test. + // Therefore pick one of the two to check, at random. const auto& server_link = (base::RandUint64() & 1) ? server_link_1 : server_link_2; std::unique_ptr<device::cablev2::authenticator::Transaction> transaction = device::cablev2::authenticator::TransactFromQRCode( device::cablev2::authenticator::NewMockPlatform( - std::move(ble_advert_callback_), &virtual_device_, + &virtual_device_, mock_bluetooth_adapter_, /*observer=*/nullptr), base::BindLambdaForTesting([&]() { return network_context_.get(); }), root_secret_, "Test Authenticator", server_link.secret, @@ -10024,7 +10042,7 @@ base::BindLambdaForTesting([&]() { return network_context_.get(); }); auto discovery = std::make_unique<device::cablev2::Discovery>( device::FidoRequestType::kGetAssertion, network_context_factory, - qr_generator_key_, std::move(ble_advert_events_), + qr_generator_key_, /*contact_device_stream=*/nullptr, /*extension_contents=*/std::vector<device::CableDiscoveryData>(), GetPairingCallback(), GetInvalidatedPairingCallback(), GetEventCallback(), @@ -10032,13 +10050,14 @@ ReplaceDiscoveryFactory( std::make_unique<DiscoveryFactory>(std::move(discovery))); + MaybeExpectDiscoveryWithScanCallback(); const std::vector<uint8_t> contact_id(/*count=*/200, /*value=*/1); std::unique_ptr<device::cablev2::authenticator::Transaction> transaction = device::cablev2::authenticator::NewLateLinkingDevice( device::CtapDeviceResponseCode::kCtap2ErrOperationDenied, device::cablev2::authenticator::NewMockPlatform( - std::move(ble_advert_callback_), &virtual_device_, + &virtual_device_, mock_bluetooth_adapter_, /*observer=*/nullptr), network_context_factory, zero_qr_secret_, peer_identity_x962_); @@ -10067,7 +10086,7 @@ auto discovery = std::make_unique<device::cablev2::Discovery>( device::FidoRequestType::kGetAssertion, base::BindLambdaForTesting([&]() { return network_context_.get(); }), - qr_generator_key_, std::move(ble_advert_events_), + qr_generator_key_, /*contact_device_stream=*/nullptr, /*extension_contents=*/std::vector<device::CableDiscoveryData>(), GetPairingCallback(), GetInvalidatedPairingCallback(), @@ -10075,10 +10094,11 @@ ReplaceDiscoveryFactory( std::make_unique<DiscoveryFactory>(std::move(discovery))); + MaybeExpectDiscoveryWithScanCallback(); transaction_ = device::cablev2::authenticator::TransactFromQRCode( device::cablev2::authenticator::NewMockPlatform( - std::move(ble_advert_callback_), &virtual_device_, this), + &virtual_device_, mock_bluetooth_adapter_, this), base::BindLambdaForTesting([&]() { return network_context_.get(); }), root_secret_, "Test Authenticator", zero_qr_secret_, peer_identity_x962_,
diff --git a/content/shell/android/browsertests/src/org/chromium/content_shell/browsertests/ContentShellBrowserTestActivity.java b/content/shell/android/browsertests/src/org/chromium/content_shell/browsertests/ContentShellBrowserTestActivity.java index 8460f17..69eed4e 100644 --- a/content/shell/android/browsertests/src/org/chromium/content_shell/browsertests/ContentShellBrowserTestActivity.java +++ b/content/shell/android/browsertests/src/org/chromium/content_shell/browsertests/ContentShellBrowserTestActivity.java
@@ -65,7 +65,7 @@ mShellManager = (ShellManager) findViewById(getShellManagerViewId()); IntentRequestTracker intentRequestTracker = IntentRequestTracker.createFromActivity(this); mWindowAndroid = - new ActivityWindowAndroid( + ActivityWindowAndroid.create( this, /* listenToActivityState= */ true, intentRequestTracker,
diff --git a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java index fd50339..fdfa35f 100644 --- a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java +++ b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ContentShellActivity.java
@@ -66,7 +66,7 @@ final boolean listenToActivityState = true; mIntentRequestTracker = IntentRequestTracker.createFromActivity(this); mWindowAndroid = - new ActivityWindowAndroid( + ActivityWindowAndroid.create( this, listenToActivityState, mIntentRequestTracker,
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn index ad5276d..2f6a9064 100644 --- a/content/test/BUILD.gn +++ b/content/test/BUILD.gn
@@ -3648,6 +3648,7 @@ "//device/fido", "//device/fido:cablev2_authenticator", "//device/fido:cablev2_test_util", + "//device/fido:mocks", "//media/mojo/mojom:web_speech_recognition", ]
diff --git a/device/BUILD.gn b/device/BUILD.gn index 8858042..892460f 100644 --- a/device/BUILD.gn +++ b/device/BUILD.gn
@@ -174,12 +174,7 @@ "fido/auth_token_requester_unittest.cc", "fido/bio/enrollment_handler_unittest.cc", "fido/ble_adapter_manager_unittest.cc", - "fido/cable/fido_ble_connection_unittest.cc", - "fido/cable/fido_ble_frames_unittest.cc", - "fido/cable/fido_ble_transaction_unittest.cc", - "fido/cable/fido_cable_device_unittest.cc", - "fido/cable/fido_cable_discovery_unittest.cc", - "fido/cable/fido_cable_handshake_handler_unittest.cc", + "fido/cable/v2_discovery_unittest.cc", "fido/cable/v2_handshake_unittest.cc", "fido/cbor_extract_unittest.cc", "fido/credential_management_handler_unittest.cc", @@ -211,8 +206,10 @@ deps += [ "//components/sync/protocol", + "//device/fido:cablev2_test_util", "//device/fido:mocks", "//services/data_decoder/public/cpp:test_support", + "//services/network:test_support", ] if (is_mac) {
diff --git a/device/fido/BUILD.gn b/device/fido/BUILD.gn index ad96823..a55e560 100644 --- a/device/fido/BUILD.gn +++ b/device/fido/BUILD.gn
@@ -112,20 +112,8 @@ "bio/enrollment_handler.h", "ble_adapter_manager.cc", "ble_adapter_manager.h", - "cable/fido_ble_connection.cc", - "cable/fido_ble_connection.h", - "cable/fido_ble_frames.cc", - "cable/fido_ble_frames.h", - "cable/fido_ble_transaction.cc", - "cable/fido_ble_transaction.h", "cable/fido_ble_uuids.cc", "cable/fido_ble_uuids.h", - "cable/fido_cable_device.cc", - "cable/fido_cable_device.h", - "cable/fido_cable_discovery.cc", - "cable/fido_cable_discovery.h", - "cable/fido_cable_handshake_handler.cc", - "cable/fido_cable_handshake_handler.h", "cable/fido_tunnel_device.cc", "cable/fido_tunnel_device.h", "cable/v2_discovery.cc", @@ -357,11 +345,14 @@ deps = [ ":cablev2_authenticator", ":fido", + ":mocks", "//components/cbor", "//crypto", "//net/traffic_annotation:test_support", "//services/network:test_support", "//services/network/public/mojom", + "//testing/gmock", + "//testing/gtest", "//third_party/blink/public/mojom:authenticator_mojo_bindings", ] } @@ -370,8 +361,8 @@ testonly = true sources = [ - "cable/mock_fido_ble_connection.cc", - "cable/mock_fido_ble_connection.h", + "cable/cable_mock_bluetooth_adapter.cc", + "cable/cable_mock_bluetooth_adapter.h", "mock_fido_device.cc", "mock_fido_device.h", "mock_fido_discovery_observer.cc", @@ -383,6 +374,7 @@ "//base", "//components/apdu", "//components/cbor", + "//device/bluetooth:mocks", "//testing/gmock", ] } @@ -398,12 +390,6 @@ libfuzzer_options = [ "max_len=2048" ] } - fuzzer_test("fido_ble_frames_fuzzer") { - sources = [ "cable/fido_ble_frames_fuzzer.cc" ] - deps = [ ":fido" ] - libfuzzer_options = [ "max_len=65535" ] - } - fuzzer_test("ctap_response_fuzzer") { sources = [ "ctap_response_fuzzer.cc" ] deps = [ @@ -416,19 +402,6 @@ seed_corpus = "response_data_fuzzer_corpus/" libfuzzer_options = [ "max_len=65537" ] } - - fuzzer_test("fido_cable_handshake_handler_fuzzer") { - sources = [ "cable/fido_cable_handshake_handler_fuzzer.cc" ] - deps = [ - ":fido", - "//base", - "//base/test:test_support", - "//device/bluetooth:mocks", - "//testing/gmock", - "//testing/gtest", - ] - libfuzzer_options = [ "max_len=2048" ] - } } fuzzer_test("v2_handshake_fuzzer") {
diff --git a/device/fido/cable/cable_mock_bluetooth_adapter.cc b/device/fido/cable/cable_mock_bluetooth_adapter.cc new file mode 100644 index 0000000..b4c109f --- /dev/null +++ b/device/fido/cable/cable_mock_bluetooth_adapter.cc
@@ -0,0 +1,153 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "device/fido/cable/cable_mock_bluetooth_adapter.h" + +#include <algorithm> +#include <ranges> + +#include "base/task/single_thread_task_runner.h" +#include "build/build_config.h" +#include "device/bluetooth/test/mock_bluetooth_device.h" +#include "device/fido/cable/fido_ble_uuids.h" + +using ::testing::_; +using ::testing::NiceMock; + +namespace device::cablev2 { + +namespace { + +// Below constants are used to construct MockBluetoothDevice for testing. +constexpr char kTestBleDeviceAddress[] = "11:12:13:14:15:16"; +constexpr char kTestBleDeviceName[] = "test_cable_device"; + +std::unique_ptr<MockBluetoothDevice> CreateTestBluetoothDevice() { + return std::make_unique<testing::NiceMock<MockBluetoothDevice>>( + /*adapter=*/nullptr, /*bluetooth_class=*/0, kTestBleDeviceName, + kTestBleDeviceAddress, /*paired=*/true, /*connected=*/true); +} + +} // namespace + +// static +scoped_refptr<CableMockBluetoothAdapter> +CableMockBluetoothAdapter::MakePoweredOn() { + auto mock_adapter = + base::MakeRefCounted<::testing::NiceMock<CableMockBluetoothAdapter>>(); + EXPECT_CALL(*mock_adapter, IsPresent()) + .WillRepeatedly(::testing::Return(true)); + EXPECT_CALL(*mock_adapter, IsPowered()) + .WillRepeatedly(::testing::Return(true)); + return mock_adapter; +} + +// static +scoped_refptr<CableMockBluetoothAdapter> +CableMockBluetoothAdapter::MakePoweredOff() { + auto mock_adapter = + base::MakeRefCounted<::testing::NiceMock<CableMockBluetoothAdapter>>(); + EXPECT_CALL(*mock_adapter, IsPresent()) + .WillRepeatedly(::testing::Return(true)); + EXPECT_CALL(*mock_adapter, IsPowered()) + .WillRepeatedly(::testing::Return(false)); + return mock_adapter; +} + +// static +scoped_refptr<CableMockBluetoothAdapter> +CableMockBluetoothAdapter::MakeNotPresent() { + auto mock_adapter = + base::MakeRefCounted<::testing::NiceMock<CableMockBluetoothAdapter>>(); + EXPECT_CALL(*mock_adapter, IsPresent()) + .WillRepeatedly(::testing::Return(false)); + return mock_adapter; +} + +// static +scoped_refptr<CableMockBluetoothAdapter> +CableMockBluetoothAdapter::MakeWithUndeterminedPermission() { + auto mock_adapter = + base::MakeRefCounted<::testing::NiceMock<CableMockBluetoothAdapter>>(); + EXPECT_CALL(*mock_adapter, IsPresent()) + .WillRepeatedly(::testing::Return(true)); + EXPECT_CALL(*mock_adapter, GetOsPermissionStatus()) + .WillRepeatedly(testing::Return(PermissionStatus::kUndetermined)); + return mock_adapter; +} + +void CableMockBluetoothAdapter::ExpectDiscoveryWithScanCallback() { + EXPECT_CALL(*this, StartScanWithFilter_(_, _)) + .WillOnce(::testing::WithArg<1>([](auto& callback) { + std::move(callback).Run( + false, device::UMABluetoothDiscoverySessionOutcome::SUCCESS); + })); +} + +void CableMockBluetoothAdapter::ExpectDiscoveryWithScanCallback( + const std::array<uint8_t, kAdvertSize> v2_advert) { + EXPECT_CALL(*this, StartScanWithFilter_(_, _)) + .WillOnce(::testing::WithArg<1>([this, v2_advert](auto& callback) { + std::move(callback).Run( + false, device::UMABluetoothDiscoverySessionOutcome::SUCCESS); + AddNewTestBluetoothDevice(v2_advert); + })); +} + +#if BUILDFLAG(IS_CHROMEOS) +void CableMockBluetoothAdapter::ExpectLEScan( + const std::array<uint8_t, kAdvertSize> v2_advert) { + EXPECT_CALL(*this, StartLowEnergyScanSession(_, _)) + .WillOnce( + [this, v2_advert]( + std::unique_ptr<BluetoothLowEnergyScanFilter> filter, + base::WeakPtr<BluetoothLowEnergyScanSession::Delegate> delegate) { + EXPECT_TRUE(filter); + delegate->OnSessionStarted(/*scan_session=*/nullptr, + /*error_code=*/std::nullopt); + auto* device = CreateNewTestBluetoothDevice(v2_advert); + delegate->OnDeviceFound(/*scan_session=*/nullptr, device); + return nullptr; + }); +} +#endif // BUILDFLAG(IS_CHROMEOS) + +BluetoothDevice* CableMockBluetoothAdapter::CreateNewTestBluetoothDevice( + base::span<const uint8_t, kAdvertSize> v2_advert) { + auto mock_device = CreateTestBluetoothDevice(); + + std::vector<uint8_t> service_data(v2_advert.begin(), v2_advert.end()); + BluetoothDevice::ServiceDataMap service_data_map; + service_data_map.emplace(kGoogleCableUUID128, std::move(service_data)); + + mock_device->UpdateAdvertisementData( + 1 /* rssi */, std::nullopt /* flags */, BluetoothDevice::UUIDList(), + std::nullopt /* tx_power */, std::move(service_data_map), + BluetoothDevice::ManufacturerDataMap()); + + auto* mock_device_ptr = mock_device.get(); + AddMockDevice(std::move(mock_device)); + + return mock_device_ptr; +} + +void CableMockBluetoothAdapter::AddNewTestBluetoothDevice( + base::span<const uint8_t, kAdvertSize> v2_advert) { + auto* device = CreateNewTestBluetoothDevice(v2_advert); + for (auto& observer : GetObservers()) { + observer.DeviceAdded(this, device); + } +} + +CableMockBluetoothAdapter::CableMockBluetoothAdapter() { + EXPECT_CALL(*this, StopScan(_)) + .WillRepeatedly(::testing::WithArg<0>([](auto callback) { + std::move(callback).Run( + false, device::UMABluetoothDiscoverySessionOutcome::SUCCESS); + })); +} + +CableMockBluetoothAdapter::~CableMockBluetoothAdapter() = default; + +} // namespace device::cablev2
diff --git a/device/fido/cable/cable_mock_bluetooth_adapter.h b/device/fido/cable/cable_mock_bluetooth_adapter.h new file mode 100644 index 0000000..ea14c36 --- /dev/null +++ b/device/fido/cable/cable_mock_bluetooth_adapter.h
@@ -0,0 +1,61 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_FIDO_CABLE_CABLE_MOCK_BLUETOOTH_ADAPTER_H_ +#define DEVICE_FIDO_CABLE_CABLE_MOCK_BLUETOOTH_ADAPTER_H_ + +#include <array> + +#include "base/containers/span.h" +#include "base/memory/scoped_refptr.h" +#include "device/bluetooth/test/mock_bluetooth_adapter.h" +#include "device/fido/cable/v2_constants.h" +#include "device/fido/public/cable_discovery_data.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace device { + +class BluetoothDevice; + +namespace cablev2 { + +// Mock BLE adapter that abstracts out authenticator logic with the following +// logic: +// - Responds to BluetoothAdapter::StartDiscoverySessionWithFilter() by +// invoking BluetoothAdapter::Observer::DeviceAdded() on a test bluetooth +// device that includes service data containing authenticator EID. +class CableMockBluetoothAdapter : public MockBluetoothAdapter { + public: + static scoped_refptr<CableMockBluetoothAdapter> MakePoweredOn(); + static scoped_refptr<CableMockBluetoothAdapter> MakePoweredOff(); + static scoped_refptr<CableMockBluetoothAdapter> MakeNotPresent(); + static scoped_refptr<CableMockBluetoothAdapter> + MakeWithUndeterminedPermission(); + + void ExpectDiscoveryWithScanCallback(); + + void ExpectDiscoveryWithScanCallback( + const std::array<uint8_t, kAdvertSize> v2_advert); + +#if BUILDFLAG(IS_CHROMEOS) + void ExpectLEScan(const std::array<uint8_t, kAdvertSize> v2_advert); +#endif // BUILDFLAG(IS_CHROMEOS) + + void AddNewTestBluetoothDevice( + base::span<const uint8_t, kAdvertSize> v2_advert); + + protected: + CableMockBluetoothAdapter(); + ~CableMockBluetoothAdapter() override; + + private: + BluetoothDevice* CreateNewTestBluetoothDevice( + base::span<const uint8_t, kAdvertSize> v2_advert); +}; + +} // namespace cablev2 + +} // namespace device + +#endif // DEVICE_FIDO_CABLE_CABLE_MOCK_BLUETOOTH_ADAPTER_H_
diff --git a/device/fido/cable/fido_ble_connection.cc b/device/fido/cable/fido_ble_connection.cc deleted file mode 100644 index b1fcd3d48..0000000 --- a/device/fido/cable/fido_ble_connection.cc +++ /dev/null
@@ -1,534 +0,0 @@ -// Copyright 2017 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "device/fido/cable/fido_ble_connection.h" - -#include <algorithm> -#include <ostream> -#include <utility> - -#include "base/barrier_closure.h" -#include "base/functional/bind.h" -#include "base/functional/callback_helpers.h" -#include "base/logging.h" -#include "base/strings/stringprintf.h" -#include "base/task/single_thread_task_runner.h" -#include "build/build_config.h" -#include "components/device_event_log/device_event_log.h" -#include "device/bluetooth/bluetooth_gatt_connection.h" -#include "device/bluetooth/bluetooth_gatt_notify_session.h" -#include "device/bluetooth/bluetooth_remote_gatt_characteristic.h" -#include "device/bluetooth/bluetooth_remote_gatt_service.h" -#include "device/bluetooth/public/cpp/bluetooth_uuid.h" -#include "device/fido/cable/fido_ble_uuids.h" - -namespace device { - -namespace { - -using ServiceRevisionsCallback = - base::OnceCallback<void(std::vector<FidoBleConnection::ServiceRevision>)>; - -constexpr const char* ToString(BluetoothDevice::ConnectErrorCode error_code) { - switch (error_code) { - case BluetoothDevice::ERROR_AUTH_CANCELED: - return "ERROR_AUTH_CANCELED"; - case BluetoothDevice::ERROR_AUTH_FAILED: - return "ERROR_AUTH_FAILED"; - case BluetoothDevice::ERROR_AUTH_REJECTED: - return "ERROR_AUTH_REJECTED"; - case BluetoothDevice::ERROR_AUTH_TIMEOUT: - return "ERROR_AUTH_TIMEOUT"; - case BluetoothDevice::ERROR_FAILED: - return "ERROR_FAILED"; - case BluetoothDevice::ERROR_INPROGRESS: - return "ERROR_INPROGRESS"; - case BluetoothDevice::ERROR_UNKNOWN: - return "ERROR_UNKNOWN"; - case BluetoothDevice::ERROR_UNSUPPORTED_DEVICE: - return "ERROR_UNSUPPORTED_DEVICE"; - default: - NOTREACHED(); - } -} - -constexpr const char* ToString(BluetoothGattService::GattErrorCode error_code) { - switch (error_code) { - case BluetoothGattService::GattErrorCode::kUnknown: - return "GATT_ERROR_UNKNOWN"; - case BluetoothGattService::GattErrorCode::kFailed: - return "GATT_ERROR_FAILED"; - case BluetoothGattService::GattErrorCode::kInProgress: - return "GATT_ERROR_IN_PROGRESS"; - case BluetoothGattService::GattErrorCode::kInvalidLength: - return "GATT_ERROR_INVALID_LENGTH"; - case BluetoothGattService::GattErrorCode::kNotPermitted: - return "GATT_ERROR_NOT_PERMITTED"; - case BluetoothGattService::GattErrorCode::kNotAuthorized: - return "GATT_ERROR_NOT_AUTHORIZED"; - case BluetoothGattService::GattErrorCode::kNotPaired: - return "GATT_ERROR_NOT_PAIRED"; - case BluetoothGattService::GattErrorCode::kNotSupported: - return "GATT_ERROR_NOT_SUPPORTED"; - default: - NOTREACHED(); - } -} - -std::ostream& operator<<(std::ostream& os, - FidoBleConnection::ServiceRevision revision) { - switch (revision) { - case FidoBleConnection::ServiceRevision::kU2f11: - return os << "U2F 1.1"; - case FidoBleConnection::ServiceRevision::kU2f12: - return os << "U2F 1.2"; - case FidoBleConnection::ServiceRevision::kFido2: - return os << "FIDO2"; - } - - NOTREACHED(); -} - -void OnWriteRemoteCharacteristic(FidoBleConnection::WriteCallback callback) { - FIDO_LOG(DEBUG) << "Writing Remote Characteristic Succeeded."; - std::move(callback).Run(true); -} - -void OnWriteRemoteCharacteristicError( - FidoBleConnection::WriteCallback callback, - BluetoothGattService::GattErrorCode error_code) { - FIDO_LOG(ERROR) << "Writing Remote Characteristic Failed: " - << ToString(error_code); - std::move(callback).Run(false); -} - -void OnReadServiceRevisionBitfield( - ServiceRevisionsCallback callback, - std::optional<device::BluetoothGattService::GattErrorCode> error_code, - const std::vector<uint8_t>& value) { - if (error_code.has_value()) { - FIDO_LOG(ERROR) << "Error while reading Service Revision Bitfield: " - << ToString(error_code.value()); - std::move(callback).Run({}); - return; - } - if (value.empty()) { - FIDO_LOG(DEBUG) << "Service Revision Bitfield is empty."; - std::move(callback).Run({}); - return; - } - - if (value.size() != 1u) { - FIDO_LOG(DEBUG) << "Service Revision Bitfield has unexpected size: " - << value.size() << ". Ignoring all but the first byte."; - } - - const uint8_t bitset = value[0]; - if (bitset & 0x1F) { - FIDO_LOG(DEBUG) << "Service Revision Bitfield has unexpected bits set: " - << base::StringPrintf("0x%02X", bitset) - << ". Ignoring all but the first three bits."; - } - - std::vector<FidoBleConnection::ServiceRevision> service_revisions; - for (auto revision : {FidoBleConnection::ServiceRevision::kU2f11, - FidoBleConnection::ServiceRevision::kU2f12, - FidoBleConnection::ServiceRevision::kFido2}) { - if (bitset & static_cast<uint8_t>(revision)) { - FIDO_LOG(DEBUG) << "Detected Support for " << revision << "."; - service_revisions.push_back(revision); - } - } - - std::move(callback).Run(std::move(service_revisions)); -} - -} // namespace - -FidoBleConnection::FidoBleConnection(BluetoothAdapter* adapter, - std::string device_address, - BluetoothUUID service_uuid, - ReadCallback read_callback) - : adapter_(adapter), - address_(std::move(device_address)), - read_callback_(std::move(read_callback)), - service_uuid_(service_uuid) { - DCHECK(adapter_); - adapter_->AddObserver(this); - DCHECK(!address_.empty()); -} - -FidoBleConnection::~FidoBleConnection() { - adapter_->RemoveObserver(this); -} - -BluetoothDevice* FidoBleConnection::GetBleDevice() { - return adapter_->GetDevice(address()); -} - -const BluetoothDevice* FidoBleConnection::GetBleDevice() const { - return adapter_->GetDevice(address()); -} - -void FidoBleConnection::Connect(ConnectionCallback callback) { - auto* device = GetBleDevice(); - if (!device) { - FIDO_LOG(ERROR) << "Failed to get Device."; - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), false)); - return; - } - - pending_connection_callback_ = std::move(callback); - FIDO_LOG(DEBUG) << "Creating a GATT connection..."; - device->CreateGattConnection( - base::BindOnce(&FidoBleConnection::OnCreateGattConnection, - weak_factory_.GetWeakPtr()), - BluetoothUUID(kGoogleCableUUID128)); -} - -void FidoBleConnection::ReadControlPointLength( - ControlPointLengthCallback callback) { - const auto* fido_service = GetFidoService(); - if (!fido_service) { - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), std::nullopt)); - return; - } - - if (!control_point_length_id_) { - FIDO_LOG(ERROR) << "Failed to get Control Point Length."; - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), std::nullopt)); - return; - } - - BluetoothRemoteGattCharacteristic* control_point_length = - fido_service->GetCharacteristic(*control_point_length_id_); - if (!control_point_length) { - FIDO_LOG(ERROR) << "No Control Point Length characteristic present."; - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), std::nullopt)); - return; - } - - FIDO_LOG(DEBUG) << "Read Control Point Length"; - control_point_length->ReadRemoteCharacteristic( - base::BindOnce(OnReadControlPointLength, std::move(callback))); -} - -void FidoBleConnection::WriteControlPoint(const std::vector<uint8_t>& data, - WriteCallback callback) { - const auto* fido_service = GetFidoService(); - if (!fido_service) { - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), false)); - return; - } - - if (!control_point_id_) { - FIDO_LOG(ERROR) << "Failed to get Control Point."; - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), false)); - return; - } - - BluetoothRemoteGattCharacteristic* control_point = - fido_service->GetCharacteristic(*control_point_id_); - if (!control_point) { - FIDO_LOG(ERROR) << "Control Point characteristic not present."; - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), false)); - return; - } - - // Attempt a write without response for performance reasons. Fall back to a - // confirmed write when the characteristic does not provide the required - // property. - BluetoothRemoteGattCharacteristic::WriteType write_type = - (control_point->GetProperties() & - BluetoothRemoteGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE) - ? BluetoothRemoteGattCharacteristic::WriteType::kWithoutResponse - : BluetoothRemoteGattCharacteristic::WriteType::kWithResponse; - - FIDO_LOG(DEBUG) << "Wrote Control Point."; - auto split_callback = base::SplitOnceCallback(std::move(callback)); - control_point->WriteRemoteCharacteristic( - data, write_type, - base::BindOnce(OnWriteRemoteCharacteristic, - std::move(split_callback.first)), - base::BindOnce(OnWriteRemoteCharacteristicError, - std::move(split_callback.second))); -} - -void FidoBleConnection::OnCreateGattConnection( - std::unique_ptr<BluetoothGattConnection> connection, - std::optional<BluetoothDevice::ConnectErrorCode> error_code) { - FIDO_LOG(DEBUG) << "GATT connection created"; - DCHECK(pending_connection_callback_); - if (error_code.has_value()) { - FIDO_LOG(ERROR) << "CreateGattConnection() failed: " - << ToString(error_code.value()); - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, - base::BindOnce(std::move(pending_connection_callback_), false)); - return; - } - connection_ = std::move(connection); - - BluetoothDevice* device = adapter_->GetDevice(address_); - if (!device) { - FIDO_LOG(ERROR) << "Failed to get Device."; - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, - base::BindOnce(std::move(pending_connection_callback_), false)); - return; - } - - if (!device->IsGattServicesDiscoveryComplete()) { - FIDO_LOG(DEBUG) << "Waiting for GATT service discovery to complete"; - waiting_for_gatt_discovery_ = true; - return; - } - - ConnectToFidoService(); -} - -void FidoBleConnection::ConnectToFidoService() { - FIDO_LOG(EVENT) << "Attempting to connect to a Fido service."; - DCHECK(pending_connection_callback_); - const auto* fido_service = GetFidoService(); - if (!fido_service) { - FIDO_LOG(ERROR) << "Failed to get Fido Service."; - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, - base::BindOnce(std::move(pending_connection_callback_), false)); - return; - } - - for (const auto* characteristic : fido_service->GetCharacteristics()) { - std::string uuid = characteristic->GetUUID().canonical_value(); - if (uuid == kFidoControlPointLengthUUID) { - control_point_length_id_ = characteristic->GetIdentifier(); - FIDO_LOG(DEBUG) << "Got Fido Control Point Length: " - << *control_point_length_id_; - continue; - } - - if (uuid == kFidoControlPointUUID) { - control_point_id_ = characteristic->GetIdentifier(); - FIDO_LOG(DEBUG) << "Got Fido Control Point: " << *control_point_id_; - continue; - } - - if (uuid == kFidoStatusUUID) { - status_id_ = characteristic->GetIdentifier(); - FIDO_LOG(DEBUG) << "Got Fido Status: " << *status_id_; - continue; - } - - if (uuid == kFidoServiceRevisionUUID) { - service_revision_id_ = characteristic->GetIdentifier(); - FIDO_LOG(DEBUG) << "Got Fido Service Revision: " << *service_revision_id_; - continue; - } - - if (uuid == kFidoServiceRevisionBitfieldUUID) { - service_revision_bitfield_id_ = characteristic->GetIdentifier(); - FIDO_LOG(DEBUG) << "Got Fido Service Revision Bitfield: " - << *service_revision_bitfield_id_; - continue; - } - - FIDO_LOG(DEBUG) << "Unknown FIDO service characteristic: " << uuid; - } - - if (!control_point_length_id_ || !control_point_id_ || !status_id_ || - (!service_revision_id_ && !service_revision_bitfield_id_)) { - FIDO_LOG(ERROR) << "Fido Characteristics missing."; - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, - base::BindOnce(std::move(pending_connection_callback_), false)); - return; - } - - // In case the bitfield characteristic is present, the client has to select a - // supported version by writing the corresponding bit. Reference: - // https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#ble-protocol-overview - if (service_revision_bitfield_id_) { - // This callback is only repeating so that it can be bound to two different - // callbacks. - auto callback = base::BindRepeating( - &FidoBleConnection::OnReadServiceRevisions, weak_factory_.GetWeakPtr()); - fido_service->GetCharacteristic(*service_revision_bitfield_id_) - ->ReadRemoteCharacteristic( - base::BindOnce(OnReadServiceRevisionBitfield, callback)); - return; - } - - StartNotifySession(); -} - -void FidoBleConnection::OnReadServiceRevisions( - std::vector<ServiceRevision> service_revisions) { - DCHECK(pending_connection_callback_); - if (service_revisions.empty()) { - FIDO_LOG(ERROR) << "Could not obtain Service Revisions."; - std::move(pending_connection_callback_).Run(false); - return; - } - - // Write the most recent supported service revision back to the - // characteristic. Note that this information is currently not used in another - // way, as we will still attempt a CTAP GetInfo() command, even if only U2F is - // supported. - // TODO(crbug.com/40547449): Consider short circuiting to the - // U2F logic if FIDO2 is not supported. - DCHECK_EQ( - *std::min_element(service_revisions.begin(), service_revisions.end()), - service_revisions.back()); - WriteServiceRevision(service_revisions.back()); -} - -void FidoBleConnection::WriteServiceRevision(ServiceRevision service_revision) { - auto callback = base::BindOnce(&FidoBleConnection::OnServiceRevisionWritten, - weak_factory_.GetWeakPtr()); - - const auto* fido_service = GetFidoService(); - if (!fido_service) { - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), false)); - return; - } - - auto split_callback = base::SplitOnceCallback(std::move(callback)); - DCHECK(service_revision_bitfield_id_); - fido_service->GetCharacteristic(*service_revision_bitfield_id_) - ->WriteRemoteCharacteristic( - base::byte_span_from_ref(static_cast<uint8_t>(service_revision)), - BluetoothRemoteGattCharacteristic::WriteType::kWithResponse, - base::BindOnce(OnWriteRemoteCharacteristic, - std::move(split_callback.first)), - base::BindOnce(OnWriteRemoteCharacteristicError, - std::move(split_callback.second))); -} - -void FidoBleConnection::OnServiceRevisionWritten(bool success) { - DCHECK(pending_connection_callback_); - if (success) { - FIDO_LOG(DEBUG) << "Service Revision successfully written."; - StartNotifySession(); - return; - } - - FIDO_LOG(ERROR) << "Failed to write Service Revision."; - std::move(pending_connection_callback_).Run(false); -} - -void FidoBleConnection::StartNotifySession() { - DCHECK(pending_connection_callback_); - const auto* fido_service = GetFidoService(); - if (!fido_service) { - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, - base::BindOnce(std::move(pending_connection_callback_), false)); - return; - } - - DCHECK(status_id_); - fido_service->GetCharacteristic(*status_id_) - ->StartNotifySession( - base::BindOnce(&FidoBleConnection::OnStartNotifySession, - weak_factory_.GetWeakPtr()), - base::BindOnce(&FidoBleConnection::OnStartNotifySessionError, - weak_factory_.GetWeakPtr())); -} - -void FidoBleConnection::OnStartNotifySession( - std::unique_ptr<BluetoothGattNotifySession> notify_session) { - notify_session_ = std::move(notify_session); - FIDO_LOG(DEBUG) << "Created notification session. Connection established."; - std::move(pending_connection_callback_).Run(true); -} - -void FidoBleConnection::OnStartNotifySessionError( - BluetoothGattService::GattErrorCode error_code) { - FIDO_LOG(ERROR) << "StartNotifySession() failed: " << ToString(error_code); - std::move(pending_connection_callback_).Run(false); -} - -void FidoBleConnection::DeviceAddressChanged(BluetoothAdapter* adapter, - BluetoothDevice* device, - const std::string& old_address) { - if (address_ == old_address) - address_ = device->GetAddress(); -} - -void FidoBleConnection::GattCharacteristicValueChanged( - BluetoothAdapter* adapter, - BluetoothRemoteGattCharacteristic* characteristic, - const std::vector<uint8_t>& value) { - if (characteristic->GetIdentifier() != status_id_) - return; - FIDO_LOG(DEBUG) << "Status characteristic value changed."; - read_callback_.Run(value); -} - -void FidoBleConnection::GattServicesDiscovered(BluetoothAdapter* adapter, - BluetoothDevice* device) { - if (adapter != adapter_ || device->GetAddress() != address_) { - return; - } - - FIDO_LOG(DEBUG) << "GATT services discovered for " << device->GetAddress(); - - if (waiting_for_gatt_discovery_) { - waiting_for_gatt_discovery_ = false; - ConnectToFidoService(); - } -} - -const BluetoothRemoteGattService* FidoBleConnection::GetFidoService() { - if (!connection_ || !connection_->IsConnected()) { - FIDO_LOG(ERROR) << "No BLE connection."; - return nullptr; - } - - DCHECK_EQ(address_, connection_->GetDeviceAddress()); - BluetoothDevice* device = GetBleDevice(); - - for (const auto* service : device->GetGattServices()) { - if (service->GetUUID() == service_uuid_) { - return service; - } - } - - FIDO_LOG(ERROR) << "No Fido service present."; - return nullptr; -} - -// static -void FidoBleConnection::OnReadControlPointLength( - ControlPointLengthCallback callback, - std::optional<device::BluetoothGattService::GattErrorCode> error_code, - const std::vector<uint8_t>& value) { - if (error_code.has_value()) { - FIDO_LOG(ERROR) << "Error reading Control Point Length: " - << ToString(error_code.value()); - std::move(callback).Run(std::nullopt); - return; - } - if (value.size() != 2) { - FIDO_LOG(ERROR) << "Wrong Control Point Length: " << value.size() - << " bytes"; - std::move(callback).Run(std::nullopt); - return; - } - - uint16_t length = (value[0] << 8) | value[1]; - FIDO_LOG(DEBUG) << "Control Point Length: " << length; - std::move(callback).Run(length); -} - -} // namespace device
diff --git a/device/fido/cable/fido_ble_connection.h b/device/fido/cable/fido_ble_connection.h deleted file mode 100644 index 54f8dcbd..0000000 --- a/device/fido/cable/fido_ble_connection.h +++ /dev/null
@@ -1,133 +0,0 @@ -// Copyright 2017 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef DEVICE_FIDO_CABLE_FIDO_BLE_CONNECTION_H_ -#define DEVICE_FIDO_CABLE_FIDO_BLE_CONNECTION_H_ - -#include <stdint.h> - -#include <memory> -#include <optional> -#include <set> -#include <string> -#include <vector> - -#include "base/component_export.h" -#include "base/functional/callback_forward.h" -#include "base/memory/scoped_refptr.h" -#include "base/memory/weak_ptr.h" -#include "device/bluetooth/bluetooth_adapter.h" -#include "device/bluetooth/bluetooth_device.h" -#include "device/bluetooth/bluetooth_gatt_service.h" - -namespace device { - -class BluetoothUUID; -class BluetoothGattConnection; -class BluetoothGattNotifySession; -class BluetoothRemoteGattCharacteristic; - -// A connection to the Fido service of an authenticator over BLE. Detailed -// specification of the BLE device can be found here: -// https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#ble -class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleConnection - : public BluetoothAdapter::Observer { - public: - // Valid Service Revisions. Reference: - // https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#ble-fido-service - enum class ServiceRevision : uint8_t { - kU2f11 = 1 << 7, - kU2f12 = 1 << 6, - kFido2 = 1 << 5, - }; - - // This callback informs clients repeatedly about changes in the device - // connection. This class makes an initial connection attempt on construction, - // which result in returned via this callback. Future invocations happen if - // devices connect or disconnect from the adapter. - using ConnectionCallback = base::OnceCallback<void(bool)>; - using WriteCallback = base::OnceCallback<void(bool)>; - using ReadCallback = base::RepeatingCallback<void(std::vector<uint8_t>)>; - using ControlPointLengthCallback = - base::OnceCallback<void(std::optional<uint16_t>)>; - - FidoBleConnection(BluetoothAdapter* adapter, - std::string device_address, - BluetoothUUID service_uuid, - ReadCallback read_callback); - - FidoBleConnection(const FidoBleConnection&) = delete; - FidoBleConnection& operator=(const FidoBleConnection&) = delete; - - ~FidoBleConnection() override; - - const std::string& address() const { return address_; } - - BluetoothDevice* GetBleDevice(); - const BluetoothDevice* GetBleDevice() const; - - virtual void Connect(ConnectionCallback callback); - virtual void ReadControlPointLength(ControlPointLengthCallback callback); - virtual void WriteControlPoint(const std::vector<uint8_t>& data, - WriteCallback callback); - - protected: - scoped_refptr<BluetoothAdapter> adapter_; - std::string address_; - ReadCallback read_callback_; - - private: - // BluetoothAdapter::Observer: - void DeviceAddressChanged(BluetoothAdapter* adapter, - BluetoothDevice* device, - const std::string& old_address) override; - void GattCharacteristicValueChanged( - BluetoothAdapter* adapter, - BluetoothRemoteGattCharacteristic* characteristic, - const std::vector<uint8_t>& value) override; - void GattServicesDiscovered(BluetoothAdapter* adapter, - BluetoothDevice* device) override; - - const BluetoothRemoteGattService* GetFidoService(); - - void OnCreateGattConnection( - std::unique_ptr<BluetoothGattConnection> connection, - std::optional<BluetoothDevice::ConnectErrorCode> error_code); - - void ConnectToFidoService(); - void OnReadServiceRevisions(std::vector<ServiceRevision> service_revisions); - - void WriteServiceRevision(ServiceRevision service_revision); - void OnServiceRevisionWritten(bool success); - - void StartNotifySession(); - void OnStartNotifySession( - std::unique_ptr<BluetoothGattNotifySession> notify_session); - void OnStartNotifySessionError( - BluetoothGattService::GattErrorCode error_code); - - static void OnReadControlPointLength( - ControlPointLengthCallback callback, - std::optional<device::BluetoothGattService::GattErrorCode> error_code, - const std::vector<uint8_t>& value); - - std::unique_ptr<BluetoothGattConnection> connection_; - std::unique_ptr<BluetoothGattNotifySession> notify_session_; - - ConnectionCallback pending_connection_callback_; - bool waiting_for_gatt_discovery_ = false; - const BluetoothUUID service_uuid_; - - std::optional<std::string> control_point_length_id_; - std::optional<std::string> control_point_id_; - std::optional<std::string> status_id_; - std::optional<std::string> service_revision_id_; - std::optional<std::string> service_revision_bitfield_id_; - - base::WeakPtrFactory<FidoBleConnection> weak_factory_{this}; -}; - -} // namespace device - -#endif // DEVICE_FIDO_CABLE_FIDO_BLE_CONNECTION_H_
diff --git a/device/fido/cable/fido_ble_connection_unittest.cc b/device/fido/cable/fido_ble_connection_unittest.cc deleted file mode 100644 index d18108e0..0000000 --- a/device/fido/cable/fido_ble_connection_unittest.cc +++ /dev/null
@@ -1,801 +0,0 @@ -// Copyright 2017 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "device/fido/cable/fido_ble_connection.h" - -#include <algorithm> -#include <bitset> -#include <cstdint> -#include <memory> -#include <optional> -#include <set> -#include <string> -#include <string_view> -#include <utility> -#include <vector> - -#include "base/functional/bind.h" -#include "base/functional/callback_helpers.h" -#include "base/location.h" -#include "base/memory/ptr_util.h" -#include "base/memory/raw_ptr.h" -#include "base/memory/scoped_refptr.h" -#include "base/run_loop.h" -#include "base/task/single_thread_task_runner.h" -#include "base/test/task_environment.h" -#include "base/test/test_future.h" -#include "build/build_config.h" -#include "device/bluetooth/bluetooth_adapter_factory.h" -#include "device/bluetooth/bluetooth_gatt_characteristic.h" -#include "device/bluetooth/bluetooth_gatt_service.h" -#include "device/bluetooth/test/bluetooth_test.h" -#include "device/bluetooth/test/mock_bluetooth_adapter.h" -#include "device/bluetooth/test/mock_bluetooth_device.h" -#include "device/bluetooth/test/mock_bluetooth_gatt_characteristic.h" -#include "device/bluetooth/test/mock_bluetooth_gatt_connection.h" -#include "device/bluetooth/test/mock_bluetooth_gatt_notify_session.h" -#include "device/bluetooth/test/mock_bluetooth_gatt_service.h" -#include "device/fido/cable/fido_ble_uuids.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -#if BUILDFLAG(IS_ANDROID) -#include "device/bluetooth/test/bluetooth_test_android.h" -#elif BUILDFLAG(IS_MAC) -#include "device/bluetooth/test/bluetooth_test_mac.h" -#elif BUILDFLAG(IS_WIN) -#include "device/bluetooth/test/bluetooth_test_win.h" -#elif BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) -#include "device/bluetooth/test/bluetooth_test_bluez.h" -#elif BUILDFLAG(IS_FUCHSIA) -#include "device/bluetooth/test/bluetooth_test_fuchsia.h" -#endif - -namespace device { - -using ::testing::_; -using ::testing::ElementsAre; -using ::testing::Invoke; -using ::testing::IsEmpty; -using ::testing::Return; - -using NiceMockBluetoothAdapter = ::testing::NiceMock<MockBluetoothAdapter>; -using NiceMockBluetoothDevice = ::testing::NiceMock<MockBluetoothDevice>; -using NiceMockBluetoothGattService = - ::testing::NiceMock<MockBluetoothGattService>; -using NiceMockBluetoothGattCharacteristic = - ::testing::NiceMock<MockBluetoothGattCharacteristic>; -using NiceMockBluetoothGattConnection = - ::testing::NiceMock<MockBluetoothGattConnection>; -using NiceMockBluetoothGattNotifySession = - ::testing::NiceMock<MockBluetoothGattNotifySession>; - -namespace { - -constexpr auto kDefaultServiceRevision = - static_cast<uint8_t>(FidoBleConnection::ServiceRevision::kFido2); - -std::vector<uint8_t> ToByteVector(std::string_view str) { - return std::vector<uint8_t>(str.begin(), str.end()); -} - -BluetoothDevice* GetMockDevice(MockBluetoothAdapter* adapter, - const std::string& address) { - const std::vector<BluetoothDevice*> devices = adapter->GetMockDevices(); - auto found = - std::ranges::find(devices, address, &BluetoothDevice::GetAddress); - return found != devices.end() ? *found : nullptr; -} - -class TestReadCallback { - public: - void OnRead(std::vector<uint8_t> value) { - value_ = std::move(value); - run_loop_->Quit(); - } - - const std::vector<uint8_t> WaitForResult() { - run_loop_->Run(); - run_loop_.emplace(); - return value_; - } - - FidoBleConnection::ReadCallback GetCallback() { - return base::BindRepeating(&TestReadCallback::OnRead, - base::Unretained(this)); - } - - private: - std::vector<uint8_t> value_; - std::optional<base::RunLoop> run_loop_{std::in_place}; -}; - -using TestConnectionFuture = base::test::TestFuture<bool>; - -using TestReadControlPointLengthFuture = - base::test::TestFuture<std::optional<uint16_t>>; - -using TestReadServiceRevisionsFuture = - base::test::TestFuture<std::set<FidoBleConnection::ServiceRevision>>; - -using TestWriteCallback = base::test::TestFuture<bool>; -} // namespace - -class FidoBleConnectionTest : public ::testing::Test { - public: - FidoBleConnectionTest() { - ON_CALL(*adapter_, GetDevice(_)) - .WillByDefault([this](const std::string& address) { - return GetMockDevice(adapter_.get(), address); - }); - - BluetoothAdapterFactory::SetAdapterForTesting(adapter_); - } - - BluetoothAdapter* adapter() { return adapter_.get(); } - MockBluetoothDevice* device() { return fido_device_; } - - void AddFidoDevice(const std::string& device_address) { - auto fido_device = std::make_unique<NiceMockBluetoothDevice>( - adapter_.get(), /* bluetooth_class */ 0u, - BluetoothTest::kTestDeviceNameU2f, device_address, /* paired */ true, - /* connected */ false); - fido_device_ = fido_device.get(); - adapter_->AddMockDevice(std::move(fido_device)); - - ON_CALL(*fido_device_, GetGattServices()) - .WillByDefault( - Invoke(fido_device_.get(), &MockBluetoothDevice::GetMockServices)); - - ON_CALL(*fido_device_, GetGattService(_)) - .WillByDefault( - Invoke(fido_device_.get(), &MockBluetoothDevice::GetMockService)); - AddFidoService(); - } - - void SetupConnectingFidoDevice(const std::string& device_address) { - ON_CALL(*fido_device_, CreateGattConnection) - .WillByDefault([this, &device_address](auto callback, - auto service_uuid) { - connection_ = - new NiceMockBluetoothGattConnection(adapter_, device_address); - std::move(callback).Run( - std::move(base::WrapUnique(connection_.get())), - /*error_code=*/std::nullopt); - }); - - ON_CALL(*fido_device_, IsGattServicesDiscoveryComplete) - .WillByDefault(Return(true)); - - ON_CALL(*fido_service_revision_bitfield_, ReadRemoteCharacteristic_) - .WillByDefault( - [=](BluetoothRemoteGattCharacteristic::ValueCallback& callback) { - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), - /*error_code=*/std::nullopt, - std::vector<uint8_t>( - {kDefaultServiceRevision}))); - }); - - ON_CALL(*fido_service_revision_bitfield_, WriteRemoteCharacteristic_) - .WillByDefault( - [=](base::span<const uint8_t> value, - BluetoothRemoteGattCharacteristic::WriteType, - base::OnceClosure& callback, - const BluetoothRemoteGattCharacteristic::ErrorCallback&) { - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, std::move(callback)); - }); - - ON_CALL(*fido_status_, StartNotifySession_(_, _)) - .WillByDefault( - [this](BluetoothRemoteGattCharacteristic::NotifySessionCallback& - callback, - BluetoothRemoteGattCharacteristic::ErrorCallback&) { - notify_session_ = new NiceMockBluetoothGattNotifySession( - fido_status_->GetWeakPtr()); - std::move(callback).Run(base::WrapUnique(notify_session_.get())); - }); - } - - void SimulateGattDiscoveryComplete(bool complete) { - EXPECT_CALL(*fido_device_, IsGattServicesDiscoveryComplete) - .WillOnce(Return(complete)); - } - - void SimulateGattConnectionError() { - EXPECT_CALL(*fido_device_, CreateGattConnection) - .WillOnce([](auto callback, auto service_uuid) { - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), - /*connection=*/nullptr, - BluetoothDevice::ERROR_FAILED)); - }); - } - - void SimulateGattNotifySessionStartError() { - EXPECT_CALL(*fido_status_, StartNotifySession_(_, _)) - .WillOnce( - [](auto&&, - BluetoothGattCharacteristic::ErrorCallback& error_callback) { - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, - base::BindOnce(std::move(error_callback), - BluetoothGattService::GattErrorCode::kFailed)); - }); - } - - void NotifyStatusChanged(const std::vector<uint8_t>& value) { - for (auto& observer : adapter_->GetObservers()) - observer.GattCharacteristicValueChanged(adapter_.get(), fido_status_, - value); - } - - void NotifyGattServicesDiscovered() { - adapter_->NotifyGattServicesDiscovered(fido_device_); - } - - void ChangeDeviceAddressAndNotifyObservers(std::string new_address) { - auto old_address = fido_device_->GetAddress(); - EXPECT_CALL(*fido_device_, GetAddress) - .WillRepeatedly(::testing::Return(new_address)); - for (auto& observer : adapter_->GetObservers()) - observer.DeviceAddressChanged(adapter_.get(), fido_device_, old_address); - } - - void SetNextReadControlPointLengthReponse(bool success, - const std::vector<uint8_t>& value) { - EXPECT_CALL(*fido_control_point_length_, ReadRemoteCharacteristic_(_)) - .WillOnce( - [success, value]( - BluetoothRemoteGattCharacteristic::ValueCallback& callback) { - std::optional<BluetoothGattService::GattErrorCode> error_code; - if (!success) - error_code = BluetoothGattService::GattErrorCode::kFailed; - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, - base::BindOnce(std::move(callback), error_code, value)); - }); - } - - void SetNextReadServiceRevisionResponse(bool success, - const std::vector<uint8_t>& value) { - EXPECT_CALL(*fido_service_revision_, ReadRemoteCharacteristic_(_)) - .WillOnce( - [success, value]( - BluetoothRemoteGattCharacteristic::ValueCallback& callback) { - std::optional<BluetoothGattService::GattErrorCode> error_code; - if (!success) - error_code = BluetoothGattService::GattErrorCode::kFailed; - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, - base::BindOnce(std::move(callback), error_code, value)); - }); - } - - void SetNextReadServiceRevisionBitfieldResponse( - bool success, - const std::vector<uint8_t>& value) { - EXPECT_CALL(*fido_service_revision_bitfield_, ReadRemoteCharacteristic_(_)) - .WillOnce( - [success, value]( - BluetoothRemoteGattCharacteristic::ValueCallback& callback) { - auto error_code = - success ? std::nullopt - : std::make_optional( - BluetoothGattService::GattErrorCode::kFailed); - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, - base::BindOnce(std::move(callback), error_code, value)); - }); - } - - void SetNextWriteControlPointResponse(bool success) { - EXPECT_CALL( - *fido_control_point_, - WriteRemoteCharacteristic_( - _, BluetoothRemoteGattCharacteristic::WriteType::kWithoutResponse, - _, _)) - .WillOnce( - [success](const auto& data, - BluetoothRemoteGattCharacteristic::WriteType, - base::OnceClosure& callback, - BluetoothRemoteGattCharacteristic::ErrorCallback& - error_callback) { - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, - success ? std::move(callback) - : base::BindOnce( - std::move(error_callback), - BluetoothGattService::GattErrorCode::kFailed)); - }); - } - - void SetNextWriteServiceRevisionResponse(std::vector<uint8_t> expected_data, - bool success) { - EXPECT_CALL( - *fido_service_revision_bitfield_, - WriteRemoteCharacteristic_( - testing::ElementsAreArray(expected_data), - BluetoothRemoteGattCharacteristic::WriteType::kWithResponse, _, _)) - .WillOnce( - [success](const auto& data, - BluetoothRemoteGattCharacteristic::WriteType, - base::OnceClosure& callback, - BluetoothRemoteGattCharacteristic::ErrorCallback& - error_callback) { - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, - success ? std::move(callback) - : base::BindOnce( - std::move(error_callback), - BluetoothGattService::GattErrorCode::kFailed)); - }); - } - - void AddFidoService() { - auto fido_service = std::make_unique<NiceMockBluetoothGattService>( - fido_device_, "fido_service", BluetoothUUID(kFidoServiceUUID), - /*is_primary=*/true); - fido_service_ = fido_service.get(); - fido_device_->AddMockService(std::move(fido_service)); - - { - auto fido_control_point = - std::make_unique<NiceMockBluetoothGattCharacteristic>( - fido_service_, "fido_control_point", - BluetoothUUID(kFidoControlPointUUID), - BluetoothGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE, - BluetoothGattCharacteristic::PERMISSION_NONE); - fido_control_point_ = fido_control_point.get(); - fido_service_->AddMockCharacteristic(std::move(fido_control_point)); - } - - { - auto fido_status = std::make_unique<NiceMockBluetoothGattCharacteristic>( - fido_service_, "fido_status", BluetoothUUID(kFidoStatusUUID), - BluetoothGattCharacteristic::PROPERTY_NOTIFY, - BluetoothGattCharacteristic::PERMISSION_NONE); - fido_status_ = fido_status.get(); - fido_service_->AddMockCharacteristic(std::move(fido_status)); - } - - { - auto fido_control_point_length = - std::make_unique<NiceMockBluetoothGattCharacteristic>( - fido_service_, "fido_control_point_length", - BluetoothUUID(kFidoControlPointLengthUUID), - BluetoothGattCharacteristic::PROPERTY_READ, - BluetoothGattCharacteristic::PERMISSION_NONE); - fido_control_point_length_ = fido_control_point_length.get(); - fido_service_->AddMockCharacteristic( - std::move(fido_control_point_length)); - } - - { - auto fido_service_revision = - std::make_unique<NiceMockBluetoothGattCharacteristic>( - fido_service_, "fido_service_revision", - BluetoothUUID(kFidoServiceRevisionUUID), - BluetoothGattCharacteristic::PROPERTY_READ, - BluetoothGattCharacteristic::PERMISSION_NONE); - fido_service_revision_ = fido_service_revision.get(); - fido_service_->AddMockCharacteristic(std::move(fido_service_revision)); - } - - { - auto fido_service_revision_bitfield = - std::make_unique<NiceMockBluetoothGattCharacteristic>( - fido_service_, "fido_service_revision_bitfield", - BluetoothUUID(kFidoServiceRevisionBitfieldUUID), - BluetoothGattCharacteristic::PROPERTY_READ | - BluetoothGattCharacteristic::PROPERTY_WRITE, - BluetoothGattCharacteristic::PERMISSION_NONE); - fido_service_revision_bitfield_ = fido_service_revision_bitfield.get(); - fido_service_->AddMockCharacteristic( - std::move(fido_service_revision_bitfield)); - } - } - - protected: - static BluetoothUUID uuid() { return BluetoothUUID(kFidoServiceUUID); } - - private: - base::test::TaskEnvironment task_environment_; - - scoped_refptr<MockBluetoothAdapter> adapter_ = - base::MakeRefCounted<NiceMockBluetoothAdapter>(); - - raw_ptr<MockBluetoothDevice> fido_device_; - raw_ptr<MockBluetoothGattService> fido_service_; - - raw_ptr<MockBluetoothGattCharacteristic> fido_control_point_; - raw_ptr<MockBluetoothGattCharacteristic> fido_status_; - raw_ptr<MockBluetoothGattCharacteristic> fido_control_point_length_; - raw_ptr<MockBluetoothGattCharacteristic> fido_service_revision_; - raw_ptr<MockBluetoothGattCharacteristic> fido_service_revision_bitfield_; - - raw_ptr<MockBluetoothGattConnection, AcrossTasksDanglingUntriaged> - connection_; - raw_ptr<MockBluetoothGattNotifySession, AcrossTasksDanglingUntriaged> - notify_session_; -}; - -TEST_F(FidoBleConnectionTest, Address) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - FidoBleConnection connection(adapter(), device_address, uuid(), - base::DoNothing()); - connection.Connect(base::DoNothing()); - EXPECT_EQ(device_address, connection.address()); -} - -TEST_F(FidoBleConnectionTest, DeviceNotPresent) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - FidoBleConnection connection(adapter(), device_address, uuid(), - base::DoNothing()); - - TestConnectionFuture connection_future; - connection.Connect(connection_future.GetCallback()); - EXPECT_TRUE(connection_future.Wait()); - EXPECT_FALSE(connection_future.Get()); -} - -TEST_F(FidoBleConnectionTest, PreConnected) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - AddFidoDevice(device_address); - SetupConnectingFidoDevice(device_address); - FidoBleConnection connection(adapter(), device_address, uuid(), - base::DoNothing()); - - TestConnectionFuture connection_future; - connection.Connect(connection_future.GetCallback()); - EXPECT_TRUE(connection_future.Wait()); - EXPECT_TRUE(connection_future.Get()); -} - -TEST_F(FidoBleConnectionTest, NoConnectionWithoutCompletedGattDiscovery) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - AddFidoDevice(device_address); - SetupConnectingFidoDevice(device_address); - FidoBleConnection connection(adapter(), device_address, uuid(), - base::DoNothing()); - - SimulateGattDiscoveryComplete(false); - TestConnectionFuture connection_future; - connection.Connect(connection_future.GetCallback()); - base::RunLoop().RunUntilIdle(); - EXPECT_FALSE(connection_future.IsReady()); - - NotifyGattServicesDiscovered(); - EXPECT_TRUE(connection_future.Wait()); - EXPECT_TRUE(connection_future.Get()); -} - -TEST_F(FidoBleConnectionTest, GattServicesDiscoveredIgnoredBeforeConnection) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - AddFidoDevice(device_address); - SetupConnectingFidoDevice(device_address); - FidoBleConnection connection(adapter(), device_address, uuid(), - base::DoNothing()); - NotifyGattServicesDiscovered(); - - SimulateGattDiscoveryComplete(false); - TestConnectionFuture connection_future; - connection.Connect(connection_future.GetCallback()); - base::RunLoop().RunUntilIdle(); - EXPECT_FALSE(connection_future.IsReady()); - - NotifyGattServicesDiscovered(); - EXPECT_TRUE(connection_future.Wait()); - EXPECT_TRUE(connection_future.Get()); -} - -TEST_F(FidoBleConnectionTest, GattServicesDiscoveredAgain) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - AddFidoDevice(device_address); - SetupConnectingFidoDevice(device_address); - FidoBleConnection connection(adapter(), device_address, uuid(), - base::DoNothing()); - - TestConnectionFuture connection_future; - connection.Connect(connection_future.GetCallback()); - NotifyGattServicesDiscovered(); - EXPECT_TRUE(connection_future.Wait()); - EXPECT_TRUE(connection_future.Get()); - - // A second call to the event handler should not trigger another attempt to - // obtain Gatt Services. - EXPECT_CALL(*device(), GetGattServices).Times(0); - EXPECT_CALL(*device(), GetGattService).Times(0); - NotifyGattServicesDiscovered(); -} - -TEST_F(FidoBleConnectionTest, SimulateGattConnectionError) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - AddFidoDevice(device_address); - SetupConnectingFidoDevice(device_address); - FidoBleConnection connection(adapter(), device_address, uuid(), - base::DoNothing()); - - SimulateGattConnectionError(); - TestConnectionFuture connection_future; - connection.Connect(connection_future.GetCallback()); - EXPECT_TRUE(connection_future.Wait()); - EXPECT_FALSE(connection_future.Get()); -} - -TEST_F(FidoBleConnectionTest, SimulateGattNotifySessionStartError) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - AddFidoDevice(device_address); - SetupConnectingFidoDevice(device_address); - FidoBleConnection connection(adapter(), device_address, uuid(), - base::DoNothing()); - - SimulateGattNotifySessionStartError(); - TestConnectionFuture connection_future; - connection.Connect(connection_future.GetCallback()); - EXPECT_TRUE(connection_future.Wait()); - EXPECT_FALSE(connection_future.Get()); -} - -TEST_F(FidoBleConnectionTest, MultipleServiceRevisions) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - AddFidoDevice(device_address); - SetupConnectingFidoDevice(device_address); - - static constexpr struct { - std::bitset<8> supported_revisions; - std::bitset<8> selected_revision; - } test_cases[] = { - // Only U2F 1.1 is supported, pick it. - {0b1000'0000, 0b1000'0000}, - // Only U2F 1.2 is supported, pick it. - {0b0100'0000, 0b0100'0000}, - // U2F 1.1 and U2F 1.2 are supported, pick U2F 1.2. - {0b1100'0000, 0b0100'0000}, - // Only FIDO2 is supported, pick it. - {0b0010'0000, 0b0010'0000}, - // U2F 1.1 and FIDO2 are supported, pick FIDO2. - {0b1010'0000, 0b0010'0000}, - // U2F 1.2 and FIDO2 are supported, pick FIDO2. - {0b0110'0000, 0b0010'0000}, - // U2F 1.1, U2F 1.2 and FIDO2 are supported, pick FIDO2. - {0b1110'0000, 0b0010'0000}, - // U2F 1.1 and a future revision are supported, pick U2F 1.1. - {0b1000'1000, 0b1000'0000}, - // U2F 1.2 and a future revision are supported, pick U2F 1.2. - {0b0100'1000, 0b0100'0000}, - // FIDO2 and a future revision are supported, pick FIDO2. - {0b0010'1000, 0b0010'0000}, - }; - - for (const auto& test_case : test_cases) { - SCOPED_TRACE(::testing::Message() - << "Supported Revisions: " << test_case.supported_revisions - << ", Selected Revision: " << test_case.selected_revision); - SetNextReadServiceRevisionBitfieldResponse( - true, {static_cast<uint8_t>(test_case.supported_revisions.to_ulong())}); - - SetNextWriteServiceRevisionResponse( - {static_cast<uint8_t>(test_case.selected_revision.to_ulong())}, true); - - FidoBleConnection connection(adapter(), device_address, uuid(), - base::DoNothing()); - TestConnectionFuture connection_future; - connection.Connect(connection_future.GetCallback()); - EXPECT_TRUE(connection_future.Wait()); - EXPECT_TRUE(connection_future.Get()); - } -} - -TEST_F(FidoBleConnectionTest, UnsupportedServiceRevisions) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - AddFidoDevice(device_address); - SetupConnectingFidoDevice(device_address); - - // Test failure cases. - static constexpr struct { - std::bitset<8> supported_revisions; - } test_cases[] = { - {0b0000'0000}, // No Service Revision. - {0b0001'0000}, // Unsupported Service Revision (4th bit). - {0b0000'1000}, // Unsupported Service Revision (3th bit). - {0b0000'0100}, // Unsupported Service Revision (2th bit). - {0b0000'0010}, // Unsupported Service Revision (1th bit). - {0b0000'0001}, // Unsupported Service Revision (0th bit). - }; - - for (const auto& test_case : test_cases) { - SCOPED_TRACE(::testing::Message() - << "Supported Revisions: " << test_case.supported_revisions); - SetNextReadServiceRevisionBitfieldResponse( - true, {static_cast<uint8_t>(test_case.supported_revisions.to_ulong())}); - - FidoBleConnection connection(adapter(), device_address, uuid(), - base::DoNothing()); - TestConnectionFuture connection_future; - connection.Connect(connection_future.GetCallback()); - EXPECT_TRUE(connection_future.Wait()); - EXPECT_FALSE(connection_future.Get()); - } -} - -TEST_F(FidoBleConnectionTest, ReadServiceRevisionsFails) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - - AddFidoDevice(device_address); - SetupConnectingFidoDevice(device_address); - SetNextReadServiceRevisionBitfieldResponse(false, {}); - - FidoBleConnection connection(adapter(), device_address, uuid(), - base::DoNothing()); - TestConnectionFuture connection_future; - connection.Connect(connection_future.GetCallback()); - EXPECT_TRUE(connection_future.Wait()); - EXPECT_FALSE(connection_future.Get()); -} - -TEST_F(FidoBleConnectionTest, WriteServiceRevisionsFails) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - - AddFidoDevice(device_address); - SetupConnectingFidoDevice(device_address); - SetNextReadServiceRevisionBitfieldResponse(true, {kDefaultServiceRevision}); - SetNextWriteServiceRevisionResponse({kDefaultServiceRevision}, false); - - FidoBleConnection connection(adapter(), device_address, uuid(), - base::DoNothing()); - TestConnectionFuture connection_future; - connection.Connect(connection_future.GetCallback()); - EXPECT_TRUE(connection_future.Wait()); - EXPECT_FALSE(connection_future.Get()); -} - -TEST_F(FidoBleConnectionTest, ReadStatusNotifications) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - TestReadCallback read_callback; - - AddFidoDevice(device_address); - SetupConnectingFidoDevice(device_address); - FidoBleConnection connection(adapter(), device_address, uuid(), - read_callback.GetCallback()); - - TestConnectionFuture connection_future; - connection.Connect(connection_future.GetCallback()); - EXPECT_TRUE(connection_future.Wait()); - EXPECT_TRUE(connection_future.Get()); - - std::vector<uint8_t> payload = ToByteVector("foo"); - NotifyStatusChanged(payload); - EXPECT_EQ(payload, read_callback.WaitForResult()); - - payload = ToByteVector("bar"); - NotifyStatusChanged(payload); - EXPECT_EQ(payload, read_callback.WaitForResult()); -} - -TEST_F(FidoBleConnectionTest, ReadControlPointLength) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - AddFidoDevice(device_address); - SetupConnectingFidoDevice(device_address); - FidoBleConnection connection(adapter(), device_address, uuid(), - base::DoNothing()); - - TestConnectionFuture connection_future; - connection.Connect(connection_future.GetCallback()); - EXPECT_TRUE(connection_future.Wait()); - EXPECT_TRUE(connection_future.Get()); - - { - TestReadControlPointLengthFuture length_future; - SetNextReadControlPointLengthReponse(false, {}); - connection.ReadControlPointLength(length_future.GetCallback()); - EXPECT_TRUE(length_future.Wait()); - EXPECT_EQ(std::nullopt, length_future.Get()); - } - - // The Control Point Length should consist of exactly two bytes, hence we - // EXPECT_EQ(std::nullopt) for payloads of size 0, 1 and 3. - { - TestReadControlPointLengthFuture length_future; - SetNextReadControlPointLengthReponse(true, {}); - connection.ReadControlPointLength(length_future.GetCallback()); - EXPECT_TRUE(length_future.Wait()); - EXPECT_EQ(std::nullopt, length_future.Get()); - } - - { - TestReadControlPointLengthFuture length_future; - SetNextReadControlPointLengthReponse(true, {0xAB}); - connection.ReadControlPointLength(length_future.GetCallback()); - EXPECT_TRUE(length_future.Wait()); - EXPECT_EQ(std::nullopt, length_future.Get()); - } - - { - TestReadControlPointLengthFuture length_future; - SetNextReadControlPointLengthReponse(true, {0xAB, 0xCD}); - connection.ReadControlPointLength(length_future.GetCallback()); - EXPECT_TRUE(length_future.Wait()); - EXPECT_EQ(0xABCD, *length_future.Get()); - } - - { - TestReadControlPointLengthFuture length_future; - SetNextReadControlPointLengthReponse(true, {0xAB, 0xCD, 0xEF}); - connection.ReadControlPointLength(length_future.GetCallback()); - EXPECT_TRUE(length_future.Wait()); - EXPECT_EQ(std::nullopt, length_future.Get()); - } -} - -TEST_F(FidoBleConnectionTest, WriteControlPoint) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - AddFidoDevice(device_address); - SetupConnectingFidoDevice(device_address); - FidoBleConnection connection(adapter(), device_address, uuid(), - base::DoNothing()); - - TestConnectionFuture connection_future; - connection.Connect(connection_future.GetCallback()); - EXPECT_TRUE(connection_future.Wait()); - EXPECT_TRUE(connection_future.Get()); - - { - TestWriteCallback write_future; - SetNextWriteControlPointResponse(false); - connection.WriteControlPoint({}, write_future.GetCallback()); - EXPECT_TRUE(write_future.Wait()); - EXPECT_FALSE(write_future.Get()); - } - - { - TestWriteCallback write_future; - SetNextWriteControlPointResponse(true); - connection.WriteControlPoint({}, write_future.GetCallback()); - EXPECT_TRUE(write_future.Wait()); - EXPECT_TRUE(write_future.Get()); - } -} - -TEST_F(FidoBleConnectionTest, ReadsAndWriteFailWhenDisconnected) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - - AddFidoDevice(device_address); - SetupConnectingFidoDevice(device_address); - FidoBleConnection connection(adapter(), device_address, uuid(), - base::DoNothing()); - - SimulateGattConnectionError(); - TestConnectionFuture connection_future; - connection.Connect(connection_future.GetCallback()); - EXPECT_TRUE(connection_future.Wait()); - EXPECT_FALSE(connection_future.Get()); - - // Reads should always fail on a disconnected device. - TestReadControlPointLengthFuture length_future; - connection.ReadControlPointLength(length_future.GetCallback()); - EXPECT_TRUE(length_future.Wait()); - EXPECT_EQ(std::nullopt, length_future.Get()); - - // Writes should always fail on a disconnected device. - TestWriteCallback write_future; - connection.WriteControlPoint({}, write_future.GetCallback()); - EXPECT_TRUE(write_future.Wait()); - EXPECT_FALSE(write_future.Get()); -} - -TEST_F(FidoBleConnectionTest, ConnectionAddressChangeWhenDeviceAddressChanges) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - static constexpr char kTestDeviceAddress2[] = "test_device_address_2"; - - AddFidoDevice(device_address); - SetupConnectingFidoDevice(device_address); - FidoBleConnection connection(adapter(), device_address, uuid(), - base::DoNothing()); - ChangeDeviceAddressAndNotifyObservers(kTestDeviceAddress2); - EXPECT_EQ(kTestDeviceAddress2, connection.address()); -} - -} // namespace device
diff --git a/device/fido/cable/fido_ble_frames.cc b/device/fido/cable/fido_ble_frames.cc deleted file mode 100644 index edea16ca..0000000 --- a/device/fido/cable/fido_ble_frames.cc +++ /dev/null
@@ -1,180 +0,0 @@ -// Copyright 2017 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "device/fido/cable/fido_ble_frames.h" - -#include <algorithm> -#include <limits> -#include <tuple> - -#include "base/check_op.h" -#include "base/compiler_specific.h" -#include "base/notreached.h" -#include "base/numerics/safe_conversions.h" -#include "device/fido/fido_parsing_utils.h" -#include "device/fido/public/fido_constants.h" - -namespace device { - -FidoBleFrame::FidoBleFrame() = default; - -FidoBleFrame::FidoBleFrame(FidoBleDeviceCommand command, - std::vector<uint8_t> data) - : command_(command), data_(std::move(data)) {} - -FidoBleFrame::FidoBleFrame(const FidoBleFrame&) = default; -FidoBleFrame& FidoBleFrame::operator=(const FidoBleFrame&) = default; - -FidoBleFrame::FidoBleFrame(FidoBleFrame&&) = default; -FidoBleFrame& FidoBleFrame::operator=(FidoBleFrame&&) = default; - -FidoBleFrame::~FidoBleFrame() = default; - -bool FidoBleFrame::IsValid() const { - switch (command_) { - case FidoBleDeviceCommand::kPing: - case FidoBleDeviceCommand::kMsg: - case FidoBleDeviceCommand::kCancel: - case FidoBleDeviceCommand::kControl: - return true; - case FidoBleDeviceCommand::kKeepAlive: - case FidoBleDeviceCommand::kError: - return data_.size() == 1; - } - NOTREACHED(); -} - -FidoBleFrame::KeepaliveCode FidoBleFrame::GetKeepaliveCode() const { - DCHECK_EQ(command_, FidoBleDeviceCommand::kKeepAlive); - DCHECK_EQ(data_.size(), 1u); - return static_cast<KeepaliveCode>(data_[0]); -} - -FidoBleFrame::ErrorCode FidoBleFrame::GetErrorCode() const { - DCHECK_EQ(command_, FidoBleDeviceCommand::kError); - DCHECK_EQ(data_.size(), 1u); - return static_cast<ErrorCode>(data_[0]); -} - -std::pair<FidoBleFrameInitializationFragment, - base::queue<FidoBleFrameContinuationFragment>> -FidoBleFrame::ToFragments(size_t max_fragment_size) const { - DCHECK_LE(data_.size(), std::numeric_limits<uint16_t>::max()); - DCHECK_GE(max_fragment_size, 3u); - - // Cast is necessary to ignore too high bits. - auto data_view = UNSAFE_TODO( - base::span(data_.data(), static_cast<uint16_t>(data_.size()))); - - // Subtract 3 to account for CMD, HLEN and LLEN bytes. - const size_t init_fragment_size = - std::min(max_fragment_size - 3, data_view.size()); - - FidoBleFrameInitializationFragment initial_fragment( - command_, data_view.size(), data_view.first(init_fragment_size)); - - base::queue<FidoBleFrameContinuationFragment> other_fragments; - data_view = data_view.subspan(init_fragment_size); - - // Subtract 1 to account for SEQ byte. - for (auto cont_data : - fido_parsing_utils::SplitSpan(data_view, max_fragment_size - 1)) { - // High bit must stay cleared. - other_fragments.emplace(cont_data, other_fragments.size() & 0x7F); - } - - return {initial_fragment, std::move(other_fragments)}; -} - -bool operator==(const FidoBleFrame& lhs, const FidoBleFrame& rhs) { - return std::forward_as_tuple(lhs.command(), lhs.data()) == - std::forward_as_tuple(rhs.command(), rhs.data()); -} - -FidoBleFrameFragment::FidoBleFrameFragment() = default; - -FidoBleFrameFragment::FidoBleFrameFragment(const FidoBleFrameFragment& frame) = - default; -FidoBleFrameFragment::~FidoBleFrameFragment() = default; - -FidoBleFrameFragment::FidoBleFrameFragment(base::span<const uint8_t> fragment) - : fragment_(fragment) {} - -bool FidoBleFrameInitializationFragment::Parse( - base::span<const uint8_t> data, - FidoBleFrameInitializationFragment* fragment) { - if (data.size() < 3) - return false; - - const auto command = static_cast<FidoBleDeviceCommand>(data[0]); - const uint16_t data_length = (static_cast<uint16_t>(data[1]) << 8) + data[2]; - if (size_t{data_length} + 3 < data.size()) { - return false; - } - - *fragment = FidoBleFrameInitializationFragment(command, data_length, - data.subspan<3>()); - return true; -} - -size_t FidoBleFrameInitializationFragment::Serialize( - std::vector<uint8_t>* buffer) const { - buffer->push_back(static_cast<uint8_t>(command_)); - buffer->push_back((data_length_ >> 8) & 0xFF); - buffer->push_back(data_length_ & 0xFF); - buffer->insert(buffer->end(), fragment().begin(), fragment().end()); - return fragment().size() + 3; -} - -bool FidoBleFrameContinuationFragment::Parse( - base::span<const uint8_t> data, - FidoBleFrameContinuationFragment* fragment) { - if (data.empty()) - return false; - const uint8_t sequence = data[0]; - *fragment = FidoBleFrameContinuationFragment(data.subspan<1>(), sequence); - return true; -} - -size_t FidoBleFrameContinuationFragment::Serialize( - std::vector<uint8_t>* buffer) const { - buffer->push_back(sequence_); - buffer->insert(buffer->end(), fragment().begin(), fragment().end()); - return fragment().size() + 1; -} - -FidoBleFrameAssembler::FidoBleFrameAssembler( - const FidoBleFrameInitializationFragment& fragment) - : data_length_(fragment.data_length()), - frame_(fragment.command(), - std::vector<uint8_t>(fragment.fragment().begin(), - fragment.fragment().end())) {} - -bool FidoBleFrameAssembler::AddFragment( - const FidoBleFrameContinuationFragment& fragment) { - if (fragment.sequence() != sequence_number_) - return false; - sequence_number_ = (sequence_number_ + 1) & 0x7F; - - if (static_cast<size_t>(data_length_) < - frame_.data().size() + fragment.fragment().size()) { - return false; - } - - frame_.data().insert(frame_.data().end(), fragment.fragment().begin(), - fragment.fragment().end()); - return true; -} - -bool FidoBleFrameAssembler::IsDone() const { - return frame_.data().size() == data_length_; -} - -FidoBleFrame* FidoBleFrameAssembler::GetFrame() { - return IsDone() ? &frame_ : nullptr; -} - -FidoBleFrameAssembler::~FidoBleFrameAssembler() = default; - -} // namespace device
diff --git a/device/fido/cable/fido_ble_frames.h b/device/fido/cable/fido_ble_frames.h deleted file mode 100644 index 417126fc..0000000 --- a/device/fido/cable/fido_ble_frames.h +++ /dev/null
@@ -1,191 +0,0 @@ -// Copyright 2017 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef DEVICE_FIDO_CABLE_FIDO_BLE_FRAMES_H_ -#define DEVICE_FIDO_CABLE_FIDO_BLE_FRAMES_H_ - -#include <stdint.h> - -#include <utility> -#include <vector> - -#include "base/component_export.h" -#include "base/containers/queue.h" -#include "base/containers/span.h" -#include "base/memory/raw_span.h" -#include "device/fido/public/fido_constants.h" - -namespace device { - -class FidoBleFrameInitializationFragment; -class FidoBleFrameContinuationFragment; - -// Encapsulates a frame, i.e., a single request to or response from a FIDO -// compliant authenticator, designed to be transported via BLE. The frame is -// further split into fragments (see FidoBleFrameFragment class). -// -// The specification of what constitues a frame can be found here: -// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-bt-protocol-v1.2-ps-20170411.html#h2_framing -// -// TODO(crbug.com/40539129): Consider refactoring U2fMessage to support BLE -// frames. -class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleFrame { - public: - // The values which can be carried in the |data| section of a KEEPALIVE - // message sent from an authenticator. - enum class KeepaliveCode : uint8_t { - // The request is still being processed. The authenticator will be sending - // this message every |kKeepAliveMillis| milliseconds until completion. - PROCESSING = 0x01, - // The authenticator is waiting for the Test of User Presence to complete. - TUP_NEEDED = 0x02, - }; - - // The types of errors an authenticator can return to the client. Carried in - // the |data| section of an ERROR command. - enum class ErrorCode : uint8_t { - INVALID_CMD = 0x01, // The command in the request is unknown/invalid. - INVALID_PAR = 0x02, // The parameters of the command are invalid/missing. - INVALID_LEN = 0x03, // The length of the request is invalid. - INVALID_SEQ = 0x04, // The sequence number is invalid. - REQ_TIMEOUT = 0x05, // The request timed out. - NA_1 = 0x06, // Value reserved (HID). - NA_2 = 0x0A, // Value reserved (HID). - NA_3 = 0x0B, // Value reserved (HID). - ENCRYPTION_FAILED = 0x0C, // Encryption failed for the request. - OTHER = 0x7F, // Other, unspecified error. - }; - - FidoBleFrame(); - FidoBleFrame(FidoBleDeviceCommand command, std::vector<uint8_t> data); - - FidoBleFrame(const FidoBleFrame&); - FidoBleFrame& operator=(const FidoBleFrame&); - - FidoBleFrame(FidoBleFrame&&); - FidoBleFrame& operator=(FidoBleFrame&&); - - ~FidoBleFrame(); - - FidoBleDeviceCommand command() const { return command_; } - - bool IsValid() const; - KeepaliveCode GetKeepaliveCode() const; - ErrorCode GetErrorCode() const; - - const std::vector<uint8_t>& data() const { return data_; } - std::vector<uint8_t>& data() { return data_; } - - // Splits the frame into fragments suitable for sending over BLE. Returns the - // first fragment via |initial_fragment|, and pushes the remaining ones back - // to the |other_fragments| vector. - // - // The |max_fragment_size| parameter ought to be at least 3. The resulting - // fragments' binary sizes will not exceed this value. - std::pair<FidoBleFrameInitializationFragment, - base::queue<FidoBleFrameContinuationFragment>> - ToFragments(size_t max_fragment_size) const; - - private: - FidoBleDeviceCommand command_ = FidoBleDeviceCommand::kMsg; - std::vector<uint8_t> data_; -}; - -COMPONENT_EXPORT(DEVICE_FIDO) -bool operator==(const FidoBleFrame& lhs, const FidoBleFrame& rhs); - -// A single frame sent over BLE may be split over multiple writes and -// notifications because the technology was not designed for large messages. -// This class represents a single fragment. Not to be used directly. -// -// A frame is divided into an initialization fragment and zero, one or more -// continuation fragments. See the below section of the spec for the details: -// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-bt-protocol-v1.2-ps-20170411.html#h2_framing-fragmentation -// -// Note: This class and its subclasses don't own the |data|. -class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleFrameFragment { - public: - base::span<const uint8_t> fragment() const { return fragment_; } - virtual size_t Serialize(std::vector<uint8_t>* buffer) const = 0; - - protected: - FidoBleFrameFragment(); - explicit FidoBleFrameFragment(base::span<const uint8_t> fragment); - FidoBleFrameFragment(const FidoBleFrameFragment& frame); - virtual ~FidoBleFrameFragment(); - - private: - base::raw_span<const uint8_t> fragment_; -}; - -// An initialization fragment of a frame. -class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleFrameInitializationFragment - : public FidoBleFrameFragment { - public: - static bool Parse(base::span<const uint8_t> data, - FidoBleFrameInitializationFragment* fragment); - - FidoBleFrameInitializationFragment() = default; - FidoBleFrameInitializationFragment(FidoBleDeviceCommand command, - uint16_t data_length, - base::span<const uint8_t> fragment) - : FidoBleFrameFragment(fragment), - command_(command), - data_length_(data_length) {} - - FidoBleDeviceCommand command() const { return command_; } - uint16_t data_length() const { return data_length_; } - - size_t Serialize(std::vector<uint8_t>* buffer) const override; - - private: - FidoBleDeviceCommand command_ = FidoBleDeviceCommand::kMsg; - uint16_t data_length_ = 0; -}; - -// A continuation fragment of a frame. -class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleFrameContinuationFragment - : public FidoBleFrameFragment { - public: - static bool Parse(base::span<const uint8_t> data, - FidoBleFrameContinuationFragment* fragment); - - FidoBleFrameContinuationFragment() = default; - FidoBleFrameContinuationFragment(base::span<const uint8_t> fragment, - uint8_t sequence) - : FidoBleFrameFragment(fragment), sequence_(sequence) {} - - uint8_t sequence() const { return sequence_; } - - size_t Serialize(std::vector<uint8_t>* buffer) const override; - - private: - uint8_t sequence_ = 0; -}; - -// The helper used to construct a FidoBleFrame from a sequence of its fragments. -class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleFrameAssembler { - public: - explicit FidoBleFrameAssembler( - const FidoBleFrameInitializationFragment& fragment); - - FidoBleFrameAssembler(const FidoBleFrameAssembler&) = delete; - FidoBleFrameAssembler& operator=(const FidoBleFrameAssembler&) = delete; - - ~FidoBleFrameAssembler(); - - bool IsDone() const; - - bool AddFragment(const FidoBleFrameContinuationFragment& fragment); - FidoBleFrame* GetFrame(); - - private: - uint16_t data_length_ = 0; - uint8_t sequence_number_ = 0; - FidoBleFrame frame_; -}; - -} // namespace device - -#endif // DEVICE_FIDO_CABLE_FIDO_BLE_FRAMES_H_
diff --git a/device/fido/cable/fido_ble_frames_unittest.cc b/device/fido/cable/fido_ble_frames_unittest.cc deleted file mode 100644 index 82c5ef9..0000000 --- a/device/fido/cable/fido_ble_frames_unittest.cc +++ /dev/null
@@ -1,147 +0,0 @@ -// Copyright 2017 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "device/fido/cable/fido_ble_frames.h" - -#include <algorithm> -#include <vector> - -#include "testing/gtest/include/gtest/gtest.h" - -namespace { - -std::vector<uint8_t> GetSomeData(size_t size) { - std::vector<uint8_t> data(size); - for (size_t i = 0; i < size; ++i) - data[i] = static_cast<uint8_t>((i * i) & 0xFF); - return data; -} - -} // namespace - -namespace device { - -TEST(FidoBleFramesTest, InitializationFragment) { - const std::vector<uint8_t> data = GetSomeData(25); - constexpr uint16_t kDataLength = 21123; - - FidoBleFrameInitializationFragment fragment(FidoBleDeviceCommand::kMsg, - kDataLength, base::span(data)); - std::vector<uint8_t> buffer; - const size_t binary_size = fragment.Serialize(&buffer); - EXPECT_EQ(buffer.size(), binary_size); - - EXPECT_EQ(data.size() + 3, binary_size); - - FidoBleFrameInitializationFragment parsed_fragment; - ASSERT_TRUE( - FidoBleFrameInitializationFragment::Parse(buffer, &parsed_fragment)); - - EXPECT_EQ(kDataLength, parsed_fragment.data_length()); - EXPECT_TRUE(std::ranges::equal(data, parsed_fragment.fragment())); - EXPECT_EQ(FidoBleDeviceCommand::kMsg, parsed_fragment.command()); -} - -TEST(FidoBleFramesTest, ContinuationFragment) { - const auto data = GetSomeData(25); - constexpr uint8_t kSequence = 61; - - FidoBleFrameContinuationFragment fragment(base::span(data), kSequence); - - std::vector<uint8_t> buffer; - const size_t binary_size = fragment.Serialize(&buffer); - EXPECT_EQ(buffer.size(), binary_size); - - EXPECT_EQ(data.size() + 1, binary_size); - - FidoBleFrameContinuationFragment parsed_fragment; - ASSERT_TRUE( - FidoBleFrameContinuationFragment::Parse(buffer, &parsed_fragment)); - - EXPECT_TRUE(std::ranges::equal(data, parsed_fragment.fragment())); - EXPECT_EQ(kSequence, parsed_fragment.sequence()); -} - -TEST(FidoBleFramesTest, SplitAndAssemble) { - for (size_t size : {0, 1, 16, 17, 18, 20, 21, 22, 35, 36, - 37, 39, 40, 41, 54, 55, 56, 60, 100, 65535}) { - SCOPED_TRACE(size); - - FidoBleFrame frame(FidoBleDeviceCommand::kPing, GetSomeData(size)); - - auto fragments = frame.ToFragments(20); - - EXPECT_EQ(frame.command(), fragments.first.command()); - EXPECT_EQ(frame.data().size(), - static_cast<size_t>(fragments.first.data_length())); - - FidoBleFrameAssembler assembler(fragments.first); - while (!fragments.second.empty()) { - ASSERT_TRUE(assembler.AddFragment(fragments.second.front())); - fragments.second.pop(); - } - - EXPECT_TRUE(assembler.IsDone()); - ASSERT_TRUE(assembler.GetFrame()); - - auto result_frame = std::move(*assembler.GetFrame()); - EXPECT_EQ(frame.command(), result_frame.command()); - EXPECT_EQ(frame.data(), result_frame.data()); - } -} - -TEST(FidoBleFramesTest, FrameAssemblerError) { - FidoBleFrame frame(FidoBleDeviceCommand::kPing, GetSomeData(30)); - - auto fragments = frame.ToFragments(20); - ASSERT_EQ(1u, fragments.second.size()); - - fragments.second.front() = - FidoBleFrameContinuationFragment(fragments.second.front().fragment(), 51); - - FidoBleFrameAssembler assembler(fragments.first); - EXPECT_FALSE(assembler.IsDone()); - EXPECT_FALSE(assembler.GetFrame()); - EXPECT_FALSE(assembler.AddFragment(fragments.second.front())); - EXPECT_FALSE(assembler.IsDone()); - EXPECT_FALSE(assembler.GetFrame()); -} - -TEST(FidoBleFramesTest, FrameGettersAndValidity) { - { - FidoBleFrame frame(FidoBleDeviceCommand::kKeepAlive, - std::vector<uint8_t>(2)); - EXPECT_FALSE(frame.IsValid()); - } - { - FidoBleFrame frame(FidoBleDeviceCommand::kError, {}); - EXPECT_FALSE(frame.IsValid()); - } - - for (auto code : {FidoBleFrame::KeepaliveCode::TUP_NEEDED, - FidoBleFrame::KeepaliveCode::PROCESSING}) { - FidoBleFrame frame(FidoBleDeviceCommand::kKeepAlive, - std::vector<uint8_t>(1, static_cast<uint8_t>(code))); - EXPECT_TRUE(frame.IsValid()); - EXPECT_EQ(code, frame.GetKeepaliveCode()); - } - - for (auto code : { - FidoBleFrame::ErrorCode::INVALID_CMD, - FidoBleFrame::ErrorCode::INVALID_PAR, - FidoBleFrame::ErrorCode::INVALID_SEQ, - FidoBleFrame::ErrorCode::INVALID_LEN, - FidoBleFrame::ErrorCode::REQ_TIMEOUT, - FidoBleFrame::ErrorCode::NA_1, - FidoBleFrame::ErrorCode::NA_2, - FidoBleFrame::ErrorCode::NA_3, - }) { - FidoBleFrame frame(FidoBleDeviceCommand::kError, - {static_cast<uint8_t>(code)}); - EXPECT_TRUE(frame.IsValid()); - EXPECT_EQ(code, frame.GetErrorCode()); - } -} - -} // namespace device
diff --git a/device/fido/cable/fido_ble_transaction.cc b/device/fido/cable/fido_ble_transaction.cc deleted file mode 100644 index b869af4..0000000 --- a/device/fido/cable/fido_ble_transaction.cc +++ /dev/null
@@ -1,222 +0,0 @@ -// Copyright 2017 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "device/fido/cable/fido_ble_transaction.h" - -#include <utility> - -#include "base/functional/bind.h" -#include "base/functional/callback_helpers.h" -#include "base/strings/string_number_conversions.h" -#include "base/task/single_thread_task_runner.h" -#include "components/device_event_log/device_event_log.h" -#include "device/fido/cable/fido_ble_connection.h" -#include "device/fido/public/fido_constants.h" - -namespace device { - -FidoBleTransaction::FidoBleTransaction(FidoBleConnection* connection, - uint16_t control_point_length) - : connection_(connection), control_point_length_(control_point_length) { - buffer_.reserve(control_point_length_); -} - -FidoBleTransaction::~FidoBleTransaction() = default; - -void FidoBleTransaction::WriteRequestFrame(FidoBleFrame request_frame, - FrameCallback callback) { - if (control_point_length_ < 3u) { - FIDO_LOG(DEBUG) << "Control Point Length is too short: " - << control_point_length_; - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), std::nullopt)); - return; - } - - DCHECK(!request_frame_ && callback_.is_null()); - request_frame_ = std::move(request_frame); - callback_ = std::move(callback); - - FidoBleFrameInitializationFragment request_init_fragment; - std::tie(request_init_fragment, request_cont_fragments_) = - request_frame_->ToFragments(control_point_length_); - WriteRequestFragment(request_init_fragment); -} - -void FidoBleTransaction::WriteRequestFragment( - const FidoBleFrameFragment& fragment) { - buffer_.clear(); - fragment.Serialize(&buffer_); - DCHECK(!has_pending_request_fragment_write_); - has_pending_request_fragment_write_ = true; - FIDO_LOG(DEBUG) << "Writing request fragment: " << base::HexEncode(buffer_); - // A weak pointer is required, since this call might time out. If that - // happens, the current FidoBleTransaction could be destroyed. - connection_->WriteControlPoint( - buffer_, base::BindOnce(&FidoBleTransaction::OnRequestFragmentWritten, - weak_factory_.GetWeakPtr())); - // WriteRequestFragment() expects an invocation of OnRequestFragmentWritten() - // soon after. - StartTimeout(); -} - -static void WriteCancel(FidoBleConnection* connection) { - FIDO_LOG(DEBUG) << "Writing control point 'Cancel'"; - connection->WriteControlPoint( - {static_cast<uint8_t>(FidoBleDeviceCommand::kCancel), 0, 0}, - base::DoNothing()); -} - -void FidoBleTransaction::OnRequestFragmentWritten(bool success) { - DCHECK(has_pending_request_fragment_write_); - has_pending_request_fragment_write_ = false; - StopTimeout(); - if (!success) { - OnError(std::nullopt); - return; - } - - if (!request_cont_fragments_.empty()) { - auto next_request_fragment = std::move(request_cont_fragments_.front()); - request_cont_fragments_.pop(); - WriteRequestFragment(next_request_fragment); - return; - } - - if (cancel_pending_) { - cancel_pending_ = false; - cancel_sent_ = true; - WriteCancel(connection_); - } - - // The transaction wrote the full request frame. It is possible that the full - // response frame was already received, at which point we process it and run - // the completim callback. - if (response_frame_assembler_ && response_frame_assembler_->IsDone()) { - ProcessResponseFrame(); - return; - } - - // Otherwise, a response should follow soon after. - StartTimeout(); -} - -void FidoBleTransaction::OnResponseFragment(std::vector<uint8_t> data) { - StopTimeout(); - if (!response_frame_assembler_) { - FidoBleFrameInitializationFragment fragment; - if (!FidoBleFrameInitializationFragment::Parse(data, &fragment)) { - FIDO_LOG(ERROR) << "Malformed Frame Initialization Fragment"; - OnError(std::nullopt); - return; - } - - response_frame_assembler_.emplace(fragment); - } else { - FidoBleFrameContinuationFragment fragment; - if (!FidoBleFrameContinuationFragment::Parse(data, &fragment) || - !response_frame_assembler_->AddFragment(fragment)) { - FIDO_LOG(ERROR) << "Malformed Frame Continuation Fragment"; - OnError(std::nullopt); - return; - } - } - - if (!response_frame_assembler_->IsDone()) { - // Expect the next reponse fragment to arrive soon. - StartTimeout(); - return; - } - - // It is possible to receive the last response fragment before the write of - // the last request fragment has been acknowledged. If this is the case, do - // not run the completion callback yet. - // It is OK to process keep alive frames before the request frame is - // acknowledged. - if (!has_pending_request_fragment_write_ || - response_frame_assembler_->GetFrame()->command() == - FidoBleDeviceCommand::kKeepAlive) { - ProcessResponseFrame(); - } -} - -void FidoBleTransaction::Cancel() { - if (cancel_sent_) { - return; - } - - if (has_pending_request_fragment_write_) { - // A mesasge is still being written. Signal that the cancelation should be - // written once complete. - cancel_pending_ = true; - } else { - cancel_sent_ = true; - WriteCancel(connection_); - } -} - -void FidoBleTransaction::ProcessResponseFrame() { - DCHECK(response_frame_assembler_ && response_frame_assembler_->IsDone()); - auto response_frame = std::move(*response_frame_assembler_->GetFrame()); - response_frame_assembler_.reset(); - - DCHECK(request_frame_.has_value()); - if (response_frame.command() == request_frame_->command()) { - request_frame_.reset(); - std::move(callback_).Run(std::move(response_frame)); - return; - } - - if (response_frame.command() == FidoBleDeviceCommand::kKeepAlive) { - if (!response_frame.IsValid()) { - FIDO_LOG(ERROR) << "Got invalid KeepAlive Command."; - OnError(std::nullopt); - return; - } - - FIDO_LOG(DEBUG) << "CMD_KEEPALIVE: " - << static_cast<int>(response_frame.GetKeepaliveCode()); - // Expect another reponse frame soon. - StartTimeout(); - return; - } - - if (response_frame.command() == FidoBleDeviceCommand::kError) { - if (!response_frame.IsValid()) { - FIDO_LOG(ERROR) << "Got invald Error Command."; - OnError(std::nullopt); - return; - } - - FIDO_LOG(ERROR) << "CMD_ERROR: " - << static_cast<int>(response_frame.GetErrorCode()); - OnError(std::move(response_frame)); - return; - } - - FIDO_LOG(ERROR) << "Got unexpected Command: " - << static_cast<int>(response_frame.command()); - OnError(std::nullopt); -} - -void FidoBleTransaction::StartTimeout() { - timer_.Start(FROM_HERE, kDeviceTimeout, - base::BindOnce(&FidoBleTransaction::OnError, - base::Unretained(this), std::nullopt)); -} - -void FidoBleTransaction::StopTimeout() { - timer_.Stop(); -} - -void FidoBleTransaction::OnError(std::optional<FidoBleFrame> response_frame) { - request_frame_.reset(); - request_cont_fragments_ = base::queue<FidoBleFrameContinuationFragment>(); - response_frame_assembler_.reset(); - // |callback_| might have been run due to a previous error. - if (callback_) - std::move(callback_).Run(std::move(response_frame)); -} - -} // namespace device
diff --git a/device/fido/cable/fido_ble_transaction.h b/device/fido/cable/fido_ble_transaction.h deleted file mode 100644 index 50bd0ef..0000000 --- a/device/fido/cable/fido_ble_transaction.h +++ /dev/null
@@ -1,77 +0,0 @@ -// Copyright 2017 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef DEVICE_FIDO_CABLE_FIDO_BLE_TRANSACTION_H_ -#define DEVICE_FIDO_CABLE_FIDO_BLE_TRANSACTION_H_ - -#include <memory> -#include <optional> -#include <vector> - -#include "base/component_export.h" -#include "base/containers/queue.h" -#include "base/memory/raw_ptr.h" -#include "base/memory/weak_ptr.h" -#include "base/timer/timer.h" -#include "device/fido/cable/fido_ble_frames.h" - -namespace device { - -class FidoBleConnection; - -// This class encapsulates logic related to a single U2F BLE request and -// response. FidoBleTransaction is owned by FidoBleDevice, which is the only -// class that should make use of this class. -class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleTransaction { - public: - using FrameCallback = base::OnceCallback<void(std::optional<FidoBleFrame>)>; - - FidoBleTransaction(FidoBleConnection* connection, - uint16_t control_point_length); - - FidoBleTransaction(const FidoBleTransaction&) = delete; - FidoBleTransaction& operator=(const FidoBleTransaction&) = delete; - - ~FidoBleTransaction(); - - void WriteRequestFrame(FidoBleFrame request_frame, FrameCallback callback); - void OnResponseFragment(std::vector<uint8_t> data); - - // Cancel requests that a cancelation command be sent if possible. - void Cancel(); - - private: - void WriteRequestFragment(const FidoBleFrameFragment& fragment); - void OnRequestFragmentWritten(bool success); - void ProcessResponseFrame(); - - void StartTimeout(); - void StopTimeout(); - void OnError(std::optional<FidoBleFrame> response_frame); - - raw_ptr<FidoBleConnection> connection_; - uint16_t control_point_length_; - - std::optional<FidoBleFrame> request_frame_; - FrameCallback callback_; - - base::queue<FidoBleFrameContinuationFragment> request_cont_fragments_; - std::optional<FidoBleFrameAssembler> response_frame_assembler_; - - std::vector<uint8_t> buffer_; - base::OneShotTimer timer_; - - bool has_pending_request_fragment_write_ = false; - // cancel_pending_ is true if a cancelation should be sent after the current - // set of frames has finished transmitting. - bool cancel_pending_ = false; - // cancel_sent_ records whether a cancel message has already been sent. - bool cancel_sent_ = false; - - base::WeakPtrFactory<FidoBleTransaction> weak_factory_{this}; -}; - -} // namespace device - -#endif // DEVICE_FIDO_CABLE_FIDO_BLE_TRANSACTION_H_
diff --git a/device/fido/cable/fido_ble_transaction_unittest.cc b/device/fido/cable/fido_ble_transaction_unittest.cc deleted file mode 100644 index 7463b8450..0000000 --- a/device/fido/cable/fido_ble_transaction_unittest.cc +++ /dev/null
@@ -1,367 +0,0 @@ -// Copyright 2018 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "device/fido/cable/fido_ble_transaction.h" - -#include <stdint.h> - -#include <memory> -#include <optional> -#include <utility> -#include <vector> - -#include "base/memory/scoped_refptr.h" -#include "base/numerics/safe_conversions.h" -#include "base/test/task_environment.h" -#include "base/test/test_future.h" -#include "device/bluetooth/test/bluetooth_test.h" -#include "device/bluetooth/test/mock_bluetooth_adapter.h" -#include "device/fido/cable/fido_ble_connection.h" -#include "device/fido/cable/fido_ble_frames.h" -#include "device/fido/cable/mock_fido_ble_connection.h" -#include "device/fido/public/fido_constants.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace device { - -namespace { - -constexpr uint16_t kDefaultControlPointLength = 20; - -using FrameFuture = base::test::TestFuture<std::optional<FidoBleFrame>>; - -std::vector<std::vector<uint8_t>> ToByteFragments(const FidoBleFrame& frame) { - std::vector<std::vector<uint8_t>> byte_fragments; - auto fragments_pair = frame.ToFragments(kDefaultControlPointLength); - - byte_fragments.reserve(1 + fragments_pair.second.size()); - byte_fragments.emplace_back(); - byte_fragments.back().reserve(kDefaultControlPointLength); - fragments_pair.first.Serialize(&byte_fragments.back()); - - while (!fragments_pair.second.empty()) { - byte_fragments.emplace_back(); - byte_fragments.back().reserve(kDefaultControlPointLength); - fragments_pair.second.front().Serialize(&byte_fragments.back()); - fragments_pair.second.pop(); - } - - return byte_fragments; -} - -} // namespace - -class FidoBleTransactionTest : public ::testing::Test { - public: - base::test::TaskEnvironment& task_environment() { return task_environment_; } - MockFidoBleConnection& connection() { return connection_; } - FidoBleTransaction& transaction() { return *transaction_; } - - void ResetTransaction(uint16_t control_point_length) { - transaction_ = std::make_unique<FidoBleTransaction>(&connection_, - control_point_length); - } - - private: - base::test::TaskEnvironment task_environment_; - - scoped_refptr<BluetoothAdapter> adapter_ = - base::MakeRefCounted<::testing::NiceMock<MockBluetoothAdapter>>(); - MockFidoBleConnection connection_{adapter_.get(), - BluetoothTestBase::kTestDeviceAddress1}; - std::unique_ptr<FidoBleTransaction> transaction_ = - std::make_unique<FidoBleTransaction>(&connection_, - kDefaultControlPointLength); -}; - -// Tests a case where the control point write fails. -TEST_F(FidoBleTransactionTest, WriteRequestFrame_FailWrite) { - EXPECT_CALL(connection(), WriteControlPointPtr) - .WillOnce( - [](auto&&, auto* cb) { std::move(*cb).Run(false /* success */); }); - - FrameFuture future; - transaction().WriteRequestFrame(FidoBleFrame(), future.GetCallback()); - EXPECT_TRUE(future.Wait()); - EXPECT_EQ(std::nullopt, future.Get()); -} - -// Tests a case where the control point write succeeds. -TEST_F(FidoBleTransactionTest, WriteRequestFrame_Success) { - EXPECT_CALL(connection(), WriteControlPointPtr) - .WillOnce( - [](auto&&, auto* cb) { std::move(*cb).Run(true /* success */); }); - - FidoBleFrame frame(FidoBleDeviceCommand::kPing, std::vector<uint8_t>(10)); - FrameFuture future; - transaction().WriteRequestFrame(frame, future.GetCallback()); - - for (auto&& byte_fragment : ToByteFragments(frame)) - transaction().OnResponseFragment(std::move(byte_fragment)); - - EXPECT_TRUE(future.Wait()); - EXPECT_EQ(frame, future.Get()); -} - -// Tests a scenario where the full response frame is obtained before the control -// point write was acknowledged. The response callback should only be run once -// the ACK is received. -TEST_F(FidoBleTransactionTest, WriteRequestFrame_DelayedWriteAck) { - FidoBleConnection::WriteCallback delayed_write_callback; - - EXPECT_CALL(connection(), WriteControlPointPtr) - .WillOnce( - [&](auto&&, auto* cb) { delayed_write_callback = std::move(*cb); }); - - FidoBleFrame frame(FidoBleDeviceCommand::kPing, std::vector<uint8_t>(10)); - FrameFuture future; - transaction().WriteRequestFrame(frame, future.GetCallback()); - - for (auto&& byte_fragment : ToByteFragments(frame)) - transaction().OnResponseFragment(std::move(byte_fragment)); - - task_environment().RunUntilIdle(); - EXPECT_FALSE(future.IsReady()); - - std::move(delayed_write_callback).Run(true); - EXPECT_TRUE(future.Wait()); - EXPECT_EQ(frame, future.Get()); -} - -// Tests a scenario where keep alive frames are obtained before the control -// point write was acknowledged. The keep alive should be processed. -TEST_F(FidoBleTransactionTest, WriteRequestFrame_DelayedWriteAck_KeepAlive) { - FidoBleConnection::WriteCallback delayed_write_callback; - - EXPECT_CALL(connection(), WriteControlPointPtr) - .WillOnce( - [&](auto&&, auto* cb) { delayed_write_callback = std::move(*cb); }); - - FidoBleFrame frame(FidoBleDeviceCommand::kPing, std::vector<uint8_t>(10)); - FidoBleFrame tup_needed_frame( - FidoBleDeviceCommand::kKeepAlive, - {base::strict_cast<uint8_t>(FidoBleFrame::KeepaliveCode::TUP_NEEDED)}); - FrameFuture future; - - // Send two keep alives then the actual response. - transaction().WriteRequestFrame(frame, future.GetCallback()); - for (auto&& byte_fragment : ToByteFragments(tup_needed_frame)) - transaction().OnResponseFragment(std::move(byte_fragment)); - for (auto&& byte_fragment : ToByteFragments(tup_needed_frame)) - transaction().OnResponseFragment(std::move(byte_fragment)); - for (auto&& byte_fragment : ToByteFragments(frame)) - transaction().OnResponseFragment(std::move(byte_fragment)); - - task_environment().RunUntilIdle(); - EXPECT_FALSE(future.IsReady()); - - std::move(delayed_write_callback).Run(true); - EXPECT_TRUE(future.Wait()); - EXPECT_EQ(frame, future.Get()); -} - -// Tests a case where the control point length is too small. -TEST_F(FidoBleTransactionTest, WriteRequestFrame_ControlPointLength_TooSmall) { - static constexpr uint16_t kTooSmallControlPointLength = 2u; - ResetTransaction(kTooSmallControlPointLength); - - EXPECT_CALL(connection(), WriteControlPointPtr).Times(0); - FidoBleFrame frame(FidoBleDeviceCommand::kPing, std::vector<uint8_t>(10)); - FrameFuture future; - transaction().WriteRequestFrame(frame, future.GetCallback()); - - EXPECT_TRUE(future.Wait()); - EXPECT_EQ(std::nullopt, future.Get()); -} - -// Tests that valid KeepaliveCodes are ignored, and only a valid -// response frame completes the request. -TEST_F(FidoBleTransactionTest, WriteRequestFrame_IgnoreValidKeepAlives) { - EXPECT_CALL(connection(), WriteControlPointPtr) - .WillOnce( - [](auto&&, auto* cb) { std::move(*cb).Run(true /* success */); }); - - FidoBleFrame frame(FidoBleDeviceCommand::kPing, std::vector<uint8_t>(10)); - FrameFuture future; - transaction().WriteRequestFrame(frame, future.GetCallback()); - - FidoBleFrame tup_needed_frame( - FidoBleDeviceCommand::kKeepAlive, - {base::strict_cast<uint8_t>(FidoBleFrame::KeepaliveCode::TUP_NEEDED)}); - for (auto&& byte_fragment : ToByteFragments(tup_needed_frame)) - transaction().OnResponseFragment(std::move(byte_fragment)); - - task_environment().RunUntilIdle(); - EXPECT_FALSE(future.IsReady()); - - FidoBleFrame processing_frame( - FidoBleDeviceCommand::kKeepAlive, - {base::strict_cast<uint8_t>(FidoBleFrame::KeepaliveCode::PROCESSING)}); - for (auto&& byte_fragment : ToByteFragments(processing_frame)) - transaction().OnResponseFragment(std::move(byte_fragment)); - - task_environment().RunUntilIdle(); - EXPECT_FALSE(future.IsReady()); - - for (auto&& byte_fragment : ToByteFragments(frame)) - transaction().OnResponseFragment(std::move(byte_fragment)); - - EXPECT_TRUE(future.Wait()); - EXPECT_EQ(frame, future.Get()); -} - -// Tests that an invalid KeepaliveCode is treated as an error. -TEST_F(FidoBleTransactionTest, WriteRequestFrame_InvalidKeepAlive_Fail) { - EXPECT_CALL(connection(), WriteControlPointPtr) - .WillOnce( - [](auto&&, auto* cb) { std::move(*cb).Run(true /* success */); }); - - FidoBleFrame frame(FidoBleDeviceCommand::kPing, std::vector<uint8_t>(10)); - FrameFuture future; - transaction().WriteRequestFrame(frame, future.GetCallback()); - - // This frame is invalid, as it does not contain data. - FidoBleFrame keep_alive_frame(FidoBleDeviceCommand::kKeepAlive, {}); - for (auto&& byte_fragment : ToByteFragments(keep_alive_frame)) - transaction().OnResponseFragment(std::move(byte_fragment)); - - EXPECT_TRUE(future.Wait()); - EXPECT_EQ(std::nullopt, future.Get()); -} - -// Tests a scenario where the response frame contains a valid error command. -TEST_F(FidoBleTransactionTest, WriteRequestFrame_ValidErrorCommand) { - EXPECT_CALL(connection(), WriteControlPointPtr) - .WillOnce( - [](auto&&, auto* cb) { std::move(*cb).Run(true /* success */); }); - - FidoBleFrame ping_frame(FidoBleDeviceCommand::kPing, - std::vector<uint8_t>(10)); - FrameFuture future; - transaction().WriteRequestFrame(ping_frame, future.GetCallback()); - - FidoBleFrame error_frame( - FidoBleDeviceCommand::kError, - {base::strict_cast<uint8_t>(FidoBleFrame::ErrorCode::INVALID_CMD)}); - - for (auto&& byte_fragment : ToByteFragments(error_frame)) - transaction().OnResponseFragment(std::move(byte_fragment)); - - EXPECT_TRUE(future.Wait()); - EXPECT_EQ(error_frame, future.Get()); -} - -// Tests a scenario where the response frame contains an invalid error command. -TEST_F(FidoBleTransactionTest, WriteRequestFrame_InvalidErrorCommand) { - EXPECT_CALL(connection(), WriteControlPointPtr) - .WillOnce( - [](auto&&, auto* cb) { std::move(*cb).Run(true /* success */); }); - - FidoBleFrame ping_frame(FidoBleDeviceCommand::kPing, - std::vector<uint8_t>(10)); - FrameFuture future; - transaction().WriteRequestFrame(ping_frame, future.GetCallback()); - - // This frame is invalid, as it does not contain data. - FidoBleFrame error_frame(FidoBleDeviceCommand::kError, {}); - - for (auto&& byte_fragment : ToByteFragments(error_frame)) - transaction().OnResponseFragment(std::move(byte_fragment)); - - EXPECT_TRUE(future.Wait()); - EXPECT_EQ(std::nullopt, future.Get()); -} - -// Tests a scenario where the command of the response frame does not match the -// command of the request frame. -TEST_F(FidoBleTransactionTest, WriteRequestFrame_InvalidResponseFrameCommand) { - EXPECT_CALL(connection(), WriteControlPointPtr) - .WillOnce( - [](auto&&, auto* cb) { std::move(*cb).Run(true /* success */); }); - - FidoBleFrame ping_frame(FidoBleDeviceCommand::kPing, - std::vector<uint8_t>(10)); - FrameFuture future; - transaction().WriteRequestFrame(ping_frame, future.GetCallback()); - - FidoBleFrame message_frame(FidoBleDeviceCommand::kMsg, - std::vector<uint8_t>(kDefaultControlPointLength)); - - for (auto&& byte_fragment : ToByteFragments(message_frame)) - transaction().OnResponseFragment(std::move(byte_fragment)); - - EXPECT_TRUE(future.Wait()); - EXPECT_EQ(std::nullopt, future.Get()); -} - -// Tests a scenario where the response initialization fragment is invalid. -TEST_F(FidoBleTransactionTest, - WriteRequestFrame_InvalidResponseInitializationFragment) { - EXPECT_CALL(connection(), WriteControlPointPtr) - .WillRepeatedly( - [](auto&&, auto* cb) { std::move(*cb).Run(true /* success */); }); - - FidoBleFrame frame(FidoBleDeviceCommand::kPing, - std::vector<uint8_t>(kDefaultControlPointLength)); - FrameFuture future; - transaction().WriteRequestFrame(frame, future.GetCallback()); - - auto byte_fragments = ToByteFragments(frame); - ASSERT_EQ(2u, byte_fragments.size()); - transaction().OnResponseFragment(std::move(byte_fragments.back())); - transaction().OnResponseFragment(std::move(byte_fragments.front())); - - EXPECT_TRUE(future.Wait()); - EXPECT_EQ(std::nullopt, future.Get()); -} - -// Tests a scenario where a response continuation fragment is invalid. -TEST_F(FidoBleTransactionTest, - WriteRequestFrame_InvalidResponseContinuationFragment) { - EXPECT_CALL(connection(), WriteControlPointPtr) - .WillRepeatedly( - [](auto&&, auto* cb) { std::move(*cb).Run(true /* success */); }); - - FidoBleFrame frame(FidoBleDeviceCommand::kPing, - std::vector<uint8_t>(kDefaultControlPointLength)); - FrameFuture future; - transaction().WriteRequestFrame(frame, future.GetCallback()); - - // Provide the initialization fragment twice. The second time should be an - // error, as it's not a valid continuation fragment. - auto byte_fragments = ToByteFragments(frame); - ASSERT_EQ(2u, byte_fragments.size()); - transaction().OnResponseFragment(byte_fragments.front()); - transaction().OnResponseFragment(byte_fragments.front()); - - EXPECT_TRUE(future.Wait()); - EXPECT_EQ(std::nullopt, future.Get()); -} - -// Tests a scenario where the order of response continuation fragments is -// invalid. -TEST_F(FidoBleTransactionTest, - WriteRequestFrame_InvalidOrderResponseContinuationFragments) { - EXPECT_CALL(connection(), WriteControlPointPtr) - .WillRepeatedly( - [](auto&&, auto* cb) { std::move(*cb).Run(true /* success */); }); - - FidoBleFrame frame(FidoBleDeviceCommand::kPing, - std::vector<uint8_t>(kDefaultControlPointLength * 2)); - FrameFuture future; - transaction().WriteRequestFrame(frame, future.GetCallback()); - - // Provide the continuation fragments in the wrong order. - auto byte_fragments = ToByteFragments(frame); - ASSERT_EQ(3u, byte_fragments.size()); - transaction().OnResponseFragment(byte_fragments[0]); - transaction().OnResponseFragment(byte_fragments[2]); - transaction().OnResponseFragment(byte_fragments[1]); - - EXPECT_TRUE(future.Wait()); - EXPECT_EQ(std::nullopt, future.Get()); -} - -} // namespace device
diff --git a/device/fido/cable/fido_cable_device.cc b/device/fido/cable/fido_cable_device.cc deleted file mode 100644 index c01e4ab01..0000000 --- a/device/fido/cable/fido_cable_device.cc +++ /dev/null
@@ -1,383 +0,0 @@ -// Copyright 2018 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "device/fido/cable/fido_cable_device.h" - -#include <utility> - -#include "base/functional/bind.h" -#include "base/numerics/safe_math.h" -#include "base/strings/string_number_conversions.h" -#include "base/task/single_thread_task_runner.h" -#include "components/device_event_log/device_event_log.h" -#include "device/fido/cable/fido_ble_frames.h" -#include "device/fido/cable/fido_ble_uuids.h" -#include "device/fido/cable/v2_handshake.h" -#include "device/fido/fido_parsing_utils.h" -#include "device/fido/public/fido_constants.h" - -namespace device { - -namespace { - -// Maximum size of EncryptionData::read_sequence_num or -// EncryptionData::write_sequence_num allowed. If we encounter -// counter larger than |kMaxCounter| FidoCableDevice should error out. -constexpr uint32_t kMaxCounter = (1 << 24) - 1; - -std::optional<std::vector<uint8_t>> ConstructV1Nonce( - base::span<const uint8_t> nonce, - bool is_sender_client, - uint32_t counter) { - if (counter > kMaxCounter) - return std::nullopt; - - auto constructed_nonce = fido_parsing_utils::Materialize(nonce); - constructed_nonce.push_back(is_sender_client ? 0x00 : 0x01); - constructed_nonce.push_back(counter >> 16 & 0xFF); - constructed_nonce.push_back(counter >> 8 & 0xFF); - constructed_nonce.push_back(counter & 0xFF); - return constructed_nonce; -} - -} // namespace - -FidoCableDevice::EncryptionData::EncryptionData( - base::span<const uint8_t, 32> session_key) - : reader(crypto::Aead::AES_256_GCM, session_key), - writer(crypto::Aead::AES_256_GCM, session_key) {} -FidoCableDevice::EncryptionData::~EncryptionData() = default; - -FidoCableDevice::FidoCableDevice(BluetoothAdapter* adapter, - std::string address) { - connection_ = std::make_unique<FidoBleConnection>( - adapter, std::move(address), BluetoothUUID(kGoogleCableUUID128), - base::BindRepeating(&FidoCableDevice::OnStatusMessage, - weak_factory_.GetWeakPtr())); -} - -FidoCableDevice::FidoCableDevice(std::unique_ptr<FidoBleConnection> connection) - : connection_(std::move(connection)) {} - -FidoCableDevice::~FidoCableDevice() = default; - -// static -std::string FidoCableDevice::GetIdForAddress(const std::string& address) { - return "ble-" + address; -} - -std::string FidoCableDevice::GetAddress() { - return connection_->address(); -} - -void FidoCableDevice::Connect() { - if (state_ != State::kInit) - return; - - StartTimeout(); - state_ = State::kConnecting; - connection_->Connect(base::BindOnce(&FidoCableDevice::OnConnected, - weak_factory_.GetWeakPtr())); -} - -void FidoCableDevice::SendPing(std::vector<uint8_t> data, - DeviceCallback callback) { - AddToPendingFrames(FidoBleDeviceCommand::kPing, std::move(data), - std::move(callback)); -} - -FidoBleConnection::ReadCallback FidoCableDevice::GetReadCallbackForTesting() { - return base::BindRepeating(&FidoCableDevice::OnStatusMessage, - weak_factory_.GetWeakPtr()); -} - -void FidoCableDevice::Cancel(CancelToken token) { - if (current_token_ && *current_token_ == token) { - transaction_->Cancel(); - return; - } - - for (auto it = pending_frames_.begin(); it != pending_frames_.end(); it++) { - if (it->token != token) { - continue; - } - - auto callback = std::move(it->callback); - pending_frames_.erase(it); - std::vector<uint8_t> cancel_reply = { - static_cast<uint8_t>(CtapDeviceResponseCode::kCtap2ErrKeepAliveCancel)}; - std::move(callback).Run( - FidoBleFrame(FidoBleDeviceCommand::kMsg, std::move(cancel_reply))); - break; - } -} - -std::string FidoCableDevice::GetId() const { - return GetIdForAddress(connection_->address()); -} - -FidoTransportProtocol FidoCableDevice::DeviceTransport() const { - return FidoTransportProtocol::kHybrid; -} - -FidoDevice::CancelToken FidoCableDevice::DeviceTransact( - std::vector<uint8_t> command, - DeviceCallback callback) { - if (!encryption_data_ || !EncryptOutgoingMessage(&command)) { - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), std::nullopt)); - state_ = State::kDeviceError; - FIDO_LOG(ERROR) << "Failed to encrypt outgoing caBLE message."; - return 0; - } - - FIDO_LOG(DEBUG) << "Sending encrypted message to caBLE client"; - return AddToPendingFrames(FidoBleDeviceCommand::kMsg, std::move(command), - std::move(callback)); -} - -void FidoCableDevice::OnResponseFrame(FrameCallback callback, - std::optional<FidoBleFrame> frame) { - // The request is done, time to reset |transaction_|. - ResetTransaction(); - state_ = frame ? State::kReady : State::kDeviceError; - - if (frame && frame->command() != FidoBleDeviceCommand::kControl) { - if (!encryption_data_ || !DecryptIncomingMessage(&frame.value())) { - state_ = State::kDeviceError; - frame = std::nullopt; - } - } - - auto self = GetWeakPtr(); - std::move(callback).Run(std::move(frame)); - - // Executing callbacks may free |this|. Check |self| first. - if (self) - Transition(); -} - -void FidoCableDevice::ResetTransaction() { - transaction_.reset(); - current_token_.reset(); -} - -void FidoCableDevice::Transition() { - switch (state_) { - case State::kInit: - Connect(); - break; - case State::kReady: - if (!pending_frames_.empty()) { - PendingFrame pending(std::move(pending_frames_.front())); - pending_frames_.pop_front(); - current_token_ = pending.token; - SendRequestFrame(std::move(pending.frame), std::move(pending.callback)); - } - break; - case State::kConnecting: - case State::kBusy: - break; - case State::kMsgError: - case State::kDeviceError: - auto self = GetWeakPtr(); - // Executing callbacks may free |this|. Check |self| first. - while (self && !pending_frames_.empty()) { - // Respond to any pending frames. - FrameCallback cb = std::move(pending_frames_.front().callback); - pending_frames_.pop_front(); - std::move(cb).Run(std::nullopt); - } - break; - } -} - -FidoDevice::CancelToken FidoCableDevice::AddToPendingFrames( - FidoBleDeviceCommand cmd, - std::vector<uint8_t> request, - DeviceCallback callback) { - const auto token = next_cancel_token_++; - pending_frames_.emplace_back( - FidoBleFrame(cmd, std::move(request)), - base::BindOnce(&FidoCableDevice::OnBleResponseReceived, - weak_factory_.GetWeakPtr(), std::move(callback)), - token); - - Transition(); - return token; -} - -void FidoCableDevice::SendHandshakeMessage( - std::vector<uint8_t> handshake_message, - DeviceCallback callback) { - AddToPendingFrames(FidoBleDeviceCommand::kControl, - std::move(handshake_message), std::move(callback)); -} - -void FidoCableDevice::SetV1EncryptionData( - base::span<const uint8_t, 32> session_key, - base::span<const uint8_t, 8> nonce) { - // Encryption data must be set at most once during Cable handshake protocol. - DCHECK(!encryption_data_); - encryption_data_.emplace(session_key); - encryption_data_->nonce = fido_parsing_utils::Materialize(nonce); -} - -void FidoCableDevice::SetSequenceNumbersForTesting(uint32_t read_seq, - uint32_t write_seq) { - encryption_data_->write_sequence_num = write_seq; - encryption_data_->read_sequence_num = read_seq; -} - -base::WeakPtr<FidoDevice> FidoCableDevice::GetWeakPtr() { - return weak_factory_.GetWeakPtr(); -} - -FidoCableDevice::PendingFrame::PendingFrame(FidoBleFrame in_frame, - FrameCallback in_callback, - CancelToken in_token) - : frame(std::move(in_frame)), - callback(std::move(in_callback)), - token(in_token) {} - -FidoCableDevice::PendingFrame::PendingFrame(PendingFrame&&) = default; - -FidoCableDevice::PendingFrame::~PendingFrame() = default; - -void FidoCableDevice::OnConnected(bool success) { - if (state_ != State::kConnecting) { - return; - } - StopTimeout(); - if (!success) { - FIDO_LOG(ERROR) << "FidoCableDevice::Connect() failed"; - state_ = State::kDeviceError; - Transition(); - return; - } - FIDO_LOG(EVENT) << "FidoCableDevice connected"; - DCHECK_EQ(State::kConnecting, state_); - StartTimeout(); - connection_->ReadControlPointLength(base::BindOnce( - &FidoCableDevice::OnReadControlPointLength, weak_factory_.GetWeakPtr())); -} - -void FidoCableDevice::OnStatusMessage(std::vector<uint8_t> data) { - if (transaction_) - transaction_->OnResponseFragment(std::move(data)); -} - -void FidoCableDevice::OnReadControlPointLength(std::optional<uint16_t> length) { - if (state_ == State::kDeviceError) { - return; - } - - StopTimeout(); - if (length) { - control_point_length_ = *length; - state_ = State::kReady; - } else { - state_ = State::kDeviceError; - } - Transition(); -} - -void FidoCableDevice::SendRequestFrame(FidoBleFrame frame, - FrameCallback callback) { - state_ = State::kBusy; - transaction_.emplace(connection_.get(), control_point_length_); - transaction_->WriteRequestFrame( - std::move(frame), - base::BindOnce(&FidoCableDevice::OnResponseFrame, - weak_factory_.GetWeakPtr(), std::move(callback))); -} - -void FidoCableDevice::StartTimeout() { - timer_.Start(FROM_HERE, kDeviceTimeout, this, &FidoCableDevice::OnTimeout); -} - -void FidoCableDevice::StopTimeout() { - timer_.Stop(); -} - -void FidoCableDevice::OnTimeout() { - FIDO_LOG(ERROR) << "FIDO Cable device timeout for " << GetId(); - state_ = State::kDeviceError; - Transition(); -} - -void FidoCableDevice::OnBleResponseReceived(DeviceCallback callback, - std::optional<FidoBleFrame> frame) { - if (!frame || !frame->IsValid()) { - state_ = State::kDeviceError; - std::move(callback).Run(std::nullopt); - return; - } - - if (frame->command() == FidoBleDeviceCommand::kError) { - ProcessBleDeviceError(frame->data()); - std::move(callback).Run(std::nullopt); - return; - } - - std::move(callback).Run(frame->data()); -} - -void FidoCableDevice::ProcessBleDeviceError(base::span<const uint8_t> data) { - if (data.size() != 1) { - FIDO_LOG(ERROR) << "Unknown BLE error received: " << base::HexEncode(data); - state_ = State::kDeviceError; - return; - } - - switch (static_cast<FidoBleFrame::ErrorCode>(data[0])) { - case FidoBleFrame::ErrorCode::INVALID_CMD: - case FidoBleFrame::ErrorCode::INVALID_PAR: - case FidoBleFrame::ErrorCode::INVALID_LEN: - state_ = State::kMsgError; - break; - default: - FIDO_LOG(ERROR) << "BLE error received: " << static_cast<int>(data[0]); - state_ = State::kDeviceError; - } -} - -bool FidoCableDevice::EncryptOutgoingMessage( - std::vector<uint8_t>* message_to_encrypt) { - const auto nonce = - ConstructV1Nonce(encryption_data_->nonce, /*is_sender_client=*/true, - encryption_data_->write_sequence_num++); - if (!nonce) - return false; - - const uint8_t additional_data[1] = { - base::strict_cast<uint8_t>(FidoBleDeviceCommand::kMsg)}; - std::vector<uint8_t> ciphertext = encryption_data_->writer.Seal( - *message_to_encrypt, *nonce, additional_data); - message_to_encrypt->swap(ciphertext); - return true; -} - -bool FidoCableDevice::DecryptIncomingMessage(FidoBleFrame* incoming_frame) { - const auto nonce = - ConstructV1Nonce(encryption_data_->nonce, /*is_sender_client=*/false, - encryption_data_->read_sequence_num); - if (!nonce) - return false; - - const uint8_t additional_data[1] = { - base::strict_cast<uint8_t>(incoming_frame->command())}; - std::optional<std::vector<uint8_t>> plaintext = encryption_data_->reader.Open( - incoming_frame->data(), *nonce, additional_data); - if (!plaintext) { - FIDO_LOG(ERROR) << "Failed to decrypt caBLE message."; - return false; - } - - encryption_data_->read_sequence_num++; - incoming_frame->data().swap(*plaintext); - return true; -} - -} // namespace device
diff --git a/device/fido/cable/fido_cable_device.h b/device/fido/cable/fido_cable_device.h deleted file mode 100644 index 3604ba48..0000000 --- a/device/fido/cable/fido_cable_device.h +++ /dev/null
@@ -1,139 +0,0 @@ -// Copyright 2018 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef DEVICE_FIDO_CABLE_FIDO_CABLE_DEVICE_H_ -#define DEVICE_FIDO_CABLE_FIDO_CABLE_DEVICE_H_ - -#include <array> -#include <memory> -#include <optional> -#include <string> -#include <vector> - -#include "base/component_export.h" -#include "base/containers/span.h" -#include "base/memory/raw_ptr.h" -#include "base/memory/weak_ptr.h" -#include "crypto/aead.h" -#include "device/fido/cable/fido_ble_connection.h" -#include "device/fido/cable/fido_ble_transaction.h" -#include "device/fido/fido_device.h" - -namespace device { - -class BluetoothAdapter; -class FidoBleFrame; - -class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDevice : public FidoDevice { - public: - using FrameCallback = FidoBleTransaction::FrameCallback; - - FidoCableDevice(BluetoothAdapter* adapter, std::string address); - // Constructor used for testing purposes. - FidoCableDevice(std::unique_ptr<FidoBleConnection> connection); - - FidoCableDevice(const FidoCableDevice&) = delete; - FidoCableDevice& operator=(const FidoCableDevice&) = delete; - - ~FidoCableDevice() override; - - // Returns FidoDevice::GetId() for a given FidoBleConnection address. - static std::string GetIdForAddress(const std::string& ble_address); - - std::string GetAddress(); - void Connect(); - void SendPing(std::vector<uint8_t> data, DeviceCallback callback); - FidoBleConnection::ReadCallback GetReadCallbackForTesting(); - - // FidoDevice: - void Cancel(CancelToken token) override; - std::string GetId() const override; - FidoTransportProtocol DeviceTransport() const override; - CancelToken DeviceTransact(std::vector<uint8_t> command, - DeviceCallback callback) override; - - void SendHandshakeMessage(std::vector<uint8_t> handshake_message, - DeviceCallback callback); - - // Configure caBLE v1 keys. - void SetV1EncryptionData(base::span<const uint8_t, 32> session_key, - base::span<const uint8_t, 8> nonce); - - // SetCountersForTesting allows tests to set the message counters. Non-test - // code must not call this function. - void SetSequenceNumbersForTesting(uint32_t read_counter, - uint32_t write_counter); - - base::WeakPtr<FidoDevice> GetWeakPtr() override; - - private: - struct PendingFrame { - PendingFrame(FidoBleFrame frame, FrameCallback callback, CancelToken token); - PendingFrame(PendingFrame&&); - ~PendingFrame(); - - FidoBleFrame frame; - FrameCallback callback; - CancelToken token; - }; - - // Encapsulates state FidoCableDevice maintains to encrypt and decrypt - // data within FidoBleFrame. - struct EncryptionData { - explicit EncryptionData(base::span<const uint8_t, 32> session_key); - ~EncryptionData(); - - crypto::Aead reader; - crypto::Aead writer; - std::array<uint8_t, 8> nonce; - uint32_t write_sequence_num = 0; - uint32_t read_sequence_num = 0; - }; - - void OnResponseFrame(FrameCallback callback, - std::optional<FidoBleFrame> frame); - void Transition(); - CancelToken AddToPendingFrames(FidoBleDeviceCommand cmd, - std::vector<uint8_t> request, - DeviceCallback callback); - void ResetTransaction(); - - void OnConnected(bool success); - void OnStatusMessage(std::vector<uint8_t> data); - - void OnReadControlPointLength(std::optional<uint16_t> length); - - void SendRequestFrame(FidoBleFrame frame, FrameCallback callback); - - void StartTimeout(); - void StopTimeout(); - void OnTimeout(); - - void OnBleResponseReceived(DeviceCallback callback, - std::optional<FidoBleFrame> frame); - void ProcessBleDeviceError(base::span<const uint8_t> data); - - bool EncryptOutgoingMessage(std::vector<uint8_t>* message_to_encrypt); - bool DecryptIncomingMessage(FidoBleFrame* incoming_frame); - - base::OneShotTimer timer_; - - std::unique_ptr<FidoBleConnection> connection_; - uint16_t control_point_length_ = 0; - - // pending_frames_ contains frames that have not yet been sent, i.e. the - // current frame is not included at the head of the list. - std::list<PendingFrame> pending_frames_; - // current_token_ contains the cancelation token of the currently running - // request, or else is empty if no request is currently pending. - std::optional<CancelToken> current_token_; - std::optional<FidoBleTransaction> transaction_; - - std::optional<EncryptionData> encryption_data_; - base::WeakPtrFactory<FidoCableDevice> weak_factory_{this}; -}; - -} // namespace device - -#endif // DEVICE_FIDO_CABLE_FIDO_CABLE_DEVICE_H_
diff --git a/device/fido/cable/fido_cable_device_unittest.cc b/device/fido/cable/fido_cable_device_unittest.cc deleted file mode 100644 index 88345c4..0000000 --- a/device/fido/cable/fido_cable_device_unittest.cc +++ /dev/null
@@ -1,328 +0,0 @@ -// Copyright 2018 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "device/fido/cable/fido_cable_device.h" - -#include <array> -#include <cstdint> -#include <limits> -#include <memory> -#include <optional> -#include <string> -#include <utility> -#include <vector> - -#include "base/check.h" -#include "base/containers/span.h" -#include "base/functional/bind.h" -#include "base/location.h" -#include "base/memory/raw_ptr.h" -#include "base/memory/scoped_refptr.h" -#include "base/strings/string_view_util.h" -#include "base/task/sequenced_task_runner.h" -#include "base/test/task_environment.h" -#include "base/test/test_future.h" -#include "crypto/aead.h" -#include "device/bluetooth/test/bluetooth_test.h" -#include "device/bluetooth/test/mock_bluetooth_adapter.h" -#include "device/fido/cable/mock_fido_ble_connection.h" -#include "device/fido/fido_parsing_utils.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace device { - -namespace { - -using ::testing::_; -using ::testing::Test; -using TestDeviceFuture = - base::test::TestFuture<std::optional<std::vector<uint8_t>>>; -using NiceMockBluetoothAdapter = ::testing::NiceMock<MockBluetoothAdapter>; - -// Sufficiently large test control point length as we are not interested -// in testing fragmentations of BLE messages. All Cable messages are encrypted -// and decrypted per request frame, not fragment. -constexpr auto kControlPointLength = std::numeric_limits<uint16_t>::max(); -// Counter value that is larger than FidoCableDevice::kMaxCounter. -constexpr uint32_t kInvalidCounter = 1 << 24; -constexpr std::array<uint8_t, 32> kTestSessionKey = {0}; -constexpr std::array<uint8_t, 8> kTestEncryptionNonce = { - {1, 1, 1, 1, 1, 1, 1, 1}}; -constexpr uint8_t kTestData[] = {'T', 'E', 'S', 'T'}; -// kCTAPFramingLength is the number of bytes of framing data at the beginning -// of transmitted BLE messages. See -// https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#ble-client-to-authenticator -constexpr size_t kCTAPFramingLength = 3; - -std::vector<uint8_t> ConstructSerializedOutgoingFragment( - base::span<const uint8_t> data) { - FidoBleFrame response_frame(FidoBleDeviceCommand::kMsg, - fido_parsing_utils::Materialize(data)); - const auto response_fragment = - std::get<0>(response_frame.ToFragments(kControlPointLength)); - - std::vector<uint8_t> outgoing_message; - response_fragment.Serialize(&outgoing_message); - return outgoing_message; -} - -class FakeCableAuthenticator { - public: - // Returns encrypted message of the ciphertext received from the client. - std::vector<uint8_t> ReplyWithSameMessage(base::span<const uint8_t> message) { - auto decrypted_message = DecryptMessage(message); - auto message_to_send = EncryptMessage(std::move(decrypted_message)); - return std::vector<uint8_t>(message_to_send.begin(), message_to_send.end()); - } - - void SetSessionKey(const std::string& session_key) { - session_key_ = session_key; - } - - void SetAuthenticatorCounter(uint32_t authenticator_counter) { - authenticator_counter_ = authenticator_counter; - } - - private: - std::string EncryptMessage(std::string message) { - crypto::Aead aead(crypto::Aead::AES_256_GCM); - DCHECK_EQ(session_key_.size(), aead.KeyLength()); - aead.Init(&session_key_); - - auto encryption_nonce = fido_parsing_utils::Materialize(nonce_); - encryption_nonce.push_back(0x01); - encryption_nonce.push_back(authenticator_counter_ >> 16 & 0xFF); - encryption_nonce.push_back(authenticator_counter_ >> 8 & 0xFF); - encryption_nonce.push_back(authenticator_counter_ & 0xFF); - DCHECK(encryption_nonce.size() == aead.NonceLength()); - - std::string ciphertext; - aead.Seal( - message, base::as_string_view(encryption_nonce), - std::string(1, base::strict_cast<uint8_t>(FidoBleDeviceCommand::kMsg)), - &ciphertext); - authenticator_counter_++; - return ciphertext; - } - - std::string DecryptMessage(base::span<const uint8_t> message) { - crypto::Aead aead(crypto::Aead::AES_256_GCM); - DCHECK_EQ(session_key_.size(), aead.KeyLength()); - aead.Init(&session_key_); - - auto encryption_nonce = fido_parsing_utils::Materialize(nonce_); - encryption_nonce.push_back(0x00); - encryption_nonce.push_back(expected_client_counter_ >> 16 & 0xFF); - encryption_nonce.push_back(expected_client_counter_ >> 8 & 0xFF); - encryption_nonce.push_back(expected_client_counter_ & 0xFF); - DCHECK(encryption_nonce.size() == aead.NonceLength()); - - std::string ciphertext; - aead.Open( - base::as_string_view(message), base::as_string_view(encryption_nonce), - std::string(1, base::strict_cast<uint8_t>(FidoBleDeviceCommand::kMsg)), - &ciphertext); - expected_client_counter_++; - return ciphertext; - } - - std::array<uint8_t, 8> nonce_ = kTestEncryptionNonce; - std::string session_key_{ - reinterpret_cast<const char*>(kTestSessionKey.data()), - kTestSessionKey.size()}; - uint32_t expected_client_counter_ = 0; - uint32_t authenticator_counter_ = 0; -}; - -} // namespace - -class FidoCableDeviceTest : public Test { - public: - FidoCableDeviceTest() { - auto connection = std::make_unique<MockFidoBleConnection>( - adapter_.get(), BluetoothTestBase::kTestDeviceAddress1); - connection_ = connection.get(); - device_ = std::make_unique<FidoCableDevice>(std::move(connection)); - device_->SetV1EncryptionData(kTestSessionKey, kTestEncryptionNonce); - connection_->read_callback() = device_->GetReadCallbackForTesting(); - } - - void ConnectWithLength(uint16_t length) { - EXPECT_CALL(*connection(), ConnectPtr).WillOnce([](auto* callback) { - std::move(*callback).Run(true); - }); - - EXPECT_CALL(*connection(), ReadControlPointLengthPtr(_)) - .WillOnce([length](auto* cb) { std::move(*cb).Run(length); }); - - device()->Connect(); - } - - FidoCableDevice* device() { return device_.get(); } - MockFidoBleConnection* connection() { return connection_; } - FakeCableAuthenticator* authenticator() { return &authenticator_; } - - protected: - base::test::TaskEnvironment task_environment_{ - base::test::TaskEnvironment::TimeSource::MOCK_TIME}; - - private: - scoped_refptr<MockBluetoothAdapter> adapter_ = - base::MakeRefCounted<NiceMockBluetoothAdapter>(); - FakeCableAuthenticator authenticator_; - raw_ptr<MockFidoBleConnection, DanglingUntriaged> connection_; - std::unique_ptr<FidoCableDevice> device_; -}; - -TEST_F(FidoCableDeviceTest, ConnectionFailureTest) { - EXPECT_CALL(*connection(), ConnectPtr).WillOnce([](auto* callback) { - std::move(*callback).Run(false); - }); - - device()->Connect(); -} - -TEST_F(FidoCableDeviceTest, StaticGetIdTest) { - std::string address = BluetoothTestBase::kTestDeviceAddress1; - EXPECT_EQ("ble-" + address, FidoCableDevice::GetIdForAddress(address)); -} - -TEST_F(FidoCableDeviceTest, GetIdTest) { - EXPECT_EQ(std::string("ble-") + BluetoothTestBase::kTestDeviceAddress1, - device()->GetId()); -} - -TEST_F(FidoCableDeviceTest, Timeout) { - EXPECT_CALL(*connection(), ConnectPtr); - TestDeviceFuture future; - device()->SendPing(std::vector<uint8_t>(), future.GetCallback()); - - task_environment_.FastForwardUntilNoTasksRemain(); - EXPECT_EQ(FidoDevice::State::kDeviceError, device()->state_for_testing()); - EXPECT_TRUE(future.IsReady()); - EXPECT_FALSE(future.Get()); -} - -TEST_F(FidoCableDeviceTest, TestCaBleDeviceSendData) { - ConnectWithLength(kControlPointLength); - - EXPECT_CALL(*connection(), WriteControlPointPtr(_, _)) - .WillRepeatedly([this](const auto& data, auto* cb) { - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(*cb), true)); - - const auto authenticator_reply = authenticator()->ReplyWithSameMessage( - base::span(data).template subspan<kCTAPFramingLength>()); - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(connection()->read_callback(), - ConstructSerializedOutgoingFragment( - authenticator_reply))); - }); - - for (size_t i = 0; i < 3; i++) { - SCOPED_TRACE(i); - - TestDeviceFuture future; - device()->DeviceTransact(fido_parsing_utils::Materialize(kTestData), - future.GetCallback()); - - EXPECT_TRUE(future.Wait()); - const auto& value = future.Get(); - ASSERT_TRUE(value); - EXPECT_THAT(*value, ::testing::ElementsAreArray(kTestData)); - } -} - -TEST_F(FidoCableDeviceTest, TestCableDeviceFailOnIncorrectSessionKey) { - constexpr char kIncorrectSessionKey[] = "11111111111111111111111111111111"; - ConnectWithLength(kControlPointLength); - - EXPECT_CALL(*connection(), WriteControlPointPtr(_, _)) - .WillOnce([this, &kIncorrectSessionKey](const auto& data, auto* cb) { - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(*cb), true)); - - authenticator()->SetSessionKey(kIncorrectSessionKey); - const auto authenticator_reply = authenticator()->ReplyWithSameMessage( - base::span(data).template subspan<kCTAPFramingLength>()); - - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(connection()->read_callback(), - ConstructSerializedOutgoingFragment( - authenticator_reply))); - }); - - TestDeviceFuture future; - device()->DeviceTransact(fido_parsing_utils::Materialize(kTestData), - future.GetCallback()); - - EXPECT_TRUE(future.Wait()); - const auto& value = future.Get(); - EXPECT_FALSE(value); -} - -TEST_F(FidoCableDeviceTest, TestCableDeviceFailOnUnexpectedCounter) { - constexpr uint32_t kIncorrectAuthenticatorCounter = 1; - ConnectWithLength(kControlPointLength); - - EXPECT_CALL(*connection(), WriteControlPointPtr(_, _)) - .WillOnce([this](const auto& data, auto* cb) { - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(*cb), true)); - - authenticator()->SetAuthenticatorCounter( - kIncorrectAuthenticatorCounter); - const auto authenticator_reply = authenticator()->ReplyWithSameMessage( - base::span(data).template subspan<kCTAPFramingLength>()); - - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(connection()->read_callback(), - ConstructSerializedOutgoingFragment( - authenticator_reply))); - }); - - TestDeviceFuture future; - device()->DeviceTransact(fido_parsing_utils::Materialize(kTestData), - future.GetCallback()); - - EXPECT_TRUE(future.Wait()); - const auto& value = future.Get(); - EXPECT_FALSE(value); -} - -// Test the unlikely event that the authenticator and client has sent/received -// requests more than FidoCableDevice::kMaxCounter amount of times. As we are -// only using 3 bytes to encapsulate counter during encryption, any counter -// value that is above FidoCableDevice::kMaxCounter -- even though it may be -// the expected counter value -- should return an error. -TEST_F(FidoCableDeviceTest, TestCableDeviceErrorOnMaxCounter) { - ConnectWithLength(kControlPointLength); - - EXPECT_CALL(*connection(), WriteControlPointPtr(_, _)) - .WillOnce([this](const auto& data, auto* cb) { - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(*cb), true)); - - authenticator()->SetAuthenticatorCounter(kInvalidCounter); - const auto authenticator_reply = authenticator()->ReplyWithSameMessage( - base::span(data).template subspan<kCTAPFramingLength>()); - - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(connection()->read_callback(), - ConstructSerializedOutgoingFragment( - authenticator_reply))); - }); - - TestDeviceFuture future; - device()->SetSequenceNumbersForTesting(kInvalidCounter, 0); - device()->DeviceTransact(fido_parsing_utils::Materialize(kTestData), - future.GetCallback()); - - EXPECT_TRUE(future.Wait()); - const auto& value = future.Get(); - EXPECT_FALSE(value); -} - -} // namespace device
diff --git a/device/fido/cable/fido_cable_discovery.cc b/device/fido/cable/fido_cable_discovery.cc deleted file mode 100644 index 31a1f3f..0000000 --- a/device/fido/cable/fido_cable_discovery.cc +++ /dev/null
@@ -1,773 +0,0 @@ -// Copyright 2018 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "device/fido/cable/fido_cable_discovery.h" - -#include <algorithm> -#include <memory> -#include <utility> - -#include "base/barrier_closure.h" -#include "base/compiler_specific.h" -#include "base/functional/bind.h" -#include "base/functional/callback_helpers.h" -#include "base/logging.h" -#include "base/metrics/histogram_functions.h" -#include "base/no_destructor.h" -#include "base/notimplemented.h" -#include "base/notreached.h" -#include "base/strings/string_number_conversions.h" -#include "base/task/sequenced_task_runner.h" -#include "base/time/time.h" -#include "build/build_config.h" -#include "components/device_event_log/device_event_log.h" -#include "device/bluetooth/bluetooth_adapter.h" -#include "device/bluetooth/bluetooth_adapter_factory.h" -#include "device/bluetooth/bluetooth_advertisement.h" -#include "device/bluetooth/bluetooth_discovery_session.h" -#include "device/bluetooth/public/cpp/bluetooth_uuid.h" -#include "device/fido/cable/fido_ble_connection.h" -#include "device/fido/cable/fido_ble_uuids.h" -#include "device/fido/cable/fido_cable_device.h" -#include "device/fido/cable/fido_cable_handshake_handler.h" -#include "device/fido/cable/fido_tunnel_device.h" -#include "device/fido/fido_parsing_utils.h" -#include "device/fido/public/features.h" - -#if BUILDFLAG(IS_MAC) -#include "device/fido/mac/util.h" -#endif - -#if BUILDFLAG(IS_CHROMEOS) -#include "device/bluetooth/bluetooth_low_energy_scan_filter.h" -#include "device/bluetooth/floss/floss_features.h" -#endif // BUILDFLAG(IS_CHROMEOS) - -namespace device { - -namespace { - -// Client name for logging in BLE scanning. -constexpr char kScanClientName[] = "FIDO"; - -// Construct advertisement data with different formats depending on client's -// operating system. Ideally, we advertise EIDs as part of Service Data, but -// this isn't available on all platforms. On Windows we use Manufacturer Data -// instead, and on Mac our only option is to advertise an additional service -// with the EID as its UUID. -std::unique_ptr<BluetoothAdvertisement::Data> ConstructAdvertisementData( - base::span<const uint8_t, kCableEphemeralIdSize> client_eid) { - auto advertisement_data = std::make_unique<BluetoothAdvertisement::Data>( - BluetoothAdvertisement::AdvertisementType::ADVERTISEMENT_TYPE_BROADCAST); - -#if BUILDFLAG(IS_MAC) - BluetoothAdvertisement::UUIDList list; - list.emplace_back(kGoogleCableUUID16); - list.emplace_back(fido_parsing_utils::ConvertBytesToUuid(client_eid)); - advertisement_data->set_service_uuids(std::move(list)); - -#elif BUILDFLAG(IS_WIN) - // References: - // https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers - // go/google-ble-manufacturer-data-format - static constexpr uint16_t kGoogleManufacturerId = 0x00E0; - static constexpr uint8_t kCableGoogleManufacturerDataType = 0x15; - - // Reference: - // https://github.com/arnar/fido-2-specs/blob/fido-client-to-authenticator-protocol.bs#L4314 - static constexpr uint8_t kCableFlags = 0x20; - - static constexpr uint8_t kCableGoogleManufacturerDataLength = - 3u + kCableEphemeralIdSize; - std::array<uint8_t, 4> kCableGoogleManufacturerDataHeader = { - kCableGoogleManufacturerDataLength, kCableGoogleManufacturerDataType, - kCableFlags, /*version=*/1}; - - BluetoothAdvertisement::ManufacturerData manufacturer_data; - std::vector<uint8_t> manufacturer_data_value; - fido_parsing_utils::Append(&manufacturer_data_value, - kCableGoogleManufacturerDataHeader); - fido_parsing_utils::Append(&manufacturer_data_value, client_eid); - manufacturer_data.emplace(kGoogleManufacturerId, - std::move(manufacturer_data_value)); - advertisement_data->set_manufacturer_data(std::move(manufacturer_data)); - -#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) - // Reference: - // https://github.com/arnar/fido-2-specs/blob/fido-client-to-authenticator-protocol.bs#L4314 - static constexpr uint8_t kCableFlags = 0x20; - - // Service data for ChromeOS and Linux is 1 byte corresponding to Cable flags, - // followed by 1 byte corresponding to Cable version number, followed by 16 - // bytes corresponding to client EID. - BluetoothAdvertisement::ServiceData service_data; - std::vector<uint8_t> service_data_value(18, 0); - // Since the remainder of this service data field is a Cable EID, set the 5th - // bit of the flag byte. - service_data_value[0] = kCableFlags; - service_data_value[1] = 1 /* version */; - std::ranges::copy(client_eid, service_data_value.begin() + 2); - service_data.emplace(kGoogleCableUUID128, std::move(service_data_value)); - advertisement_data->set_service_data(std::move(service_data)); -#endif - - return advertisement_data; -} - -} // namespace - -// FidoCableDiscovery::ObservedDeviceData ------------------------------------- - -FidoCableDiscovery::ObservedDeviceData::ObservedDeviceData() = default; -FidoCableDiscovery::ObservedDeviceData::~ObservedDeviceData() = default; - -// FidoCableDiscovery --------------------------------------------------------- - -FidoCableDiscovery::FidoCableDiscovery( - std::vector<CableDiscoveryData> discovery_data) - : FidoDeviceDiscovery(FidoTransportProtocol::kHybrid), - discovery_data_(std::move(discovery_data)) { -// Windows currently does not support multiple EIDs, thus we ignore any extra -// discovery data. -// TODO(crbug.com/40573698): Add support for multiple EIDs on Windows. -#if BUILDFLAG(IS_WIN) - if (discovery_data_.size() > 1u) { - FIDO_LOG(ERROR) << "discovery_data_.size()=" << discovery_data_.size() - << ", trimming to 1."; - discovery_data_.erase(discovery_data_.begin() + 1, discovery_data_.end()); - } -#endif - for (const CableDiscoveryData& data : discovery_data_) { - if (data.version != CableDiscoveryData::Version::V1) { - continue; - } - has_v1_discovery_data_ = true; - break; - } -} - -FidoCableDiscovery::~FidoCableDiscovery() { - // Work around dangling advertisement references. (crbug/846522) - for (auto advertisement : advertisements_) { - advertisement.second->Unregister(base::DoNothing(), base::DoNothing()); - } - - if (adapter_) - adapter_->RemoveObserver(this); -} - -std::unique_ptr<FidoDiscoveryBase::EventStream<base::span<const uint8_t, 20>>> -FidoCableDiscovery::GetV2AdvertStream() { - DCHECK(!advert_callback_); - - std::unique_ptr<EventStream<base::span<const uint8_t, 20>>> ret; - std::tie(advert_callback_, ret) = - EventStream<base::span<const uint8_t, 20>>::New(); - return ret; -} - -std::unique_ptr<FidoCableHandshakeHandler> -FidoCableDiscovery::CreateV1HandshakeHandler( - FidoCableDevice* device, - const CableDiscoveryData& discovery_data, - const CableEidArray& authenticator_eid) { - std::unique_ptr<FidoCableHandshakeHandler> handler; - switch (discovery_data.version) { - case CableDiscoveryData::Version::V1: { - // Nonce is embedded as first 8 bytes of client EID. - std::array<uint8_t, 8> nonce; - const bool ok = fido_parsing_utils::ExtractArray( - discovery_data.v1->client_eid, 0, &nonce); - DCHECK(ok); - - return std::make_unique<FidoCableV1HandshakeHandler>( - device, nonce, discovery_data.v1->session_pre_key); - } - - case CableDiscoveryData::Version::V2: - case CableDiscoveryData::Version::INVALID: - NOTREACHED(); - } -} - -// static -const BluetoothUUID& FidoCableDiscovery::GoogleCableUUID() { - static const base::NoDestructor<BluetoothUUID> kUUID(kGoogleCableUUID128); - return *kUUID; -} - -const BluetoothUUID& FidoCableDiscovery::FIDOCableUUID() { - static const base::NoDestructor<BluetoothUUID> kUUID(kFIDOCableUUID128); - return *kUUID; -} - -// static -bool FidoCableDiscovery::IsCableDevice(const BluetoothDevice* device) { - const auto& uuid1 = GoogleCableUUID(); - const auto& uuid2 = FIDOCableUUID(); - return device->GetServiceData().contains(uuid1) || - device->GetUUIDs().contains(uuid1) || - device->GetServiceData().contains(uuid2) || - device->GetUUIDs().contains(uuid2); -} - -void FidoCableDiscovery::OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter) { - if (!adapter->IsPresent()) { - FIDO_LOG(DEBUG) << "No BLE adapter present"; - NotifyDiscoveryStarted(false); - return; - } - - DCHECK(!adapter_); - adapter_ = std::move(adapter); - DCHECK(adapter_); - - adapter_->AddObserver(this); - BluetoothAdapter::PermissionStatus bluetooth_permission = - BluetoothAdapter::PermissionStatus::kAllowed; -#if BUILDFLAG(IS_MAC) - switch (fido::mac::ProcessIsSigned()) { - case fido::mac::CodeSigningState::kSigned: - bluetooth_permission = adapter_->GetOsPermissionStatus(); - FIDO_LOG(DEBUG) << "Bluetooth authorized: " - << static_cast<int>(bluetooth_permission); - break; - case fido::mac::CodeSigningState::kNotSigned: - FIDO_LOG(DEBUG) - << "Build not signed. Assuming Bluetooth permission is granted."; - break; - } -#endif - - if (bluetooth_permission == BluetoothAdapter::PermissionStatus::kAllowed) { - FIDO_LOG(DEBUG) << "BLE adapter address " << adapter_->GetAddress(); - if (adapter_->IsPowered()) { - OnSetPowered(); - } - } - - // FidoCableDiscovery blocks its transport availability callback on the - // DiscoveryStarted() calls of all instantiated discoveries. Hence, this call - // must not be put behind the BLE adapter getting powered on (which is - // dependent on the UI), or else the UI and this discovery will wait on each - // other indefinitely (see crbug.com/1018416). - NotifyDiscoveryStarted(true); -} - -void FidoCableDiscovery::OnSetPowered() { - DCHECK(adapter()); - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(&FidoCableDiscovery::StartCableDiscovery, - weak_factory_.GetWeakPtr())); -} - -void FidoCableDiscovery::SetDiscoverySession( - std::unique_ptr<BluetoothDiscoverySession> discovery_session) { - discovery_session_ = std::move(discovery_session); -} - -void FidoCableDiscovery::DeviceAdded(BluetoothAdapter* adapter, - BluetoothDevice* device) { - if (!IsCableDevice(device)) - return; - - CableDeviceFound(adapter, device); -} - -void FidoCableDiscovery::DeviceChanged(BluetoothAdapter* adapter, - BluetoothDevice* device) { - if (!IsCableDevice(device)) - return; - - CableDeviceFound(adapter, device); -} - -void FidoCableDiscovery::DeviceRemoved(BluetoothAdapter* adapter, - BluetoothDevice* device) { - const auto& device_address = device->GetAddress(); - if (IsCableDevice(device) && - // It only matters if V1 devices are "removed" because V2 devices do not - // transport data over BLE. - active_devices_.contains(device_address)) { - FIDO_LOG(DEBUG) << "caBLE device removed: " << device_address; - RemoveDevice(FidoCableDevice::GetIdForAddress(device_address)); - } -} - -void FidoCableDiscovery::AdapterPoweredChanged(BluetoothAdapter* adapter, - bool powered) { - if (!powered) { - // In order to prevent duplicate client EIDs from being advertised when - // BluetoothAdapter is powered back on, unregister all existing client - // EIDs. - StopAdvertisements(base::DoNothing()); - return; - } - -#if BUILDFLAG(IS_WIN) - // On Windows, the power-on event appears to race against initialization of - // the adapter, such that one of the WinRT API calls inside - // BluetoothAdapter::StartDiscoverySessionWithFilter() can fail with "Device - // not ready for use". So wait for things to actually be ready. - // TODO(crbug.com/40670639): Remove this delay once the Bluetooth layer - // handles the spurious failure. - base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( - FROM_HERE, - base::BindOnce(&FidoCableDiscovery::StartCableDiscovery, - weak_factory_.GetWeakPtr()), - base::Milliseconds(500)); -#else - StartCableDiscovery(); -#endif // BUILDFLAG(IS_WIN) -} - -void FidoCableDiscovery::AdapterDiscoveringChanged(BluetoothAdapter* adapter, - bool is_scanning) { - FIDO_LOG(DEBUG) << "AdapterDiscoveringChanged() is_scanning=" << is_scanning; - - // Ignore updates while we're not scanning for caBLE devices ourselves. Other - // things in Chrome may start or stop scans at any time. - if (!discovery_session_) { - return; - } -} - -#if BUILDFLAG(IS_CHROMEOS) -void FidoCableDiscovery::OnDeviceFound( - device::BluetoothLowEnergyScanSession* scan_session, - device::BluetoothDevice* device) { - DeviceAdded(adapter_.get(), device); -} - -void FidoCableDiscovery::OnDeviceLost( - device::BluetoothLowEnergyScanSession* scan_session, - device::BluetoothDevice* device) { - DeviceRemoved(adapter_.get(), device); -} - -void FidoCableDiscovery::OnSessionStarted( - device::BluetoothLowEnergyScanSession* scan_session, - std::optional<device::BluetoothLowEnergyScanSession::ErrorCode> - error_code) { - if (error_code) { - FIDO_LOG(ERROR) << "Failed to start caBLE LE scan session, error_code = " - << static_cast<int>(error_code.value()); - le_scan_session_.reset(); - return; - } - - FIDO_LOG(DEBUG) << "LE scan session started."; - - // Advertising is delayed by 500ms to ensure that any UI has a chance to - // appear as we don't want to start broadcasting without the user being - // aware. - base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( - FROM_HERE, - base::BindOnce(&FidoCableDiscovery::StartAdvertisement, - weak_factory_.GetWeakPtr()), - base::Milliseconds(500)); -} - -void FidoCableDiscovery::OnSessionInvalidated( - device::BluetoothLowEnergyScanSession* scan_session) { - FIDO_LOG(EVENT) << "LE scan session invalidated"; - le_scan_session_.reset(); -} -#endif // BUILDFLAG(IS_CHROMEOS) - -void FidoCableDiscovery::StartCableDiscovery() { -#if BUILDFLAG(IS_CHROMEOS) - if (floss::features::IsFlossEnabled()) { - device::BluetoothLowEnergyScanFilter::Pattern google_pattern( - /*start_position=*/0, - device::BluetoothLowEnergyScanFilter::AdvertisementDataType:: - kServiceData, - /* kServiceData takes the 16-bit UUID as a little endian byte vector. */ - std::vector<uint8_t>{kGoogleCableUUID[3], kGoogleCableUUID[2]}); - device::BluetoothLowEnergyScanFilter::Pattern fido_pattern( - /*start_position=*/0, - device::BluetoothLowEnergyScanFilter::AdvertisementDataType:: - kServiceData, - std::vector<uint8_t>{kFIDOCableUUID[3], kFIDOCableUUID[2]}); - auto filter = device::BluetoothLowEnergyScanFilter::Create( - device::BluetoothLowEnergyScanFilter::Range::kFar, - /*device_found_timeout=*/base::Seconds(1), - /*device_lost_timeout=*/base::Seconds(7), - {google_pattern, fido_pattern}, - /*rssi_sampling_period=*/base::Seconds(1)); - if (!filter) { - FIDO_LOG(ERROR) - << "Failed to start LE scanning due to failure to create filter."; - return; - } - - le_scan_session_ = adapter_->StartLowEnergyScanSession( - std::move(filter), weak_factory_.GetWeakPtr()); - return; - } -#endif // BUILDFLAG(IS_CHROMEOS) - - adapter()->StartDiscoverySessionWithFilter( - std::make_unique<BluetoothDiscoveryFilter>( - BluetoothTransport::BLUETOOTH_TRANSPORT_LE), - kScanClientName, - base::BindOnce(&FidoCableDiscovery::OnStartDiscoverySession, - weak_factory_.GetWeakPtr()), - base::BindOnce(&FidoCableDiscovery::OnStartDiscoverySessionError, - weak_factory_.GetWeakPtr())); -} - -void FidoCableDiscovery::OnStartDiscoverySession( - std::unique_ptr<BluetoothDiscoverySession> session) { - FIDO_LOG(DEBUG) << "Discovery session started."; - SetDiscoverySession(std::move(session)); - // Advertising is delayed by 500ms to ensure that any UI has a chance to - // appear as we don't want to start broadcasting without the user being - // aware. - base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( - FROM_HERE, - base::BindOnce(&FidoCableDiscovery::StartAdvertisement, - weak_factory_.GetWeakPtr()), - base::Milliseconds(500)); -} - -void FidoCableDiscovery::OnStartDiscoverySessionError() { - FIDO_LOG(ERROR) << "Failed to start caBLE discovery"; -} - -void FidoCableDiscovery::StartAdvertisement() { - DCHECK(adapter()); - bool advertisements_pending = false; - for (const auto& data : discovery_data_) { - if (data.version != CableDiscoveryData::Version::V1) { - continue; - } - - if (!advertisements_pending) { - FIDO_LOG(DEBUG) << "Starting to advertise clientEIDs."; - advertisements_pending = true; - } - adapter()->RegisterAdvertisement( - ConstructAdvertisementData(data.v1->client_eid), - base::BindOnce(&FidoCableDiscovery::OnAdvertisementRegistered, - weak_factory_.GetWeakPtr(), data.v1->client_eid), - base::BindOnce([](BluetoothAdvertisement::ErrorCode error_code) { - FIDO_LOG(ERROR) << "Failed to register advertisement: " << error_code; - })); - } -} - -void FidoCableDiscovery::StopAdvertisements(base::OnceClosure callback) { - // Destructing a BluetoothAdvertisement invokes its Unregister() method, but - // there may be references to the advertisement outside this - // FidoCableDiscovery (see e.g. crbug/846522). Hence, merely clearing - // |advertisements_| is not sufficient; we need to manually invoke - // Unregister() for every advertisement in order to stop them. On the other - // hand, |advertisements_| must not be cleared before the Unregister() - // callbacks return either, in case we do hold the only reference to a - // BluetoothAdvertisement. - FIDO_LOG(DEBUG) << "Stopping " << advertisements_.size() - << " caBLE advertisements"; - auto barrier_closure = base::BarrierClosure( - advertisements_.size(), - base::BindOnce(&FidoCableDiscovery::OnAdvertisementsStopped, - weak_factory_.GetWeakPtr(), std::move(callback))); - auto error_closure = base::BindRepeating( - [](base::RepeatingClosure cb, BluetoothAdvertisement::ErrorCode code) { - FIDO_LOG(ERROR) << "BluetoothAdvertisement::Unregister() failed: " - << code; - cb.Run(); - }, - barrier_closure); - for (auto advertisement : advertisements_) { - advertisement.second->Unregister(barrier_closure, error_closure); - } -} - -void FidoCableDiscovery::OnAdvertisementsStopped(base::OnceClosure callback) { - FIDO_LOG(DEBUG) << "Advertisements stopped"; - advertisements_.clear(); - std::move(callback).Run(); -} - -void FidoCableDiscovery::OnAdvertisementRegistered( - const CableEidArray& client_eid, - scoped_refptr<BluetoothAdvertisement> advertisement) { - FIDO_LOG(DEBUG) << "Advertisement registered"; - advertisements_.emplace(client_eid, std::move(advertisement)); -} - -void FidoCableDiscovery::CableDeviceFound(BluetoothAdapter* adapter, - BluetoothDevice* device) { - const std::string device_address = device->GetAddress(); - if (active_devices_.contains(device_address)) { - return; - } - - std::optional<V1DiscoveryDataAndEID> v1_match = GetCableDiscoveryData(device); - if (!v1_match) { - return; - } - - if (active_authenticator_eids_.contains(v1_match->second)) { - return; - } - active_authenticator_eids_.insert(v1_match->second); - active_devices_.insert(device_address); - - FIDO_LOG(EVENT) << "Found new caBLEv1 device."; - -#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) - // Speed up GATT service discovery on ChromeOS/BlueZ. - // SetConnectionLatency() is NOTIMPLEMENTED() on other platforms. - device->SetConnectionLatency(BluetoothDevice::CONNECTION_LATENCY_LOW, - base::DoNothing(), base::BindOnce([]() { - FIDO_LOG(ERROR) - << "SetConnectionLatency() failed"; - })); -#endif // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) - - auto cable_device = - std::make_unique<FidoCableDevice>(adapter, device_address); - - std::unique_ptr<FidoCableHandshakeHandler> handshake_handler = - CreateV1HandshakeHandler(cable_device.get(), v1_match->first, - v1_match->second); - auto* const handshake_handler_ptr = handshake_handler.get(); - active_handshakes_.emplace_back(std::move(cable_device), - std::move(handshake_handler)); - - StopAdvertisements( - base::BindOnce(&FidoCableDiscovery::ConductEncryptionHandshake, - weak_factory_.GetWeakPtr(), handshake_handler_ptr, - v1_match->first.version)); -} - -void FidoCableDiscovery::ConductEncryptionHandshake( - FidoCableHandshakeHandler* handshake_handler, - CableDiscoveryData::Version cable_version) { - handshake_handler->InitiateCableHandshake(base::BindOnce( - &FidoCableDiscovery::ValidateAuthenticatorHandshakeMessage, - weak_factory_.GetWeakPtr(), cable_version, handshake_handler)); -} - -void FidoCableDiscovery::ValidateAuthenticatorHandshakeMessage( - CableDiscoveryData::Version cable_version, - FidoCableHandshakeHandler* handshake_handler, - std::optional<std::vector<uint8_t>> handshake_response) { - const bool ok = handshake_response.has_value() && - handshake_handler->ValidateAuthenticatorHandshakeMessage( - *handshake_response); - - bool found = false; - for (auto it = active_handshakes_.begin(); it != active_handshakes_.end(); - it++) { - if (it->second.get() != handshake_handler) { - continue; - } - - found = true; - if (ok) { - AddDevice(std::move(it->first)); - } - active_handshakes_.erase(it); - break; - } - DCHECK(found); - - if (ok) { - FIDO_LOG(DEBUG) << "Authenticator handshake validated"; - } else { - FIDO_LOG(DEBUG) << "Authenticator handshake invalid"; - } -} - -std::optional<FidoCableDiscovery::V1DiscoveryDataAndEID> -FidoCableDiscovery::GetCableDiscoveryData(const BluetoothDevice* device) { - const std::vector<uint8_t>* service_data = - device->GetServiceDataForUUID(GoogleCableUUID()); - if (!service_data) { - service_data = device->GetServiceDataForUUID(FIDOCableUUID()); - } - std::optional<CableEidArray> maybe_eid_from_service_data = - MaybeGetEidFromServiceData(device); - std::vector<CableEidArray> uuids = GetUUIDs(device); - - const std::string address = device->GetAddress(); - const auto it = observed_devices_.find(address); - const bool known = it != observed_devices_.end(); - if (known) { - std::unique_ptr<ObservedDeviceData>& data = it->second; - if (maybe_eid_from_service_data == data->service_data && - uuids == data->uuids) { - // Duplicate data. Ignore. - return std::nullopt; - } - } - - // New or updated device information. - if (known) { - FIDO_LOG(DEBUG) << "Updated information for caBLE device " << address - << ":"; - } else { - FIDO_LOG(DEBUG) << "New caBLE device " << address << ":"; - } - - std::optional<FidoCableDiscovery::V1DiscoveryDataAndEID> result; - if (maybe_eid_from_service_data.has_value()) { - result = - GetCableDiscoveryDataFromAuthenticatorEid(*maybe_eid_from_service_data); - FIDO_LOG(DEBUG) << " Service data: " - << ResultDebugString(*maybe_eid_from_service_data, result); - } else if (service_data) { - FIDO_LOG(DEBUG) << " Service data: " << base::HexEncode(*service_data); - } else { - FIDO_LOG(DEBUG) << " Service data: <none>"; - } - - if (!uuids.empty()) { - FIDO_LOG(DEBUG) << " UUIDs:"; - for (const auto& uuid : uuids) { - auto eid_result = GetCableDiscoveryDataFromAuthenticatorEid(uuid); - FIDO_LOG(DEBUG) << " " << ResultDebugString(uuid, eid_result); - if (!result && eid_result) { - result = std::move(eid_result); - } - } - } - - std::array<uint8_t, 16 + 4> v2_advert; - if (advert_callback_ && service_data && - service_data->size() == v2_advert.size()) { - UNSAFE_TODO( - memcpy(v2_advert.data(), service_data->data(), v2_advert.size())); - advert_callback_.Run(v2_advert); - } - - auto observed_data = std::make_unique<ObservedDeviceData>(); - observed_data->service_data = maybe_eid_from_service_data; - observed_data->uuids = uuids; - observed_devices_.insert_or_assign(address, std::move(observed_data)); - - return result; -} - -// static -std::optional<CableEidArray> FidoCableDiscovery::MaybeGetEidFromServiceData( - const BluetoothDevice* device) { - const std::vector<uint8_t>* service_data = - device->GetServiceDataForUUID(GoogleCableUUID()); - if (!service_data) { - return std::nullopt; - } - - // Received service data from authenticator must have a flag that signals that - // the service data includes Cable EID. - if (service_data->empty() || !(service_data->at(0) >> 5 & 1u)) - return std::nullopt; - - CableEidArray received_authenticator_eid; - bool extract_success = fido_parsing_utils::ExtractArray( - *service_data, 2, &received_authenticator_eid); - if (!extract_success) - return std::nullopt; - return received_authenticator_eid; -} - -// static -std::vector<CableEidArray> FidoCableDiscovery::GetUUIDs( - const BluetoothDevice* device) { - std::vector<CableEidArray> ret; - - const auto service_uuids = device->GetUUIDs(); - for (const auto& uuid : service_uuids) { - std::vector<uint8_t> uuid_binary = uuid.GetBytes(); - CableEidArray authenticator_eid; - DCHECK_EQ(authenticator_eid.size(), uuid_binary.size()); - UNSAFE_TODO(memcpy(authenticator_eid.data(), uuid_binary.data(), - std::min(uuid_binary.size(), authenticator_eid.size()))); - - ret.emplace_back(std::move(authenticator_eid)); - } - - return ret; -} - -std::optional<FidoCableDiscovery::V1DiscoveryDataAndEID> -FidoCableDiscovery::GetCableDiscoveryDataFromAuthenticatorEid( - CableEidArray authenticator_eid) { - for (const auto& candidate : discovery_data_) { - if (candidate.version == CableDiscoveryData::Version::V1 && - candidate.MatchV1(authenticator_eid)) { - return V1DiscoveryDataAndEID(candidate, authenticator_eid); - } - } - - return std::nullopt; -} - -void FidoCableDiscovery::StartInternal() { - BluetoothAdapterFactory::Get()->GetAdapter(base::BindOnce( - &FidoCableDiscovery::OnGetAdapter, weak_factory_.GetWeakPtr())); -} - -// static -std::string FidoCableDiscovery::ResultDebugString( - const CableEidArray& eid, - const std::optional<FidoCableDiscovery::V1DiscoveryDataAndEID>& result) { - static const uint8_t kAppleContinuity[16] = { - 0xd0, 0x61, 0x1e, 0x78, 0xbb, 0xb4, 0x45, 0x91, - 0xa5, 0xf8, 0x48, 0x79, 0x10, 0xae, 0x43, 0x66, - }; - static const uint8_t kAppleUnknown[16] = { - 0x9f, 0xa4, 0x80, 0xe0, 0x49, 0x67, 0x45, 0x42, - 0x93, 0x90, 0xd3, 0x43, 0xdc, 0x5d, 0x04, 0xae, - }; - static const uint8_t kAppleMedia[16] = { - 0x89, 0xd3, 0x50, 0x2b, 0x0f, 0x36, 0x43, 0x3a, - 0x8e, 0xf4, 0xc5, 0x02, 0xad, 0x55, 0xf8, 0xdc, - }; - static const uint8_t kAppleNotificationCenter[16] = { - 0x79, 0x05, 0xf4, 0x31, 0xb5, 0xce, 0x4e, 0x99, - 0xa4, 0x0f, 0x4b, 0x1e, 0x12, 0x2d, 0x00, 0xd0, - }; - static const uint8_t kCable[16] = { - 0x00, 0x00, 0xfd, 0xe2, 0x00, 0x00, 0x10, 0x00, - 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb, - }; - - std::string ret = base::HexEncode(eid) + ""; - - if (!result) { - // Try to identify some common UUIDs that are random and thus otherwise look - // like potential EIDs. - if (UNSAFE_TODO(memcmp(eid.data(), kAppleContinuity, eid.size())) == 0) { - ret += " (Apple Continuity service)"; - } else if (UNSAFE_TODO(memcmp(eid.data(), kAppleUnknown, eid.size())) == - 0) { - ret += " (Apple service)"; - } else if (UNSAFE_TODO(memcmp(eid.data(), kAppleMedia, eid.size())) == 0) { - ret += " (Apple Media service)"; - } else if (UNSAFE_TODO(memcmp(eid.data(), kAppleNotificationCenter, - eid.size())) == 0) { - ret += " (Apple Notification service)"; - } else if (UNSAFE_TODO(memcmp(eid.data(), kCable, eid.size())) == 0) { - ret += " (caBLE indicator)"; - } - return ret; - } - - if (result) { - ret += " (version one match)"; - } - - return ret; -} - -void FidoCableDiscovery::Stop() { - FidoDeviceDiscovery::Stop(); - StopAdvertisements(base::DoNothing()); -} - -} // namespace device
diff --git a/device/fido/cable/fido_cable_discovery.h b/device/fido/cable/fido_cable_discovery.h deleted file mode 100644 index 10425a5e..0000000 --- a/device/fido/cable/fido_cable_discovery.h +++ /dev/null
@@ -1,202 +0,0 @@ -// Copyright 2018 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef DEVICE_FIDO_CABLE_FIDO_CABLE_DISCOVERY_H_ -#define DEVICE_FIDO_CABLE_FIDO_CABLE_DISCOVERY_H_ - -#include <stdint.h> - -#include <array> -#include <map> -#include <memory> -#include <string> -#include <vector> - -#include "base/component_export.h" -#include "base/containers/span.h" -#include "base/memory/scoped_refptr.h" -#include "base/memory/weak_ptr.h" -#include "device/bluetooth/bluetooth_adapter.h" -#include "device/fido/cable/v2_constants.h" -#include "device/fido/fido_device_discovery.h" -#include "device/fido/public/cable_discovery_data.h" - -#if BUILDFLAG(IS_CHROMEOS) -#include "device/bluetooth/bluetooth_low_energy_scan_session.h" -#endif // BUILDFLAG(IS_CHROMEOS) - -namespace device { - -class BluetoothDevice; -class BluetoothAdvertisement; -class FidoCableDevice; -class FidoCableHandshakeHandler; - -class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery - : public FidoDeviceDiscovery, -#if BUILDFLAG(IS_CHROMEOS) - public device::BluetoothLowEnergyScanSession::Delegate, -#endif // BUILDFLAG(IS_CHROMEOS) - public BluetoothAdapter::Observer { - public: - explicit FidoCableDiscovery(std::vector<CableDiscoveryData> discovery_data); - - FidoCableDiscovery(const FidoCableDiscovery&) = delete; - FidoCableDiscovery& operator=(const FidoCableDiscovery&) = delete; - - ~FidoCableDiscovery() override; - - // FidoDeviceDiscovery: - void Stop() override; - - // GetV2AdvertStream returns a stream of caBLEv2 BLE adverts. Only a single - // stream is supported. - std::unique_ptr<EventStream<base::span<const uint8_t, cablev2::kAdvertSize>>> - GetV2AdvertStream(); - - const std::map<CableEidArray, scoped_refptr<BluetoothAdvertisement>>& - AdvertisementsForTesting() const { - return advertisements_; - } - - protected: - virtual std::unique_ptr<FidoCableHandshakeHandler> CreateV1HandshakeHandler( - FidoCableDevice* device, - const CableDiscoveryData& discovery_data, - const CableEidArray& authenticator_eid); - - private: - // V1DiscoveryDataAndEID represents a match against caBLEv1 pairing data. It - // contains the CableDiscoveryData that matched and the BLE EID that triggered - // the match. - using V1DiscoveryDataAndEID = std::pair<CableDiscoveryData, CableEidArray>; - - // ObservedDeviceData contains potential EIDs observed from a BLE device. This - // information is kept in order to de-duplicate device-log entries and make - // debugging easier. - struct ObservedDeviceData { - ObservedDeviceData(); - ~ObservedDeviceData(); - - std::optional<CableEidArray> service_data; - std::vector<CableEidArray> uuids; - }; - - static const BluetoothUUID& GoogleCableUUID(); - static const BluetoothUUID& FIDOCableUUID(); - static bool IsCableDevice(const BluetoothDevice* device); - - // ResultDebugString returns a string containing a hex dump of |eid| and a - // description of |result|, if present. - static std::string ResultDebugString( - const CableEidArray& eid, - const std::optional<V1DiscoveryDataAndEID>& result); - static std::optional<CableEidArray> MaybeGetEidFromServiceData( - const BluetoothDevice* device); - static std::vector<CableEidArray> GetUUIDs(const BluetoothDevice* device); - - void StartCableDiscovery(); - void OnStartDiscoverySession(std::unique_ptr<BluetoothDiscoverySession>); - void OnStartDiscoverySessionError(); - void StartAdvertisement(); - void OnAdvertisementRegistered( - const CableEidArray& client_eid, - scoped_refptr<BluetoothAdvertisement> advertisement); - - void OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter); - void OnSetPowered(); - - void SetDiscoverySession( - std::unique_ptr<BluetoothDiscoverySession> discovery_session); - - BluetoothAdapter* adapter() { return adapter_.get(); } - - // Attempt to stop all on-going advertisements in best-effort basis. - // Once all the callbacks for Unregister() function is received, invoke - // |callback|. - void StopAdvertisements(base::OnceClosure callback); - void OnAdvertisementsStopped(base::OnceClosure callback); - void CableDeviceFound(BluetoothAdapter* adapter, BluetoothDevice* device); - void ConductEncryptionHandshake(FidoCableHandshakeHandler* handshake_handler, - CableDiscoveryData::Version cable_version); - void ValidateAuthenticatorHandshakeMessage( - CableDiscoveryData::Version cable_version, - FidoCableHandshakeHandler* handshake_handler, - std::optional<std::vector<uint8_t>> handshake_response); - - std::optional<V1DiscoveryDataAndEID> GetCableDiscoveryData( - const BluetoothDevice* device); - std::optional<V1DiscoveryDataAndEID> - GetCableDiscoveryDataFromAuthenticatorEid(CableEidArray authenticator_eid); - - // FidoDeviceDiscovery: - void StartInternal() override; - - // BluetoothAdapter::Observer: - void DeviceAdded(BluetoothAdapter* adapter, BluetoothDevice* device) override; - void DeviceChanged(BluetoothAdapter* adapter, - BluetoothDevice* device) override; - void DeviceRemoved(BluetoothAdapter* adapter, - BluetoothDevice* device) override; - void AdapterPoweredChanged(BluetoothAdapter* adapter, bool powered) override; - void AdapterDiscoveringChanged(BluetoothAdapter* adapter, - bool discovering) override; - -#if BUILDFLAG(IS_CHROMEOS) - // device::BluetoothLowEnergyScanSession::Delegate: - void OnDeviceFound(device::BluetoothLowEnergyScanSession* scan_session, - device::BluetoothDevice* device) override; - void OnDeviceLost(device::BluetoothLowEnergyScanSession* scan_session, - device::BluetoothDevice* device) override; - void OnSessionStarted( - device::BluetoothLowEnergyScanSession* scan_session, - std::optional<device::BluetoothLowEnergyScanSession::ErrorCode> - error_code) override; - void OnSessionInvalidated( - device::BluetoothLowEnergyScanSession* scan_session) override; -#endif // BUILDFLAG(IS_CHROMEOS) - - scoped_refptr<BluetoothAdapter> adapter_; - std::unique_ptr<BluetoothDiscoverySession> discovery_session_; -#if BUILDFLAG(IS_CHROMEOS) - std::unique_ptr<device::BluetoothLowEnergyScanSession> le_scan_session_; -#endif // BUILDFLAG(IS_CHROMEOS) - - std::vector<CableDiscoveryData> discovery_data_; - base::RepeatingCallback<void(base::span<const uint8_t, cablev2::kAdvertSize>)> - advert_callback_; - - // active_authenticator_eids_ contains authenticator EIDs for which a - // handshake is currently running. Further advertisements for the same EIDs - // will be ignored. - std::set<CableEidArray> active_authenticator_eids_; - - // active_devices_ contains the BLE addresses of devices for which a - // handshake is already running. Further advertisements from these devices - // will be ignored. However, devices may rotate their BLE address at will so - // this is not completely effective. - std::set<std::string> active_devices_; - - // Note that on Windows, |advertisements_| is the only reference holder of - // BluetoothAdvertisement. - std::map<CableEidArray, scoped_refptr<BluetoothAdvertisement>> - advertisements_; - - std::vector<std::pair<std::unique_ptr<FidoCableDevice>, - std::unique_ptr<FidoCableHandshakeHandler>>> - active_handshakes_; - - // observed_devices_ caches the information from observed caBLE devices so - // that the device-log isn't spammed. - base::flat_map<std::string, std::unique_ptr<ObservedDeviceData>> - observed_devices_; - - bool has_v1_discovery_data_ = false; - - base::WeakPtrFactory<FidoCableDiscovery> weak_factory_{this}; -}; - -} // namespace device - -#endif // DEVICE_FIDO_CABLE_FIDO_CABLE_DISCOVERY_H_
diff --git a/device/fido/cable/fido_cable_discovery_unittest.cc b/device/fido/cable/fido_cable_discovery_unittest.cc deleted file mode 100644 index 0354ab2..0000000 --- a/device/fido/cable/fido_cable_discovery_unittest.cc +++ /dev/null
@@ -1,773 +0,0 @@ -// Copyright 2018 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "device/fido/cable/fido_cable_discovery.h" - -#include <algorithm> -#include <memory> -#include <string_view> -#include <utility> - -#include "base/containers/span.h" -#include "base/run_loop.h" -#include "base/task/single_thread_task_runner.h" -#include "base/test/task_environment.h" -#include "build/build_config.h" -#include "device/bluetooth/bluetooth_adapter_factory.h" -#include "device/bluetooth/bluetooth_advertisement.h" -#include "device/bluetooth/test/bluetooth_test.h" -#include "device/bluetooth/test/mock_bluetooth_adapter.h" -#include "device/fido/cable/fido_ble_uuids.h" -#include "device/fido/cable/fido_cable_device.h" -#include "device/fido/cable/fido_cable_handshake_handler.h" -#include "device/fido/fido_parsing_utils.h" -#include "device/fido/mock_fido_discovery_observer.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -#if BUILDFLAG(IS_CHROMEOS) -#include "base/test/scoped_feature_list.h" -#include "device/bluetooth/floss/floss_features.h" -#endif // BUILDFLAG(IS_CHROMEOS) - -#if BUILDFLAG(IS_MAC) -#include "device/fido/mac/util.h" -#endif // BUILDFLAG(IS_MAC) - -using ::testing::_; -using ::testing::NiceMock; -using ::testing::Sequence; - -namespace device { - -namespace { - -constexpr auto kTestCableVersion = CableDiscoveryData::Version::V1; -#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) -constexpr auto kTestCableVersionNumber = 1; -#endif - -// Constants required for discovering and constructing a Cable device that -// are given by the relying party via an extension. -constexpr CableEidArray kClientEid = {{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, - 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, - 0x14, 0x15}}; - -constexpr char kUuidFormattedClientEid[] = - "00010203-0405-0607-0809-101112131415"; - -constexpr CableEidArray kAuthenticatorEid = { - {0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01}}; - -constexpr CableEidArray kInvalidAuthenticatorEid = { - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00}}; - -constexpr CableSessionPreKeyArray kTestSessionPreKey = { - {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}; - -// TODO(crbug.com/40573698): Add support for multiple EIDs on Windows. -#if !BUILDFLAG(IS_WIN) -constexpr CableEidArray kSecondaryClientEid = { - {0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, - 0x03, 0x02, 0x01, 0x00}}; - -constexpr char kUuidFormattedSecondaryClientEid[] = - "15141312-1110-0908-0706-050403020100"; - -constexpr CableEidArray kSecondaryAuthenticatorEid = { - {0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, - 0xee, 0xee, 0xee, 0xee}}; - -constexpr CableSessionPreKeyArray kSecondarySessionPreKey = { - {0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, - 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, - 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd}}; -#endif // !BUILDFLAG(IS_WIN) - -// Below constants are used to construct MockBluetoothDevice for testing. -constexpr char kTestBleDeviceAddress[] = "11:12:13:14:15:16"; - -constexpr char kTestBleDeviceName[] = "test_cable_device"; - -std::unique_ptr<MockBluetoothDevice> CreateTestBluetoothDevice() { - return std::make_unique<testing::NiceMock<MockBluetoothDevice>>( - nullptr /* adapter */, 0 /* bluetooth_class */, kTestBleDeviceName, - kTestBleDeviceAddress, true /* paired */, true /* connected */); -} - -ACTION_P(ReturnFromAsyncCall, closure) { - closure.Run(); -} - -// Matcher to compare the content of advertisement data received from the -// client. -MATCHER_P2(IsAdvertisementContent, - expected_client_eid, - expected_uuid_formatted_client_eid, - "") { -#if BUILDFLAG(IS_MAC) - return std::ranges::contains(*arg->service_uuids(), - expected_uuid_formatted_client_eid); - -#elif BUILDFLAG(IS_WIN) - const auto manufacturer_data = arg->manufacturer_data(); - const auto manufacturer_data_value = manufacturer_data->find(0x00E0); - - if (manufacturer_data_value == manufacturer_data->end()) { - return false; - } - - const auto& manufacturer_data_payload = manufacturer_data_value->second; - return manufacturer_data_payload.size() >= 4u && - manufacturer_data_payload[0] == manufacturer_data_payload.size() - 1 && - manufacturer_data_payload[1] == 0x15 && // Manufacturer Data Type - manufacturer_data_payload[2] == 0x20 && // Cable Flags - manufacturer_data_payload[3] == kTestCableVersionNumber && - std::equal(manufacturer_data_payload.begin() + 4, - manufacturer_data_payload.end(), - expected_client_eid.begin(), expected_client_eid.end()); - -#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) - const auto service_data = arg->service_data(); - const auto service_data_with_uuid = service_data->find(kGoogleCableUUID128); - - if (service_data_with_uuid == service_data->end()) { - return false; - } - - const auto& service_data_value = service_data_with_uuid->second; - return (service_data_value[0] >> 5 & 1) && - service_data_value[1] == kTestCableVersionNumber && - service_data_value.size() == 18u && - std::equal(service_data_value.begin() + 2, service_data_value.end(), - expected_client_eid.begin(), expected_client_eid.end()); - -#else - return true; -#endif -} - -class CableMockBluetoothAdvertisement : public BluetoothAdvertisement { - public: - MOCK_METHOD2(Unregister, - void(SuccessCallback success_callback, - ErrorCallback error_callback)); - - void ExpectUnregisterAndSucceed() { - EXPECT_CALL(*this, Unregister(_, _)) - .WillOnce(::testing::WithArg<0>([](auto success_cb) { - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, std::move(success_cb)); - })); - } - - private: - ~CableMockBluetoothAdvertisement() override = default; -}; - -// Mock BLE adapter that abstracts out authenticator logic with the following -// logic: -// - Responds to BluetoothAdapter::RegisterAdvertisement() by always invoking -// success callback. -// - Responds to BluetoothAdapter::StartDiscoverySessionWithFilter() by -// invoking BluetoothAdapter::Observer::DeviceAdded() on a test bluetooth -// device that includes service data containing authenticator EID. -class CableMockAdapter : public MockBluetoothAdapter { - public: - static scoped_refptr<CableMockAdapter> MakePoweredOn() { - auto mock_adapter = - base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); - EXPECT_CALL(*mock_adapter, IsPresent()) - .WillRepeatedly(::testing::Return(true)); - EXPECT_CALL(*mock_adapter, IsPowered()) - .WillRepeatedly(::testing::Return(true)); - return mock_adapter; - } - static scoped_refptr<CableMockAdapter> MakePoweredOff() { - auto mock_adapter = - base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); - EXPECT_CALL(*mock_adapter, IsPresent()) - .WillRepeatedly(::testing::Return(true)); - EXPECT_CALL(*mock_adapter, IsPowered()) - .WillRepeatedly(::testing::Return(false)); - return mock_adapter; - } - static scoped_refptr<CableMockAdapter> MakeNotPresent() { - auto mock_adapter = - base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); - EXPECT_CALL(*mock_adapter, IsPresent()) - .WillRepeatedly(::testing::Return(false)); - return mock_adapter; - } - static scoped_refptr<CableMockAdapter> MakeWithUndeterminedPermission() { - auto mock_adapter = - base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); - EXPECT_CALL(*mock_adapter, IsPresent()) - .WillRepeatedly(::testing::Return(true)); - EXPECT_CALL(*mock_adapter, GetOsPermissionStatus()) - .WillRepeatedly(testing::Return(PermissionStatus::kUndetermined)); - return mock_adapter; - } - - MOCK_METHOD3(RegisterAdvertisement, - void(std::unique_ptr<BluetoothAdvertisement::Data>, - CreateAdvertisementCallback, - AdvertisementErrorCallback)); - - BluetoothDevice* CreateNewTestBluetoothDevice( - base::span<const uint8_t, kCableEphemeralIdSize> authenticator_eid) { - auto mock_device = CreateTestBluetoothDevice(); - - std::vector<uint8_t> service_data(18); - service_data[0] = 1 << 5; - std::ranges::copy(authenticator_eid, service_data.begin() + 2); - BluetoothDevice::ServiceDataMap service_data_map; - service_data_map.emplace(kGoogleCableUUID128, std::move(service_data)); - - mock_device->UpdateAdvertisementData( - 1 /* rssi */, std::nullopt /* flags */, BluetoothDevice::UUIDList(), - std::nullopt /* tx_power */, std::move(service_data_map), - BluetoothDevice::ManufacturerDataMap()); - - auto* mock_device_ptr = mock_device.get(); - AddMockDevice(std::move(mock_device)); - - return mock_device_ptr; - } - - void AddNewTestBluetoothDevice( - base::span<const uint8_t, kCableEphemeralIdSize> authenticator_eid) { - auto* device = CreateNewTestBluetoothDevice(authenticator_eid); - for (auto& observer : GetObservers()) { - observer.DeviceAdded(this, device); - } - } - - void AddNewTestAppleBluetoothDevice( - base::span<const uint8_t, kCableEphemeralIdSize> authenticator_eid) { - auto mock_device = CreateTestBluetoothDevice(); - // Apple doesn't allow advertising service data, so we advertise a 16 bit - // UUID plus the EID converted into 128 bit UUID. - mock_device->AddUUID(BluetoothUUID("fde2")); - mock_device->AddUUID(BluetoothUUID( - fido_parsing_utils::ConvertBytesToUuid(authenticator_eid))); - - auto* mock_device_ptr = mock_device.get(); - AddMockDevice(std::move(mock_device)); - - for (auto& observer : GetObservers()) { - observer.DeviceAdded(this, mock_device_ptr); - } - } - - void ExpectRegisterAdvertisementWithResponse( - bool simulate_success, - base::span<const uint8_t> expected_client_eid, - std::string_view expected_uuid_formatted_client_eid, - Sequence sequence = Sequence(), - scoped_refptr<CableMockBluetoothAdvertisement> advertisement = nullptr) { - if (!advertisement) { - advertisement = base::MakeRefCounted<CableMockBluetoothAdvertisement>(); - EXPECT_CALL(*advertisement, Unregister(_, _)) - .WillRepeatedly(::testing::WithArg<0>( - [](auto callback) { std::move(callback).Run(); })); - } - - EXPECT_CALL(*this, - RegisterAdvertisement( - IsAdvertisementContent(expected_client_eid, - expected_uuid_formatted_client_eid), - _, _)) - .InSequence(sequence) - .WillOnce(::testing::WithArgs<1, 2>( - [simulate_success, advertisement](auto success_callback, - auto failure_callback) { - simulate_success ? std::move(success_callback).Run(advertisement) - : std::move(failure_callback) - .Run(BluetoothAdvertisement::ErrorCode:: - INVALID_ADVERTISEMENT_ERROR_CODE); - })); - } - - void ExpectDiscoveryWithScanCallback() { - EXPECT_CALL(*this, StartScanWithFilter_(_, _)) - .WillOnce(::testing::WithArg<1>([](auto& callback) { - std::move(callback).Run( - false, device::UMABluetoothDiscoverySessionOutcome::SUCCESS); - })); - } - - void ExpectDiscoveryWithScanCallback( - base::span<const uint8_t, kCableEphemeralIdSize> eid, - bool is_apple_device = false) { - EXPECT_CALL(*this, StartScanWithFilter_(_, _)) - .WillOnce( - ::testing::WithArg<1>([this, eid, is_apple_device](auto& callback) { - std::move(callback).Run( - false, device::UMABluetoothDiscoverySessionOutcome::SUCCESS); - if (is_apple_device) { - AddNewTestAppleBluetoothDevice(eid); - } else { - AddNewTestBluetoothDevice(eid); - } - })); - } - -#if BUILDFLAG(IS_CHROMEOS) - void ExpectLEScan(base::span<const uint8_t, kCableEphemeralIdSize> eid) { - EXPECT_CALL(*this, StartLowEnergyScanSession(_, _)) - .WillOnce( - [this, eid](std::unique_ptr<BluetoothLowEnergyScanFilter> filter, - base::WeakPtr<BluetoothLowEnergyScanSession::Delegate> - delegate) { - EXPECT_TRUE(filter); - delegate->OnSessionStarted(/*scan_session=*/nullptr, - /*error_code=*/std::nullopt); - auto* device = CreateNewTestBluetoothDevice(eid); - delegate->OnDeviceFound(/*scan_session=*/nullptr, device); - return nullptr; - }); - } -#endif // BUILDFLAG(IS_CHROMEOS) - - protected: - ~CableMockAdapter() override = default; -}; - -class FakeHandshakeHandler : public FidoCableV1HandshakeHandler { - public: - FakeHandshakeHandler(FidoCableDevice* device, - base::span<const uint8_t, 8> nonce, - base::span<const uint8_t, 32> session_pre_key) - : FidoCableV1HandshakeHandler(device, nonce, session_pre_key) {} - ~FakeHandshakeHandler() override = default; - - void InitiateCableHandshake(FidoDevice::DeviceCallback callback) override { - std::move(callback).Run(std::vector<uint8_t>()); - } - - bool ValidateAuthenticatorHandshakeMessage( - base::span<const uint8_t> response) override { - return true; - } -}; - -// Fake discovery that encapsulates exactly the same behavior as -// FidoCableDiscovery except that it uses FakeHandshakeHandler instead of -// FidoHandshakeHandler to conduct handshake with the authenticator. -class FakeFidoCableDiscovery : public FidoCableDiscovery { - public: - explicit FakeFidoCableDiscovery( - std::vector<CableDiscoveryData> discovery_data) - : FidoCableDiscovery(std::move(discovery_data)) {} - ~FakeFidoCableDiscovery() override = default; - - private: - std::unique_ptr<FidoCableHandshakeHandler> CreateV1HandshakeHandler( - FidoCableDevice* device, - const CableDiscoveryData& discovery_data, - const CableEidArray& eid) override { - // Nonce is embedded as first 8 bytes of client EID. - std::array<uint8_t, 8> nonce; - CHECK(fido_parsing_utils::ExtractArray(eid, 0, &nonce)); - return std::make_unique<FakeHandshakeHandler>( - device, nonce, discovery_data.v1->session_pre_key); - } -}; - -} // namespace - -class FidoCableDiscoveryTest : public ::testing::Test { - public: - std::unique_ptr<FidoCableDiscovery> CreateDiscovery() { - std::vector<CableDiscoveryData> discovery_data; - discovery_data.emplace_back(kTestCableVersion, kClientEid, - kAuthenticatorEid, kTestSessionPreKey); - return std::make_unique<FakeFidoCableDiscovery>(std::move(discovery_data)); - } - - base::test::TaskEnvironment task_environment_{ - base::test::TaskEnvironment::TimeSource::MOCK_TIME}; -}; - -// Tests discovery without a BLE adapter. -TEST_F(FidoCableDiscoveryTest, TestDiscoveryFails) { - auto cable_discovery = CreateDiscovery(); - NiceMock<MockFidoDiscoveryObserver> mock_observer; - EXPECT_CALL(mock_observer, - DiscoveryStarted(cable_discovery.get(), false, - std::vector<FidoAuthenticator*>())); - EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _)).Times(0); - cable_discovery->set_observer(&mock_observer); - - auto mock_adapter = CableMockAdapter::MakeNotPresent(); - BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); - cable_discovery->Start(); - task_environment_.FastForwardUntilNoTasksRemain(); -} - -// Tests discovery with a powered-off BLE adapter. Not calling -// DiscoveryStarted() in the case of a present-but-unpowered adapter leads to a -// deadlock between the discovery and the UI (see crbug.com/1018416). -TEST_F(FidoCableDiscoveryTest, TestDiscoveryStartedWithUnpoweredAdapter) { - auto cable_discovery = CreateDiscovery(); - NiceMock<MockFidoDiscoveryObserver> mock_observer; - EXPECT_CALL(mock_observer, - DiscoveryStarted(cable_discovery.get(), true, - std::vector<FidoAuthenticator*>())); - EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _)).Times(0); - cable_discovery->set_observer(&mock_observer); - - auto mock_adapter = CableMockAdapter::MakePoweredOff(); - BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); - cable_discovery->Start(); - task_environment_.FastForwardUntilNoTasksRemain(); -} - -// Tests regular successful discovery flow for Cable device. -TEST_F(FidoCableDiscoveryTest, TestDiscoveryFindsNewDevice) { - auto cable_discovery = CreateDiscovery(); - NiceMock<MockFidoDiscoveryObserver> mock_observer; - EXPECT_CALL(mock_observer, - DiscoveryStarted(cable_discovery.get(), true, - std::vector<FidoAuthenticator*>())); - EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _)); - cable_discovery->set_observer(&mock_observer); - - auto mock_adapter = CableMockAdapter::MakePoweredOn(); - mock_adapter->ExpectDiscoveryWithScanCallback(kAuthenticatorEid); - mock_adapter->ExpectRegisterAdvertisementWithResponse( - true /* simulate_success */, kClientEid, kUuidFormattedClientEid); - - BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); - cable_discovery->Start(); - task_environment_.FastForwardUntilNoTasksRemain(); -} - -// Tests successful discovery flow for Apple Cable device. -TEST_F(FidoCableDiscoveryTest, TestDiscoveryFindsNewAppleDevice) { - auto cable_discovery = CreateDiscovery(); - NiceMock<MockFidoDiscoveryObserver> mock_observer; - EXPECT_CALL(mock_observer, - DiscoveryStarted(cable_discovery.get(), true, - std::vector<FidoAuthenticator*>())); - EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _)); - cable_discovery->set_observer(&mock_observer); - - auto mock_adapter = CableMockAdapter::MakePoweredOn(); - mock_adapter->ExpectDiscoveryWithScanCallback(kAuthenticatorEid, true); - mock_adapter->ExpectRegisterAdvertisementWithResponse( - true /* simulate_success */, kClientEid, kUuidFormattedClientEid); - - BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); - cable_discovery->Start(); - task_environment_.FastForwardUntilNoTasksRemain(); -} - -#if BUILDFLAG(IS_MAC) - -// Tests that the discovery will not attempt to call bluetooth functions like -// IsPowered() if the build is signed and the OS reports an undetermined -// permission status. -TEST_F(FidoCableDiscoveryTest, TestDiscoveryDoesNotUseBluetoothIfUnauthorized) { - fido::mac::ScopedProcessIsSignedOverride scoped_process_is_signed_override( - fido::mac::CodeSigningState::kSigned); - auto cable_discovery = CreateDiscovery(); - NiceMock<MockFidoDiscoveryObserver> mock_observer; - EXPECT_CALL(mock_observer, - DiscoveryStarted(cable_discovery.get(), true, - std::vector<FidoAuthenticator*>())); - cable_discovery->set_observer(&mock_observer); - - auto mock_adapter = CableMockAdapter::MakeWithUndeterminedPermission(); - EXPECT_CALL(*mock_adapter, IsPowered()).Times(0); - BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); - cable_discovery->Start(); - task_environment_.FastForwardUntilNoTasksRemain(); -} - -// Tests that the discovery will assume bluetooth permission is granted if the -// build is not signed. -TEST_F(FidoCableDiscoveryTest, - TestDiscoveryAssumesBluetoothAuthorizedIfUnsigned) { - fido::mac::ScopedProcessIsSignedOverride scoped_process_is_signed_override( - fido::mac::CodeSigningState::kNotSigned); - auto cable_discovery = CreateDiscovery(); - NiceMock<MockFidoDiscoveryObserver> mock_observer; - EXPECT_CALL(mock_observer, - DiscoveryStarted(cable_discovery.get(), true, - std::vector<FidoAuthenticator*>())); - EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _)); - cable_discovery->set_observer(&mock_observer); - - auto mock_adapter = CableMockAdapter::MakeWithUndeterminedPermission(); - EXPECT_CALL(*mock_adapter, IsPowered()) - .WillRepeatedly(::testing::Return(true)); - mock_adapter->ExpectDiscoveryWithScanCallback(kAuthenticatorEid); - mock_adapter->ExpectRegisterAdvertisementWithResponse( - true /* simulate_success */, kClientEid, kUuidFormattedClientEid); - - BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); - cable_discovery->Start(); - task_environment_.FastForwardUntilNoTasksRemain(); -} - -#endif // BUILDFLAG(IS_MAC) - -// Tests a scenario where upon broadcasting advertisement and scanning, client -// discovers a device with an incorrect authenticator EID. Observer::AddDevice() -// must not be called. -TEST_F(FidoCableDiscoveryTest, TestDiscoveryFindsIncorrectDevice) { - auto cable_discovery = CreateDiscovery(); - NiceMock<MockFidoDiscoveryObserver> mock_observer; - EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _)).Times(0); - EXPECT_CALL(mock_observer, DiscoveryStarted(cable_discovery.get(), true, - testing::IsEmpty())); - cable_discovery->set_observer(&mock_observer); - - auto mock_adapter = CableMockAdapter::MakePoweredOn(); - mock_adapter->ExpectRegisterAdvertisementWithResponse( - true /* simulate_success */, kClientEid, kUuidFormattedClientEid); - mock_adapter->ExpectDiscoveryWithScanCallback(kInvalidAuthenticatorEid); - - BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); - cable_discovery->Start(); - task_environment_.FastForwardUntilNoTasksRemain(); -} - -// Windows currently does not support multiple EIDs, so the following tests are -// not applicable. -// TODO(crbug.com/40573698): Support multiple EIDs on Windows and enable -// these tests. -#if !BUILDFLAG(IS_WIN) -// Tests Cable discovery flow when multiple(2) sets of client/authenticator EIDs -// are passed on from the relying party. We should expect 2 invocations of -// BluetoothAdapter::RegisterAdvertisement(). -TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithMultipleEids) { - std::vector<CableDiscoveryData> discovery_data; - discovery_data.emplace_back(kTestCableVersion, kClientEid, kAuthenticatorEid, - kTestSessionPreKey); - discovery_data.emplace_back(kTestCableVersion, kSecondaryClientEid, - kSecondaryAuthenticatorEid, - kSecondarySessionPreKey); - auto cable_discovery = - std::make_unique<FakeFidoCableDiscovery>(std::move(discovery_data)); - auto mock_adapter = CableMockAdapter::MakePoweredOn(); - mock_adapter->ExpectDiscoveryWithScanCallback(kAuthenticatorEid); - - NiceMock<MockFidoDiscoveryObserver> mock_observer; - EXPECT_CALL(mock_observer, - DiscoveryStarted(cable_discovery.get(), true, - std::vector<FidoAuthenticator*>())); - EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _)); - cable_discovery->set_observer(&mock_observer); - - Sequence sequence; - mock_adapter->ExpectRegisterAdvertisementWithResponse( - true /* simulate_success */, kClientEid, kUuidFormattedClientEid, - sequence); - mock_adapter->ExpectRegisterAdvertisementWithResponse( - true /* simulate_success */, kSecondaryClientEid, - kUuidFormattedSecondaryClientEid, sequence); - - BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); - cable_discovery->Start(); - task_environment_.FastForwardUntilNoTasksRemain(); -} - -// Tests a scenario where only one of the two client EID's are advertised -// successfully. Since at least one advertisement are successfully processed, -// scanning process should be invoked. -TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithPartialAdvertisementSuccess) { - std::vector<CableDiscoveryData> discovery_data; - discovery_data.emplace_back(kTestCableVersion, kClientEid, kAuthenticatorEid, - kTestSessionPreKey); - discovery_data.emplace_back(kTestCableVersion, kSecondaryClientEid, - kSecondaryAuthenticatorEid, - kSecondarySessionPreKey); - auto cable_discovery = - std::make_unique<FakeFidoCableDiscovery>(std::move(discovery_data)); - NiceMock<MockFidoDiscoveryObserver> mock_observer; - EXPECT_CALL(mock_observer, - DiscoveryStarted(cable_discovery.get(), true, - std::vector<FidoAuthenticator*>())); - EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _)); - cable_discovery->set_observer(&mock_observer); - - auto mock_adapter = CableMockAdapter::MakePoweredOn(); - Sequence sequence; - mock_adapter->ExpectRegisterAdvertisementWithResponse( - true /* simulate_success */, kClientEid, kUuidFormattedClientEid, - sequence); - mock_adapter->ExpectRegisterAdvertisementWithResponse( - false /* simulate_success */, kSecondaryClientEid, - kUuidFormattedSecondaryClientEid, sequence); - mock_adapter->ExpectDiscoveryWithScanCallback(kAuthenticatorEid); - - BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); - cable_discovery->Start(); - task_environment_.FastForwardUntilNoTasksRemain(); -} - -// Test the scenario when all advertisement for client EID's fails. -TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithAdvertisementFailures) { - std::vector<CableDiscoveryData> discovery_data; - discovery_data.emplace_back(kTestCableVersion, kClientEid, kAuthenticatorEid, - kTestSessionPreKey); - discovery_data.emplace_back(kTestCableVersion, kSecondaryClientEid, - kSecondaryAuthenticatorEid, - kSecondarySessionPreKey); - auto cable_discovery = - std::make_unique<FakeFidoCableDiscovery>(std::move(discovery_data)); - - NiceMock<MockFidoDiscoveryObserver> mock_observer; - EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _)).Times(0); - EXPECT_CALL(mock_observer, DiscoveryStarted(cable_discovery.get(), true, - testing::IsEmpty())); - cable_discovery->set_observer(&mock_observer); - - auto mock_adapter = CableMockAdapter::MakePoweredOn(); - Sequence sequence; - mock_adapter->ExpectRegisterAdvertisementWithResponse( - false /* simulate_success */, kClientEid, kUuidFormattedClientEid, - sequence); - mock_adapter->ExpectRegisterAdvertisementWithResponse( - false /* simulate_success */, kSecondaryClientEid, - kUuidFormattedSecondaryClientEid, sequence); - mock_adapter->ExpectDiscoveryWithScanCallback(); - - BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); - cable_discovery->Start(); - task_environment_.FastForwardUntilNoTasksRemain(); - EXPECT_TRUE(cable_discovery->AdvertisementsForTesting().empty()); -} -#endif // !BUILDFLAG(IS_WIN) - -TEST_F(FidoCableDiscoveryTest, TestUnregisterAdvertisementUponDestruction) { - auto cable_discovery = CreateDiscovery(); - auto advertisement = base::MakeRefCounted<CableMockBluetoothAdvertisement>(); - advertisement->ExpectUnregisterAndSucceed(); - - auto mock_adapter = CableMockAdapter::MakePoweredOn(); - mock_adapter->ExpectDiscoveryWithScanCallback(); - mock_adapter->ExpectRegisterAdvertisementWithResponse( - true /* simulate_success */, kClientEid, kUuidFormattedClientEid, - Sequence(), std::move(advertisement)); - - BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); - cable_discovery->Start(); - task_environment_.FastForwardUntilNoTasksRemain(); - - EXPECT_EQ(1u, cable_discovery->AdvertisementsForTesting().size()); - cable_discovery.reset(); -} - -TEST_F(FidoCableDiscoveryTest, TestUnregisterAdvertisementUponStop) { - auto cable_discovery = CreateDiscovery(); - auto advertisement = base::MakeRefCounted<CableMockBluetoothAdvertisement>(); - advertisement->ExpectUnregisterAndSucceed(); - - auto mock_adapter = CableMockAdapter::MakePoweredOn(); - mock_adapter->ExpectDiscoveryWithScanCallback(); - mock_adapter->ExpectRegisterAdvertisementWithResponse( - true /* simulate_success */, kClientEid, kUuidFormattedClientEid, - Sequence(), std::move(advertisement)); - - BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); - cable_discovery->Start(); - task_environment_.FastForwardUntilNoTasksRemain(); - EXPECT_EQ(1u, cable_discovery->AdvertisementsForTesting().size()); - - cable_discovery->Stop(); - task_environment_.FastForwardUntilNoTasksRemain(); - EXPECT_EQ(0u, cable_discovery->AdvertisementsForTesting().size()); -} - -TEST_F(FidoCableDiscoveryTest, TestStopWithNoAdvertisementsSucceeds) { - auto mock_adapter = CableMockAdapter::MakePoweredOff(); - BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); - - auto cable_discovery = CreateDiscovery(); - NiceMock<MockFidoDiscoveryObserver> mock_observer; - EXPECT_CALL(mock_observer, - DiscoveryStarted(cable_discovery.get(), true, - std::vector<FidoAuthenticator*>())); - cable_discovery->set_observer(&mock_observer); - cable_discovery->Start(); - task_environment_.FastForwardUntilNoTasksRemain(); - - EXPECT_EQ(0u, cable_discovery->AdvertisementsForTesting().size()); - cable_discovery->Stop(); -} - -// Tests that cable discovery resumes after Bluetooth adapter is powered on. -TEST_F(FidoCableDiscoveryTest, TestResumeDiscoveryAfterPoweredOn) { - auto cable_discovery = CreateDiscovery(); - NiceMock<MockFidoDiscoveryObserver> mock_observer; - EXPECT_CALL(mock_observer, - DiscoveryStarted(cable_discovery.get(), true, - std::vector<FidoAuthenticator*>())); - EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _)); - cable_discovery->set_observer(&mock_observer); - - auto mock_adapter = - base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); - EXPECT_CALL(*mock_adapter, IsPresent()) - .WillRepeatedly(::testing::Return(true)); - - // After BluetoothAdapter is powered on, we expect that Cable discovery starts - // again. - mock_adapter->ExpectDiscoveryWithScanCallback(kAuthenticatorEid); - mock_adapter->ExpectRegisterAdvertisementWithResponse( - true /* simulate_success */, kClientEid, kUuidFormattedClientEid); - - // Wait until error callback for SetPowered() is invoked. Then, simulate - // Bluetooth adapter power change by invoking - // MockBluetoothAdapter::NotifyAdapterPoweredChanged(). - { - base::RunLoop run_loop; - auto quit = run_loop.QuitClosure(); - EXPECT_CALL(*mock_adapter, IsPowered) - .WillRepeatedly(::testing::DoAll(ReturnFromAsyncCall(quit), - ::testing::Return(false))); - - BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); - cable_discovery->Start(); - run_loop.Run(); - } - - mock_adapter->NotifyAdapterPoweredChanged(true); - task_environment_.FastForwardUntilNoTasksRemain(); -} - -#if BUILDFLAG(IS_CHROMEOS) -// Tests regular successful discovery flow for Cable device on Floss. -TEST_F(FidoCableDiscoveryTest, TestDiscoveryFindsNewDeviceFloss) { - base::test::ScopedFeatureList feature_list; - feature_list.InitAndEnableFeature(floss::features::kFlossEnabled); - - auto cable_discovery = CreateDiscovery(); - NiceMock<MockFidoDiscoveryObserver> mock_observer; - EXPECT_CALL(mock_observer, - DiscoveryStarted(cable_discovery.get(), true, - std::vector<FidoAuthenticator*>())); - EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _)); - cable_discovery->set_observer(&mock_observer); - - auto mock_adapter = CableMockAdapter::MakePoweredOn(); - mock_adapter->ExpectLEScan(kAuthenticatorEid); - mock_adapter->ExpectRegisterAdvertisementWithResponse( - true /* simulate_success */, kClientEid, kUuidFormattedClientEid); - - BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); - cable_discovery->Start(); - task_environment_.FastForwardUntilNoTasksRemain(); -} -#endif // BUILDFLAG(IS_CHROMEOS) - -} // namespace device
diff --git a/device/fido/cable/fido_cable_handshake_handler.cc b/device/fido/cable/fido_cable_handshake_handler.cc deleted file mode 100644 index 6844eeec..0000000 --- a/device/fido/cable/fido_cable_handshake_handler.cc +++ /dev/null
@@ -1,175 +0,0 @@ -// Copyright 2018 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "device/fido/cable/fido_cable_handshake_handler.h" - -#include <algorithm> -#include <array> -#include <string_view> -#include <tuple> -#include <utility> - -#include "base/containers/map_util.h" -#include "base/containers/span.h" -#include "base/functional/bind.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/string_view_util.h" -#include "base/task/single_thread_task_runner.h" -#include "components/cbor/reader.h" -#include "components/cbor/values.h" -#include "components/cbor/writer.h" -#include "components/device_event_log/device_event_log.h" -#include "crypto/aead.h" -#include "crypto/hash.h" -#include "crypto/hkdf.h" -#include "crypto/hmac.h" -#include "crypto/random.h" -#include "crypto/secure_util.h" -#include "device/fido/cable/fido_cable_device.h" -#include "device/fido/cable/noise.h" -#include "device/fido/cable/v2_handshake.h" -#include "device/fido/fido_parsing_utils.h" -#include "device/fido/public/fido_constants.h" -#include "third_party/boringssl/src/include/openssl/digest.h" -#include "third_party/boringssl/src/include/openssl/ec_key.h" -#include "third_party/boringssl/src/include/openssl/ecdh.h" -#include "third_party/boringssl/src/include/openssl/hkdf.h" -#include "third_party/boringssl/src/include/openssl/obj.h" -#include "third_party/boringssl/src/include/openssl/sha.h" - -namespace device { - -namespace { - -// Length of CBOR encoded authenticator hello message concatenated with -// 16 byte message authentication code. -constexpr size_t kCableAuthenticatorHandshakeMessageSize = 66; - -// Length of CBOR encoded client hello message concatenated with 16 byte message -// authenticator code. -constexpr size_t kClientHelloMessageSize = 58; - -constexpr size_t kCableHandshakeMacMessageSize = 16; - -std::vector<uint8_t> ConstructHandshakeMessage( - std::string_view handshake_key, - base::span<const uint8_t, 16> client_random_nonce) { - cbor::Value::MapValue map; - map.emplace(0, kCableClientHelloMessage); - map.emplace(1, client_random_nonce); - auto hello = *cbor::Writer::Write(cbor::Value(std::move(map))); - - const auto mac = - crypto::hmac::SignSha256(base::as_byte_span(handshake_key), hello); - - constexpr size_t kMacOffset = - kClientHelloMessageSize - kCableHandshakeMacMessageSize; - - CHECK_EQ(hello.size(), kMacOffset); - hello.resize(kClientHelloMessageSize); - auto out_mac = base::span(hello).subspan(kMacOffset); - out_mac.copy_from(base::span(mac).first<kCableHandshakeMacMessageSize>()); - - return hello; -} - -} // namespace - -FidoCableHandshakeHandler::~FidoCableHandshakeHandler() {} - -FidoCableV1HandshakeHandler::FidoCableV1HandshakeHandler( - FidoCableDevice* cable_device, - base::span<const uint8_t, 8> nonce, - base::span<const uint8_t, 32> session_pre_key) - : cable_device_(cable_device), - nonce_(fido_parsing_utils::Materialize(nonce)), - session_pre_key_(fido_parsing_utils::Materialize(session_pre_key)), - handshake_key_(crypto::HkdfSha256(base::as_string_view(session_pre_key_), - base::as_string_view(nonce_), - kCableHandshakeKeyInfo, - /*derived_key_size=*/32)) { - crypto::RandBytes(client_session_random_); -} - -FidoCableV1HandshakeHandler::~FidoCableV1HandshakeHandler() = default; - -void FidoCableV1HandshakeHandler::InitiateCableHandshake( - FidoDevice::DeviceCallback callback) { - auto handshake_message = - ConstructHandshakeMessage(handshake_key_, client_session_random_); - - FIDO_LOG(DEBUG) << "Sending the caBLE handshake message"; - cable_device_->SendHandshakeMessage(std::move(handshake_message), - std::move(callback)); -} - -bool FidoCableV1HandshakeHandler::ValidateAuthenticatorHandshakeMessage( - base::span<const uint8_t> response) { - if (response.size() != kCableAuthenticatorHandshakeMessageSize) { - return false; - } - - constexpr size_t kMacOffset = - kCableAuthenticatorHandshakeMessageSize - kCableHandshakeMacMessageSize; - const auto [authenticator_hello, expected_truncated_mac] = - response.split_at(kMacOffset); - const auto actual_mac = crypto::hmac::SignSha256( - base::as_byte_span(handshake_key_), authenticator_hello); - - const auto actual_truncated_mac = - base::span(actual_mac).first(std::size(expected_truncated_mac)); - if (!crypto::SecureMemEqual(expected_truncated_mac, actual_truncated_mac)) { - return false; - } - - const auto authenticator_hello_cbor = cbor::Reader::Read(authenticator_hello); - if (!authenticator_hello_cbor || !authenticator_hello_cbor->is_map() || - authenticator_hello_cbor->GetMap().size() != 2) { - return false; - } - - const auto authenticator_hello_msg = - authenticator_hello_cbor->GetMap().find(cbor::Value(0)); - if (authenticator_hello_msg == authenticator_hello_cbor->GetMap().end() || - !authenticator_hello_msg->second.is_string() || - authenticator_hello_msg->second.GetString() != - kCableAuthenticatorHelloMessage) { - return false; - } - - const auto* authenticator_random_nonce = - base::FindOrNull(authenticator_hello_cbor->GetMap(), cbor::Value(1)); - if (!authenticator_random_nonce || - !authenticator_random_nonce->is_bytestring()) { - return false; - } - - auto sized_nonce_span = - base::span(authenticator_random_nonce->GetBytestring()) - .to_fixed_extent<16>(); - if (!sized_nonce_span) { - return false; - } - - cable_device_->SetV1EncryptionData( - base::as_byte_span( - GetEncryptionKeyAfterSuccessfulHandshake(*sized_nonce_span)), - nonce_); - - return true; -} - -std::array<uint8_t, 32> -FidoCableV1HandshakeHandler::GetEncryptionKeyAfterSuccessfulHandshake( - base::span<const uint8_t, 16> authenticator_random_nonce) const { - std::vector<uint8_t> nonce_message; - fido_parsing_utils::Append(&nonce_message, nonce_); - fido_parsing_utils::Append(&nonce_message, client_session_random_); - fido_parsing_utils::Append(&nonce_message, authenticator_random_nonce); - return crypto::HkdfSha256<32>(session_pre_key_, - crypto::hash::Sha256(nonce_message), - kCableDeviceEncryptionKeyInfo); -} - -} // namespace device
diff --git a/device/fido/cable/fido_cable_handshake_handler.h b/device/fido/cable/fido_cable_handshake_handler.h deleted file mode 100644 index e25e724..0000000 --- a/device/fido/cable/fido_cable_handshake_handler.h +++ /dev/null
@@ -1,81 +0,0 @@ -// Copyright 2018 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef DEVICE_FIDO_CABLE_FIDO_CABLE_HANDSHAKE_HANDLER_H_ -#define DEVICE_FIDO_CABLE_FIDO_CABLE_HANDSHAKE_HANDLER_H_ - -#include <stdint.h> - -#include <array> -#include <string> -#include <vector> - -#include "base/component_export.h" -#include "base/containers/span.h" -#include "base/gtest_prod_util.h" -#include "base/memory/raw_ptr.h" -#include "base/memory/weak_ptr.h" -#include "device/fido/cable/noise.h" -#include "device/fido/cable/v2_handshake.h" -#include "device/fido/fido_device.h" -#include "device/fido/public/cable_discovery_data.h" -#include "third_party/boringssl/src/include/openssl/base.h" - -namespace device { - -class FidoCableDevice; - -// FidoCableHandshakeHandler abstracts FidoCableV1HandshakeHandler to allow -// tests to inject fake handshake handlers. -class FidoCableHandshakeHandler { - public: - virtual ~FidoCableHandshakeHandler() = 0; - virtual void InitiateCableHandshake(FidoDevice::DeviceCallback callback) = 0; - virtual bool ValidateAuthenticatorHandshakeMessage( - base::span<const uint8_t> response) = 0; -}; - -// Handles exchanging handshake messages with external authenticator and -// validating the handshake messages to derive a shared session key to be used -// for message encryption. -// See: fido-client-to-authenticator-protocol.html#cable-encryption-handshake of -// the most up-to-date spec. -class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableV1HandshakeHandler - : public FidoCableHandshakeHandler { - public: - FidoCableV1HandshakeHandler(FidoCableDevice* device, - base::span<const uint8_t, 8> nonce, - base::span<const uint8_t, 32> session_pre_key); - - FidoCableV1HandshakeHandler(const FidoCableV1HandshakeHandler&) = delete; - FidoCableV1HandshakeHandler& operator=(const FidoCableV1HandshakeHandler&) = - delete; - - ~FidoCableV1HandshakeHandler() override; - - // FidoCableHandshakeHandler: - void InitiateCableHandshake(FidoDevice::DeviceCallback callback) override; - bool ValidateAuthenticatorHandshakeMessage( - base::span<const uint8_t> response) override; - - private: - FRIEND_TEST_ALL_PREFIXES(FidoCableHandshakeHandlerTest, HandShakeSuccess); - FRIEND_TEST_ALL_PREFIXES(FidoCableHandshakeHandlerTest, - HandshakeFailWithIncorrectAuthenticatorResponse); - - std::array<uint8_t, 32> GetEncryptionKeyAfterSuccessfulHandshake( - base::span<const uint8_t, 16> authenticator_random_nonce) const; - - const raw_ptr<FidoCableDevice> cable_device_; - std::array<uint8_t, 8> nonce_; - std::array<uint8_t, 32> session_pre_key_; - std::array<uint8_t, 16> client_session_random_; - std::string handshake_key_; - - base::WeakPtrFactory<FidoCableV1HandshakeHandler> weak_factory_{this}; -}; - -} // namespace device - -#endif // DEVICE_FIDO_CABLE_FIDO_CABLE_HANDSHAKE_HANDLER_H_
diff --git a/device/fido/cable/fido_cable_handshake_handler_fuzzer.cc b/device/fido/cable/fido_cable_handshake_handler_fuzzer.cc deleted file mode 100644 index 74fbc1e..0000000 --- a/device/fido/cable/fido_cable_handshake_handler_fuzzer.cc +++ /dev/null
@@ -1,47 +0,0 @@ -// Copyright 2018 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "device/fido/cable/fido_cable_handshake_handler.h" - -#include <stddef.h> -#include <stdint.h> - -#include <array> - -#include "base/compiler_specific.h" -#include "base/containers/span.h" -#include "base/memory/ref_counted.h" -#include "device/bluetooth/test/mock_bluetooth_adapter.h" -#include "device/fido/cable/fido_cable_device.h" -#include "device/fido/public/fido_constants.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace { - -constexpr std::array<uint8_t, 32> kTestSessionPreKey = {{ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -}}; - -constexpr std::array<uint8_t, 8> kTestNonce = {{ - 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08, -}}; - -constexpr char kTestDeviceAddress[] = "Fake_Address"; - -} // namespace - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* raw_data, size_t size) { - auto data_span = UNSAFE_TODO(base::span(raw_data, size)); - auto adapter = - base::MakeRefCounted<::testing::NiceMock<device::MockBluetoothAdapter>>(); - device::FidoCableDevice test_cable_device(adapter.get(), kTestDeviceAddress); - - device::FidoCableV1HandshakeHandler handshake_handler_v1( - &test_cable_device, kTestNonce, kTestSessionPreKey); - handshake_handler_v1.ValidateAuthenticatorHandshakeMessage(data_span); - return 0; -}
diff --git a/device/fido/cable/fido_cable_handshake_handler_unittest.cc b/device/fido/cable/fido_cable_handshake_handler_unittest.cc deleted file mode 100644 index 235b393..0000000 --- a/device/fido/cable/fido_cable_handshake_handler_unittest.cc +++ /dev/null
@@ -1,407 +0,0 @@ -// Copyright 2018 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "device/fido/cable/fido_cable_handshake_handler.h" - -#include <array> -#include <cstdint> -#include <limits> -#include <memory> -#include <optional> -#include <string> -#include <string_view> -#include <utility> -#include <vector> - -#include "base/containers/span.h" -#include "base/functional/bind.h" -#include "base/location.h" -#include "base/memory/raw_ptr.h" -#include "base/memory/scoped_refptr.h" -#include "base/numerics/safe_conversions.h" -#include "base/strings/string_view_util.h" -#include "base/task/sequenced_task_runner.h" -#include "base/test/task_environment.h" -#include "base/test/test_future.h" -#include "components/cbor/reader.h" -#include "components/cbor/values.h" -#include "crypto/hash.h" -#include "crypto/hkdf.h" -#include "crypto/hmac.h" -#include "crypto/secure_util.h" -#include "device/bluetooth/test/bluetooth_test.h" -#include "device/bluetooth/test/mock_bluetooth_adapter.h" -#include "device/fido/cable/fido_ble_frames.h" -#include "device/fido/cable/fido_cable_device.h" -#include "device/fido/cable/mock_fido_ble_connection.h" -#include "device/fido/fido_parsing_utils.h" -#include "device/fido/public/fido_constants.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace device { - -namespace { - -using ::testing::_; -using ::testing::Test; -using TestDeviceFuture = - base::test::TestFuture<std::optional<std::vector<uint8_t>>>; -using NiceMockBluetoothAdapter = ::testing::NiceMock<MockBluetoothAdapter>; - -// Sufficiently large test control point length as we are not interested -// in testing fragmentations of BLE messages. All Cable messages are encrypted -// and decrypted per request frame, not fragment. -constexpr auto kControlPointLength = std::numeric_limits<uint16_t>::max(); - -constexpr std::array<uint8_t, 16> kAuthenticatorSessionRandom = {{ - 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, - 0x01, 0x02, 0x03, 0x04, -}}; - -constexpr std::array<uint8_t, 32> kTestSessionPreKey = {{ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, -}}; - -constexpr std::array<uint8_t, 32> kIncorrectSessionPreKey = {{ - 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, - 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, - 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, -}}; - -constexpr std::array<uint8_t, 8> kTestNonce = {{ - 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08, -}}; - -constexpr std::array<uint8_t, 8> kIncorrectNonce = {{ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, -}}; - -constexpr std::array<uint8_t, 50> kValidAuthenticatorHello = {{ - // Map(2) - 0xA2, - // Key(0) - 0x00, - // Text(28) - 0x78, 0x1C, - // "caBLE v1 authenticator hello" - 0x63, 0x61, 0x42, 0x4C, 0x45, 0x20, 0x76, 0x31, 0x20, 0x61, 0x75, 0x74, - 0x68, 0x65, 0x6E, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6F, 0x72, 0x20, 0x68, - 0x65, 0x6C, 0x6C, 0x6F, - // Key(1) - 0x01, - // Bytes(16) - 0x50, - // Authenticator random session - 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, - 0x01, 0x02, 0x03, 0x04, -}}; - -constexpr std::array<uint8_t, 43> kInvalidAuthenticatorHello = {{ - // Map(2) - 0xA2, - // Key(0) - 0x00, - // Text(21) - 0x75, - // "caBLE INVALID MESSAGE" - 0x63, 0x61, 0x42, 0x4C, 0x45, 0x20, 0x49, 0x4E, 0x56, 0x41, 0x4C, 0x49, - 0x44, 0x20, 0x4D, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, - // Key(1) - 0x01, - // Bytes(16) - 0x50, - // Authenticator random session - 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, 0x01, 0x02, 0x03, 0x04, - 0x01, 0x02, 0x03, 0x04, -}}; - -constexpr char kIncorrectHandshakeKey[] = "INCORRECT_HANDSHAKE_KEY_12345678"; - -// Returns the expected encryption key that should be constructed given that -// the client random nonce is |client_random_nonce| and other determining -// factors (i.e. authenticator session random, session pre key, and nonce) are -// |kAuthenticatorSessionRandom|, |kTestSessionPreKey|, and |kTestNonce|, -// respectively. -std::array<uint8_t, 32> GetExpectedEncryptionKey( - base::span<const uint8_t> client_random_nonce) { - std::vector<uint8_t> nonce_message = - fido_parsing_utils::Materialize(kTestNonce); - fido_parsing_utils::Append(&nonce_message, client_random_nonce); - fido_parsing_utils::Append(&nonce_message, kAuthenticatorSessionRandom); - return crypto::HkdfSha256<32>(kTestSessionPreKey, - crypto::hash::Sha256(nonce_message), - kCableDeviceEncryptionKeyInfo); -} - -// Given a hello message and handshake key from the authenticator, construct -// a handshake message by concatenating hello message and its mac message -// derived from |handshake_key|. -std::vector<uint8_t> ConstructAuthenticatorHelloReply( - base::span<const uint8_t> hello_msg, - std::string_view handshake_key) { - auto reply = fido_parsing_utils::Materialize(hello_msg); - const auto hmac = - crypto::hmac::SignSha256(base::as_byte_span(handshake_key), hello_msg); - - fido_parsing_utils::Append(&reply, base::span(hmac).first<16>()); - return reply; -} - -// Constructs incoming handshake message from the authenticator into a BLE -// control fragment. -std::vector<uint8_t> ConstructSerializedOutgoingFragment( - base::span<const uint8_t> data) { - if (data.empty()) - return std::vector<uint8_t>(); - - FidoBleFrame response_frame(FidoBleDeviceCommand::kControl, - fido_parsing_utils::Materialize(data)); - const auto response_fragment = - std::get<0>(response_frame.ToFragments(kControlPointLength)); - - std::vector<uint8_t> outgoing_message; - response_fragment.Serialize(&outgoing_message); - return outgoing_message; -} - -// Authenticator abstraction that handles logic related to validating handshake -// messages from the client and sending rely handshake message back to the -// client. Session key and nonce are assumed to be |kTestSessionPreKey| and -// |kTestNonce| respectively. -class FakeCableAuthenticator { - public: - FakeCableAuthenticator() { - handshake_key_ = crypto::HkdfSha256( - base::as_string_view(kTestSessionPreKey), - base::as_string_view(kTestNonce), kCableHandshakeKeyInfo, 32); - } - - // Receives handshake message from the client, check its validity and if the - // handshake message is valid, store |client_session_random| embedded in the - // handshake message. - bool ConfirmClientHandshakeMessage( - base::span<const uint8_t> handshake_message) { - if (handshake_message.size() <= 16) - return false; - - // Handshake message from client should be concatenation of client hello - // message (42 bytes) with message authentication code (16 bytes). - if (handshake_message.size() != 58) - return false; - - const auto [client_hello, expected_mac] = handshake_message.split_at(42u); - const auto actual_mac = crypto::hmac::SignSha256( - base::as_byte_span(handshake_key_), client_hello); - const auto actual_mac_truncated = - base::span(actual_mac).first(std::size(expected_mac)); - if (!crypto::SecureMemEqual(expected_mac, actual_mac_truncated)) { - return false; - } - - const auto& client_hello_cbor = cbor::Reader::Read(client_hello); - if (!client_hello_cbor) - return false; - - const auto& message_map = client_hello_cbor->GetMap(); - auto hello_message_it = message_map.find(cbor::Value(0)); - auto client_random_nonce_it = message_map.find(cbor::Value(1)); - if (hello_message_it == message_map.end() || - client_random_nonce_it == message_map.end()) - return false; - - if (!hello_message_it->second.is_string() || - hello_message_it->second.GetString() != kCableClientHelloMessage) { - return false; - } - - if (!client_random_nonce_it->second.is_bytestring() || - client_random_nonce_it->second.GetBytestring().size() != 16) { - return false; - } - - client_session_random_ = - std::move(client_random_nonce_it->second.GetBytestring()); - return true; - } - - std::vector<uint8_t> RelyWithAuthenticatorHandShakeMessage( - base::span<const uint8_t> handshake_message) { - if (!ConfirmClientHandshakeMessage(handshake_message)) - return std::vector<uint8_t>(); - - return ConstructAuthenticatorHelloReply(kValidAuthenticatorHello, - handshake_key_); - } - - private: - std::string handshake_key_; - std::vector<uint8_t> client_session_random_; - std::vector<uint8_t> authenticator_session_random_ = - fido_parsing_utils::Materialize(kAuthenticatorSessionRandom); -}; - -} // namespace - -class FidoCableHandshakeHandlerTest : public Test { - public: - FidoCableHandshakeHandlerTest() { - auto connection = std::make_unique<MockFidoBleConnection>( - adapter_.get(), BluetoothTestBase::kTestDeviceAddress1); - connection_ = connection.get(); - device_ = std::make_unique<FidoCableDevice>(std::move(connection)); - - connection_->read_callback() = device_->GetReadCallbackForTesting(); - } - - std::unique_ptr<FidoCableV1HandshakeHandler> CreateHandshakeHandler( - std::array<uint8_t, 8> nonce, - std::array<uint8_t, 32> session_pre_key) { - return std::make_unique<FidoCableV1HandshakeHandler>(device_.get(), nonce, - session_pre_key); - } - - void ConnectWithLength(uint16_t length) { - EXPECT_CALL(*connection(), ConnectPtr).WillOnce([](auto* callback) { - std::move(*callback).Run(true); - }); - - EXPECT_CALL(*connection(), ReadControlPointLengthPtr(_)) - .WillOnce([length](auto* cb) { std::move(*cb).Run(length); }); - - device()->Connect(); - } - - FidoCableDevice* device() { return device_.get(); } - MockFidoBleConnection* connection() { return connection_; } - FakeCableAuthenticator* authenticator() { return &authenticator_; } - TestDeviceFuture& future() { return future_; } - - protected: - base::test::TaskEnvironment task_environment_; - - private: - scoped_refptr<MockBluetoothAdapter> adapter_ = - base::MakeRefCounted<NiceMockBluetoothAdapter>(); - FakeCableAuthenticator authenticator_; - raw_ptr<MockFidoBleConnection, DanglingUntriaged> connection_; - std::unique_ptr<FidoCableDevice> device_; - TestDeviceFuture future_; -}; - -// Checks that outgoing handshake message from the client is a BLE frame with -// Control command type. -MATCHER(IsControlFrame, "") { - return !arg.empty() && - arg[0] == base::strict_cast<uint8_t>(FidoBleDeviceCommand::kControl); -} - -TEST_F(FidoCableHandshakeHandlerTest, HandShakeSuccess) { - ConnectWithLength(kControlPointLength); - - EXPECT_CALL(*connection(), WriteControlPointPtr(IsControlFrame(), _)) - .WillOnce([this](const auto& data, auto* cb) { - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(*cb), true)); - - const auto client_ble_handshake_message = - base::span(data).template subspan<3>(); - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, - base::BindOnce( - connection()->read_callback(), - ConstructSerializedOutgoingFragment( - authenticator()->RelyWithAuthenticatorHandShakeMessage( - client_ble_handshake_message)))); - }); - - auto handshake_handler = - CreateHandshakeHandler(kTestNonce, kTestSessionPreKey); - handshake_handler->InitiateCableHandshake(future().GetCallback()); - - EXPECT_TRUE(future().Wait()); - const auto& value = future().Get(); - ASSERT_TRUE(value); - EXPECT_TRUE(handshake_handler->ValidateAuthenticatorHandshakeMessage(*value)); - EXPECT_EQ(GetExpectedEncryptionKey(handshake_handler->client_session_random_), - handshake_handler->GetEncryptionKeyAfterSuccessfulHandshake( - kAuthenticatorSessionRandom)); -} - -TEST_F(FidoCableHandshakeHandlerTest, HandShakeWithIncorrectSessionPreKey) { - ConnectWithLength(kControlPointLength); - - EXPECT_CALL(*connection(), WriteControlPointPtr(IsControlFrame(), _)) - .WillOnce([this](const auto& data, auto* cb) { - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(*cb), true)); - - const auto client_ble_handshake_message = - base::span(data).template subspan<3>(); - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, - base::BindOnce( - connection()->read_callback(), - ConstructSerializedOutgoingFragment( - authenticator()->RelyWithAuthenticatorHandShakeMessage( - client_ble_handshake_message)))); - }); - - auto handshake_handler = - CreateHandshakeHandler(kTestNonce, kIncorrectSessionPreKey); - handshake_handler->InitiateCableHandshake(future().GetCallback()); - - EXPECT_TRUE(future().Wait()); - EXPECT_FALSE(future().Get()); -} - -TEST_F(FidoCableHandshakeHandlerTest, HandshakeFailWithIncorrectNonce) { - ConnectWithLength(kControlPointLength); - - EXPECT_CALL(*connection(), WriteControlPointPtr(IsControlFrame(), _)) - .WillOnce([this](const auto& data, auto* cb) { - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(*cb), true)); - - const auto client_ble_handshake_message = - base::span(data).template subspan<3>(); - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, - base::BindOnce( - connection()->read_callback(), - ConstructSerializedOutgoingFragment( - authenticator()->RelyWithAuthenticatorHandShakeMessage( - client_ble_handshake_message)))); - }); - - auto handshake_handler = - CreateHandshakeHandler(kIncorrectNonce, kTestSessionPreKey); - handshake_handler->InitiateCableHandshake(future().GetCallback()); - - EXPECT_TRUE(future().Wait()); - EXPECT_FALSE(future().Get()); -} - -TEST_F(FidoCableHandshakeHandlerTest, - HandshakeFailWithIncorrectAuthenticatorResponse) { - auto handshake_handler = - CreateHandshakeHandler(kTestNonce, kTestSessionPreKey); - - EXPECT_NE(kIncorrectHandshakeKey, handshake_handler->handshake_key_); - const auto authenticator_reply_with_invalid_key = - ConstructAuthenticatorHelloReply(kValidAuthenticatorHello, - kIncorrectHandshakeKey); - EXPECT_FALSE(handshake_handler->ValidateAuthenticatorHandshakeMessage( - authenticator_reply_with_invalid_key)); - - const auto authenticator_reply_with_invalid_hello_msg = - ConstructAuthenticatorHelloReply(kInvalidAuthenticatorHello, - handshake_handler->handshake_key_); - EXPECT_FALSE(handshake_handler->ValidateAuthenticatorHandshakeMessage( - authenticator_reply_with_invalid_hello_msg)); -} - -} // namespace device
diff --git a/device/fido/cable/mock_fido_ble_connection.cc b/device/fido/cable/mock_fido_ble_connection.cc deleted file mode 100644 index f52ed49..0000000 --- a/device/fido/cable/mock_fido_ble_connection.cc +++ /dev/null
@@ -1,38 +0,0 @@ -// Copyright 2017 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "device/fido/cable/mock_fido_ble_connection.h" - -#include "base/functional/callback_helpers.h" -#include "device/bluetooth/public/cpp/bluetooth_uuid.h" -#include "device/fido/cable/fido_ble_uuids.h" - -#include <utility> - -namespace device { - -MockFidoBleConnection::MockFidoBleConnection(BluetoothAdapter* adapter, - std::string device_address) - : FidoBleConnection(adapter, - std::move(device_address), - BluetoothUUID(kFidoServiceUUID), - base::DoNothing()) {} - -MockFidoBleConnection::~MockFidoBleConnection() = default; - -void MockFidoBleConnection::Connect(ConnectionCallback callback) { - ConnectPtr(&callback); -} - -void MockFidoBleConnection::ReadControlPointLength( - ControlPointLengthCallback callback) { - ReadControlPointLengthPtr(&callback); -} - -void MockFidoBleConnection::WriteControlPoint(const std::vector<uint8_t>& data, - WriteCallback callback) { - WriteControlPointPtr(data, &callback); -} - -} // namespace device
diff --git a/device/fido/cable/mock_fido_ble_connection.h b/device/fido/cable/mock_fido_ble_connection.h deleted file mode 100644 index 6a67af3..0000000 --- a/device/fido/cable/mock_fido_ble_connection.h +++ /dev/null
@@ -1,46 +0,0 @@ -// Copyright 2017 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef DEVICE_FIDO_CABLE_MOCK_FIDO_BLE_CONNECTION_H_ -#define DEVICE_FIDO_CABLE_MOCK_FIDO_BLE_CONNECTION_H_ - -#include <string> -#include <vector> - -#include "base/component_export.h" -#include "device/fido/cable/fido_ble_connection.h" -#include "testing/gmock/include/gmock/gmock.h" - -namespace device { - -class BluetoothAdapter; - -class MockFidoBleConnection : public FidoBleConnection { - public: - MockFidoBleConnection(BluetoothAdapter* adapter, std::string device_address); - - MockFidoBleConnection(const MockFidoBleConnection&) = delete; - MockFidoBleConnection& operator=(const MockFidoBleConnection&) = delete; - - ~MockFidoBleConnection() override; - - // GMock cannot mock a method taking a move-only type. - // TODO(crbug.com/40524294): Remove these workarounds once support for - // move-only types is added to GMock. - MOCK_METHOD1(ConnectPtr, void(ConnectionCallback* cb)); - MOCK_METHOD1(ReadControlPointLengthPtr, void(ControlPointLengthCallback* cb)); - MOCK_METHOD2(WriteControlPointPtr, - void(const std::vector<uint8_t>& data, WriteCallback* cb)); - - void Connect(ConnectionCallback cb) override; - void ReadControlPointLength(ControlPointLengthCallback cb) override; - void WriteControlPoint(const std::vector<uint8_t>& data, - WriteCallback cb) override; - - ReadCallback& read_callback() { return read_callback_; } -}; - -} // namespace device - -#endif // DEVICE_FIDO_CABLE_MOCK_FIDO_BLE_CONNECTION_H_
diff --git a/device/fido/cable/v2_discovery.cc b/device/fido/cable/v2_discovery.cc index 730f870..0cb6eea 100644 --- a/device/fido/cable/v2_discovery.cc +++ b/device/fido/cable/v2_discovery.cc
@@ -10,7 +10,13 @@ #include "base/functional/callback_helpers.h" #include "base/metrics/histogram_functions.h" #include "base/strings/string_number_conversions.h" +#include "base/task/sequenced_task_runner.h" #include "components/device_event_log/device_event_log.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/bluetooth/bluetooth_discovery_filter.h" +#include "device/bluetooth/bluetooth_discovery_session.h" +#include "device/bluetooth/public/cpp/bluetooth_uuid.h" +#include "device/fido/cable/fido_ble_uuids.h" #include "device/fido/cable/fido_tunnel_device.h" #include "device/fido/cable/pairing.h" #include "device/fido/cable/v2_handshake.h" @@ -19,10 +25,22 @@ #include "device/fido/public/features.h" #include "third_party/boringssl/src/include/openssl/aes.h" +#if BUILDFLAG(IS_CHROMEOS) +#include "device/bluetooth/bluetooth_low_energy_scan_filter.h" +#include "device/bluetooth/floss/floss_features.h" +#endif // BUILDFLAG(IS_CHROMEOS) + +#if BUILDFLAG(IS_MAC) +#include "device/fido/mac/util.h" +#endif + namespace device::cablev2 { namespace { +// Client name for logging in BLE scanning. +constexpr char kScanClientName[] = "FIDO"; + // CableV2DiscoveryEvent enumerates several steps that occur while listening for // BLE adverts. Do not change the assigned values since they are used in // histograms, only append new values. Keep synced with enums.xml. @@ -50,7 +68,6 @@ FidoRequestType request_type, NetworkContextFactory network_context_factory, std::optional<base::span<const uint8_t, kQRKeySize>> qr_generator_key, - std::unique_ptr<AdvertEventStream> advert_stream, std::unique_ptr<EventStream<std::unique_ptr<Pairing>>> contact_device_stream, const std::vector<CableDiscoveryData>& extension_contents, @@ -65,15 +82,12 @@ network_context_factory_(std::move(network_context_factory)), qr_keys_(KeysFromQRGeneratorKey(qr_generator_key)), extension_keys_(KeysFromExtension(extension_contents)), - advert_stream_(std::move(advert_stream)), contact_device_stream_(std::move(contact_device_stream)), pairing_callback_(std::move(pairing_callback)), invalidated_pairing_callback_(std::move(invalidated_pairing_callback)), event_callback_(std::move(event_callback)), must_support_ctap_(must_support_ctap) { static_assert(kQRKeySize == kQRSecretSize + kQRSeedSize); - advert_stream_->Connect( - base::BindRepeating(&Discovery::OnBLEAdvertSeen, base::Unretained(this))); if (contact_device_stream_) { contact_device_stream_->Connect(base::BindRepeating( @@ -81,10 +95,17 @@ } } -Discovery::~Discovery() = default; +Discovery::~Discovery() { + if (adapter_) { + adapter_->RemoveObserver(this); + } +} void Discovery::StartInternal() { - DCHECK(!started_); + CHECK(!started_); + + BluetoothAdapterFactory::Get()->GetAdapter( + base::BindOnce(&Discovery::OnGetAdapter, weak_factory_.GetWeakPtr())); RecordEvent(CableV2DiscoveryEvent::kStarted); if (pairing_callback_) { @@ -99,12 +120,18 @@ } started_ = true; - NotifyDiscoveryStarted(true); +} - std::vector<std::array<uint8_t, kAdvertSize>> pending_adverts( - std::move(pending_adverts_)); - for (const auto& advert : pending_adverts) { - OnBLEAdvertSeen(advert); +void Discovery::GetDiscoveryData(const BluetoothDevice* device) { + const std::vector<uint8_t>* service_data = + device->GetServiceDataForUUID(GoogleCableUUID()); + if (!service_data) { + service_data = device->GetServiceDataForUUID(FIDOCableUUID()); + } + + if (service_data && service_data->size() == kAdvertSize) { + OnBLEAdvertSeen(*(base::span<const uint8_t>(*service_data) + .to_fixed_extent<kAdvertSize>())); } } @@ -112,12 +139,6 @@ const std::array<uint8_t, kAdvertSize> advert_array = fido_parsing_utils::Materialize<kAdvertSize>(advert); - if (!started_) { - // Server-linked devices may have started advertising already. - pending_adverts_.push_back(advert_array); - return; - } - if (device_committed_) { // A device has already been accepted. Ignore other adverts. return; @@ -161,14 +182,13 @@ if (event_callback_) { event_callback_->Run(Event::kBLEAdvertReceived); } - AddDevice(std::make_unique<cablev2::FidoTunnelDevice>( + AddDevice(std::make_unique<FidoTunnelDevice>( network_context_factory_, pairing_callback_, event_callback_, qr_keys_->qr_secret, qr_keys_->local_identity_seed, *plaintext, must_support_ctap_)); return; } } - // Check whether the EID matches the extension. for (const auto& extension : extension_keys_) { std::optional<CableEidArray> plaintext = @@ -252,4 +272,219 @@ return ret; } +// static +const BluetoothUUID& Discovery::GoogleCableUUID() { + static const base::NoDestructor<BluetoothUUID> kUUID(kGoogleCableUUID128); + return *kUUID; +} + +const BluetoothUUID& Discovery::FIDOCableUUID() { + static const base::NoDestructor<BluetoothUUID> kUUID(kFIDOCableUUID128); + return *kUUID; +} + +// static +bool Discovery::IsCableDevice(const BluetoothDevice* device) { + const auto& uuid1 = GoogleCableUUID(); + const auto& uuid2 = FIDOCableUUID(); + return device->GetServiceData().contains(uuid1) || + device->GetUUIDs().contains(uuid1) || + device->GetServiceData().contains(uuid2) || + device->GetUUIDs().contains(uuid2); +} + +// static +std::vector<CableEidArray> Discovery::GetUUIDs(const BluetoothDevice* device) { + std::vector<CableEidArray> ret; + + const auto service_uuids = device->GetUUIDs(); + for (const auto& uuid : service_uuids) { + std::vector<uint8_t> uuid_binary = uuid.GetBytes(); + CableEidArray authenticator_eid; + CHECK_EQ(authenticator_eid.size(), uuid_binary.size()); + base::span(authenticator_eid).copy_from(uuid_binary); + ret.emplace_back(std::move(authenticator_eid)); + } + + return ret; +} + +void Discovery::OnGetAdapter(scoped_refptr<BluetoothAdapter> bt_adapter) { + if (!bt_adapter->IsPresent()) { + FIDO_LOG(DEBUG) << "No BLE adapter present"; + NotifyDiscoveryStarted(false); + return; + } + + CHECK(!adapter()); + adapter_ = std::move(bt_adapter); + CHECK(adapter()); + + adapter()->AddObserver(this); + BluetoothAdapter::PermissionStatus bluetooth_permission = + BluetoothAdapter::PermissionStatus::kAllowed; +#if BUILDFLAG(IS_MAC) + switch (fido::mac::ProcessIsSigned()) { + case fido::mac::CodeSigningState::kSigned: + bluetooth_permission = adapter()->GetOsPermissionStatus(); + FIDO_LOG(DEBUG) << "Bluetooth authorized: " + << static_cast<int>(bluetooth_permission); + break; + case fido::mac::CodeSigningState::kNotSigned: + FIDO_LOG(DEBUG) + << "Build not signed. Assuming Bluetooth permission is granted."; + break; + } +#endif + + if (bluetooth_permission == BluetoothAdapter::PermissionStatus::kAllowed) { + FIDO_LOG(DEBUG) << "BLE adapter address " << adapter()->GetAddress(); + if (adapter()->IsPowered()) { + OnSetPowered(); + } + } + + // Discovery blocks its transport availability callback on the + // DiscoveryStarted() calls of all instantiated discoveries. Hence, this call + // must not be put behind the BLE adapter getting powered on (which is + // dependent on the UI), or else the UI and this discovery will wait on each + // other indefinitely (see crbug.com/1018416). + NotifyDiscoveryStarted(true); +} + +void Discovery::OnSetPowered() { + CHECK(adapter()); + base::SequencedTaskRunner::GetCurrentDefault()->PostTask( + FROM_HERE, base::BindOnce(&Discovery::StartCableDiscovery, + weak_factory_.GetWeakPtr())); +} + +void Discovery::SetDiscoverySession( + std::unique_ptr<BluetoothDiscoverySession> discovery_session) { + discovery_session_ = std::move(discovery_session); +} + +void Discovery::DeviceAdded(BluetoothAdapter* adapter, + BluetoothDevice* device) { + if (!IsCableDevice(device)) { + return; + } + + GetDiscoveryData(device); +} + +void Discovery::DeviceChanged(BluetoothAdapter* adapter, + BluetoothDevice* device) { + if (!IsCableDevice(device)) { + return; + } + + GetDiscoveryData(device); +} + +void Discovery::AdapterPoweredChanged(BluetoothAdapter* adapter, bool powered) { +#if BUILDFLAG(IS_WIN) + // On Windows, the power-on event appears to race against initialization of + // the adapter, such that one of the WinRT API calls inside + // BluetoothAdapter::StartDiscoverySessionWithFilter() can fail with "Device + // not ready for use". So wait for things to actually be ready. + // TODO(crbug.com/40670639): Remove this delay once the Bluetooth layer + // handles the spurious failure. + base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( + FROM_HERE, + base::BindOnce(&Discovery::StartCableDiscovery, + weak_factory_.GetWeakPtr()), + base::Milliseconds(500)); +#else + StartCableDiscovery(); +#endif // BUILDFLAG(IS_WIN) +} + +#if BUILDFLAG(IS_CHROMEOS) +void Discovery::OnDeviceFound( + device::BluetoothLowEnergyScanSession* scan_session, + device::BluetoothDevice* device) { + DeviceAdded(adapter(), device); +} + +void Discovery::OnDeviceLost( + device::BluetoothLowEnergyScanSession* scan_session, + device::BluetoothDevice* device) { + DeviceRemoved(adapter(), device); +} + +void Discovery::OnSessionStarted( + device::BluetoothLowEnergyScanSession* scan_session, + std::optional<device::BluetoothLowEnergyScanSession::ErrorCode> + error_code) { + if (error_code) { + FIDO_LOG(ERROR) << "Failed to start caBLE LE scan session, error_code = " + << static_cast<int>(error_code.value()); + le_scan_session_.reset(); + return; + } + + FIDO_LOG(DEBUG) << "LE scan session started."; +} + +void Discovery::OnSessionInvalidated( + device::BluetoothLowEnergyScanSession* scan_session) { + FIDO_LOG(EVENT) << "LE scan session invalidated"; + le_scan_session_.reset(); +} + +#endif // BUILDFLAG(IS_CHROMEOS) + +void Discovery::StartCableDiscovery() { +#if BUILDFLAG(IS_CHROMEOS) + if (floss::features::IsFlossEnabled()) { + device::BluetoothLowEnergyScanFilter::Pattern google_pattern( + /*start_position=*/0, + device::BluetoothLowEnergyScanFilter::AdvertisementDataType:: + kServiceData, + /* kServiceData takes the 16-bit UUID as a little endian byte vector. */ + std::vector<uint8_t>{kGoogleCableUUID[3], kGoogleCableUUID[2]}); + device::BluetoothLowEnergyScanFilter::Pattern fido_pattern( + /*start_position=*/0, + device::BluetoothLowEnergyScanFilter::AdvertisementDataType:: + kServiceData, + std::vector<uint8_t>{kFIDOCableUUID[3], kFIDOCableUUID[2]}); + auto filter = device::BluetoothLowEnergyScanFilter::Create( + device::BluetoothLowEnergyScanFilter::Range::kFar, + /*device_found_timeout=*/base::Seconds(1), + /*device_lost_timeout=*/base::Seconds(7), + {google_pattern, fido_pattern}, + /*rssi_sampling_period=*/base::Seconds(1)); + if (!filter) { + FIDO_LOG(ERROR) + << "Failed to start LE scanning due to failure to create filter."; + return; + } + + le_scan_session_ = adapter()->StartLowEnergyScanSession( + std::move(filter), weak_factory_.GetWeakPtr()); + return; + } +#endif // BUILDFLAG(IS_CHROMEOS) + + adapter()->StartDiscoverySessionWithFilter( + std::make_unique<BluetoothDiscoveryFilter>( + BluetoothTransport::BLUETOOTH_TRANSPORT_LE), + kScanClientName, + base::BindOnce(&Discovery::OnStartDiscoverySession, + weak_factory_.GetWeakPtr()), + base::BindOnce(&Discovery::OnStartDiscoverySessionError, + weak_factory_.GetWeakPtr())); +} + +void Discovery::OnStartDiscoverySession( + std::unique_ptr<BluetoothDiscoverySession> session) { + FIDO_LOG(DEBUG) << "Discovery session started."; + SetDiscoverySession(std::move(session)); +} + +void Discovery::OnStartDiscoverySessionError() { + FIDO_LOG(ERROR) << "Failed to start caBLE discovery"; +} + } // namespace device::cablev2
diff --git a/device/fido/cable/v2_discovery.h b/device/fido/cable/v2_discovery.h index f8a9be7c..5762691 100644 --- a/device/fido/cable/v2_discovery.h +++ b/device/fido/cable/v2_discovery.h
@@ -15,7 +15,10 @@ #include "base/containers/span.h" #include "base/functional/callback.h" #include "base/memory/raw_ptr.h" +#include "base/memory/scoped_refptr.h" #include "base/memory/weak_ptr.h" +#include "build/build_config.h" +#include "device/bluetooth/bluetooth_adapter.h" #include "device/fido/cable/pairing.h" #include "device/fido/cable/v2_constants.h" #include "device/fido/fido_device_discovery.h" @@ -24,23 +27,28 @@ #include "device/fido/public/fido_constants.h" #include "services/network/public/mojom/network_context.mojom-forward.h" +#if BUILDFLAG(IS_CHROMEOS) +#include "device/bluetooth/bluetooth_low_energy_scan_session.h" +#endif // BUILDFLAG(IS_CHROMEOS) + namespace device::cablev2 { struct Pairing; class FidoTunnelDevice; // Discovery creates caBLEv2 devices, either based on |pairings|, or when a BLE -// advert is seen that matches |qr_generator_key|. It does not actively scan for -// BLE adverts itself. Rather it depends on |OnBLEAdvertSeen| getting called. -class COMPONENT_EXPORT(DEVICE_FIDO) Discovery : public FidoDeviceDiscovery { +// advert is seen that matches |qr_generator_key|. +class COMPONENT_EXPORT(DEVICE_FIDO) Discovery + : public FidoDeviceDiscovery, +#if BUILDFLAG(IS_CHROMEOS) + public device::BluetoothLowEnergyScanSession::Delegate, +#endif // BUILDFLAG(IS_CHROMEOS) + public BluetoothAdapter::Observer { public: - using AdvertEventStream = EventStream<base::span<const uint8_t, kAdvertSize>>; - Discovery( FidoRequestType request_type, NetworkContextFactory network_context_factory, std::optional<base::span<const uint8_t, kQRKeySize>> qr_generator_key, - std::unique_ptr<AdvertEventStream> advert_stream, // contact_device_stream contains a series of pairings indicating that the // given device should be contacted. The pairings may be duplicated. It // may be nullptr. @@ -62,6 +70,26 @@ Discovery(const Discovery&) = delete; Discovery& operator=(const Discovery&) = delete; + // BluetoothAdapter::Observer: + void DeviceAdded(BluetoothAdapter* adapter, BluetoothDevice* device) override; + void DeviceChanged(BluetoothAdapter* adapter, + BluetoothDevice* device) override; + void AdapterPoweredChanged(BluetoothAdapter* adapter, bool powered) override; + +#if BUILDFLAG(IS_CHROMEOS) + // device::BluetoothLowEnergyScanSession::Delegate: + void OnDeviceFound(device::BluetoothLowEnergyScanSession* scan_session, + device::BluetoothDevice* device) override; + void OnDeviceLost(device::BluetoothLowEnergyScanSession* scan_session, + device::BluetoothDevice* device) override; + void OnSessionStarted( + device::BluetoothLowEnergyScanSession* scan_session, + std::optional<device::BluetoothLowEnergyScanSession::ErrorCode> + error_code) override; + void OnSessionInvalidated( + device::BluetoothLowEnergyScanSession* scan_session) override; +#endif // BUILDFLAG(IS_CHROMEOS) + // FidoDeviceDiscovery: void StartInternal() override; @@ -82,11 +110,38 @@ static std::vector<UnpairedKeys> KeysFromExtension( const std::vector<CableDiscoveryData>& extension_contents); + static const BluetoothUUID& GoogleCableUUID(); + static const BluetoothUUID& FIDOCableUUID(); + static bool IsCableDevice(const BluetoothDevice* device); + + static std::optional<CableEidArray> MaybeGetEidFromServiceData( + const BluetoothDevice* device); + static std::vector<CableEidArray> GetUUIDs(const BluetoothDevice* device); + + void StartCableDiscovery(); + void OnStartDiscoverySession(std::unique_ptr<BluetoothDiscoverySession>); + void OnStartDiscoverySessionError(); + + void OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter); + void OnSetPowered(); + + void SetDiscoverySession( + std::unique_ptr<BluetoothDiscoverySession> discovery_session); + + void GetDiscoveryData(const BluetoothDevice* device); + + BluetoothAdapter* adapter() { return adapter_.get(); } + + scoped_refptr<BluetoothAdapter> adapter_; + std::unique_ptr<BluetoothDiscoverySession> discovery_session_; +#if BUILDFLAG(IS_CHROMEOS) + std::unique_ptr<device::BluetoothLowEnergyScanSession> le_scan_session_; +#endif // BUILDFLAG(IS_CHROMEOS) + const FidoRequestType request_type_; NetworkContextFactory network_context_factory_; const std::optional<UnpairedKeys> qr_keys_; const std::vector<UnpairedKeys> extension_keys_; - std::unique_ptr<AdvertEventStream> advert_stream_; std::unique_ptr<EventStream<std::unique_ptr<Pairing>>> contact_device_stream_; const std::optional<base::RepeatingCallback<void(std::unique_ptr<Pairing>)>> pairing_callback_; @@ -98,7 +153,7 @@ base::flat_set<std::array<uint8_t, kAdvertSize>> observed_adverts_; bool started_ = false; bool device_committed_ = false; - std::vector<std::array<uint8_t, kAdvertSize>> pending_adverts_; + base::WeakPtrFactory<Discovery> weak_factory_{this}; };
diff --git a/device/fido/cable/v2_discovery_unittest.cc b/device/fido/cable/v2_discovery_unittest.cc new file mode 100644 index 0000000..3106c1c9 --- /dev/null +++ b/device/fido/cable/v2_discovery_unittest.cc
@@ -0,0 +1,330 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "device/fido/cable/v2_discovery.h" + +#include <array> +#include <memory> +#include <optional> +#include <string> +#include <vector> + +#include "base/functional/callback.h" +#include "base/test/bind.h" +#include "base/test/task_environment.h" +#include "build/build_config.h" +#include "crypto/random.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/fido/cable/cable_mock_bluetooth_adapter.h" +#include "device/fido/cable/pairing.h" +#include "device/fido/cable/v2_constants.h" +#include "device/fido/cable/v2_handshake.h" +#include "device/fido/cable/v2_test_util.h" +#include "device/fido/fido_authenticator.h" +#include "device/fido/mock_fido_discovery_observer.h" +#include "device/fido/public/cable_discovery_data.h" +#include "device/fido/public/fido_constants.h" +#include "services/network/public/mojom/network_context.mojom.h" +#include "services/network/test/test_network_context.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if BUILDFLAG(IS_CHROMEOS) +#include "base/test/scoped_feature_list.h" +#include "device/bluetooth/bluetooth_low_energy_scan_filter.h" +#include "device/bluetooth/floss/floss_features.h" +#endif // BUILDFLAG(IS_CHROMEOS) + +#if BUILDFLAG(IS_MAC) +#include "device/fido/mac/util.h" +#endif + +using ::testing::_; +using ::testing::NiceMock; + +namespace device::cablev2 { +namespace { + +ACTION_P(ReturnFromAsyncCall, closure) { + closure.Run(); +} + +class MockTunnelServer : public network::TestNetworkContext { + public: + // Too many arguments for gmock. :( + void CreateWebSocket( + const GURL& url, + const std::vector<std::string>& requested_protocols, + net::StorageAccessApiStatus storage_access_api_status, + const net::IsolationInfo& isolation_info, + std::vector<network::mojom::HttpHeaderPtr> additional_headers, + const network::OriginatingProcessId& process_id, + const url::Origin& origin, + network::mojom::ClientSecurityStatePtr client_security_state, + uint32_t options, + const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, + mojo::PendingRemote<network::mojom::WebSocketHandshakeClient> + handshake_client, + mojo::PendingRemote<network::mojom::URLLoaderNetworkServiceObserver> + url_loader_network_observer, + mojo::PendingRemote<network::mojom::WebSocketAuthenticationHandler> + auth_handler, + mojo::PendingRemote<network::mojom::TrustedHeaderClient> header_client, + const std::optional<base::UnguessableToken>& throttling_profile_id) + override { + create_called_ = true; + } + + bool create_called_ = false; +}; + +} // namespace + +class CableV2DiscoveryTest : public ::testing::Test { + public: + void SetUp() override { + mock_tunnel_server_ = std::make_unique<MockTunnelServer>(); + + discovery_ = std::make_unique<Discovery>( + device::FidoRequestType::kGetAssertion, + base::BindLambdaForTesting([&]() -> network::mojom::NetworkContext* { + return mock_tunnel_server_.get(); + }), + qr_generator_key_, /*contact_device_stream=*/nullptr, + /*extension_contents=*/std::vector<device::CableDiscoveryData>(), + /*pairing_callback=*/std::nullopt, + /*invalidated_pairing_callback=*/std::nullopt, + /*event_callback=*/std::nullopt, /*must_support_ctap=*/true); + } + + void TearDown() override { + discovery_.reset(); + mock_tunnel_server_.reset(); + } + + Discovery* discovery() { return discovery_.get(); } + + base::test::TaskEnvironment& task_environment() { return task_environment_; } + + MockTunnelServer& mock_tunnel_server() { return *mock_tunnel_server_.get(); } + + std::array<uint8_t, kAdvertSize> GetV2Advert() { + CableEidArray plaintext_eid; + device::cablev2::eid::Components components; + components.tunnel_server_domain = kTunnelServer; + components.routing_id = {0}; + crypto::RandBytes(components.nonce); + + plaintext_eid = device::cablev2::eid::FromComponents(components); + + return eid::Encrypt( + plaintext_eid, + Derive<kEIDKeySize>(zero_qr_secret_, {}, + device::cablev2::DerivedValueType::kEIDKey)); + } + + std::array<uint8_t, kAdvertSize> GetNonMatchingV2Advert() { + CableEidArray plaintext_eid; + device::cablev2::eid::Components components; + components.tunnel_server_domain = kTunnelServer; + components.routing_id = {0}; + crypto::RandBytes(components.nonce); + + plaintext_eid = device::cablev2::eid::FromComponents(components); + + return eid::Encrypt( + plaintext_eid, + Derive<kEIDKeySize>(one_qr_secret_, {}, + device::cablev2::DerivedValueType::kEIDKey)); + } + + private: + std::unique_ptr<Discovery> discovery_; + base::test::TaskEnvironment task_environment_{ + base::test::TaskEnvironment::TimeSource::MOCK_TIME}; + std::unique_ptr<MockTunnelServer> mock_tunnel_server_; + + const std::array<uint8_t, kQRKeySize> qr_generator_key_ = {0}; + const std::array<uint8_t, kQRSecretSize> zero_qr_secret_ = {0}; + const std::array<uint8_t, kQRSecretSize> one_qr_secret_ = {1}; +}; + +// Tests discovery without a BLE adapter. +TEST_F(CableV2DiscoveryTest, TestDiscoveryFails) { + NiceMock<MockFidoDiscoveryObserver> mock_observer; + EXPECT_CALL( + mock_observer, + DiscoveryStarted(discovery(), false, std::vector<FidoAuthenticator*>())); + EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _)).Times(0); + discovery()->set_observer(&mock_observer); + + auto mock_adapter = CableMockBluetoothAdapter::MakeNotPresent(); + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); + discovery()->Start(); + task_environment().FastForwardUntilNoTasksRemain(); +} + +// Tests discovery with a powered-off BLE adapter. Not calling +// DiscoveryStarted() in the case of a present-but-unpowered adapter leads to a +// deadlock between the discovery and the UI (see crbug.com/1018416). +TEST_F(CableV2DiscoveryTest, TestDiscoveryStartedWithUnpoweredAdapter) { + NiceMock<MockFidoDiscoveryObserver> mock_observer; + EXPECT_CALL( + mock_observer, + DiscoveryStarted(discovery(), true, std::vector<FidoAuthenticator*>())); + EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _)).Times(0); + discovery()->set_observer(&mock_observer); + + auto mock_adapter = CableMockBluetoothAdapter::MakePoweredOff(); + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); + discovery()->Start(); + task_environment().FastForwardUntilNoTasksRemain(); +} + +// Tests regular successful discovery flow for device. +TEST_F(CableV2DiscoveryTest, TestDiscoveryFindsNewDevice) { + NiceMock<MockFidoDiscoveryObserver> mock_observer; + EXPECT_CALL( + mock_observer, + DiscoveryStarted(discovery(), true, std::vector<FidoAuthenticator*>())); + EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _)); + discovery()->set_observer(&mock_observer); + + auto mock_adapter = CableMockBluetoothAdapter::MakePoweredOn(); + mock_adapter->ExpectDiscoveryWithScanCallback(GetV2Advert()); + + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); + discovery()->Start(); + task_environment().FastForwardUntilNoTasksRemain(); + EXPECT_TRUE(mock_tunnel_server().create_called_); +} + +#if BUILDFLAG(IS_MAC) + +// Tests that the discovery will not attempt to call bluetooth functions like +// IsPowered() if the build is signed and the OS reports an undetermined +// permission status. +TEST_F(CableV2DiscoveryTest, TestDiscoveryDoesNotUseBluetoothIfUnauthorized) { + fido::mac::ScopedProcessIsSignedOverride scoped_process_is_signed_override( + fido::mac::CodeSigningState::kSigned); + NiceMock<MockFidoDiscoveryObserver> mock_observer; + EXPECT_CALL( + mock_observer, + DiscoveryStarted(discovery(), true, std::vector<FidoAuthenticator*>())); + discovery()->set_observer(&mock_observer); + + auto mock_adapter = + CableMockBluetoothAdapter::MakeWithUndeterminedPermission(); + EXPECT_CALL(*mock_adapter, IsPowered()).Times(0); + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); + discovery()->Start(); + task_environment().FastForwardUntilNoTasksRemain(); +} + +// Tests that the discovery will assume bluetooth permission is granted if the +// build is not signed. +TEST_F(CableV2DiscoveryTest, + TestDiscoveryAssumesBluetoothAuthorizedIfUnsigned) { + fido::mac::ScopedProcessIsSignedOverride scoped_process_is_signed_override( + fido::mac::CodeSigningState::kNotSigned); + NiceMock<MockFidoDiscoveryObserver> mock_observer; + EXPECT_CALL( + mock_observer, + DiscoveryStarted(discovery(), true, std::vector<FidoAuthenticator*>())); + EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _)); + discovery()->set_observer(&mock_observer); + + auto mock_adapter = + CableMockBluetoothAdapter::MakeWithUndeterminedPermission(); + EXPECT_CALL(*mock_adapter, IsPowered()) + .WillRepeatedly(::testing::Return(true)); + mock_adapter->ExpectDiscoveryWithScanCallback(GetV2Advert()); + + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); + discovery()->Start(); + task_environment().FastForwardUntilNoTasksRemain(); +} + +#endif // BUILDFLAG(IS_MAC) + +// Tests a scenario where upon broadcasting advertisement and scanning, client +// discovers a device with an incorrect authenticator EID. Observer::AddDevice() +// must not be called. +TEST_F(CableV2DiscoveryTest, TestDiscoveryFindsIncorrectDevice) { + NiceMock<MockFidoDiscoveryObserver> mock_observer; + EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _)).Times(0); + EXPECT_CALL(mock_observer, + DiscoveryStarted(discovery(), true, testing::IsEmpty())); + discovery()->set_observer(&mock_observer); + + auto mock_adapter = CableMockBluetoothAdapter::MakePoweredOn(); + mock_adapter->ExpectDiscoveryWithScanCallback(GetNonMatchingV2Advert()); + + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); + discovery()->Start(); + task_environment().FastForwardUntilNoTasksRemain(); +} + +// Tests that cable discovery resumes after Bluetooth adapter is powered on. +TEST_F(CableV2DiscoveryTest, TestResumeDiscoveryAfterPoweredOn) { + NiceMock<MockFidoDiscoveryObserver> mock_observer; + EXPECT_CALL( + mock_observer, + DiscoveryStarted(discovery(), true, std::vector<FidoAuthenticator*>())); + EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _)); + discovery()->set_observer(&mock_observer); + + auto mock_adapter = + base::MakeRefCounted<::testing::NiceMock<CableMockBluetoothAdapter>>(); + EXPECT_CALL(*mock_adapter, IsPresent()) + .WillRepeatedly(::testing::Return(true)); + + // After BluetoothAdapter is powered on, we expect that Cable discovery starts + // again. + mock_adapter->ExpectDiscoveryWithScanCallback(GetV2Advert()); + + // Wait until error callback for SetPowered() is invoked. Then, simulate + // Bluetooth adapter power change by invoking + // MockBluetoothAdapter::NotifyAdapterPoweredChanged(). + { + base::RunLoop run_loop; + auto quit = run_loop.QuitClosure(); + EXPECT_CALL(*mock_adapter, IsPowered()) + .WillRepeatedly(::testing::DoAll(ReturnFromAsyncCall(quit), + ::testing::Return(false))); + + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); + discovery()->Start(); + run_loop.Run(); + } + + mock_adapter->NotifyAdapterPoweredChanged(true); + task_environment().FastForwardUntilNoTasksRemain(); + EXPECT_TRUE(mock_tunnel_server().create_called_); +} + +#if BUILDFLAG(IS_CHROMEOS) +// Tests regular successful discovery flow for Cable device on Floss. +TEST_F(CableV2DiscoveryTest, TestDiscoveryFindsNewDeviceFloss) { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndEnableFeature(floss::features::kFlossEnabled); + + NiceMock<MockFidoDiscoveryObserver> mock_observer; + EXPECT_CALL( + mock_observer, + DiscoveryStarted(discovery(), true, std::vector<FidoAuthenticator*>())); + EXPECT_CALL(mock_observer, AuthenticatorAdded(_, _)); + discovery()->set_observer(&mock_observer); + + auto mock_adapter = CableMockBluetoothAdapter::MakePoweredOn(); + mock_adapter->ExpectLEScan(GetV2Advert()); + + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); + discovery()->Start(); + task_environment().FastForwardUntilNoTasksRemain(); + EXPECT_TRUE(mock_tunnel_server().create_called_); +} +#endif // BUILDFLAG(IS_CHROMEOS) + +} // namespace device::cablev2
diff --git a/device/fido/cable/v2_test_util.cc b/device/fido/cable/v2_test_util.cc index 08a6e9c..e553a55 100644 --- a/device/fido/cable/v2_test_util.cc +++ b/device/fido/cable/v2_test_util.cc
@@ -23,6 +23,9 @@ #include "components/cbor/values.h" #include "components/cbor/writer.h" #include "crypto/random.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/fido/cable/cable_mock_bluetooth_adapter.h" +#include "device/fido/cable/fido_ble_uuids.h" #include "device/fido/cable/v2_authenticator.h" #include "device/fido/cable/v2_discovery.h" #include "device/fido/cable/v2_handshake.h" @@ -38,10 +41,14 @@ #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" #include "services/network/public/mojom/client_security_state.mojom.h" #include "services/network/test/test_network_context.h" +#include "testing/gmock/include/gmock/gmock.h" #include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h" #include "third_party/boringssl/src/include/openssl/ec_key.h" #include "url/gurl.h" +using ::testing::_; +using ::testing::Sequence; + namespace device::cablev2 { namespace { @@ -394,10 +401,10 @@ // messages to the given |VirtualCtap2Device|. class TestPlatform : public authenticator::Platform { public: - TestPlatform(Discovery::AdvertEventStream::Callback ble_advert_callback, - device::VirtualCtap2Device* ctap2_device, + TestPlatform(device::VirtualCtap2Device* ctap2_device, + scoped_refptr<CableMockBluetoothAdapter> mock_adapter, authenticator::Observer* observer) - : ble_advert_callback_(ble_advert_callback), + : mock_adapter_(mock_adapter), ctap2_device_(ctap2_device), observer_(observer) {} @@ -490,7 +497,7 @@ private: void DoSendBLEAdvert(base::span<const uint8_t, kAdvertSize> advert) { - ble_advert_callback_.Run(advert); + mock_adapter_->AddNewTestBluetoothDevice(advert); } std::vector<uint8_t> ToCTAP2Command( @@ -626,7 +633,7 @@ std::move(response)); } - Discovery::AdvertEventStream::Callback ble_advert_callback_; + scoped_refptr<CableMockBluetoothAdapter> mock_adapter_; const raw_ptr<device::VirtualCtap2Device> ctap2_device_; const raw_ptr<authenticator::Observer> observer_; base::WeakPtrFactory<TestPlatform> weak_factory_{this}; @@ -958,11 +965,10 @@ namespace authenticator { std::unique_ptr<authenticator::Platform> NewMockPlatform( - Discovery::AdvertEventStream::Callback ble_advert_callback, device::VirtualCtap2Device* ctap2_device, + scoped_refptr<CableMockBluetoothAdapter> mock_adapter, authenticator::Observer* observer) { - return std::make_unique<TestPlatform>(ble_advert_callback, ctap2_device, - observer); + return std::make_unique<TestPlatform>(ctap2_device, mock_adapter, observer); } // NewLateLinkingDevice returns a caBLEv2 authenticator that sends linking
diff --git a/device/fido/cable/v2_test_util.h b/device/fido/cable/v2_test_util.h index f41ada8d..11d347e3 100644 --- a/device/fido/cable/v2_test_util.h +++ b/device/fido/cable/v2_test_util.h
@@ -22,6 +22,8 @@ namespace cablev2 { +class CableMockBluetoothAdapter; + // ContactCallback is called when a mock tunnel server (see // |NewMockTunnelServer|) is asked to contact a phone. This simulates a tunnel // server using a cloud messaging solution to wake a device. @@ -57,9 +59,11 @@ // forwarded to |ble_advert_callback|. |observer| may be |nullptr| but, if not, // then corresponding calls to the mock `Platform` are forwarded to the // observer. +// `mock_adapter` needs to have been set for return by the +// `BluetoothAdapterFactory`. std::unique_ptr<Platform> NewMockPlatform( - Discovery::AdvertEventStream::Callback ble_advert_callback, device::VirtualCtap2Device* ctap2_device, + scoped_refptr<CableMockBluetoothAdapter> mock_adapter, Observer* observer); // NewLateLinkingDevice returns a caBLEv2 device that fails all CTAP requests
diff --git a/device/fido/fido_discovery_factory.cc b/device/fido/fido_discovery_factory.cc index fa36f31..e07997f 100644 --- a/device/fido/fido_discovery_factory.cc +++ b/device/fido/fido_discovery_factory.cc
@@ -10,7 +10,6 @@ #include "build/build_config.h" #include "components/device_event_log/device_event_log.h" #include "device/bluetooth/bluetooth_adapter_factory.h" -#include "device/fido/cable/fido_cable_discovery.h" #include "device/fido/cable/pairing.h" #include "device/fido/cable/v2_discovery.h" #include "device/fido/enclave/enclave_discovery.h" @@ -86,8 +85,6 @@ #endif // BUILDFLAG(IS_WIN) if (device::BluetoothAdapterFactory::Get()->IsLowEnergySupported() && (cable_data_.has_value() || qr_generator_key_.has_value())) { - auto v1_discovery = std::make_unique<FidoCableDiscovery>( - cable_data_.value_or(std::vector<CableDiscoveryData>())); std::vector<std::unique_ptr<FidoDiscoveryBase>> ret; const bool have_v2_discovery_data = @@ -97,15 +94,12 @@ if (qr_generator_key_.has_value() || have_v2_discovery_data) { ret.emplace_back(std::make_unique<cablev2::Discovery>( request_type_.value(), network_context_factory_, - qr_generator_key_, v1_discovery->GetV2AdvertStream(), - std::move(contact_device_stream_), + qr_generator_key_, std::move(contact_device_stream_), cable_data_.value_or(std::vector<CableDiscoveryData>()), std::move(cable_pairing_callback_), std::move(cable_invalidated_pairing_callback_), std::move(cable_event_callback_), cable_must_support_ctap_)); } - - ret.emplace_back(std::move(v1_discovery)); return ret; } return {};
diff --git a/extensions/browser/api/test/test_api_observer_registry.h b/extensions/browser/api/test/test_api_observer_registry.h index eedd58e..41eb9c6 100644 --- a/extensions/browser/api/test/test_api_observer_registry.h +++ b/extensions/browser/api/test/test_api_observer_registry.h
@@ -55,7 +55,12 @@ TestApiObserverRegistry(); ~TestApiObserverRegistry(); - base::ObserverList<TestApiObserver> observers_; + // TODO(crbug.com/484371187): Investigate if reentrancy can be removed. + base::ObserverList< + TestApiObserver, + /*check_empty=*/false, + base::ObserverListReentrancyPolicy::kAllowReentrancyUntriaged> + observers_; }; } // namespace extensions
diff --git a/extensions/browser/extension_icon_image.h b/extensions/browser/extension_icon_image.h index e4ff60e..e060f84d 100644 --- a/extensions/browser/extension_icon_image.h +++ b/extensions/browser/extension_icon_image.h
@@ -126,7 +126,12 @@ // asynchronous from creation). bool did_complete_initial_load_; - base::ObserverList<Observer>::Unchecked observers_; + // TODO(crbug.com/484371187): Investigate if reentrancy can be removed. + base::ObserverList< + Observer, + /*check_empty=*/false, + base::ObserverListReentrancyPolicy::kAllowReentrancyUntriaged>::Unchecked + observers_; raw_ptr<Source, DanglingUntriaged> source_; // Owned by ImageSkia storage. gfx::ImageSkia image_skia_;
diff --git a/extensions/browser/extension_prefs.h b/extensions/browser/extension_prefs.h index fe10fde..b0dddbfc 100644 --- a/extensions/browser/extension_prefs.h +++ b/extensions/browser/extension_prefs.h
@@ -951,7 +951,12 @@ bool extensions_disabled_; - base::ObserverList<ExtensionPrefsObserver>::Unchecked observer_list_; + // TODO(crbug.com/484371187): Investigate if reentrancy can be removed. + base::ObserverList< + ExtensionPrefsObserver, + /*check_empty=*/false, + base::ObserverListReentrancyPolicy::kAllowReentrancyUntriaged>::Unchecked + observer_list_; }; } // namespace extensions
diff --git a/extensions/browser/extension_registry.h b/extensions/browser/extension_registry.h index c934f298..d5f9162 100644 --- a/extensions/browser/extension_registry.h +++ b/extensions/browser/extension_registry.h
@@ -211,8 +211,12 @@ // subset of `enabled_extensions_`. ExtensionSet ready_extensions_; - base::ObserverList<ExtensionRegistryObserver>::UncheckedAndDanglingUntriaged - observers_; + // TODO(crbug.com/484371187): Investigate if reentrancy can be removed. + base::ObserverList< + ExtensionRegistryObserver, + /*check_empty=*/false, + base::ObserverListReentrancyPolicy::kAllowReentrancyUntriaged>:: + UncheckedAndDanglingUntriaged observers_; const raw_ptr<content::BrowserContext> browser_context_; };
diff --git a/extensions/browser/process_manager.h b/extensions/browser/process_manager.h index 1cfc4c7f..7fce14c 100644 --- a/extensions/browser/process_manager.h +++ b/extensions/browser/process_manager.h
@@ -386,7 +386,12 @@ // True if we have created the startup set of background hosts. bool startup_background_hosts_created_; - base::ObserverList<ProcessManagerObserver> observer_list_; + // TODO(crbug.com/484371187): Investigate if reentrancy can be removed. + base::ObserverList< + ProcessManagerObserver, + /*check_empty=*/false, + base::ObserverListReentrancyPolicy::kAllowReentrancyUntriaged> + observer_list_; // ID Counter used to set ProcessManager::BackgroundPageData close_sequence_id // members. These IDs are tracked per extension in background_page_data_ and
diff --git a/gpu/command_buffer/service/shared_image/ozone_image_gl_textures_holder.cc b/gpu/command_buffer/service/shared_image/ozone_image_gl_textures_holder.cc index d93c918..525bdcb 100644 --- a/gpu/command_buffer/service/shared_image/ozone_image_gl_textures_holder.cc +++ b/gpu/command_buffer/service/shared_image/ozone_image_gl_textures_holder.cc
@@ -12,7 +12,6 @@ #include "gpu/command_buffer/service/shared_image/shared_image_backing.h" #include "gpu/command_buffer/service/shared_image/shared_image_format_service_utils.h" #include "gpu/command_buffer/service/texture_manager.h" -#include "ui/gfx/buffer_types.h" #include "ui/gfx/native_pixmap.h" #include "ui/gl/gl_bindings.h" #include "ui/gl/scoped_binders.h" @@ -44,59 +43,6 @@ NOTREACHED(); } -gfx::BufferPlane GetBufferPlane(viz::SharedImageFormat format, - int plane_index) { - DCHECK(format.IsValidPlaneIndex(plane_index)); - switch (format.plane_config()) { - case viz::SharedImageFormat::PlaneConfig::kY_U_V: - switch (plane_index) { - case 0: - return gfx::BufferPlane::Y; - case 1: - return gfx::BufferPlane::U; - case 2: - return gfx::BufferPlane::V; - } - case viz::SharedImageFormat::PlaneConfig::kY_V_U: - switch (plane_index) { - case 0: - return gfx::BufferPlane::Y; - case 1: - return gfx::BufferPlane::V; - case 2: - return gfx::BufferPlane::U; - } - case viz::SharedImageFormat::PlaneConfig::kY_UV: - switch (plane_index) { - case 0: - return gfx::BufferPlane::Y; - case 1: - return gfx::BufferPlane::UV; - } - case viz::SharedImageFormat::PlaneConfig::kY_UV_A: - switch (plane_index) { - case 0: - return gfx::BufferPlane::Y; - case 1: - return gfx::BufferPlane::UV; - case 2: - return gfx::BufferPlane::A; - } - case viz::SharedImageFormat::PlaneConfig::kY_U_V_A: - switch (plane_index) { - case 0: - return gfx::BufferPlane::Y; - case 1: - return gfx::BufferPlane::U; - case 2: - return gfx::BufferPlane::V; - case 3: - return gfx::BufferPlane::A; - } - } - NOTREACHED(); -} - // Create a NativePixmapGLBinding for the given `pixmap`. On failure, returns // nullptr. std::unique_ptr<ui::NativePixmapGLBinding> GetBinding( @@ -118,16 +64,17 @@ // formats. viz::SharedImageFormat plane_format = format; gfx::Size plane_size; - // The plane is DEFAULT for single planar formats and multi planar with - // external sampler. - gfx::BufferPlane buffer_plane; + // The `buffer_plane_index` is unset for single-planar formats and + // multi-planar with external sampler, and only set for per-plane multi-planar + // textures. + std::optional<int> buffer_plane_index; if (format.is_single_plane() || format.PrefersExternalSampler()) { plane_size = size; - buffer_plane = gfx::BufferPlane::DEFAULT; + buffer_plane_index = std::nullopt; } else { plane_format = GetFormatForPlane(format, plane_index); plane_size = format.GetPlaneSize(plane_index, size); - buffer_plane = GetBufferPlane(format, plane_index); + buffer_plane_index = plane_index; } // The target should be GL_TEXTURE_2D unless external sampling is being @@ -136,7 +83,6 @@ // the buffer format passed in here must be the single-planar format of the // plane). if (format.PrefersExternalSampler()) { - CHECK_EQ(buffer_plane, gfx::BufferPlane::DEFAULT); target = GL_TEXTURE_EXTERNAL_OES; } else { target = GL_TEXTURE_2D; @@ -153,7 +99,7 @@ api->glTexParameteriFn(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); std::unique_ptr<ui::NativePixmapGLBinding> np_gl_binding = - gl_ozone->ImportNativePixmap(pixmap, plane_format, buffer_plane, + gl_ozone->ImportNativePixmap(pixmap, plane_format, buffer_plane_index, plane_size, color_space, target, gl_texture_service_id); if (!np_gl_binding) {
diff --git a/gpu/ipc/service/shared_image_stub.cc b/gpu/ipc/service/shared_image_stub.cc index f103936..e4ec9d3 100644 --- a/gpu/ipc/service/shared_image_stub.cc +++ b/gpu/ipc/service/shared_image_stub.cc
@@ -290,12 +290,23 @@ TRACE_EVENT2("gpu", "SharedImageStub::OnCreateSharedImageWithData", "width", params->si_info->meta.size.width(), "height", params->si_info->meta.size.height()); - bool needs_gl = HasGLES2ReadOrWriteUsage(params->si_info->meta.usage); + + auto& metadata = params->si_info->meta; + + bool needs_gl = HasGLES2ReadOrWriteUsage(metadata.usage); if (!MakeContextCurrent(needs_gl)) { OnError(); return; } + auto min_size = metadata.format.MaybeEstimatedSizeInBytes(metadata.size); + if (params->pixel_data_size == 0 || !min_size || + params->pixel_data_size < min_size.value()) { + LOG(ERROR) << "SharedImageStub: upload data size is invalid"; + OnError(); + return; + } + base::CheckedNumeric<size_t> safe_required_span_size = params->pixel_data_offset; safe_required_span_size += params->pixel_data_size; @@ -318,10 +329,8 @@ memory.subspan(params->pixel_data_offset, params->pixel_data_size); if (!factory_->CreateSharedImage( - params->mailbox, params->si_info->meta.format, - params->si_info->meta.size, params->si_info->meta.color_space, - params->si_info->meta.surface_origin, - params->si_info->meta.alpha_type, params->si_info->meta.usage, + params->mailbox, metadata.format, metadata.size, metadata.color_space, + metadata.surface_origin, metadata.alpha_type, metadata.usage, GetLabel(params->si_info->debug_label), subspan)) { LOG(ERROR) << kSICreationFailureError; OnError();
diff --git a/infra/config/generated/cq-builders.md b/infra/config/generated/cq-builders.md index ec1f37aa..ca00ee8 100644 --- a/infra/config/generated/cq-builders.md +++ b/infra/config/generated/cq-builders.md
@@ -555,11 +555,14 @@ * [`//components/cast/.+`](https://cs.chromium.org/chromium/src/components/cast/) * [`//components/cast_receiver/.+`](https://cs.chromium.org/chromium/src/components/cast_receiver/) * [`//components/cast_streaming/.+`](https://cs.chromium.org/chromium/src/components/cast_streaming/) + * [`//components/viz/common/display/overlay_strategy\.h`](https://cs.chromium.org/search?q=+file:components/viz/common/display/overlay_strategy\.h) + * [`//components/viz/service/display/overlay_strategy_underlay\.h`](https://cs.chromium.org/search?q=+file:components/viz/service/display/overlay_strategy_underlay\.h) * [`//third_party/cast_core/.+`](https://cs.chromium.org/chromium/src/third_party/cast_core/) * [`//third_party/openscreen/.+`](https://cs.chromium.org/chromium/src/third_party/openscreen/) * [`//ui/events/platform/platform_event_dispatcher\.h`](https://cs.chromium.org/search?q=+file:ui/events/platform/platform_event_dispatcher\.h) * [`//ui/gfx/client_native_pixmap\.h`](https://cs.chromium.org/search?q=+file:ui/gfx/client_native_pixmap\.h) * [`//ui/gfx/client_native_pixmap_factory\.h`](https://cs.chromium.org/search?q=+file:ui/gfx/client_native_pixmap_factory\.h) + * [`//ui/gfx/native_pixmap\.h`](https://cs.chromium.org/search?q=+file:ui/gfx/native_pixmap\.h) * [`//ui/gl/gl_surface_egl\.h`](https://cs.chromium.org/search?q=+file:ui/gl/gl_surface_egl\.h) * [`//ui/ozone/common/gl_ozone_egl\.h`](https://cs.chromium.org/search?q=+file:ui/ozone/common/gl_ozone_egl\.h) * [`//ui/ozone/platform/cast/.+`](https://cs.chromium.org/chromium/src/ui/ozone/platform/cast/)
diff --git a/infra/config/generated/cq-usage/full.cfg b/infra/config/generated/cq-usage/full.cfg index 1020c24..45320f48 100644 --- a/infra/config/generated/cq-usage/full.cfg +++ b/infra/config/generated/cq-usage/full.cfg
@@ -2975,6 +2975,18 @@ gerrit_host_regexp: ".*" gerrit_project_regexp: ".*" gerrit_ref_regexp: ".*" + path_regexp: "components/viz/common/display/overlay_strategy\\.h" + } + location_filters { + gerrit_host_regexp: ".*" + gerrit_project_regexp: ".*" + gerrit_ref_regexp: ".*" + path_regexp: "components/viz/service/display/overlay_strategy_underlay\\.h" + } + location_filters { + gerrit_host_regexp: ".*" + gerrit_project_regexp: ".*" + gerrit_ref_regexp: ".*" path_regexp: "third_party/cast_core/.+" } location_filters { @@ -3005,6 +3017,12 @@ gerrit_host_regexp: ".*" gerrit_project_regexp: ".*" gerrit_ref_regexp: ".*" + path_regexp: "ui/gfx/native_pixmap\\.h" + } + location_filters { + gerrit_host_regexp: ".*" + gerrit_project_regexp: ".*" + gerrit_ref_regexp: ".*" path_regexp: "ui/gl/gl_surface_egl\\.h" } location_filters {
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg index 77da564c..7a806a3 100644 --- a/infra/config/generated/luci/commit-queue.cfg +++ b/infra/config/generated/luci/commit-queue.cfg
@@ -5129,6 +5129,18 @@ gerrit_host_regexp: ".*" gerrit_project_regexp: ".*" gerrit_ref_regexp: ".*" + path_regexp: "components/viz/common/display/overlay_strategy\\.h" + } + location_filters { + gerrit_host_regexp: ".*" + gerrit_project_regexp: ".*" + gerrit_ref_regexp: ".*" + path_regexp: "components/viz/service/display/overlay_strategy_underlay\\.h" + } + location_filters { + gerrit_host_regexp: ".*" + gerrit_project_regexp: ".*" + gerrit_ref_regexp: ".*" path_regexp: "third_party/cast_core/.+" } location_filters { @@ -5159,6 +5171,12 @@ gerrit_host_regexp: ".*" gerrit_project_regexp: ".*" gerrit_ref_regexp: ".*" + path_regexp: "ui/gfx/native_pixmap\\.h" + } + location_filters { + gerrit_host_regexp: ".*" + gerrit_project_regexp: ".*" + gerrit_ref_regexp: ".*" path_regexp: "ui/gl/gl_surface_egl\\.h" } location_filters {
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star b/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star index 780f3ed..c8e3325 100644 --- a/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star +++ b/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
@@ -184,11 +184,14 @@ "components/cast/.+", "components/cast_receiver/.+", "components/cast_streaming/.+", + r"components/viz/common/display/overlay_strategy\.h", + r"components/viz/service/display/overlay_strategy_underlay\.h", "third_party/cast_core/.+", "third_party/openscreen/.+", r"ui/events/platform/platform_event_dispatcher\.h", r"ui/gfx/client_native_pixmap\.h", r"ui/gfx/client_native_pixmap_factory\.h", + r"ui/gfx/native_pixmap\.h", r"ui/gl/gl_surface_egl\.h", r"ui/ozone/common/gl_ozone_egl\.h", "ui/ozone/platform/cast/.+",
diff --git a/internal b/internal index af90ff5..64463e9 160000 --- a/internal +++ b/internal
@@ -1 +1 @@ -Subproject commit af90ff55ed4003f9e5ff3d7f84adf74ea2bcd44e +Subproject commit 64463e9445c6e4d54c906a615bf7c2abd03dce9b
diff --git a/ios/chrome/browser/app_bar/coordinator/BUILD.gn b/ios/chrome/browser/app_bar/coordinator/BUILD.gn index af5684e..f356259 100644 --- a/ios/chrome/browser/app_bar/coordinator/BUILD.gn +++ b/ios/chrome/browser/app_bar/coordinator/BUILD.gn
@@ -25,6 +25,7 @@ "//ios/chrome/browser/shared/model/url:constants", "//ios/chrome/browser/shared/model/web_state_list", "//ios/chrome/browser/shared/public/commands", + "//ios/chrome/browser/shared/public/features", "//ios/chrome/browser/shared/ui/symbols", "//ios/chrome/browser/url_loading/model", "//ios/chrome/browser/url_loading/model:url_loading_params_header",
diff --git a/ios/chrome/browser/app_bar/coordinator/app_bar_coordinator.mm b/ios/chrome/browser/app_bar/coordinator/app_bar_coordinator.mm index 6dde08a..40a5cb3f 100644 --- a/ios/chrome/browser/app_bar/coordinator/app_bar_coordinator.mm +++ b/ios/chrome/browser/app_bar/coordinator/app_bar_coordinator.mm
@@ -14,11 +14,16 @@ #import "ios/chrome/browser/shared/model/browser/browser.h" #import "ios/chrome/browser/shared/model/profile/profile_ios.h" #import "ios/chrome/browser/shared/public/commands/command_dispatcher.h" +#import "ios/chrome/browser/shared/public/commands/guided_tour_commands.h" #import "ios/chrome/browser/shared/public/commands/scene_commands.h" #import "ios/chrome/browser/shared/public/commands/tab_grid_commands.h" #import "ios/chrome/browser/shared/public/commands/tab_groups_commands.h" +#import "ios/chrome/browser/shared/public/features/features.h" #import "ios/chrome/browser/url_loading/model/url_loading_browser_agent.h" +@interface AppBarCoordinator () <GuidedTourCommands> +@end + @implementation AppBarCoordinator { AppBarContainerViewController* _containerViewController; AppBarViewController* _viewController; @@ -78,6 +83,12 @@ _containerViewController = [[AppBarContainerViewController alloc] init]; [_containerViewController setAppBar:_viewController]; + + if (IsBestOfAppGuidedTourEnabled()) { + [_regularBrowser->GetCommandDispatcher() + startDispatchingToTarget:self + forProtocol:@protocol(GuidedTourCommands)]; + } } - (void)stop { @@ -101,4 +112,18 @@ : nullptr]; } +#pragma mark - GuidedTourCommands + +- (void)highlightViewInStep:(GuidedTourStep)step { + if (step == GuidedTourStep::kNTP) { + [_viewController toggleSpotlightView:YES]; + } +} + +- (void)stepCompleted:(GuidedTourStep)step { + if (step == GuidedTourStep::kNTP) { + [_viewController toggleSpotlightView:NO]; + } +} + @end
diff --git a/ios/chrome/browser/app_bar/ui/BUILD.gn b/ios/chrome/browser/app_bar/ui/BUILD.gn index 4586406e..018f2d3f 100644 --- a/ios/chrome/browser/app_bar/ui/BUILD.gn +++ b/ios/chrome/browser/app_bar/ui/BUILD.gn
@@ -4,6 +4,8 @@ source_set("ui") { sources = [ + "app_bar_constants.h", + "app_bar_constants.mm", "app_bar_consumer.h", "app_bar_container_view.h", "app_bar_container_view.mm", @@ -19,6 +21,7 @@ "//ios/chrome/app/strings", "//ios/chrome/browser/intents/model:model_donation_helper", "//ios/chrome/browser/shared/public/commands", + "//ios/chrome/browser/shared/public/features", "//ios/chrome/browser/shared/ui:buildflags", "//ios/chrome/browser/shared/ui/symbols", "//ios/chrome/browser/shared/ui/util",
diff --git a/ios/chrome/browser/app_bar/ui/app_bar_constants.h b/ios/chrome/browser/app_bar/ui/app_bar_constants.h new file mode 100644 index 0000000..229f414 --- /dev/null +++ b/ios/chrome/browser/app_bar/ui/app_bar_constants.h
@@ -0,0 +1,13 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IOS_CHROME_BROWSER_APP_BAR_UI_APP_BAR_CONSTANTS_H_ +#define IOS_CHROME_BROWSER_APP_BAR_UI_APP_BAR_CONSTANTS_H_ + +#import <Foundation/Foundation.h> + +// The width of the app bar when in landscape. +extern const CGFloat kAppBarHeight; + +#endif // IOS_CHROME_BROWSER_APP_BAR_UI_APP_BAR_CONSTANTS_H_
diff --git a/ios/chrome/browser/app_bar/ui/app_bar_constants.mm b/ios/chrome/browser/app_bar/ui/app_bar_constants.mm new file mode 100644 index 0000000..8cea1e7 --- /dev/null +++ b/ios/chrome/browser/app_bar/ui/app_bar_constants.mm
@@ -0,0 +1,7 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "ios/chrome/browser/app_bar/ui/app_bar_constants.h" + +const CGFloat kAppBarHeight = 77;
diff --git a/ios/chrome/browser/app_bar/ui/app_bar_view_controller.h b/ios/chrome/browser/app_bar/ui/app_bar_view_controller.h index 271e3824..2d96135 100644 --- a/ios/chrome/browser/app_bar/ui/app_bar_view_controller.h +++ b/ios/chrome/browser/app_bar/ui/app_bar_view_controller.h
@@ -17,6 +17,9 @@ // View controller for the app bar. @interface AppBarViewController : UIViewController <AppBarConsumer> +// Unhides the spotlight anchor view if `shouldShow`. +- (void)toggleSpotlightView:(BOOL)shouldShow; + // The mutator. @property(nonatomic, weak) id<AppBarMutator> mutator; // This view controller's LayoutGuideCenter.
diff --git a/ios/chrome/browser/app_bar/ui/app_bar_view_controller.mm b/ios/chrome/browser/app_bar/ui/app_bar_view_controller.mm index 670b1f77..2b067fa 100644 --- a/ios/chrome/browser/app_bar/ui/app_bar_view_controller.mm +++ b/ios/chrome/browser/app_bar/ui/app_bar_view_controller.mm
@@ -6,10 +6,12 @@ #import "base/metrics/user_metrics.h" #import "base/metrics/user_metrics_action.h" +#import "ios/chrome/browser/app_bar/ui/app_bar_constants.h" #import "ios/chrome/browser/app_bar/ui/app_bar_mutator.h" #import "ios/chrome/browser/intents/model/intents_donation_helper.h" #import "ios/chrome/browser/shared/public/commands/scene_commands.h" #import "ios/chrome/browser/shared/public/commands/tab_grid_commands.h" +#import "ios/chrome/browser/shared/public/features/features.h" #import "ios/chrome/browser/shared/ui/buildflags.h" #import "ios/chrome/browser/shared/ui/symbols/symbols.h" #import "ios/chrome/browser/shared/ui/util/layout_guide_names.h" @@ -36,6 +38,9 @@ const CGFloat kButtonShadowOffset = 1; // The duration of the animation to update the TabGrid button. const CGFloat kTabGridAnimationDuration = 0.25; +// Spacing between tab grid button and the tab grid spotlight view anchor. +const CGFloat kSpotlightViewHorizontalInset = 12; +const CGFloat kSpotlightViewVerticalInset = 2; // Returns the configuration for all the symbols. UIImageSymbolConfiguration* AppBarSymbolConfiguration() { @@ -51,16 +56,13 @@ AppBarSymbolConfiguration()); } -// Remove the "unused-function" check as this is only used when some buildflag -// is enabled. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-function" +#if BUILDFLAG(IOS_USE_BRANDED_ASSETS) // Returns a custom symbol with the common configuration. UIImage* CustomAppBarSymbol(NSString* symbol_name) { return CustomSymbolWithConfiguration(symbol_name, AppBarSymbolConfiguration()); } -#pragma clang diagnostic pop +#endif } // namespace @@ -81,6 +83,7 @@ UIMenu* _assistantButtonMenu; UIMenu* _openNewTabButtonMenu; UIMenu* _tabGridButtonMenu; + UIView* _spotlightView; } - (void)updateForAngle:(CGFloat)angle { @@ -108,11 +111,26 @@ stackView.translatesAutoresizingMaskIntoConstraints = NO; stackView.distribution = UIStackViewDistributionFillEqually; - [self.view addSubview:stackView]; + UIView* view = self.view; + [view addSubview:stackView]; - AddSameConstraints(stackView, self.view); + [NSLayoutConstraint activateConstraints:@[ + [stackView.leadingAnchor constraintEqualToAnchor:view.leadingAnchor], + [stackView.topAnchor constraintEqualToAnchor:view.topAnchor], + [stackView.trailingAnchor constraintEqualToAnchor:view.trailingAnchor], + [stackView.bottomAnchor + constraintLessThanOrEqualToAnchor:view.bottomAnchor], + [view.heightAnchor constraintEqualToConstant:kAppBarHeight], + ]]; - [self.layoutGuideCenter referenceView:self.view underName:kAppBarGuide]; + [self.layoutGuideCenter referenceView:view underName:kAppBarGuide]; +} + +#pragma mark - Public + +- (void)toggleSpotlightView:(BOOL)shouldShow { + CHECK(IsBestOfAppGuidedTourEnabled()); + _spotlightView.hidden = !shouldShow; } #pragma mark - AppBarConsumer @@ -232,6 +250,22 @@ [button addSubview:_tabCountLabel]; AddSameCenterConstraints(_tabCountLabel, button.imageView); + if (IsBestOfAppGuidedTourEnabled()) { + _spotlightView = [[UIView alloc] init]; + _spotlightView.translatesAutoresizingMaskIntoConstraints = NO; + _spotlightView.userInteractionEnabled = NO; + [button addSubview:_spotlightView]; + AddSameConstraintsToSidesWithInsets( + _spotlightView, button, + LayoutSides::kTop | LayoutSides::kTrailing | LayoutSides::kLeading | + LayoutSides::kBottom, + NSDirectionalEdgeInsetsMake( + kSpotlightViewVerticalInset, kSpotlightViewHorizontalInset, + kSpotlightViewVerticalInset, kSpotlightViewHorizontalInset)); + [self.layoutGuideCenter referenceView:_spotlightView + underName:kTabSwitcherGuide]; + } + return button; }
diff --git a/ios/chrome/browser/assistant/aim/coordinator/assistant_aim_coordinator.h b/ios/chrome/browser/assistant/aim/coordinator/assistant_aim_coordinator.h deleted file mode 100644 index c56071b8..0000000 --- a/ios/chrome/browser/assistant/aim/coordinator/assistant_aim_coordinator.h +++ /dev/null
@@ -1,15 +0,0 @@ -// Copyright 2025 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef IOS_CHROME_BROWSER_ASSISTANT_AIM_COORDINATOR_ASSISTANT_AIM_COORDINATOR_H_ -#define IOS_CHROME_BROWSER_ASSISTANT_AIM_COORDINATOR_ASSISTANT_AIM_COORDINATOR_H_ - -#import "ios/chrome/browser/shared/coordinator/chrome_coordinator/chrome_coordinator.h" - -// Coordinator for the "AI Mode" flow. -@interface AssistantAIMCoordinator : ChromeCoordinator - -@end - -#endif // IOS_CHROME_BROWSER_ASSISTANT_AIM_COORDINATOR_ASSISTANT_AIM_COORDINATOR_H_
diff --git a/ios/chrome/browser/assistant/aim/ui/assistant_aim_view_controller.h b/ios/chrome/browser/assistant/aim/ui/assistant_aim_view_controller.h deleted file mode 100644 index fbd92bd7..0000000 --- a/ios/chrome/browser/assistant/aim/ui/assistant_aim_view_controller.h +++ /dev/null
@@ -1,14 +0,0 @@ -// Copyright 2025 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef IOS_CHROME_BROWSER_ASSISTANT_AIM_UI_ASSISTANT_AIM_VIEW_CONTROLLER_H_ -#define IOS_CHROME_BROWSER_ASSISTANT_AIM_UI_ASSISTANT_AIM_VIEW_CONTROLLER_H_ - -#import <UIKit/UIKit.h> - -@interface AssistantAIMViewController : UIViewController - -@end - -#endif // IOS_CHROME_BROWSER_ASSISTANT_AIM_UI_ASSISTANT_AIM_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/assistant/ui/tests/BUILD.gn b/ios/chrome/browser/assistant/ui/tests/BUILD.gn index 9661301..f147ff2e 100644 --- a/ios/chrome/browser/assistant/ui/tests/BUILD.gn +++ b/ios/chrome/browser/assistant/ui/tests/BUILD.gn
@@ -11,7 +11,6 @@ deps = [ "//base", "//base/test:test_support", - "//ios/chrome/browser/assistant/aim/ui", "//ios/chrome/browser/assistant/ui", "//testing/gtest", "//third_party/ocmock",
diff --git a/ios/chrome/browser/browser_content/ui_bundled/browser_content_view_controller.mm b/ios/chrome/browser/browser_content/ui_bundled/browser_content_view_controller.mm index 1d3d9fc..5745202 100644 --- a/ios/chrome/browser/browser_content/ui_bundled/browser_content_view_controller.mm +++ b/ios/chrome/browser/browser_content/ui_bundled/browser_content_view_controller.mm
@@ -34,8 +34,10 @@ - (void)viewDidLoad { [super viewDidLoad]; - self.view.autoresizingMask = - UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + if (!IsChromeNextIaEnabled()) { + self.view.autoresizingMask = + UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + } } - (void)viewDidLayoutSubviews {
diff --git a/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm b/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm index 327f061..de3a6a7 100644 --- a/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm +++ b/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm
@@ -331,6 +331,7 @@ #import "ios/chrome/browser/tips_manager/model/tips_manager_ios_factory.h" #import "ios/chrome/browser/tips_notifications/coordinator/enhanced_safe_browsing_promo_coordinator.h" #import "ios/chrome/browser/tips_notifications/coordinator/lens_promo_coordinator.h" +#import "ios/chrome/browser/tips_notifications/coordinator/price_tracking_promo_coordinator.h" #import "ios/chrome/browser/tips_notifications/coordinator/search_what_you_see_promo_coordinator.h" #import "ios/chrome/browser/toolbar/coordinator/toolbar_coordinator.h" #import "ios/chrome/browser/toolbar/legacy/ui_bundled/accessory/toolbar_accessory_presenter.h" @@ -751,6 +752,7 @@ QuickDeleteCoordinator* _quickDeleteCoordinator; LensPromoCoordinator* _lensPromoCoordinator; EnhancedSafeBrowsingPromoCoordinator* _enhancedSafeBrowsingPromoCoordinator; + PriceTrackingPromoCoordinator* _priceTrackingPromoCoordinator; AutoDeletionCoordinator* _autoDeletionCoordinator; TrustedVaultReauthenticationCoordinator* _trustedVaultReauthenticationCoordinator; @@ -1790,6 +1792,7 @@ [self dismissEditAddressBottomSheet]; [self dismissLensPromo]; [self dismissEnhancedSafeBrowsingPromo]; + [self dismissPriceTrackingPromo]; [self dismissAutoDeletionActionSheet]; [self hideGoogleOne]; [self stopTrustedVaultReauthentication]; @@ -2689,6 +2692,19 @@ _searchWhatYouSeePromoCoordinator = nil; } +- (void)showPriceTrackingPromo { + [_priceTrackingPromoCoordinator stop]; + _priceTrackingPromoCoordinator = [[PriceTrackingPromoCoordinator alloc] + initWithBaseViewController:self.viewController + browser:self.browser]; + [_priceTrackingPromoCoordinator start]; +} + +- (void)dismissPriceTrackingPromo { + [_priceTrackingPromoCoordinator stop]; + _priceTrackingPromoCoordinator = nil; +} + - (void)showNotificationsOptInFromAccessPoint: (NotificationOptInAccessPoint)accessPoint baseViewController: @@ -2894,6 +2910,7 @@ [self dismissEnhancedSafeBrowsingPromo]; [self dismissAutoDeletionActionSheet]; [self dismissSearchWhatYouSeePromo]; + [self dismissPriceTrackingPromo]; [self dismissNotificationsOptIn]; [self hideWelcomeBackPromo];
diff --git a/ios/chrome/browser/browser_view/ui_bundled/browser_view_controller.mm b/ios/chrome/browser/browser_view/ui_bundled/browser_view_controller.mm index 6533095..e83592a 100644 --- a/ios/chrome/browser/browser_view/ui_bundled/browser_view_controller.mm +++ b/ios/chrome/browser/browser_view/ui_bundled/browser_view_controller.mm
@@ -249,6 +249,8 @@ __weak id<BrowserCoordinatorCommands> _browserCoordinatorHandler; __weak id<ToolbarCommands> _toolbarHandler; + + NSArray<NSLayoutConstraint*>* _NTPConstraints; } // Activates/deactivates the object. This will enable/disable the ability for @@ -859,7 +861,13 @@ self.view.autoresizingMask = initialViewAutoresizing; [self addChildViewController:self.browserContentViewController]; - [self.view addSubview:self.contentArea]; + if (IsChromeNextIaEnabled()) { + self.contentArea.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:self.contentArea]; + AddSameConstraints(self.view, self.contentArea); + } else { + [self.view addSubview:self.contentArea]; + } [self.browserContentViewController didMoveToParentViewController:self]; if (!IsComposeboxIpadEnabled()) { [self.view addSubview:self.typingShield]; @@ -923,10 +931,12 @@ [self updateToolbarState]; } - if (self.ntpCoordinator.isNTPActiveForCurrentWebState && - self.webUsageEnabled) { - self.ntpCoordinator.viewController.view.frame = - [self ntpFrameForCurrentWebState]; + if (!IsChromeNextIaEnabled()) { + if (self.ntpCoordinator.isNTPActiveForCurrentWebState && + self.webUsageEnabled) { + self.ntpCoordinator.viewController.view.frame = + [self ntpFrameForCurrentWebState]; + } } } @@ -1376,8 +1386,22 @@ self.currentWebState->GetNavigationManager()->LoadIfNecessary(); self.browserContentViewController.contentView = nil; self.browserContentViewController.contentViewController = viewController; + if (IsChromeNextIaEnabled()) { + viewController.view.translatesAutoresizingMaskIntoConstraints = NO; + [self updateNTPConstraints]; + } [NTPCoordinator constrainNamedGuideForFeedIPH]; } else { + if (IsChromeNextIaEnabled()) { + if (ios::provider::IsFullscreenSmoothScrollingSupported()) { + view.translatesAutoresizingMaskIntoConstraints = NO; + AddSameConstraints(self.browserContentViewController.view, view); + } else { + // TODO(crbug.com/483998779): Handle the rotation of the web content + // in a better way. + view.autoresizingMask = UIViewAutoresizingFlexibleWidth; + } + } self.browserContentViewController.contentView = view; } // Resize horizontal viewport if Smooth Scrolling is on. @@ -1460,6 +1484,31 @@ [_browserCoordinatorHandler hideComposebox]; } +// Updates the constraints for the NTP view. +- (void)updateNTPConstraints { + CHECK(IsChromeNextIaEnabled()); + [NSLayoutConstraint deactivateConstraints:_NTPConstraints]; + DCHECK(self.ntpCoordinator.isNTPActiveForCurrentWebState); + UIViewController* NTPViewController = self.ntpCoordinator.viewController; + NSLayoutYAxisAnchor* topAnchor = + (CanShowTabStrip(self) || !IsSplitToolbarMode(self) || _isOffTheRecord) + ? self.self.toolbarCoordinator.primaryToolbarViewController.view + .bottomAnchor + : self.view.topAnchor; + _NTPConstraints = @[ + [NTPViewController.view.topAnchor constraintEqualToAnchor:topAnchor], + [NTPViewController.view.bottomAnchor + constraintEqualToAnchor:self.toolbarCoordinator + .secondaryToolbarViewController.view + .topAnchor], + [NTPViewController.view.leadingAnchor + constraintEqualToAnchor:self.view.leadingAnchor], + [NTPViewController.view.trailingAnchor + constraintEqualToAnchor:self.view.trailingAnchor], + ]; + [NSLayoutConstraint activateConstraints:_NTPConstraints]; +} + // Returns the appropriate frame for the NTP. - (CGRect)ntpFrameForCurrentWebState { DCHECK(self.ntpCoordinator.isNTPActiveForCurrentWebState); @@ -1543,6 +1592,12 @@ return; } + if (IsChromeNextIaEnabled() && + self.ntpCoordinator.isNTPActiveForCurrentWebState && + self.webUsageEnabled) { + [self updateNTPConstraints]; + } + self.fullscreenController->BrowserTraitCollectionChangedBegin(); if (self.currentWebState) {
diff --git a/ios/chrome/browser/cobrowse/DEPS b/ios/chrome/browser/cobrowse/DEPS new file mode 100644 index 0000000..26acaa56 --- /dev/null +++ b/ios/chrome/browser/cobrowse/DEPS
@@ -0,0 +1,6 @@ +include_rules = [ + # keep-sorted start + "+ios/chrome/browser/assistant/coordinator", + "+ios/chrome/browser/assistant/ui", + # keep-sorted end +]
diff --git a/ios/chrome/browser/assistant/aim/coordinator/BUILD.gn b/ios/chrome/browser/cobrowse/coordinator/BUILD.gn similarity index 92% rename from ios/chrome/browser/assistant/aim/coordinator/BUILD.gn rename to ios/chrome/browser/cobrowse/coordinator/BUILD.gn index 2262113..94a0bea7 100644 --- a/ios/chrome/browser/assistant/aim/coordinator/BUILD.gn +++ b/ios/chrome/browser/cobrowse/coordinator/BUILD.gn
@@ -8,9 +8,9 @@ "assistant_aim_coordinator.mm", ] deps = [ - "//ios/chrome/browser/assistant/aim/ui", "//ios/chrome/browser/assistant/coordinator:commands", "//ios/chrome/browser/assistant/ui", + "//ios/chrome/browser/cobrowse/ui", "//ios/chrome/browser/shared/coordinator/chrome_coordinator", "//ios/chrome/browser/shared/model/browser", "//ios/chrome/browser/shared/public/commands",
diff --git a/ios/chrome/browser/cobrowse/coordinator/assistant_aim_coordinator.h b/ios/chrome/browser/cobrowse/coordinator/assistant_aim_coordinator.h new file mode 100644 index 0000000..1c64724d2 --- /dev/null +++ b/ios/chrome/browser/cobrowse/coordinator/assistant_aim_coordinator.h
@@ -0,0 +1,15 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IOS_CHROME_BROWSER_COBROWSE_COORDINATOR_ASSISTANT_AIM_COORDINATOR_H_ +#define IOS_CHROME_BROWSER_COBROWSE_COORDINATOR_ASSISTANT_AIM_COORDINATOR_H_ + +#import "ios/chrome/browser/shared/coordinator/chrome_coordinator/chrome_coordinator.h" + +// Coordinator for the "AI Mode" flow. +@interface AssistantAIMCoordinator : ChromeCoordinator + +@end + +#endif // IOS_CHROME_BROWSER_COBROWSE_COORDINATOR_ASSISTANT_AIM_COORDINATOR_H_
diff --git a/ios/chrome/browser/assistant/aim/coordinator/assistant_aim_coordinator.mm b/ios/chrome/browser/cobrowse/coordinator/assistant_aim_coordinator.mm similarity index 91% rename from ios/chrome/browser/assistant/aim/coordinator/assistant_aim_coordinator.mm rename to ios/chrome/browser/cobrowse/coordinator/assistant_aim_coordinator.mm index 9d2ae9c2..57672fc 100644 --- a/ios/chrome/browser/assistant/aim/coordinator/assistant_aim_coordinator.mm +++ b/ios/chrome/browser/cobrowse/coordinator/assistant_aim_coordinator.mm
@@ -2,12 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "ios/chrome/browser/assistant/aim/coordinator/assistant_aim_coordinator.h" +#import "ios/chrome/browser/cobrowse/coordinator/assistant_aim_coordinator.h" -#import "ios/chrome/browser/assistant/aim/ui/assistant_aim_view_controller.h" #import "ios/chrome/browser/assistant/coordinator/assistant_container_commands.h" #import "ios/chrome/browser/assistant/ui/assistant_container_delegate.h" #import "ios/chrome/browser/assistant/ui/assistant_container_view_controller.h" +#import "ios/chrome/browser/cobrowse/ui/assistant_aim_view_controller.h" #import "ios/chrome/browser/shared/model/browser/browser.h" #import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
diff --git a/ios/chrome/browser/assistant/aim/ui/BUILD.gn b/ios/chrome/browser/cobrowse/ui/BUILD.gn similarity index 100% rename from ios/chrome/browser/assistant/aim/ui/BUILD.gn rename to ios/chrome/browser/cobrowse/ui/BUILD.gn
diff --git a/ios/chrome/browser/cobrowse/ui/assistant_aim_view_controller.h b/ios/chrome/browser/cobrowse/ui/assistant_aim_view_controller.h new file mode 100644 index 0000000..671f4ec --- /dev/null +++ b/ios/chrome/browser/cobrowse/ui/assistant_aim_view_controller.h
@@ -0,0 +1,14 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IOS_CHROME_BROWSER_COBROWSE_UI_ASSISTANT_AIM_VIEW_CONTROLLER_H_ +#define IOS_CHROME_BROWSER_COBROWSE_UI_ASSISTANT_AIM_VIEW_CONTROLLER_H_ + +#import <UIKit/UIKit.h> + +@interface AssistantAIMViewController : UIViewController + +@end + +#endif // IOS_CHROME_BROWSER_COBROWSE_UI_ASSISTANT_AIM_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/assistant/aim/ui/assistant_aim_view_controller.mm b/ios/chrome/browser/cobrowse/ui/assistant_aim_view_controller.mm similarity index 96% rename from ios/chrome/browser/assistant/aim/ui/assistant_aim_view_controller.mm rename to ios/chrome/browser/cobrowse/ui/assistant_aim_view_controller.mm index 1eaef6b..ef52964 100644 --- a/ios/chrome/browser/assistant/aim/ui/assistant_aim_view_controller.mm +++ b/ios/chrome/browser/cobrowse/ui/assistant_aim_view_controller.mm
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "ios/chrome/browser/assistant/aim/ui/assistant_aim_view_controller.h" +#import "ios/chrome/browser/cobrowse/ui/assistant_aim_view_controller.h" #import "ios/chrome/common/ui/colors/semantic_color_names.h"
diff --git a/ios/chrome/browser/content_suggestions/magic_stack/coordinator/magic_stack_ranking_model.mm b/ios/chrome/browser/content_suggestions/magic_stack/coordinator/magic_stack_ranking_model.mm index 77238876..fe606d1 100644 --- a/ios/chrome/browser/content_suggestions/magic_stack/coordinator/magic_stack_ranking_model.mm +++ b/ios/chrome/browser/content_suggestions/magic_stack/coordinator/magic_stack_ranking_model.mm
@@ -29,7 +29,7 @@ #import "components/segmentation_platform/embedder/home_modules/autofill_passwords_ephemeral_module.h" #import "components/segmentation_platform/embedder/home_modules/constants.h" #import "components/segmentation_platform/embedder/home_modules/enhanced_safe_browsing_ephemeral_module.h" -#import "components/segmentation_platform/embedder/home_modules/home_modules_card_registry.h" +#import "components/segmentation_platform/embedder/home_modules/home_modules_card_registry_ios.h" #import "components/segmentation_platform/embedder/home_modules/lens_ephemeral_module.h" #import "components/segmentation_platform/embedder/home_modules/save_passwords_ephemeral_module.h" #import "components/segmentation_platform/embedder/home_modules/send_tab_notification_promo.h" @@ -690,7 +690,7 @@ card = _priceTrackingPromoMediator.priceTrackingPromoConfigToShow; break; } - } else if (segmentation_platform::home_modules::HomeModulesCardRegistry:: + } else if (segmentation_platform::home_modules::HomeModulesCardRegistryIOS:: IsEphemeralTipsModuleLabel(label) && areTipsCardsEnabled) { TipIdentifier tipIdentifier = TipIdentifierForOutputLabel(label);
diff --git a/ios/chrome/browser/cross_platform_promos/model/cross_platform_promos_notification_client.mm b/ios/chrome/browser/cross_platform_promos/model/cross_platform_promos_notification_client.mm index 88e29e47..ed4eac59 100644 --- a/ios/chrome/browser/cross_platform_promos/model/cross_platform_promos_notification_client.mm +++ b/ios/chrome/browser/cross_platform_promos/model/cross_platform_promos_notification_client.mm
@@ -150,7 +150,7 @@ // TODO (crbug.com/479493988): Create the Tab Groups iOS promo. break; case desktop_to_mobile_promos::PromoType::kPriceTracking: - // TODO (crbug.com/479493988): Create the Price Tracking iOS promo. + cross_platform_service->ShowPriceTrackingPromo(browser); break; case desktop_to_mobile_promos::PromoType::kAddress: case desktop_to_mobile_promos::PromoType::kPayment:
diff --git a/ios/chrome/browser/cross_platform_promos/model/cross_platform_promos_service.mm b/ios/chrome/browser/cross_platform_promos/model/cross_platform_promos_service.mm index 0414afa5..0d68bede 100644 --- a/ios/chrome/browser/cross_platform_promos/model/cross_platform_promos_service.mm +++ b/ios/chrome/browser/cross_platform_promos/model/cross_platform_promos_service.mm
@@ -138,6 +138,9 @@ case desktop_to_mobile_promos::PromoType::kPassword: ShowCPEPromo(browser); break; + case desktop_to_mobile_promos::PromoType::kPriceTracking: + ShowPriceTrackingPromo(browser); + break; default: // If the promo type is unknown, clear the pref to avoid re-triggering. profile_->GetPrefs()->ClearPref(prefs::kIOSPromoReminder);
diff --git a/ios/chrome/browser/first_run/guided_tour/coordinator/BUILD.gn b/ios/chrome/browser/first_run/guided_tour/coordinator/BUILD.gn index ccdd18bd..7420fef 100644 --- a/ios/chrome/browser/first_run/guided_tour/coordinator/BUILD.gn +++ b/ios/chrome/browser/first_run/guided_tour/coordinator/BUILD.gn
@@ -17,6 +17,7 @@ "//ios/chrome/browser/shared/coordinator/chrome_coordinator", "//ios/chrome/browser/shared/coordinator/layout_guide", "//ios/chrome/browser/shared/public/commands", + "//ios/chrome/browser/shared/public/features", "//ios/chrome/browser/shared/ui/symbols", "//ios/chrome/browser/shared/ui/util", "//ios/chrome/browser/shared/ui/util:util_swift",
diff --git a/ios/chrome/browser/first_run/guided_tour/coordinator/guided_tour_coordinator.mm b/ios/chrome/browser/first_run/guided_tour/coordinator/guided_tour_coordinator.mm index 404b951..b568e60 100644 --- a/ios/chrome/browser/first_run/guided_tour/coordinator/guided_tour_coordinator.mm +++ b/ios/chrome/browser/first_run/guided_tour/coordinator/guided_tour_coordinator.mm
@@ -7,6 +7,7 @@ #import "base/notreached.h" #import "ios/chrome/browser/bubble/ui_bundled/guided_tour/guided_tour_bubble_view_controller_presenter.h" #import "ios/chrome/browser/shared/coordinator/layout_guide/layout_guide_util.h" +#import "ios/chrome/browser/shared/public/features/features.h" #import "ios/chrome/browser/shared/ui/util/layout_guide_names.h" #import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h" #import "ios/chrome/browser/shared/ui/util/util_swift.h" @@ -17,6 +18,7 @@ namespace { // Corner radius of the spotlight cutouts. const CGFloat kNTPTabGridButtonSpotlightCornerRadius = 7.0f; +const CGFloat kNTPAppBarTabGridButtonSpotlightCornerRadius = 14.0f; const CGFloat kNTPTabGridPageControlCornerRadius = 13.0f; } // namespace @@ -81,6 +83,12 @@ // Returns the view to which the bubble view will be anchored. - (UIView*)anchorView { if (_step == GuidedTourStep::kNTP) { + if (IsChromeNextIaEnabled()) { + UIButton* tabSwitcherButton = + static_cast<UIButton*>([LayoutGuideCenterForBrowser(nil) + referencedViewUnderName:kTabSwitcherGuide]); + return tabSwitcherButton; + } LegacyToolbarButton* tabSwitcherButton = static_cast<LegacyToolbarButton*>( [LayoutGuideCenterForBrowser(self.browser) referencedViewUnderName:kTabSwitcherGuide]); @@ -158,7 +166,10 @@ // The corner radius of the spotlight cutout for this Bubble View. - (CGFloat)backgroundCutoutCornerRadius { - return _step == GuidedTourStep::kNTP ? kNTPTabGridButtonSpotlightCornerRadius + CGFloat NTPTabGridButtonSpotlightCornerRadius = + IsChromeNextIaEnabled() ? kNTPAppBarTabGridButtonSpotlightCornerRadius + : kNTPTabGridButtonSpotlightCornerRadius; + return _step == GuidedTourStep::kNTP ? NTPTabGridButtonSpotlightCornerRadius : kNTPTabGridPageControlCornerRadius; }
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm index aa3067fa..85b20c7 100644 --- a/ios/chrome/browser/flags/about_flags.mm +++ b/ios/chrome/browser/flags/about_flags.mm
@@ -1775,12 +1775,6 @@ flag_descriptions::kViewCertificateInformationDescription, flags_ui::kOsIos, FEATURE_VALUE_TYPE(page_info_certificate::kViewCertificateInformation)}, - {"page-visibility-page-content-annotations", - flag_descriptions::kPageVisibilityPageContentAnnotationsName, - flag_descriptions::kPageVisibilityPageContentAnnotationsDescription, - flags_ui::kOsIos, - FEATURE_VALUE_TYPE(page_content_annotations::features:: - kPageVisibilityPageContentAnnotations)}, {"cpe-passkey-prf-support", flag_descriptions::kCredentialProviderPasskeyPRFName, flag_descriptions::kCredentialProviderPasskeyPRFDescription, @@ -2752,6 +2746,9 @@ {"disable-u18-feedback-ios", flag_descriptions::kDisableU18FeedbackIosName, flag_descriptions::kDisableU18FeedbackIosDescription, flags_ui::kOsIos, FEATURE_VALUE_TYPE(kDisableU18FeedbackIos)}, + {"fullscreen-refactoring", flag_descriptions::kFullscreenRefactoringName, + flag_descriptions::kFullscreenRefactoringDescription, flags_ui::kOsIos, + FEATURE_VALUE_TYPE(kFullscreenRefactoring)}, }); bool SkipConditionalFeatureEntry(const flags_ui::FeatureEntry& entry) {
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc index 15d20de..f6a3346 100644 --- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc +++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -627,6 +627,10 @@ "When enabled, the startup sign-in promo is always displayed when starting " "Chrome."; +const char kFullscreenRefactoringName[] = "FullscreenRefactoring"; +const char kFullscreenRefactoringDescription[] = + "Enables the FullscreenRefactoring feature."; + const char kFullscreenScrollThresholdName[] = "Fullscreen Scroll Threshold"; const char kFullscreenScrollThresholdDescription[] = "When enabled, scrolling must exceed a small threshold before the web view " @@ -1295,12 +1299,6 @@ const char kPageContentAnnotationsRemotePageMetadataDescription[] = "Enables fetching of page load metadata to be persisted on-device."; -const char kPageVisibilityPageContentAnnotationsName[] = - "Page visibility content annotations"; -const char kPageVisibilityPageContentAnnotationsDescription[] = - "Enables annotating the page visibility model for each page load " - "on-device."; - const char kPasswordFormClientsideClassifierName[] = "Clientside password form classifier."; const char kPasswordFormClientsideClassifierDescription[] =
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h index 78345c9..1d939e12 100644 --- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h +++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -381,6 +381,9 @@ extern const char kForceStartupSigninPromoName[]; extern const char kForceStartupSigninPromoDescription[]; +extern const char kFullscreenRefactoringName[]; +extern const char kFullscreenRefactoringDescription[]; + extern const char kFullscreenScrollThresholdName[]; extern const char kFullscreenScrollThresholdDescription[]; @@ -792,9 +795,6 @@ extern const char kPageContentAnnotationsRemotePageMetadataName[]; extern const char kPageContentAnnotationsRemotePageMetadataDescription[]; -extern const char kPageVisibilityPageContentAnnotationsName[]; -extern const char kPageVisibilityPageContentAnnotationsDescription[]; - extern const char kPasswordFormClientsideClassifierName[]; extern const char kPasswordFormClientsideClassifierDescription[];
diff --git a/ios/chrome/browser/main/ui/browser_layout_view_controller.mm b/ios/chrome/browser/main/ui/browser_layout_view_controller.mm index addccde..a25ca22f 100644 --- a/ios/chrome/browser/main/ui/browser_layout_view_controller.mm +++ b/ios/chrome/browser/main/ui/browser_layout_view_controller.mm
@@ -48,6 +48,9 @@ - (void)viewDidLoad { [super viewDidLoad]; + if (IsChromeNextIaEnabled()) { + self.view.translatesAutoresizingMaskIntoConstraints = NO; + } // Register for trait changes that affect the tab strip visibility. NSArray<UITrait>* traits = TraitCollectionSetForTraits(@[ UITraitHorizontalSizeClass.class, UITraitVerticalSizeClass.class, @@ -96,13 +99,19 @@ // Add the new active view controller. [self addChildViewController:browserViewController]; - // If the BVC's view has a transform, then its frame isn't accurate. - // Instead, remove the transform, set the frame, then reapply the transform. - CGAffineTransform oldTransform = browserViewController.view.transform; - browserViewController.view.transform = CGAffineTransformIdentity; - browserViewController.view.frame = self.view.bounds; - browserViewController.view.transform = oldTransform; - [self.view insertSubview:browserViewController.view atIndex:0]; + if (IsChromeNextIaEnabled()) { + browserViewController.view.translatesAutoresizingMaskIntoConstraints = NO; + [self.view insertSubview:browserViewController.view atIndex:0]; + AddSameConstraints(self.view, browserViewController.view); + } else { + // If the BVC's view has a transform, then its frame isn't accurate. + // Instead, remove the transform, set the frame, then reapply the transform. + CGAffineTransform oldTransform = browserViewController.view.transform; + browserViewController.view.transform = CGAffineTransformIdentity; + browserViewController.view.frame = self.view.bounds; + browserViewController.view.transform = oldTransform; + [self.view insertSubview:browserViewController.view atIndex:0]; + } [browserViewController didMoveToParentViewController:self]; _browserViewController = browserViewController;
diff --git a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_view_controller.mm b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_view_controller.mm index 769b929..de6f716 100644 --- a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_view_controller.mm +++ b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_view_controller.mm
@@ -419,7 +419,7 @@ if (yOffsetBeforeRotation < 0) { weakSelf.collectionView.contentOffset = CGPointMake(0, yOffsetBeforeRotation - heightAboveFeedDifference); - [weakSelf updateNTPLayout]; + [weakSelf updateNTPLayoutForWidth:size.width]; } [weakSelf.view setNeedsLayout]; [weakSelf.view layoutIfNeeded]; @@ -606,17 +606,7 @@ } - (void)updateNTPLayout { - [self updateFeedInsetsForContentAbove]; - if (self.feedVisible) { - [self updateFeedInsetsForMinimumHeight]; - } - - // Reload data to ensure the Most Visited tiles and fake omnibox are correctly - // positioned, in particular during a rotation while a ViewController is - // presented in front of the NTP. - [self updateFakeOmniboxOnNewWidth:self.collectionView.bounds.size.width]; - // Ensure initial fake omnibox layout. - [self updateFakeOmniboxForScrollPosition]; + [self updateNTPLayoutForWidth:self.collectionView.bounds.size.width]; } - (void)updateHeightAboveFeed { @@ -1781,6 +1771,20 @@ return NO; } +// Lays out content above feed and adjusts content suggestions for the given +// `width`. +- (void)updateNTPLayoutForWidth:(CGFloat)width { + [self updateFeedInsetsForContentAbove]; + if (self.feedVisible) { + [self updateFeedInsetsForMinimumHeight]; + } + + // Reload data to ensure the Most Visited tiles and fake omnibox are correctly + // positioned, in particular during a rotation while a ViewController is + // presented in front of the NTP. + [self updateFakeOmniboxOnNewWidth:width]; +} + #pragma mark - Helpers - (CGFloat)minimumNTPHeight {
diff --git a/ios/chrome/browser/scene/coordinator/BUILD.gn b/ios/chrome/browser/scene/coordinator/BUILD.gn index b4e3017..625eecd 100644 --- a/ios/chrome/browser/scene/coordinator/BUILD.gn +++ b/ios/chrome/browser/scene/coordinator/BUILD.gn
@@ -20,7 +20,6 @@ "//ios/chrome/app/profile", "//ios/chrome/browser/ai_prototyping/coordinator", "//ios/chrome/browser/app_bar/coordinator", - "//ios/chrome/browser/assistant/aim/coordinator", "//ios/chrome/browser/assistant/coordinator", "//ios/chrome/browser/assistant/coordinator:commands", "//ios/chrome/browser/authentication/account_menu/coordinator", @@ -29,6 +28,7 @@ "//ios/chrome/browser/authentication/ui_bundled:continuation", "//ios/chrome/browser/authentication/ui_bundled/change_profile", "//ios/chrome/browser/authentication/ui_bundled/signin:signin_headers", + "//ios/chrome/browser/cobrowse/coordinator", "//ios/chrome/browser/default_browser/model:utils", "//ios/chrome/browser/feature_engagement/model", "//ios/chrome/browser/history/ui_bundled:coordinator", @@ -47,6 +47,7 @@ "//ios/chrome/browser/scene/ui", "//ios/chrome/browser/settings/ui_bundled:settings_root", "//ios/chrome/browser/settings/ui_bundled/password/password_checkup", + "//ios/chrome/browser/shared/coordinator/layout_guide", "//ios/chrome/browser/shared/coordinator/root_coordinator", "//ios/chrome/browser/shared/coordinator/scene:scene_state_header", "//ios/chrome/browser/shared/coordinator/scene/state",
diff --git a/ios/chrome/browser/scene/coordinator/DEPS b/ios/chrome/browser/scene/coordinator/DEPS index ce447cf9..fddb9b5 100644 --- a/ios/chrome/browser/scene/coordinator/DEPS +++ b/ios/chrome/browser/scene/coordinator/DEPS
@@ -2,10 +2,10 @@ # keep-sorted start "+ios/chrome/browser/ai_prototyping/coordinator", "+ios/chrome/browser/app_bar/coordinator", - "+ios/chrome/browser/assistant/aim/coordinator", "+ios/chrome/browser/assistant/coordinator", "+ios/chrome/browser/authentication/account_menu", "+ios/chrome/browser/authentication/ui_bundled", + "+ios/chrome/browser/cobrowse/coordinator", "+ios/chrome/browser/feature_engagement/model", "+ios/chrome/browser/history/ui_bundled", "+ios/chrome/browser/incognito_interstitial/ui_bundled",
diff --git a/ios/chrome/browser/scene/coordinator/scene_coordinator.mm b/ios/chrome/browser/scene/coordinator/scene_coordinator.mm index 22b1aee4..c39ff63 100644 --- a/ios/chrome/browser/scene/coordinator/scene_coordinator.mm +++ b/ios/chrome/browser/scene/coordinator/scene_coordinator.mm
@@ -27,7 +27,6 @@ #import "ios/chrome/app/profile/profile_state.h" #import "ios/chrome/browser/ai_prototyping/coordinator/ai_prototyping_coordinator.h" #import "ios/chrome/browser/app_bar/coordinator/app_bar_coordinator.h" -#import "ios/chrome/browser/assistant/aim/coordinator/assistant_aim_coordinator.h" #import "ios/chrome/browser/assistant/coordinator/assistant_container_coordinator.h" #import "ios/chrome/browser/authentication/account_menu/coordinator/account_menu_coordinator.h" #import "ios/chrome/browser/authentication/account_menu/coordinator/account_menu_coordinator_delegate.h" @@ -37,6 +36,7 @@ #import "ios/chrome/browser/authentication/ui_bundled/signin/signin_coordinator.h" #import "ios/chrome/browser/authentication/ui_bundled/signin/signin_utils.h" #import "ios/chrome/browser/authentication/ui_bundled/signin_notification_infobar_delegate.h" +#import "ios/chrome/browser/cobrowse/coordinator/assistant_aim_coordinator.h" #import "ios/chrome/browser/default_browser/model/utils.h" #import "ios/chrome/browser/feature_engagement/model/tracker_factory.h" #import "ios/chrome/browser/history/ui_bundled/history_coordinator_factory.h" @@ -56,6 +56,7 @@ #import "ios/chrome/browser/scene/ui/scene_view_controller.h" #import "ios/chrome/browser/settings/ui_bundled/password/password_checkup/password_checkup_coordinator.h" #import "ios/chrome/browser/settings/ui_bundled/settings_navigation_controller.h" +#import "ios/chrome/browser/shared/coordinator/layout_guide/layout_guide_util.h" #import "ios/chrome/browser/shared/coordinator/scene/scene_state.h" #import "ios/chrome/browser/shared/coordinator/scene/state/incognito_state.h" #import "ios/chrome/browser/shared/model/application_context/application_context.h" @@ -234,6 +235,7 @@ [_tabGridCoordinator start]; if (IsUseSceneViewControllerEnabled()) { _viewController = [[SceneViewController alloc] init]; + _viewController.layoutGuideCenter = LayoutGuideCenterForBrowser(nil); UIViewController* tabGridViewController = _tabGridCoordinator.viewController; [_viewController addChildViewController:tabGridViewController];
diff --git a/ios/chrome/browser/scene/ui/BUILD.gn b/ios/chrome/browser/scene/ui/BUILD.gn index ec95b12..bec2cc5 100644 --- a/ios/chrome/browser/scene/ui/BUILD.gn +++ b/ios/chrome/browser/scene/ui/BUILD.gn
@@ -4,12 +4,19 @@ source_set("ui") { sources = [ + "scene_view.h", + "scene_view.mm", "scene_view_controller.h", "scene_view_controller.mm", + "scene_view_delegate.h", ] deps = [ "//base", + "//ios/chrome/browser/app_bar/ui", + "//ios/chrome/browser/shared/public/features", + "//ios/chrome/browser/shared/ui/util", + "//ios/chrome/browser/shared/ui/util:util_swift", "//ios/chrome/common/ui/util", ] }
diff --git a/ios/chrome/browser/scene/ui/DEPS b/ios/chrome/browser/scene/ui/DEPS new file mode 100644 index 0000000..efc7c4b --- /dev/null +++ b/ios/chrome/browser/scene/ui/DEPS
@@ -0,0 +1,5 @@ +include_rules = [ + # keep-sorted start + "+ios/chrome/browser/app_bar/ui", + # keep-sorted end +]
diff --git a/ios/chrome/browser/scene/ui/scene_view.h b/ios/chrome/browser/scene/ui/scene_view.h new file mode 100644 index 0000000..7476d9bb --- /dev/null +++ b/ios/chrome/browser/scene/ui/scene_view.h
@@ -0,0 +1,20 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IOS_CHROME_BROWSER_SCENE_UI_SCENE_VIEW_H_ +#define IOS_CHROME_BROWSER_SCENE_UI_SCENE_VIEW_H_ + +#import <UIKit/UIKit.h> + +@protocol SceneViewDelegate; + +// The view at the root of the app. +@interface SceneView : UIView + +// The delegate for the scene view. +@property(nonatomic, weak) id<SceneViewDelegate> delegate; + +@end + +#endif // IOS_CHROME_BROWSER_SCENE_UI_SCENE_VIEW_H_
diff --git a/ios/chrome/browser/scene/ui/scene_view.mm b/ios/chrome/browser/scene/ui/scene_view.mm new file mode 100644 index 0000000..f1b7b4c --- /dev/null +++ b/ios/chrome/browser/scene/ui/scene_view.mm
@@ -0,0 +1,16 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "ios/chrome/browser/scene/ui/scene_view.h" + +#import "ios/chrome/browser/scene/ui/scene_view_delegate.h" + +@implementation SceneView + +- (void)didMoveToWindow { + [super didMoveToWindow]; + [self.delegate sceneViewDidMoveToWindow:self]; +} + +@end
diff --git a/ios/chrome/browser/scene/ui/scene_view_controller.h b/ios/chrome/browser/scene/ui/scene_view_controller.h index c1e595a4d..1758d2133 100644 --- a/ios/chrome/browser/scene/ui/scene_view_controller.h +++ b/ios/chrome/browser/scene/ui/scene_view_controller.h
@@ -7,12 +7,16 @@ #import <UIKit/UIKit.h> +@class LayoutGuideCenter; + // A view controller that can act as the `rootViewController` for a scene's // window. @interface SceneViewController : UIViewController // A view to contain the TabGrid and BVC. @property(nonatomic, readonly) UIView* appContainer; +// This view controller's LayoutGuideCenter. +@property(nonatomic, strong) LayoutGuideCenter* layoutGuideCenter; // Sets the app bar. - (void)setAppBar:(UIViewController*)appBar;
diff --git a/ios/chrome/browser/scene/ui/scene_view_controller.mm b/ios/chrome/browser/scene/ui/scene_view_controller.mm index f9f421d..5319176 100644 --- a/ios/chrome/browser/scene/ui/scene_view_controller.mm +++ b/ios/chrome/browser/scene/ui/scene_view_controller.mm
@@ -5,48 +5,156 @@ #import "ios/chrome/browser/scene/ui/scene_view_controller.h" #import "base/check.h" +#import "ios/chrome/browser/app_bar/ui/app_bar_constants.h" +#import "ios/chrome/browser/scene/ui/scene_view.h" +#import "ios/chrome/browser/scene/ui/scene_view_delegate.h" +#import "ios/chrome/browser/shared/public/features/features.h" +#import "ios/chrome/browser/shared/ui/util/layout_guide_names.h" +#import "ios/chrome/browser/shared/ui/util/util_swift.h" #import "ios/chrome/common/ui/util/constraints_ui_util.h" +@interface SceneViewController () <SceneViewDelegate> +@end + @implementation SceneViewController { // The app bar. UIViewController* _appBar; -} - -- (instancetype)init { - self = [super init]; - if (self) { - _appContainer = [[UIView alloc] init]; - } - return self; + // The view containing the app (the part outside the app bar). + UIView* _appContentView; + NSArray<NSLayoutConstraint*>* _portraitConstraints; + NSArray<NSLayoutConstraint*>* _landscapeLeftConstraints; + NSArray<NSLayoutConstraint*>* _landscapeRightConstraints; } #pragma mark - UIViewController +- (void)loadView { + SceneView* view = [[SceneView alloc] init]; + view.delegate = self; + self.view = view; +} + - (void)viewDidLoad { + CHECK(self.layoutGuideCenter); [super viewDidLoad]; - UIView* appContainer = self.appContainer; UIView* view = self.view; - appContainer.translatesAutoresizingMaskIntoConstraints = NO; - [view addSubview:appContainer]; - appContainer.frame = view.bounds; - AddSameConstraints(appContainer, view); + _appContentView = [[UIView alloc] init]; + _appContentView.translatesAutoresizingMaskIntoConstraints = NO; + [view addSubview:_appContentView]; + _appContentView.frame = view.bounds; + [self.layoutGuideCenter referenceView:_appContentView + underName:kAppContentGuide]; + if (!IsChromeNextIaEnabled()) { + AddSameConstraints(_appContentView, view); + } +} + +- (void)viewWillTransitionToSize:(CGSize)size + withTransitionCoordinator: + (id<UIViewControllerTransitionCoordinator>)coordinator { + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + + __weak __typeof(self) weakSelf = self; + [coordinator + animateAlongsideTransition:^( + id<UIViewControllerTransitionCoordinatorContext> context) { + [weakSelf updateLayout]; + } + completion:nil]; } #pragma mark - Public +- (UIView*)appContainer { + [self loadViewIfNeeded]; + return _appContentView; +} + - (void)setAppBar:(UIViewController*)appBar { CHECK(!_appBar); + [self loadViewIfNeeded]; _appBar = appBar; UIView* appBarView = appBar.view; appBarView.translatesAutoresizingMaskIntoConstraints = NO; - UIView* appBarContainer = self.view; + UIView* view = self.view; [self addChildViewController:appBar]; - [appBarContainer addSubview:appBarView]; + [view addSubview:appBarView]; - AddSameCenterConstraints(appBarContainer, appBarView); + AddSameCenterConstraints(view, appBarView); [appBar didMoveToParentViewController:self]; + + UIView* appBarRealView = + [self.layoutGuideCenter referencedViewUnderName:kAppBarGuide]; + + _portraitConstraints = @[ + [_appContentView.topAnchor constraintEqualToAnchor:view.topAnchor], + [_appContentView.leadingAnchor constraintEqualToAnchor:view.leadingAnchor], + [_appContentView.trailingAnchor + constraintEqualToAnchor:view.trailingAnchor], + [_appContentView.bottomAnchor + constraintEqualToAnchor:appBarRealView.topAnchor], + ]; + _landscapeLeftConstraints = @[ + [_appContentView.topAnchor constraintEqualToAnchor:view.topAnchor], + [_appContentView.leadingAnchor constraintEqualToAnchor:view.leadingAnchor + constant:kAppBarHeight], + [_appContentView.trailingAnchor + constraintEqualToAnchor:view.trailingAnchor], + [_appContentView.bottomAnchor constraintEqualToAnchor:view.bottomAnchor], + ]; + _landscapeRightConstraints = @[ + [_appContentView.topAnchor constraintEqualToAnchor:view.topAnchor], + [_appContentView.leadingAnchor constraintEqualToAnchor:view.leadingAnchor], + [_appContentView.trailingAnchor constraintEqualToAnchor:view.trailingAnchor + constant:-kAppBarHeight], + [_appContentView.bottomAnchor constraintEqualToAnchor:view.bottomAnchor], + ]; + + [self updateLayout]; +} + +#pragma mark - SceneViewDelegate + +- (void)sceneViewDidMoveToWindow:(SceneView*)sceneView { + [self updateLayout]; +} + +#pragma mark - Private + +// Updates the layout to adapt to screen changes. +- (void)updateLayout { + UIWindowScene* windowScene = self.view.window.windowScene; + if (!windowScene || !_appBar) { + return; + } + + UIInterfaceOrientation orientation = + windowScene.effectiveGeometry.interfaceOrientation; + + [NSLayoutConstraint deactivateConstraints:_portraitConstraints]; + [NSLayoutConstraint deactivateConstraints:_landscapeLeftConstraints]; + [NSLayoutConstraint deactivateConstraints:_landscapeRightConstraints]; + + switch (orientation) { + case UIInterfaceOrientationLandscapeLeft: + [NSLayoutConstraint activateConstraints:_landscapeLeftConstraints]; + break; + + case UIInterfaceOrientationLandscapeRight: + [NSLayoutConstraint activateConstraints:_landscapeRightConstraints]; + break; + + case UIInterfaceOrientationPortrait: + [NSLayoutConstraint activateConstraints:_portraitConstraints]; + break; + + default: + break; + } + + [self.view layoutIfNeeded]; } @end
diff --git a/ios/chrome/browser/scene/ui/scene_view_delegate.h b/ios/chrome/browser/scene/ui/scene_view_delegate.h new file mode 100644 index 0000000..57c984c --- /dev/null +++ b/ios/chrome/browser/scene/ui/scene_view_delegate.h
@@ -0,0 +1,16 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IOS_CHROME_BROWSER_SCENE_UI_SCENE_VIEW_DELEGATE_H_ +#define IOS_CHROME_BROWSER_SCENE_UI_SCENE_VIEW_DELEGATE_H_ + +// The delegate for the scene view. +@protocol SceneViewDelegate <NSObject> + +// Called when the scene view moves to a window. +- (void)sceneViewDidMoveToWindow:(SceneView*)sceneView; + +@end + +#endif // IOS_CHROME_BROWSER_SCENE_UI_SCENE_VIEW_DELEGATE_H_
diff --git a/ios/chrome/browser/segmentation_platform/model/segmentation_platform_service_factory.mm b/ios/chrome/browser/segmentation_platform/model/segmentation_platform_service_factory.mm index 5a09043..a999d27 100644 --- a/ios/chrome/browser/segmentation_platform/model/segmentation_platform_service_factory.mm +++ b/ios/chrome/browser/segmentation_platform/model/segmentation_platform_service_factory.mm
@@ -135,7 +135,7 @@ params->profile_prefs = profile->GetPrefs(); auto home_modules_card_registry = - std::make_unique<home_modules::HomeModulesCardRegistry>( + home_modules::HomeModulesCardRegistry::Create( profile->GetPrefs(), GetApplicationContext()->GetLocalState()); params->configs = GetSegmentationPlatformConfig(home_modules_card_registry.get());
diff --git a/ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h b/ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h index 45b531241..8666971 100644 --- a/ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h +++ b/ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h
@@ -151,6 +151,10 @@ - (void)showSearchWhatYouSeePromo; - (void)dismissSearchWhatYouSeePromo; +// Shows and dismisses the Price Tracking promo. +- (void)showPriceTrackingPromo; +- (void)dismissPriceTrackingPromo; + // Shows the notifications opt-in view from `accessPoint`. - (void)showNotificationsOptInFromAccessPoint: (NotificationOptInAccessPoint)accessPoint
diff --git a/ios/chrome/browser/shared/public/features/features.h b/ios/chrome/browser/shared/public/features/features.h index 8f0eef9..3c74327 100644 --- a/ios/chrome/browser/shared/public/features/features.h +++ b/ios/chrome/browser/shared/public/features/features.h
@@ -964,4 +964,10 @@ // Returns true if the DisableU18FeedbackIos feature is enabled. bool IsDisableU18FeedbackIosEnabled(); +// Enables the FullscreenRefactoring feature. +BASE_DECLARE_FEATURE(kFullscreenRefactoring); + +// Returns true if the FullscreenRefactoring feature is enabled. +bool IsFullscreenRefactoringEnabled(); + #endif // IOS_CHROME_BROWSER_SHARED_PUBLIC_FEATURES_FEATURES_H_
diff --git a/ios/chrome/browser/shared/public/features/features.mm b/ios/chrome/browser/shared/public/features/features.mm index 6b5aba5a..45c5cdb1 100644 --- a/ios/chrome/browser/shared/public/features/features.mm +++ b/ios/chrome/browser/shared/public/features/features.mm
@@ -1150,3 +1150,9 @@ bool IsDisableU18FeedbackIosEnabled() { return base::FeatureList::IsEnabled(kDisableU18FeedbackIos); } + +BASE_FEATURE(kFullscreenRefactoring, base::FEATURE_DISABLED_BY_DEFAULT); + +bool IsFullscreenRefactoringEnabled() { + return base::FeatureList::IsEnabled(kFullscreenRefactoring); +}
diff --git a/ios/chrome/browser/shared/ui/util/layout_guide_names.h b/ios/chrome/browser/shared/ui/util/layout_guide_names.h index ee16d27..6e4f5f2 100644 --- a/ios/chrome/browser/shared/ui/util/layout_guide_names.h +++ b/ios/chrome/browser/shared/ui/util/layout_guide_names.h
@@ -13,6 +13,9 @@ // create a constant for it below. Because these constants are in the global // namespace, all guide names should end in 'Guide', for clarity. +// A guide that is constrained to match the frame of the part of the app outside +// of the AppBar. The toolbars are inside the frame. +extern GuideName* const kAppContentGuide; // A guide that is constrained to match the frame of the tab's content area. extern GuideName* const kContentAreaGuide; // A guide that is constrained to match the frame of the primary toolbar. This
diff --git a/ios/chrome/browser/shared/ui/util/layout_guide_names.mm b/ios/chrome/browser/shared/ui/util/layout_guide_names.mm index 8e7213eb..9af51930 100644 --- a/ios/chrome/browser/shared/ui/util/layout_guide_names.mm +++ b/ios/chrome/browser/shared/ui/util/layout_guide_names.mm
@@ -4,6 +4,7 @@ #import "ios/chrome/browser/shared/ui/util/layout_guide_names.h" +GuideName* const kAppContentGuide = @"AppContentGuide"; GuideName* const kContentAreaGuide = @"ContentAreaGuide"; GuideName* const kPrimaryToolbarGuide = @"kPrimaryToolbarGuide"; GuideName* const kSecondaryToolbarGuide = @"kSecondaryToolbarGuide";
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_grid_coordinator.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_grid_coordinator.mm index b45bdbf6..66ab472 100644 --- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_grid_coordinator.mm +++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_grid_coordinator.mm
@@ -691,8 +691,6 @@ animationEnabled:(BOOL)animationEnabled isIncognito:(BOOL)isIncognito completion:(ProceduralBlock)completionHandler { - TabGridTransitionType transitionType = [self determineTabGridTransitionType]; - Browser* browser = isIncognito ? self.incognitoBrowser : self.regularBrowser; if (!browser) { // The browser can be nil here, for example when switching account. Do not @@ -709,22 +707,29 @@ animationEnabled = NO; } - if (animationEnabled) { - self.transitionHandler = [[TabGridTransitionHandler alloc] - initWithTransitionType:transitionType - direction:direction - tabGridTransitionLayoutProvider:self - tabGridViewController:_viewController - browserLayoutViewController:self.browserLayoutViewController - layoutGuideCenter:LayoutGuideCenterForBrowser(browser) - isRegularBrowserNTP:isRegularBrowserNTP - incognito:isIncognito]; + UIView* appContentView = IsChromeNextIaEnabled() + ? [LayoutGuideCenterForBrowser(nil) + referencedViewUnderName:kAppContentGuide] + : nil; + auto params = std::make_unique<TabGridTransitionHandlerInitParams>( + direction, self.browserLayoutViewController, _viewController, + appContentView); + if (animationEnabled) { + if (UIAccessibilityIsReduceMotionEnabled()) { + self.transitionHandler = [[TabGridTransitionHandler alloc] + initWithReducedMotionCommonParams:std::move(params)]; + } else { + self.transitionHandler = [[TabGridTransitionHandler alloc] + initWithCommonParams:std::move(params) + tabGridTransitionLayoutProvider:self + browserLayoutGuideCenter:LayoutGuideCenterForBrowser(browser) + isRegularBrowserNTP:isRegularBrowserNTP + incognito:isIncognito]; + } } else { self.transitionHandler = [[TabGridTransitionHandler alloc] - initWithDisabledAnimationWithDirection:direction - browserLayoutViewController:self.browserLayoutViewController - tabGridViewController:_viewController]; + initWithNoAnimationCommonParams:std::move(params)]; } [self.transitionHandler performTransitionWithCompletion:completionHandler]; } @@ -892,15 +897,6 @@ return transitionHandler; } -// Determines the transion type to be used in the transition. -- (TabGridTransitionType)determineTabGridTransitionType { - if (UIAccessibilityIsReduceMotionEnabled()) { - return TabGridTransitionType::kReducedMotion; - } - - return TabGridTransitionType::kNormal; -} - // YES if there are tabs present on `page`. Should be called for regular or // incognito. - (BOOL)tabsPresentForPage:(TabGridPage)page {
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/transitions/BUILD.gn b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/transitions/BUILD.gn index e72f2fb5..b4f7f95 100644 --- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/transitions/BUILD.gn +++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/transitions/BUILD.gn
@@ -32,6 +32,7 @@ "//ios/chrome/browser/shared/ui/util:util_swift", "//ios/chrome/browser/tab_switcher/ui_bundled/tab_grid:tab_grid_paging", "//ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/transitions/animations", + "//ios/chrome/common/ui/util", ] }
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/transitions/tab_grid_transition_handler.h b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/transitions/tab_grid_transition_handler.h index ae95eef9..62fd8f0 100644 --- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/transitions/tab_grid_transition_handler.h +++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/transitions/tab_grid_transition_handler.h
@@ -7,6 +7,8 @@ #import <UIKit/UIKit.h> +#import <memory> + #import "base/ios/block_types.h" @protocol TabGridTransitionContextProvider; @@ -17,44 +19,54 @@ kFromBrowserToTabGrid, }; -// Transition types available. -enum class TabGridTransitionType { - kNormal, - kReducedMotion, -}; - @class LayoutGuideCenter; @class TabGridTransitionHandler; @protocol TabGridTransitionLayoutProviding; +// Parameters for the initialization of the transition handler. +struct TabGridTransitionHandlerInitParams { + TabGridTransitionDirection direction; + UIViewController<TabGridTransitionContextProvider>* + browser_layout_view_controller; + UIViewController* tab_grid_view_controller; + // The view associated with the AppContent named guide. + UIView* app_content_view; + + TabGridTransitionHandlerInitParams( + TabGridTransitionDirection direction, + UIViewController<TabGridTransitionContextProvider>* + browser_layout_view_controller, + UIViewController* tab_grid_view_controller, + UIView* app_content_view) + : direction(direction), + browser_layout_view_controller(browser_layout_view_controller), + tab_grid_view_controller(tab_grid_view_controller), + app_content_view(app_content_view) {} + + TabGridTransitionHandlerInitParams() = delete; +}; + // Handler for the transitions between the TabGrid and the Browser. @interface TabGridTransitionHandler : NSObject -// Creates the transition object based on the provided `transitionType`, -// `direction`, `tabGridTransitionLayoutProvider`, `tabGridViewController`, -// `layoutViewController`, `layoutGuideCenter`, `isRegularBrowserNTP`, -// and `isIncognito`. -- (instancetype)initWithTransitionType:(TabGridTransitionType)transitionType - direction:(TabGridTransitionDirection)direction - tabGridTransitionLayoutProvider: - (id<TabGridTransitionLayoutProviding>)tabGridTransitionLayoutProvider - tabGridViewController:(UIViewController*)tabGridViewController - browserLayoutViewController: - (UIViewController<TabGridTransitionContextProvider>*) - browserLayoutViewController - layoutGuideCenter:(LayoutGuideCenter*)layoutGuideCenter - isRegularBrowserNTP:(BOOL)isRegularBrowserNTP - incognito:(BOOL)incognito +// Creates a transition handler with full animations. +- (instancetype)initWithCommonParams: + (std::unique_ptr<TabGridTransitionHandlerInitParams>)params + tabGridTransitionLayoutProvider: + (id<TabGridTransitionLayoutProviding>)tabGridTransitionLayoutProvider + browserLayoutGuideCenter: + (LayoutGuideCenter*)browserLayoutGuideCenter + isRegularBrowserNTP:(BOOL)isRegularBrowserNTP + incognito:(BOOL)incognito NS_DESIGNATED_INITIALIZER; + +// Creates a transition handler with disabled animations (Reduced Motion). +- (instancetype)initWithReducedMotionCommonParams: + (std::unique_ptr<TabGridTransitionHandlerInitParams>)params NS_DESIGNATED_INITIALIZER; -// Creates the transition object for a non-animated transition in `direction`. -- (instancetype) - initWithDisabledAnimationWithDirection:(TabGridTransitionDirection)direction - browserLayoutViewController: - (UIViewController<TabGridTransitionContextProvider>*) - browserLayoutViewController - tabGridViewController: - (UIViewController*)tabGridViewController +// Creates a transition handler with no animations. +- (instancetype)initWithNoAnimationCommonParams: + (std::unique_ptr<TabGridTransitionHandlerInitParams>)params NS_DESIGNATED_INITIALIZER; - (instancetype)init NS_UNAVAILABLE;
diff --git a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/transitions/tab_grid_transition_handler.mm b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/transitions/tab_grid_transition_handler.mm index b68c8b8..13870a26 100644 --- a/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/transitions/tab_grid_transition_handler.mm +++ b/ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/transitions/tab_grid_transition_handler.mm
@@ -20,14 +20,22 @@ #import "ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/transitions/tab_grid_transition_item.h" #import "ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/transitions/tab_grid_transition_layout.h" #import "ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/transitions/tab_grid_transition_layout_providing.h" +#import "ios/chrome/common/ui/util/constraints_ui_util.h" + +namespace { +// Transition types available. +enum class TabGridTransitionType { + kNormal, + kReducedMotion, + kDisabledAnimation, +}; +} // namespace @implementation TabGridTransitionHandler { TabGridTransitionType _transitionType; - TabGridTransitionDirection _direction; - UIViewController* _tabGridViewController; - UIViewController<TabGridTransitionContextProvider>* - _browserLayoutViewController; + // The common parameters for all transitions. + std::unique_ptr<TabGridTransitionHandlerInitParams> _params; // Transition layout provider for the tab grid. __weak id<TabGridTransitionLayoutProviding> _tabGridTransitionLayoutProvider; @@ -45,72 +53,68 @@ BOOL _activeCellPinned; // The layout guide center associated to the current browser. - LayoutGuideCenter* _layoutGuideCenter; + LayoutGuideCenter* _browserLayoutGuideCenter; // Whether the transition is for a tab that is a regular (non-icongnito) NTP. BOOL _isRegularBrowserNTP; // Whether the transition is for an incognito tab. BOOL _incognito; - - // Whether the animations are disabled. When true, most of the other ivars are - // not set. - BOOL _disabledAnimations; } #pragma mark - Public -- (instancetype)initWithTransitionType:(TabGridTransitionType)transitionType - direction:(TabGridTransitionDirection)direction - tabGridTransitionLayoutProvider: - (id<TabGridTransitionLayoutProviding>)tabGridTransitionLayoutProvider - tabGridViewController:(UIViewController*)tabGridViewController - browserLayoutViewController: - (UIViewController<TabGridTransitionContextProvider>*) - browserLayoutViewController - layoutGuideCenter:(LayoutGuideCenter*)layoutGuideCenter - isRegularBrowserNTP:(BOOL)isRegularBrowserNTP - incognito:(BOOL)incognito { +- (instancetype)initWithCommonParams: + (std::unique_ptr<TabGridTransitionHandlerInitParams>)params + tabGridTransitionLayoutProvider: + (id<TabGridTransitionLayoutProviding>)tabGridTransitionLayoutProvider + browserLayoutGuideCenter: + (LayoutGuideCenter*)browserLayoutGuideCenter + isRegularBrowserNTP:(BOOL)isRegularBrowserNTP + incognito:(BOOL)incognito { self = [super init]; if (self) { - _disabledAnimations = NO; + _transitionType = TabGridTransitionType::kNormal; + _params = std::move(params); + + // Full animation setup TabGridTransitionLayout* transitionLayout = [tabGridTransitionLayoutProvider transitionLayoutForIsIncognito:incognito]; - _transitionType = transitionType; - _direction = direction; _tabGridTransitionLayoutProvider = tabGridTransitionLayoutProvider; - _tabGridViewController = tabGridViewController; - _browserLayoutViewController = browserLayoutViewController; _tabGridCellItem = transitionLayout.activeCell; _activeGrid = transitionLayout.activeGrid; _pinnedTabsViewController = transitionLayout.pinnedTabs; _activeCellPinned = transitionLayout.isActiveCellPinned; - _layoutGuideCenter = layoutGuideCenter; + _browserLayoutGuideCenter = browserLayoutGuideCenter; _isRegularBrowserNTP = isRegularBrowserNTP; _incognito = incognito; } return self; } -- (instancetype) - initWithDisabledAnimationWithDirection:(TabGridTransitionDirection)direction - browserLayoutViewController: - (UIViewController<TabGridTransitionContextProvider>*) - browserLayoutViewController - tabGridViewController: - (UIViewController*)tabGridViewController { +- (instancetype)initWithReducedMotionCommonParams: + (std::unique_ptr<TabGridTransitionHandlerInitParams>)params { self = [super init]; if (self) { - _disabledAnimations = YES; - _direction = direction; - _tabGridViewController = tabGridViewController; - _browserLayoutViewController = browserLayoutViewController; + _transitionType = TabGridTransitionType::kReducedMotion; + _params = std::move(params); + } + return self; +} + +- (instancetype)initWithNoAnimationCommonParams: + (std::unique_ptr<TabGridTransitionHandlerInitParams>)params { + self = [super init]; + if (self) { + _transitionType = TabGridTransitionType::kDisabledAnimation; + _params = std::move(params); } return self; } - (void)performTransitionWithCompletion:(ProceduralBlock)completion { - switch (_direction) { + CHECK(_params); + switch (_params->direction) { case TabGridTransitionDirection::kFromBrowserToTabGrid: [self performBrowserToTabGridTransitionWithCompletion:completion]; break; @@ -136,11 +140,7 @@ }; [self prepareBrowserToTabGridTransition]; - if (_disabledAnimations) { - animationCompletion(); - } else { - [self performTransitionAnimationWithCompletion:animationCompletion]; - } + [self performTransitionAnimationWithCompletion:animationCompletion]; } // Performs the Tab Grid to Browser transition with a `completion` block. @@ -156,49 +156,53 @@ }; [self prepareTabGridToBrowserTransition]; - if (_disabledAnimations) { - animationCompletion(); - } else { - [self performTransitionAnimationWithCompletion:animationCompletion]; - } + [self performTransitionAnimationWithCompletion:animationCompletion]; } // Prepares items for the Browser to Tab Grid transition. - (void)prepareBrowserToTabGridTransition { - [_browserLayoutViewController willMoveToParentViewController:nil]; + [_params->browser_layout_view_controller willMoveToParentViewController:nil]; } // Prepares items for the Tab Grid to Browser transition. - (void)prepareTabGridToBrowserTransition { - [_tabGridViewController addChildViewController:_browserLayoutViewController]; + UIViewController* tabGrid = _params->tab_grid_view_controller; + UIViewController* browserLayout = _params->browser_layout_view_controller; + [tabGrid addChildViewController:browserLayout]; if (IsChromeNextIaEnabled()) { - CGRect frame = _tabGridViewController.view.bounds; - // TODO(crbug.com/483998779): Use autolayout instead of fixed margins. - frame.size.height -= 50; - _browserLayoutViewController.view.frame = frame; - } else { - _browserLayoutViewController.view.frame = - _tabGridViewController.view.bounds; - } - [_tabGridViewController.view addSubview:_browserLayoutViewController.view]; + // Remove from superview to ensure all constraints are gone. + [browserLayout.view removeFromSuperview]; - _browserLayoutViewController.view.accessibilityViewIsModal = YES; + UIView* appContentGuide = _params->app_content_view; + browserLayout.view.frame = [tabGrid.view convertRect:appContentGuide.bounds + fromView:appContentGuide]; + [tabGrid.view addSubview:browserLayout.view]; + AddSameConstraints(browserLayout.view, appContentGuide); + } else { + browserLayout.view.frame = tabGrid.view.bounds; + [tabGrid.view addSubview:browserLayout.view]; + } + + browserLayout.view.accessibilityViewIsModal = YES; } // Takes all necessary actions to finish Browser to TabGrid transition. - (void)finalizeBrowserToTabGridTransition { - [_browserLayoutViewController.view removeFromSuperview]; - [_browserLayoutViewController removeFromParentViewController]; + UIViewController* tabGrid = _params->tab_grid_view_controller; + UIViewController* browserLayout = _params->browser_layout_view_controller; + [browserLayout.view removeFromSuperview]; + [browserLayout removeFromParentViewController]; - [_tabGridViewController setNeedsStatusBarAppearanceUpdate]; + [tabGrid setNeedsStatusBarAppearanceUpdate]; } // Takes all necessary actions to finish TabGrid to Browser transition. - (void)finalizeTabGridToBrowserTransition { - [_browserLayoutViewController - didMoveToParentViewController:_tabGridViewController]; + UIViewController* tabGrid = _params->tab_grid_view_controller; + UIViewController* browserLayout = _params->browser_layout_view_controller; + [browserLayout didMoveToParentViewController:tabGrid]; - [_browserLayoutViewController setNeedsStatusBarAppearanceUpdate]; + [browserLayout setNeedsStatusBarAppearanceUpdate]; } // Performs transition animation. @@ -216,7 +220,7 @@ TabGridAnimationParameters* animationParameters = [self createAnimationParameters]; - switch (_direction) { + switch (_params->direction) { case TabGridTransitionDirection::kFromTabGridToBrowser: { animation = [[GridToTabAnimation alloc] initWithAnimationParameters:animationParameters]; @@ -235,12 +239,16 @@ case TabGridTransitionType::kReducedMotion: { animation = [[TabGridReducedAnimation alloc] - initWithAnimatedView:_browserLayoutViewController.view - beingPresented:_direction == TabGridTransitionDirection:: - kFromTabGridToBrowser]; + initWithAnimatedView:_params->browser_layout_view_controller.view + beingPresented:_params->direction == + TabGridTransitionDirection:: + kFromTabGridToBrowser]; break; } + case TabGridTransitionType::kDisabledAnimation: + completion(); + return; } CHECK(animation); @@ -249,10 +257,12 @@ // Creates animation parameters for the transition. - (TabGridAnimationParameters*)createAnimationParameters { + UIViewController* tabGrid = _params->tab_grid_view_controller; + UIViewController* browserLayout = _params->browser_layout_view_controller; // Get the content area frame. - UIView* tabContentView = _browserLayoutViewController.view; + UIView* tabContentView = browserLayout.view; NamedGuide* contentAreaGuide = - [_browserLayoutViewController contentAreaGuide]; + [_params->browser_layout_view_controller contentAreaGuide]; CGRect contentAreaFrame = [contentAreaGuide.owningView convertRect:contentAreaGuide.layoutFrame toView:tabContentView]; @@ -261,13 +271,12 @@ // using the `contentAreaFrame.origin.y`. This dynamically handles the // presence of the Tab Strip and Toolbar across different devices. BOOL topToolbarHidden = [self shouldHideTopToolbar]; - CGFloat topToolbarHeight = - topToolbarHidden ? _tabGridViewController.view.safeAreaInsets.top - : contentAreaFrame.origin.y; + CGFloat topToolbarHeight = topToolbarHidden ? tabGrid.view.safeAreaInsets.top + : contentAreaFrame.origin.y; // Get the "bottom toolbar height" (everything below the web content area). - UIView* bottomToolbarView = - [_layoutGuideCenter referencedViewUnderName:kSecondaryToolbarGuide]; + UIView* bottomToolbarView = [_browserLayoutGuideCenter + referencedViewUnderName:kSecondaryToolbarGuide]; CGRect bottomToolbarFrameInWindow = [bottomToolbarView.superview convertRect:bottomToolbarView.frame toView:nil]; @@ -284,21 +293,21 @@ // of the content below the status bar is needed when doing a Tab to Grid // transition. UIView* topToolbarSnapshotView = - topToolbarHidden && - _direction == TabGridTransitionDirection::kFromTabGridToBrowser + topToolbarHidden && _params->direction == + TabGridTransitionDirection::kFromTabGridToBrowser ? nil : [self snapshotOfViewPortionAboveRect:tabContentView middleRect:contentAreaFrame]; // Get the animation's destination and origin frames. CGRect destinationFrame = - _direction == TabGridTransitionDirection::kFromBrowserToTabGrid + _params->direction == TabGridTransitionDirection::kFromBrowserToTabGrid ? _tabGridCellItem.originalFrame - : _browserLayoutViewController.view.frame; + : browserLayout.view.frame; CGRect originFrame = - _direction == TabGridTransitionDirection::kFromBrowserToTabGrid - ? _browserLayoutViewController.view.frame + _params->direction == TabGridTransitionDirection::kFromBrowserToTabGrid + ? browserLayout.view.frame : _tabGridCellItem.originalFrame; CHECK(_tabGridCellItem); @@ -309,7 +318,7 @@ activeGrid:_activeGrid pinnedTabs:_pinnedTabsViewController activeCellPinned:_activeCellPinned - animatedView:_browserLayoutViewController.view + animatedView:browserLayout.view contentSnapshot:_tabGridCellItem.snapshot topToolbarHeight:topToolbarHeight bottomToolbarHeight:bottomToolbarHeight @@ -360,8 +369,9 @@ // Returns YES if the transition should hide the top toolbar (use the safe area // insets instead of the top toolbar LayoutGuide). - (BOOL)shouldHideTopToolbar { - return _isRegularBrowserNTP && !CanShowTabStrip(_tabGridViewController) && - IsSplitToolbarMode(_tabGridViewController); + UIViewController* tabGrid = _params->tab_grid_view_controller; + return _isRegularBrowserNTP && !CanShowTabStrip(tabGrid) && + IsSplitToolbarMode(tabGrid); } @end
diff --git a/ios/chrome/browser/tips_notifications/coordinator/BUILD.gn b/ios/chrome/browser/tips_notifications/coordinator/BUILD.gn index fc3781b..4158971c 100644 --- a/ios/chrome/browser/tips_notifications/coordinator/BUILD.gn +++ b/ios/chrome/browser/tips_notifications/coordinator/BUILD.gn
@@ -8,6 +8,8 @@ "enhanced_safe_browsing_promo_coordinator.mm", "lens_promo_coordinator.h", "lens_promo_coordinator.mm", + "price_tracking_promo_coordinator.h", + "price_tracking_promo_coordinator.mm", "search_what_you_see_promo_coordinator.h", "search_what_you_see_promo_coordinator.mm", ] @@ -21,6 +23,7 @@ "//ios/chrome/browser/shared/public/commands", "//ios/chrome/browser/tips_notifications/ui:enhanced_safe_browsing_promo", "//ios/chrome/browser/tips_notifications/ui:lens_promo", + "//ios/chrome/browser/tips_notifications/ui:price_tracking_promo", "//ios/chrome/browser/tips_notifications/ui:search_what_you_see_promo", "//ios/chrome/common/ui/button_stack", "//ios/chrome/common/ui/confirmation_alert",
diff --git a/ios/chrome/browser/tips_notifications/coordinator/price_tracking_promo_coordinator.h b/ios/chrome/browser/tips_notifications/coordinator/price_tracking_promo_coordinator.h new file mode 100644 index 0000000..45e004a --- /dev/null +++ b/ios/chrome/browser/tips_notifications/coordinator/price_tracking_promo_coordinator.h
@@ -0,0 +1,15 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IOS_CHROME_BROWSER_TIPS_NOTIFICATIONS_COORDINATOR_PRICE_TRACKING_PROMO_COORDINATOR_H_ +#define IOS_CHROME_BROWSER_TIPS_NOTIFICATIONS_COORDINATOR_PRICE_TRACKING_PROMO_COORDINATOR_H_ + +#import "ios/chrome/browser/shared/coordinator/chrome_coordinator/chrome_coordinator.h" + +// A coordinator to present a Price Tracking Promo screen. +@interface PriceTrackingPromoCoordinator : ChromeCoordinator + +@end + +#endif // IOS_CHROME_BROWSER_TIPS_NOTIFICATIONS_COORDINATOR_PRICE_TRACKING_PROMO_COORDINATOR_H_
diff --git a/ios/chrome/browser/tips_notifications/coordinator/price_tracking_promo_coordinator.mm b/ios/chrome/browser/tips_notifications/coordinator/price_tracking_promo_coordinator.mm new file mode 100644 index 0000000..76791e0a --- /dev/null +++ b/ios/chrome/browser/tips_notifications/coordinator/price_tracking_promo_coordinator.mm
@@ -0,0 +1,110 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "ios/chrome/browser/tips_notifications/coordinator/price_tracking_promo_coordinator.h" + +#import "ios/chrome/browser/shared/model/browser/browser.h" +#import "ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h" +#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h" +#import "ios/chrome/browser/shared/public/commands/scene_commands.h" +#import "ios/chrome/browser/shared/public/commands/settings_commands.h" +#import "ios/chrome/browser/tips_notifications/ui/price_tracking_promo_view_controller.h" +#import "ios/chrome/browser/tips_notifications/ui/tips_promo_view_controller.h" +#import "ios/chrome/common/ui/button_stack/button_stack_action_delegate.h" +#import "ios/chrome/common/ui/confirmation_alert/confirmation_alert_action_handler.h" + +@interface PriceTrackingPromoCoordinator () < + ButtonStackActionDelegate, + ConfirmationAlertActionHandler, + UIAdaptivePresentationControllerDelegate> +@end + +@implementation PriceTrackingPromoCoordinator { + PriceTrackingPromoViewController* _viewController; + BOOL _showNotificationSettingsOnDismiss; +} + +#pragma mark - ChromeCoordinator + +- (void)start { + _viewController = [[PriceTrackingPromoViewController alloc] init]; + _viewController.actionDelegate = self; + + UIBarButtonItem* dismissButton = [[UIBarButtonItem alloc] + initWithBarButtonSystemItem:UIBarButtonSystemItemDone + target:self + action:@selector(dismissViewController)]; + _viewController.navigationItem.rightBarButtonItem = dismissButton; + + UINavigationController* navigationController = [[UINavigationController alloc] + initWithRootViewController:_viewController]; + navigationController.modalPresentationStyle = UIModalPresentationFormSheet; + [self.baseViewController presentViewController:navigationController + animated:YES + completion:nil]; + + _showNotificationSettingsOnDismiss = NO; +} + +- (void)stop { + ProceduralBlock completion = nil; + if (_showNotificationSettingsOnDismiss) { + CommandDispatcher* dispatcher = self.browser->GetCommandDispatcher(); + id<SettingsCommands> handler = + HandlerForProtocol(dispatcher, SettingsCommands); + completion = ^{ + [handler showNotificationsSettings]; + }; + } + [_viewController.presentingViewController + dismissViewControllerAnimated:YES + completion:completion]; + _viewController = nil; +} + +#pragma mark - ButtonStackActionDelegate + +- (void)didTapPrimaryActionButton { + _showNotificationSettingsOnDismiss = YES; + [self dismissScreen]; +} + +- (void)didTapSecondaryActionButton { + [self dismissScreen]; +} + +- (void)didTapTertiaryActionButton { + // Not used. +} + +#pragma mark - ConfirmationAlertPrimaryAction + +- (void)confirmationAlertPrimaryAction { + _showNotificationSettingsOnDismiss = YES; + [self dismissScreen]; +} + +#pragma mark - UIAdaptivePresentationControllerDelegate + +- (void)presentationControllerDidDismiss: + (UIPresentationController*)presentationController { + [self dismissScreen]; +} + +#pragma mark - Private methods + +// Dismisses the coordinator and its view controller. +- (void)dismissViewController { + [self dismissScreen]; +} + +// Sends a command that will stop this coordinator and dismiss the screen. +- (void)dismissScreen { + CommandDispatcher* dispatcher = self.browser->GetCommandDispatcher(); + id<BrowserCoordinatorCommands> handler = + HandlerForProtocol(dispatcher, BrowserCoordinatorCommands); + [handler dismissPriceTrackingPromo]; +} + +@end
diff --git a/ios/chrome/browser/tips_notifications/model/tips_notification_presenter.mm b/ios/chrome/browser/tips_notifications/model/tips_notification_presenter.mm index 6ae2c3c..7cb01fb4 100644 --- a/ios/chrome/browser/tips_notifications/model/tips_notification_presenter.mm +++ b/ios/chrome/browser/tips_notifications/model/tips_notification_presenter.mm
@@ -237,8 +237,8 @@ } void TipsNotificationPresenter::ShowPriceTrackingPromo() { - // TODO (crbug.com/485573580): Present the Price Tracking promo once it is - // created. + [HandlerForProtocol(browser_->GetCommandDispatcher(), + BrowserCoordinatorCommands) showPriceTrackingPromo]; } bool TipsNotificationPresenter::HasIdentitiesOnDevice() {
diff --git a/ios/chrome/browser/tips_notifications/ui/BUILD.gn b/ios/chrome/browser/tips_notifications/ui/BUILD.gn index 7133cbf..021d530 100644 --- a/ios/chrome/browser/tips_notifications/ui/BUILD.gn +++ b/ios/chrome/browser/tips_notifications/ui/BUILD.gn
@@ -77,3 +77,18 @@ ] frameworks = [ "UIKit.framework" ] } + +source_set("price_tracking_promo") { + sources = [ + "price_tracking_promo_view_controller.h", + "price_tracking_promo_view_controller.mm", + ] + public_deps = [ ":shared" ] + deps = [ + "//ios/chrome/app/strings", + "//ios/chrome/browser/shared/ui/util", + "//ios/chrome/common/ui/button_stack", + "//ui/base", + ] + frameworks = [ "UIKit.framework" ] +}
diff --git a/ios/chrome/browser/tips_notifications/ui/price_tracking_promo_view_controller.h b/ios/chrome/browser/tips_notifications/ui/price_tracking_promo_view_controller.h new file mode 100644 index 0000000..4831a63 --- /dev/null +++ b/ios/chrome/browser/tips_notifications/ui/price_tracking_promo_view_controller.h
@@ -0,0 +1,18 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IOS_CHROME_BROWSER_TIPS_NOTIFICATIONS_UI_PRICE_TRACKING_PROMO_VIEW_CONTROLLER_H_ +#define IOS_CHROME_BROWSER_TIPS_NOTIFICATIONS_UI_PRICE_TRACKING_PROMO_VIEW_CONTROLLER_H_ + +#import <UIKit/UIKit.h> + +#import "ios/chrome/browser/tips_notifications/ui/tips_promo_view_controller.h" + +// The view controller for a Price Tracking promo that encourages users to turn +// on price tracking notifications. +@interface PriceTrackingPromoViewController : TipsPromoViewController + +@end + +#endif // IOS_CHROME_BROWSER_TIPS_NOTIFICATIONS_UI_PRICE_TRACKING_PROMO_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/tips_notifications/ui/price_tracking_promo_view_controller.mm b/ios/chrome/browser/tips_notifications/ui/price_tracking_promo_view_controller.mm new file mode 100644 index 0000000..2c49a16 --- /dev/null +++ b/ios/chrome/browser/tips_notifications/ui/price_tracking_promo_view_controller.mm
@@ -0,0 +1,58 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "ios/chrome/browser/tips_notifications/ui/price_tracking_promo_view_controller.h" + +#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h" +#import "ios/chrome/common/ui/button_stack/button_stack_configuration.h" +#import "ios/chrome/grit/ios_strings.h" +#import "ui/base/l10n/l10n_util_mac.h" + +namespace { + +// The name of the file containing the price tracking promo animation. +NSString* const kAnimationName = @"price_tracking_promo"; + +// Returns the color provider for the animation. +NSDictionary<NSString*, UIColor*>* ColorProvider(int shadow_color) { + return @{@"Shadow.*.*.Color" : UIColorFromRGB(shadow_color)}; +} + +// Accessibility identifier for the Tab Groups Promo view. +NSString* const kPriceTrackingPromoAccessibilityIdentifier = + @"PriceTrackingPromoAccessbilityIdentifier"; + +} // namespace + +@implementation PriceTrackingPromoViewController + +#pragma mark - UIViewController + +- (void)viewDidLoad { + self.animationName = kAnimationName; + self.lightModeColorProvider = ColorProvider(0xDADCE0); + self.darkModeColorProvider = ColorProvider(0x5F6368); + + self.animationTextProvider = @{ + @"Price drop on your tracked product" : l10n_util::GetNSString( + IDS_IOS_PRICE_TRACKING_PROMO_MOCK_NOTIFICATION_TITLE), + @"Your item is now 15% off" : l10n_util::GetNSString( + IDS_IOS_PRICE_TRACKING_PROMO_MOCK_NOTIFICATION_SUBTITLE), + }; + + self.titleText = l10n_util::GetNSString(IDS_IOS_PRICE_TRACKING_PROMO_TITLE); + self.subtitleText = + l10n_util::GetNSString(IDS_IOS_PRICE_TRACKING_PROMO_SUBTITLE); + + self.configuration.primaryActionString = l10n_util::GetNSString( + IDS_IOS_PRICE_TRACKING_PROMO_TURN_ON_NOTIFICATIONS_BUTTON); + self.configuration.secondaryActionString = + l10n_util::GetNSString(IDS_IOS_PRICE_TRACKING_PROMO_KEEP_BROWSING_BUTTON); + + [super viewDidLoad]; + self.view.accessibilityIdentifier = + kPriceTrackingPromoAccessibilityIdentifier; +} + +@end
diff --git a/ios/chrome/browser/tips_notifications/ui/resources/BUILD.gn b/ios/chrome/browser/tips_notifications/ui/resources/BUILD.gn index 9cb68b6..7213f2a5 100644 --- a/ios/chrome/browser/tips_notifications/ui/resources/BUILD.gn +++ b/ios/chrome/browser/tips_notifications/ui/resources/BUILD.gn
@@ -7,6 +7,7 @@ "enhanced_safe_browsing_promo.json", "enhanced_safe_browsing_promo_darkmode.json", "lens_promo.json", + "price_tracking_promo.json", "search_what_you_see_promo.json", "search_what_you_see_promo_darkmode.json", ]
diff --git a/ios/chrome/browser/tips_notifications/ui/resources/price_tracking_promo.json b/ios/chrome/browser/tips_notifications/ui/resources/price_tracking_promo.json new file mode 100644 index 0000000..4211eb8 --- /dev/null +++ b/ios/chrome/browser/tips_notifications/ui/resources/price_tracking_promo.json
@@ -0,0 +1 @@ +{"v":"5.12.1","fr":60,"ip":0,"op":1,"w":375,"h":416,"nm":"catherine price tracking","ddd":0,"assets":[{"id":"image_0","w":375,"h":813,"u":"","p":"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSgBBwcHCggKEwoKEygaFhooKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKP/AABEIAy0BdwMBEQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/APUag+mCmAGgQlMBKACmAlMBtACVQBTAaaYhpoEJTAQ0wGmmIQ0wG1SASmA2mIQ9aYCUxCGmAhpiGmmAUwCmIbTAKACmAGgQlMAoAKAFpgFABQIKACgQtAC0AFABQAUAFABQAUAFAG1XinWFACUAFMQlABTASmA2mAlMApgNNMQ00CCmA00wGmmAhpiG1SASmA2mIQ0wEpgIaYhDTENNMApgFMQlMBKAA0wEoEFMAoAKAFpgFABQIKACgBaBBQAtABQAUAFABQAUAFAG1XinWFACUABpgJQAUxDaYCUwEpgFMBppgNNBIUwGmmA2mAhpiGmqASmAhpiGmmAlMBDTEIaYDTTEFMApgJTEJQAlMAoAKYgoAKACmAUALQAUAFAgpgLSAKBC0AFMAoAKACgApAbVeKdYlABQAhpgFACUxCGmAlMBKACqAaaYDTQISmIQ0wGmmDENMQ00wEqgENAhDTAbVAIaYhDTAaaYBTEJTASmIDQAlMAoAKACmIKACgApgFAC5oAM0AFABQIKAFp2EGaLAGaQwpgFAhaANmvEOsKAEpgFABTAQ0CENMBKaASgAqgGmmA00CENMQ00xiUxCGmIaaoBKAENMBppoQlMBDVAIaYhppgFACGmISmAlMAoEFMBKACmAUCCgApgGaADNAC5oAKAFzQAZoAM0wDNAgoAKACgAoA268U6hM0AFABTAKAENMQ00AFMBKYCGmAhpgNoAQ0xDTTASmIQ0wG0wEpiENUA00CEpoBDTAbTEJTAKYDaYBTEJTAQ0AFAgpgFMAoAKACgQUwCgAoAKADNABmgAzQAZpgGaADNABmgAzQBuV4p0hQAUwCgAoAaaYhKAENMApgNpgIaYCUAIaYDTTASmIQ0xDaYCUwGmqENNABTAaaYhKYCUxCGmAlMBKYBQISmAUAFMAoEJTAM0AGaADNMQZoAKADNABmgAzQAZoAWgApgFABQAUAbleMdIUAJmgAzTATNACUCENMApgJQA0mmAlMBDQAlMBppgFMQ00xDTTASmA00wEpiENMBDTAaaBBTAQ0wEzTASmITNMApiEoAM0AGaYCUAGaYBmgQZoAM0wDNAgzQAmaADNABmmAuaADNABmgAzQAE4GWwB78UAbma8c6QzQAmaADNACZpgFACZpgJQIQmmAlMAoAbQAhpoBKYCGmIaTTASmIaaYDTTAKYhDTAbTASgBKYhDTASmAlMBDTEFMBM0AJmgQmaYBmgAzQAUwCgQUAFMAoAKACgAoAKYFO71OztOJ7hFb+6Dk/kK1hRnP4UQ5xRj3XiqMZFpbu57NIdo/KumOCf22ZSrpbGVca/qM2QJlhHpGuP1Nbxw9KPS5i67ZnTSSznM8skh/22JrZWj8KsZubZ7NXxp7QUAJQAZoAM0AJmmAZoATNMBKBCUwEJoATNMBKYCE0ANJpgJTAaTTEJTASmIQ0wGk0wEoEJTAQ0wG0xBTATNACZpgJTEJmmAUAFACZpgGaADNMQZoATNACZpgGaBBmgCO4uIraMvcSpGnqxxVRi5O0VcTaW5hXvieFMrZxNKf77/Kv+JrshgnvN2MZV0tjCvNVvrzIlnZUP8ABH8orqhSp0/hRzyquRSCgdBWrbZm22LUiFAouMXFK4Hsua+Qse2GaLAGaAEzQAZpgJmgAoEJmmAZoAQmgBpNMBKAEJpgJmmAhpgITQA00wEpiENMQ0mmAlACE0wEJpoQ0mmAlMBM0wEJoEJmmAUwEpgJmgQZoAM0wEzQAmaADNMQZoAM0wILu6gtIvMuZVjXtnqfoO9XCnKo7RVyXJR3Ocv/ABM75Wwj2D/npJyfwFd1PBxWtR/I554jojBmlluJPMuJHlf1Y5rrVoq0VZHPKbY0UiBaLjFApXAcBSuAoFAwpXA9gr5M9oKBBmgAzTAM0AJmgAzQAmaAEzQAlMAzTAaTQAlMBM0AJmmAhNMBKYCE0xDSaBCUwEJoAaTTQCUwEzTEITTAbTAKAEpgFMQhNMBCaAEzQAUIApiEoAM0wGySJGheRlRF5LMcAU0m3ZCbS1ZzmpeJesenLk/89XHH4Cu+lgutX7jnnXtpE5yaWSeUyTyNJIf4mOa7VaK5YqyOWU3IaBSIHYpDFpALii4xQKQDqBhSAMUAeu5r5U9gM0AGaAEzQAZosAZoATNMAzQAmaAEzTASgBM0wEJpgITQAlMBKAEJpgNJpiEzTAQmgQ0mmAUAITTAbmmIDTASmAmaAEJpgITTEJmmAUAJmgBM0IAzTATNAGdqusW+ngqx8yftGv8AX0rooYaVXXZdzKdRQOQv9QuNQfNw/wAg+7GvCivTp04UlaP3nFOq5FaruZCgUrgOApXGLSuAoFIYtIYtAC0gCgBaQHrO6vmLHsBmgAzQAZoATNFgDNMBM0AGaAEzQAZpgITQAhNMBKAEpgJmmAhNACZpgJmmA3NACUCCmA0mmIQmgBKYCUwEJoATNMQmaYBQAmaYCZpgJmgQmaAEJwCSQAOST2pgc1rHiH70OnH2abH/AKD/AI16NDB296r93+Zy1a9tInN5JJYkljySepruucbdxRSEKKljHikAtIBRSGKKBi0gFoAKVwCgBaQHq2a+aPYDNABmgAzQAZpgGaAEzQAmaADNMBM0AGaYCZoATNACE0wEpgJmgBpNMBM0AFADc0wDNMQ0mgQUwEzTAaTQAmaYBQAmaYhM0AITTASmAUARzzR28LSzOEjUZLGnGLm+WO4pNRV2cZrWsyagxiizHajt3f6/4V69DDqjq9ZfkcNWs5aIyhXQcwoqQHUhjhSAUUhjqQAKAHCkMWkwCkAtMAoAKAPVK+bPYCgAoATNABmgAzRYAzTAM0AJmgBN1ABmmAmaADNAhCaYxpNACZpgFACE0AJmmAlMQlACZpiEJoAQmmAlACZpgJmgBDTEJQAUwCmBXvLqKzt2mnfai/mT6D3q6dOVSXLEUpKKuzh9W1ObUptz5SFT8kYPA9z717FGjGgrLfuedVqubKQrUxFFIBwqRjqQCikAopDFFAxaQBmgBaQC5oAM0ALmgApAepZr509gM0AGaADNACZpgGaACgAoATNAgzTGJmgBM0AJmmAUAJQAZoAQmmAmaAEzTEJQAmaYhCaAEpgFACE0wG5oATNABTEFACGmBDd3MVpbvNO21F/M+w96uEJVJcsdxSkoq7OC1XUpdRuPMk+WNfuIDwo/xr26VGNGPLHfqzzatVzZTBqzIcKQDqQCipGOpAKKQC5oGLSAWgYtIAoAKADNAC5pAGaAPUs188ewGaBBmgAzQAmaYwzQAZoATNABmgBKYBQAmaAEJoATNABmmAmaACgQmaYCZoACaYhuaACmAmaAENACUwEoAKYCGgQlADJpUgieWZgsaDLMe1VGLk+VbibSV2cFreqPqVxnlYE+4n9T717lCgqEbdXuefWquTM4GtWc44UgHA0hjs1ICikMcDSAWkAtAC5oGLmkAZoC4uaAFpDCgAoAKAPT818+ewFAC5pAGaADNMBKADNABmgBM0AGaAEzQAmaYBmgAoATNFgEzTATNABmnYBM0CCgBM0xCUAGaAEpgJQAUAIaYCUCCgDh/E+r/bZvs9u3+jRnqP429fp6V7WEw/so88vif4HBXrX0RiA11HJcWpYxQaAHCkAoNSMeDSAUGkAuaAAGkMdmgAzQAuaAFBpALmgYuaACkAtAHplfPnshmgAzRYBc0WAM0WATNFgDNFgDNFgDNABTAKAEzQAmaYgzQAmaYCZoAKBhTsAhoASgAzQIQ0AJTAKBBQAmaAEpiA0Ac34t1X7PF9jgb97IP3hH8K+n1P8AKvRwOH5n7SWy2ObEVeVcqOMzXqs85u+rFBqWK44GpAUGgY4GkA4GpAXNAxc0gF3UrDuGaAFzQMXNIBc0ALmgBc0ALmkAoNAC5pAel5rwD2hc0AGaADNMAzQAZpAG6iwBuosAbqdgDNACZoEGaAEzQIKACmAUDEzTAM0AJmgBKYBQAlAgoAKYCGgBKACgCnqt8mnWMlxJgkcIv95uwrahRdaagjOpPkVzzW4nkuJ3mlbdI5LMT3NfQKKilGOyPJnLmYwGgkXNSIXNIB2aQCg0gFBoHccDSsFxQaQ7i5oAM0gFzQO4uaVgHZoHcM0guOzQAoNAxc0gFBoA9LzXgntBmlYAzTsAZosAUgDNFgCmAUAGaADNABmiwgzQAZoAM0AGaBBmgBM0wEoAKAEzTAKACgApgJQAUAJQAUAefeK9T+3X5jjbNvASq4/iPc/59K93BUPZU7vdnm4mrzPQw811nGLmpAUGkwFBqQHA0guLmgLi5oC4uaQxQaAFzSAXNFh3FBpBcdmgYA0guOBoHcXNILig0guOB5oAcDSGek5rwj2wzQAuaQBmgAzQAuaADNABmgAzQAZpgJQAUAGaADNABmgQmaADNMQZpAFABQAUwCgBKYAaAEoAKAMjxTqH9n6W2w4mm/dp6j1P4D+YrrwdH2tTXZGNefLG3c83Jr3zyZO7DNSSLmkAZpAKDUiHZpDFBpCFzQMcDQAZoAXNIBQaAFzQMXNKwC5pDHA0BcUGkO44GkA4UgHikxnpGa8I9wM0ALmgAzQAUALQAUAFABQAUAFABQIKACgApgFABQAUCCgAoAKACgAoAKAEoAMUwPOPFuofbtVdUOYYf3ae+Op/P+Ve/gqPsqeu71PLxNTmehh5rrOUM0hBmpC4uaBC5pAKDSAXNKwxc0WC4ob3pAKDSGO3UALuoAcDQAuaQCg0AKDSGOBpAOBpAPBpDHipC56PXhnvBmgQuaADNAxc0WAM0AGaADNAC5osAZoAM0WAM0AGaBC0AFABQIKACgBKACgAzQAZoAWgAoAKAM7X73+z9JuJ1OJMbI/948D8uv4VvhqXtaqiZ1pcsG0eVk5NfSHjSd2NzQSGaQgzSELmkFxc0guLmgAzSC47NAXFzQMUGlYLig0WGKDSC44GkAoNADgaBjgaQDgaTAcKTC48VIyQVLA9Grwz3xc0AGaACgBaACgAzQIM0DDNAC0AGaADNABmgQuaACgAoELmgAzQAUALQAlABigAxQAtABQBxHxBvczW9mp4Qea49zwP0z+devltOydR+hw4ufT+tf6/E43NeoecJmgQZpCFzUiDNAC5pCDNAxQ1ADgaADNIBc0DHA0BcXNIYoNADt1KwDhSAcDQMeKkY8UgHipYEi1LA9FzXiWPoBc0CDNIBc0DDNACigAoAWgAoAKACgAoEGaAFzQAUAFAC0AFAhaACgBaACgAoAKADrQB5Lr159u1a6nByrOdv+6OB+gFfTYen7OlGJ41afNK5nE1qYiZoJDNIQZpMAzSELmgQA0hjs0AGaAHZoGANADgaQC5oAcDQMcDSAcDSGOFIB4pASCpYx61IyRalgejV4h9AFABmkAtABQIM0ALmgYuaACgBaACgAoAKACgBaBBQMKBC0AFABQIdQAUAFABSAz9fuvsei3k4OGEZVf948D+db4aHtKsYmdaVoOx5Ix59hX0p40nd3Gk0EDc0iQzSEJmgQoNIAzQIdmgYoNABmkA4GgYoNAC5pAOBoAcKQxwpAOFIB4oGSCkwJFqRj1pMY/cFHNQxno2a8Q+gCgBaACkAUALQAUALQAUALQAUALQAUAFABmgQtAwpAKKACgQUALQIWgAoAKAOV+Idz5WlW8APMsu4/RR/iRXpZZC9Ry7I5cXK0UjzsmvaZ5Q0mgkaTSJDdSEG6kAZoELmgBc0AGaAHA0gFBoGOBoAcDSGKKAHikA4UgHikMeKQDxSYyRakYrSBBkmpbHYu6bpct9+9n3RW/b+83/ANanCm6mvQmVRR0W532a8I+jFoAWkIKBhQAUALQAUALQAUAKKAFoAKQBQAUAKKACgBaACgBRQIWgAoEFIAoA8++I8+7U7eAHiKHJHuxP9AK9vK42puXdnnYyWtjjya9I4BpNBLEpCEzSEGaQgzQIUGgYoNADgaQCigBRQMcKQDhQA8UgHCkxjxSAfQMetICQcVLGhpk+YIgLOeAB1NQ2Ukbml6OEKz32Gk6rH2H19a1hRvrIynV6RNlpPSumxkjbFfKH1YZoAUGgBRQAtABQAUALQAoouAUAKKQC0AFABQAUAAoAWgBRQAtIAoAWgQUAFAC0CPJ/Gdx53iS9OchHEY/4CAP55r6PBR5aETyMVK836/8AAMImuo5WNJoJEJpCEzSYgzSEGaAHA0AKDSAXNAxwNADhQA4GkMcKQDxSAeKQDxQMeBSAdkKOTUtjH2sM99L5dsvH8TnotTZydkU2oq7Ol07T4bBcj95MRzIf6eldNOko6vc551HItPLWtiSF5PenYDphXyR9YLQAtABQAopALQAUAAoAWgBaAFoAWgAoAKQBQAooAKAFoAXFAC0CCgApALQA5RyPrQB4jqc3n39zKT/rJXf82Jr6unHlgo+R4NWV3cpsasyGk0iRCaQhM0hBmgBQaQCg0AOBoAcKAHCkMfQAopAPFIB4pDJBQMetIBHlCjjrUt2KSuaWn6PLc4kvCYouoT+Jv8KqNKU9XoiJVFHRHQxrHbxCOFAiDoAK6owUVZGDbbuxjSVVgIXkp2GRNJTsB2Yr48+sCgBRQAUgCgBaAFoAKAAUALSAWgBaAFoAKACgBRQAtACikAtABQACgBaAFxSAjuZPKtppf7kbN+QJqormkkTJ2i2eFseB9BX1p8/N62GE0EDSaQhuaRIZpAGaQC0AOFADhSAcKAHCgB4pDHikA8UgHigY7IUc0rjQ62invZPLtkz6seg+pqVeTtEbtHWR0enaXBZYd/3s/wDfPb6CuiFBR1erMZ1HLRbF1pa3sZkTSU7DIWkp2AiaSgZE0lMZ3or4w+sFoAWgAoAUUCFoAKACkAtABQAtACigBaQBQAtACigBRQAtIAoAKAFoAWgBaAM/xHJ5Xh/Un6Yt3/UEf1rbDLmqxXmjGu7U5eh4q55NfUnhT+JkRNIgaTSJG5pAFABmkA4GgBwpAPFADhQA8UgHikA8Uhjx70AIZPmCoCzHgAck1LkWo9zWsNFeXEl8SidfLB5P1NaQoOWstCJVUtIm9GI4IxHCqog6ACuqMFFWRztt6sY8lUBE0lOwyFpKBkbSUARM9AyMvQM9Hr4w+sFoAUUAFIB1ABQAUCFpXAKAFoAWgBaACgAoAWkAooAKAHUAFAAKQC0AOoAWgDE8avs8K6gfVFX83UV1YFXrxMMT/Dfy/M8bY5NfTHgN6jCaQhppCEzSASkAtACikA8UAPBpAOFADxSAkFIYpkCDk0m7DSuWbKxub8gqPLh7u39BThCVTbYJSUN9zorCxt7Ff3S7pO7tyTXVToxhr1MJVHLcsNL71qSRNLQBE0lAyJpKAImkoGRmSgBhemMYXosB6fXxR9aLQAooAKAFoAWgApALQIUUAKKAFpAFABQAooAKAFoAXFIBaACgAFADhQAooAWgDnfiC23wpdf7Txj/AMfH+FdmX/7xH5/kc2Ldqf3HkDGvozwWxhNIQ0mkA3NIBaAFFIBwoAcKQDxQA8UgHZAHNIEh0Ky3Mnl26F29ug+ppaydol2SV2b2n6NHCRJdkSy9dv8ACP8AGuinh1vMylWb0iapkwMDgCumxiRNLRYCJpaLDI2kosBG0lAyMyUWAjZ6BjC9ADC9MY0tQB6uK+JPrhaAFpCCgB1ABQAUALQAopAKKAFoAKAAUgFoELQAooAWgAoAMUALQAtAC4pAKBQI5j4lHb4WYes8Y/mf6V3Zb/H+TOXGv918zyQ19AeEMJoAZmkAUhBQAooAcKQD1oGOFAAZOQFBLHoB1qGxqNzVsNHkmxJeExx9kHU/4VrTw8p6y0RMqijpE3oUitowkCKij0rsjBQVomDbk7sGl96oCJpPekBE0lMCNpKBkbSUgIzJRYYwyUWAYXosA0vQMaXoAQtQB6/XxB9eLigAoAWgQtIAoAKAFoAUUAKKQC0XAKAAUALQAtACigQtAAKAFpALRcBaAHAUgHAUAcj8UTt8NRD+9dJ/6C1ehlf8f5M4sc/3a9f0Z5Oa988MjNILjaQCUALQIUUgHCgYpYLSbGlcsWVnPfN+7G2Pu56fh60QhKo/dG2obnRWNhb2Qyg3Sd3brXbToRhr1MJVHItNLWxJE0tICJpKAI2kosBG0lICMyUAMMlAxhegBpegY0vQAm6gYm6gBN1ID2Wvhz68Wi4C0AFAC0AFAC0gAUALQAtAC0CCgBRQACgBaAFFAC0AApALQA4UhCigB4FIBwFIDjvitx4etf8Ar6X/ANAevSyr+M/Q4Mw/hr1/Rnk5Ne6eGMNAxtACUAFIBc0DBC8jiOJS7noBU3bdkUkt2bdhoyriS9O9uvljoPrXVTwvWp9xnKr0ia+8KoVQAo6AV1pJKyMSNpaAI2kpDIzJQBG0lICNpKAGGSkAwvQMYXoAaXoATfQMTdQAbvegA3UAGaAPaa+GPsRaAFFAhaQBQACgBaAFFAC0ALQAUAAoELSAWgBaAFoAKAAUAOApAOFIBwFAhwFIB4FIDivi0caBZD1uh/6A1enlX8V+h5+Y/wANev6M8nJr3DxBpoAaTQMSgBCwUUm7DSuWbGwnvSG/1cP989/pVU6Uqu2w5SUN9zobS2hso9sK8nqx6mvQp0Y01oc8puW5I0laEkTSe9IZGZKQyNpKQEZkoAjL0gGmSgBhekA0vQA0vQMQtQAm6gA3UDANSuAoNACg0Ae218MfYi0AKKAFoAKAAUALQAtIQopgLQAUAApALQAtIBRQKwtABQAoouA4UgHCi4DhSEPApAPUUgOI+L3GhWH/AF9f+yNXqZT/ABZeh52Zfw16/oeTNXtniDTQMaTRcYwsSwVAWY8ADvUtlJGvp+kYxLe8nqI+34110sLf3qn3GcqttImuzgDA4A7V27GBE0nvQMjaSkMiaSkBG0nvSGRs9ADC9IBpelcBhegBpekA3dTAN1IYm6gA3UAGaAHA0ALmgBc0Ae44r4Y+yFoABQAtABQACgBaQC0CFFAC0AFACikAUALQAooAWgBRSAUUAOpCHCkA8CgQ8CkBIopAcH8YuNG00ety3/oBr1cp/iS9P1PNzP8Ahx9Tyc17Z4gxjSGEEMt3L5cC5Pc9hRFSm+WJekVdnRWNjDYpkfPKermvRo0I09ephOo5ehLJJ71uQQPJSAiZ/ekURNJUgML0DIy9IBhek2A0vSuA0tQA0tSAQtQAm6gYmaADJpgLmgBc0ALmgBc0AKGoA92r4Y+yCgBaACgAoAUUgCgBaQhRQAtABQACgBaAFpDFFAhaAFFADhSAcBQIeKQDwKQD1FIQ9RSA4H4ynGkaYO5uW/8AQDXq5T/El6fqeZmn8OPqeTOcV7dzxUrk1hYS375B2Qjq/r9KqlSlVemxUpqG+50MMMVpEI4VAX9TXpwpxpq0TmlJyd2MkkqwK8j0hkDPSbGRM9SMjL0rgML1NxjC9K4DC1ADS1ACFqQCbqADdQAZpgGaQC5pjFzQAZouAuaAFzTAM0Ae9V8KfZC0AFABQACgBaQCigBaAFoEFAwpAAoAWgBaAFFAC0gHCgQ6kA5aBDxSAeKQDxSESLQB5z8aZANP0pc8iaQ/+OivVyl2nP0PMzRe5H1PN9M0x70iWbK24/Nq9+jRdV3ex4k6ihotzoflhjCRqFRRgAV6UYqKsjmu27srSyUxorSSUrlFd3pNlIhZ6m4ETPSuMYXqbjGFqAGlqQDS1ADd1ABuoATNABmgBc0AKDQAuaYCg0gDNAC5pgLmkMXNMD32vhj7IKQBQAYoAWi4BQAtACigBaQBQAUALQACgBaQCigQooAcKQDhQA8UhD1pAPFIB60CIrq5WCMknBpN2KjFs4Tx/pk99aaRfXiFLOSWXylPWTAXJ+nP417eQ0va1Jt7JI8jO60YRjTjucxIwUBVGAOgFfWLTY+bRUlkplIqSPSuUitI9SyiB3pNjIWepbGMLVIxhagBpalcBpai4CbqVwEz60XAM0XAM0AGaYC5ouAuaAFzTAM0AKGoAUGgBQaYC5pAfQFfDH2gUCFoAKAFoAKQC4oAWgAoAKADFIBRQAtACigBaAFpCHCgBwpAOFADxSAetIRHczrBGSTzSbsOKuaHg3w0/iCYahqSldMRvkjP/Lcj/wBl/nWd7mOKxSoL2cPi/L/glH9oRljTw/GgCqonwo6Afu6+m4d/5e/L9T5fHfZPFJnr6S5wopyvSuUirI9JstFd3pNlIgd6kZEXpFDC1K4DS1K4DS1K4CZpXAN1FwEzQAuadwFzQAtFwCncAzQAoNAC5pgLmgAzQAtAC5pgfQlfDH2YUAGKQC4oAKAFoAWgAoAKQBigBaAFxQAUAKKQC0AOFIBRQIcKAHikA8UhDZZViQk0hpXH+F9Dl8UamWl3JpkDfvX6eYf7g/r6VD1JxOIWGjp8T/q56+iRwQpFCipGgCqqjAAHQUjwtZO7PDv2h5/+JtokWfu28r/m4H9K+p4eVqdR+aPPx28UeOSv1r6A40VJXqblIqu1S2WiB2pDIWalcpETNU3GMLUrgJmlcBM0XATNABmgBc0ALQAZoAXNMBc0ALmmAZouAtO4Bmi4C5oAXNAADTA+iMV8MfaBQAuKBBigAouAtIYUAGKAFoAKAFFABQIUUgFoAUUgFoAdQA4UhDhQArMFUk0gWpX0rTrjxJrK2NsxSJfmnlA+4n+J7VDd9CqtWOHp88t+h7Lp9lbaXYRWdlGI4IhhVH8z71J8/OcqknKT1Y6RqASPn79oO4DeLLCIHmOxBP4yP/hX1uQK2Hm+7/Q87HfHFHk0r17LZyFWR6RSRWdqm5SIXapuUiFmpNjGFqm4xpNK4Dc0XAM0AFABRcAzTAUGgBc0ALmmAZoAWgBc0XAM0wFzRcBc07gLmgAouB9F4r4e59qLSEFABQAU7gFFxC0AFFwFxRcAoAXFIAxQMWgQUALSAcOtACikA8UCFzikBQuHnu7mKyskMlzM2xFHr/hUtmqtBOctkeveE9Bh8PaStumHuH+eeXHLv/gOgqDwcTiHiJ8z26GpI1BkkVZHoLR82fHG4834g3S5/wBVbwx/+O7v/Zq+xyVWwafds8jG/wAa3kecSNXpnOirI1JstFd2qWyiFmqGyiJjSuMYTSATNACZouAZouMXNAhc0AGaYC0AFABmgBc07gLmgAzQAuaYBQAuaAFzQAuaYH0fivhj7UTFABimFhcUCDFIApgLQAUCFxQAtABQAUgDFAC4oAWgBfSgBfWkA4UAVdQuRBExzipbsXCN2egfDbw2dPs/7Uv48X9yPkDDmKPsPqeprM8rH4n2kvZw2X4s7KRqDhSKsjUForSNQWkfK3xTvPtfj/XZAeFuPKH0RQn/ALKa+3y2PJhKa8r/AHniYp81aRxsjV1tmSRWdqlstEDtUNlIhY0rlDCaVwGZpXGJmi4C0AFABTAM0ALmmAuaLgGaAFzTAWgQUAFABmgBc0wFzQAuaADNMD6UxXwx9sFABQAYoEGKAFoAKLiCi4BQAUAFABigBcUCFxQMWgQtAC96QDJZBGhJoGlc0vAWgnXtV+3XaZ061bIB6SyDt9B3rNsxxuI9hDkj8T/BHrrtx7UjwkitI1BokVZGoLSIC2XGemaTZokfG+u3f2zWNQuic+fcyy59dzk/1r7+lHkpQj2SPnZvmnJ+ZlSNTbBFd2qWWiBzUtlEbGlcYwmpuMaTQAZouAZpgLmgAzQAUwFoAKYBmgBc0AGadwFzQAuaADNAhaYBQAUAGaYH0zivhj7ewYoAMUCDFABigAxQAYoAMUAGKBC4ouFhMUBYXFAhcUAGKAFoABQIXoOtAFaC0n1rVYNOtM75T8zf3F7tUt9C5TjRg6kuh7bptjBpenwWVogSGJdoA7+9QfOTnKpJzlux0jUDSK0jUFpFaRqRokYfivUP7N8Nate5wbe0lkU+4U4/XFa0Ie0qxh3aHN8kHI+PnOAF9BivvZvU+ajsV3as2zREDtUtlohY1DYyNjSuMaTSGJQMM0wDNAC5oEGadwFzRcAzTAXNABmmAUALQAUAGaYC5oAM0wFzQAuaADNMR9OYr4U+4DFMAxQIKAFoEFABQAUAFACUwCgBaQgxTAMUALigQCgCvezCKM1Ldi4Ruz0j4deHzpWnG9u0xfXQBIPVE7L/AI1B4+PxHtZ8kdkdVI1BxoqyNSNEVpGoKSK0jUGqR5/8ar8Wfw9v0zh7uSO2UfVgx/8AHVNejlMOfFR8tfuOfGy5aEvPQ+Y5G5NfWt6nhIruahstELmpbLImNTcZGTSGJmgYmaAFzQAZpgLQAUAGaYBmi4C5p3EFFwFzRcBaYBmgAzTAWgAoAKYBmgBc0AfUGK+GPtwxQAYouAYoAMUXEGKYBigLBigAxQIMUAGKAFAoAXFFxBii4BigBHO1c07gkangXRf7Z1v7VcLmztDuIPR37D8Ov5VDZz42v7Gnyx3f5HrDtxSPDSK8jUjRIqyNQaJFaRqRaRWkag0SPFP2i9QxHoenBuplumH0wi/zevfyKnrOp2Vjzczl7sYHh0jZr3mzzEiBmqGykRMagoiY0hjTRcYlAxuaLgLQAUwFzQAA0ALmgAzTAWgAoAM0wFzQIKYC0XAKdwFzQAZoAKYC0AfUuK+FPtwxQAmKLgGKYBilcAxTuAUXAKLgFABincVgouFgxRcQYoAMUAFFwIvKlu7iK1tl3TSttUf1pXHdQTnLZHsOhaZFo2lQ2cPVRl27sx6mkfO1qrrTc2WpGpEpFWRqDVIrSNSLSK0hoLRWc0jRI+ZfjZqv9oePr2NWzHZIlov1A3N/48xH4V9blNP2eFT/AJnc8PHz5q1ux565rtbOVIgc1JaI2NK4xhNK4xpNIYmaACncYUAFAgzTAWgAoAKYC5oAM0ALmgAzTAWgApgFAC5oAKdxWFzQAZpgfVWK+EufbhincAxQAYoATFABigAxQAmKADFFwDFFwDFO4BigBaBBii4DZDhTRcLHZfDnRwA+q3C/M3yQg9h3P40jzcxr/wDLqPzO2kag8xIrSNQaJFWRqRokVpGpFpFdzQaJFW5uI7aGSedgsMSmR2PZQMk/kKEm3ZF7K7PjTVb2TUNQur2Y/vLmV52z6sxb+tfcxgqcI010SPl5S55Ofcz3akUiJjSuMjJqShhpXGITQFhM0DDNAC5pgFAC0AFMAoAM0CFzTAKACgAzTAWgAzQAuaYBRcBaLgFMAzQB9YYr4U+2DFACYoAMUXAMUXATbRcAxTAMUAJtoAMUXATFFwDFFwFxRcBCKLgT6Vp0mq6lFapnaTmQjsvf/Cjczq1VRg5s9chiS2t44YQFRFCqBTPnW3J3ZHI1BaRWkakWkVpGpGiRXkNBokVnNBaRwPxo1b+y/AN8iNiW+ZbNPXDcv/44rfnXfllL2uJinstfuOfG1PZ0W110PmCRsmvqpO7ueAkQs1S2WiJjUNjGE0hjSaBoaTSGJTAM0XAXNABmmAuaADNAC5pgFAC0AFMAouAZpiFzQAUAFAC5pgGaAFzQAZpgfWuK+GPtgxSATFABimAYoATFABigBNtABigBMUAGKADFAC4oAY/AzQCPQvBWlCw077RKuLifk57DsP8APvTR4uNre0nyrZG9I1M5Eiu7UjVFaRqRaRWkag0SK7tQWkQMaVy0jwL9ofWhPrWn6PE2Vs4jPL/10k6D8FGf+B19DktLlhOs+uiPJzOpeUaa9Tx5zXrNnnIhJqShjGpBDCaChpNIYlABQMKACmAUAFAgzTAXNFwDNMBc0ALmgAzTAWgAoAKYBmgAzTuIWgAoAKYH13ivhT7YMUAJigAxRcBNtABtpgGKQBtpgJii4Bii4CYouAYouAYoA0vDmmnUtTRXH7mM739/QU0c+Kreyp3W7PSSQqhV4A4ApnhLzIHag0SK8jUi0itI1BokV3NBaRA5pFpELuqgtIwVAMsx6AdzS9Cz478Waw2u+ItS1RicXU7SID2Tog/BQor7OhT9hRhT7I+Zqz9rUlMxHaquSRk0rjIyaVyhuaQxpoGBNACZoAWgAzQAUwFoAKACmAUAFMQuaADNAC5pgGaAFzQAZpgLQAUwCgApiPsDFfDH2gYoGJigLhtpAGKBibaAExQAEUAGDQAmKADFACYoACM8Dk+lFwPQvDmnjTtNUMP30nzOferR4eKq+1npsaDtQYpEEjUFpFd2oNEiu7Ui0iBzSLSIWNItI8/+NWu/2N4GuYon23Oot9jjx1CkEyH/AL4BH/AhXfltD22ISey1ZzY2r7Ki7bvQ+X3bk19RKV3c8BKyIWNRcoYTSKGE0hjc0DEJoAQmgBM0ALmmAZoAXNABmgBc0AGaAFpgFABQAU7gFFwDNMQtFwDNMBc0AGaAFzTAKLgfY+2viD7K4m2kFxNtAXE20DDbQAmKQ7ibaADb6UBcMUAJigYmKAArQBreGNP+16gJHH7qHk+57f5+lNI5cXV5IWW7O4dqs8dIgdqC0iB2oNEiB2pFpEDmkWkQsaRaRExoKPm749+IBqfi4adA2bfS0MJweDM2Gf8ALCr9Qa+jyqj7Ki6j3l+SPEzCrz1FBdDy9jXoHERMaQxhNK5SGk0DsNJouOwlILBTuAUAFMAoAM0CDNAC5pgLmgAzQAZoAXNFwCgBaYBQAUAFMAzQIM07gLmi4BmncD7NxXxB9gG2gLibaBibaQBtoHcTFAAVoAQrQMTFIA20DExQABSzBVBLE4A9TQF7as7rSbVbGxSMffIyx9TWi0PFrVPazuWHemZpEDtQaJELtSKRC7Ui0iFjSLSI2NIsxvFOsxeH/Duo6tMARawl1U/xv0RfxYgfjWlKm6tRU11JqTVODm+h8c3c8txPJNcSGSaRzJI5/iZjlj+JJNfYtKKUI7LQ+Zu5Nye7KrGouOwwmlcoaTSGMJouMQmlcBM0XGGaLgFO4C5p3AM0XELTAKACmAUCDNAC0AFMAzQAuaAFBoAM0AGaAFpgFABTAKAPtLFfFH1tw20hhigBNtAxMUAGKQw20BcTbQFxCtAxNtABtoHc1PD1p5t15zj5I+nuacUcuKqcseVdTp2etDzkiFmpXKSImakWkRM1IpIhY0XLSI2NIojNIaPCv2ivEm6ez8O2z/LEBd3WD1YgiNT9BlvxWvcyihbmrv0R5eZVb2pL5nh7NXrtnmWI2NTcoYTSuMaTSuMZmlcYmaLgFAwzQIM07gKDQAZp3AXNFwDNO4C5p3AKYhaLgFABTAKADNAhc0wDNAC5oAM0ALmgAzQAUAfa+K+LPrA20BcTbQFw20DuGKQXDFAXE20DDFA7iYpBcTbQAqxl2CqMknAoBux1NjCtrapGvXqT6mtVoeZUlzyuSM9ArETNSHYjZqRaRGxpFJEbGgoYaQyjrOpW2j6Vd6jfvstLWJpZD3wB0HueAPciqhFzkox3YSkoJyZ8beIdWuNb1q91K8/4+LuVpnGchc9FHsBhR7CvroU1RhGkuh83ObqTc31MtjQ2IYTSuMYTU3KGE0AJmgYhNAwzQAZoAXNMQUALRcAzTuAuaBBmncBc0XAM07gLRcAzTuAtO4goAKACmAUCDNAC0wCgBc0AfbmK+LPq7htpAG2gBNtABtoANtAwxRcBMUh3ExQAbaBl/SYA0plb7q8D600YV52VkaxarOSwwtSKSGFqRViMmgaQwmkUMJoGNNIaPEP2jPE+xLPw3avy2Lu8we3/ACyQ/iC34LXs5TQ1deXTRep5mY1tFSXXc8FZq9e55tiNjSuOwwtSHYYTSGNJoATNAwzQAUAFMAzQAuaAFzQAuaADNMQUALQAZpiFzQAZp3AXNFwDNO4C5p3AM0XEFO4C0AFMAoAM0CPuLFfFn1QYoAMUDE20BcMUgDFAxMUAGKADFIAVCzAL1PFAXtqbMCCKJUHarRxyfM7ilqBJDS1BVhhNIdhpNAxpNIY00DsZviPWLXQNDvdVvyfs9rGZGUdXPRVHuxIA+tXTpyqzUI7smpNU4uT2R8ba/q11rWr3eo6g++6upDLIR0BPQD2AwB7AV9XGEaUFSjsj52U3Uk5y6mYxoBEZNIaQwmlcYmaLjGk0rgJmi4wzRcAzTuAoNFxBmncAoAWmAUALmgABoAXNACg0CDNMBaACgAzTELmgAzTuAuadwDNFwFzTuAZouIWncD7kr4w+oCgAxSGGKADFABigAxQO4mKQBigCxZx/MXPbgU0Z1H0Lm6mY2EJoHYaTQMQmlcY0mgBKQxtAz58/aG8XreahF4csZMwWTiW7YHhp8fKn/AAcn/ab/Zr3crw/JF15ei/zPJzCtzP2S+Z4qzV6NzgI2NK5QwmlcYwmlcBCaQxCeaBiUXAKYBmgAzQIWncAzRcBaYC5ouAZp3EFAC0wDNAC5oAM0ALmgBc0CDNMBaACgAzTELmgAzTuAuaLgfdGK+OPpwxQAYoAMUAGKAFxSAMUAGKBgFycCgLlxPlQAUzF6hmi4goGJmgBuaQwoGIaBnL/ABH8Up4Q8KXOojabx/3NpG38cxBwSPRQCx9h710YXDvEVFBf0jDEVlRg5Hx7dTyTzSSzSNLI7F3dzlnYnJYn1JJNfTtpWjHZHgK71e7K5NRcojY0rjGk0hjc0gGk0DDNIBM0wFzRcAzQAtO4BQAUwDNAC5oEGadwFzRcAzTuAuadxBmgAoAWmAZoAXNAC5oAM0ALmmIM0ALQAZpiPuzFfHn0wYoAMUAGKQwoAMUAGKADFAEkS9zQiZMlzTJDNK4WEzQAUDCgBKQxHKqrM7KqKCWZjgADqSfSgD5I+LfjRvF/iV5bd2/su1zDZJ0ymfmkI9XIB/3Qo9a+lweH+rUtfilueDia3tqmmyOEJre5iMLUhjGNK4xhNK4wzSuMaTQAmaADNAC5oAM0wFzQIWncAzRcApgFAC0AFMAzQAuaYgzQAuaLgGadwFzTuIKLgLQAZpgGaAFzQAuaAFBoA+78V8hc+kDFAXFxQAYoAMUAGKAExSAAKB3JQMCmQLQAUDCkAUAFABQB41+0D44XT9Pfwxp0n+l3SBr11PMUJ6R/7z9/Rf8AeFenluF55e1nsvxZ52OxHKvZx3Z84O5ZiTXsyld3Z5aViMmpuUMJpXGNJpDGZpDAmgBM0DDNABTAWgQUAGaADNFwFzQAZpgLRcQuadwDNABmmAtABQAUwDNAhc07hYM0XAXNO4Bmi4C5p3EFAC0AGaYH3pXyB9GFABQAUAGKVxhigBcUAKooAdQIKACgAoAKACgDnPH3iq28H+GrjU7jbJP/AKu1gJx50pHA+g6k9gD7Vth6Eq9RQiY16yowcmfG+rahc6nqFxeXszT3M8jSyyt1dick/wD1uwwO1fS2jCKhDZHg3cm5S3ZRY1NxjCaQxpPFIY0mgBuaVxiZoGGaLgGaAFzQAtMAzQAZoAWmAUCCgAzQAZoAUGgBc0xBmi4C5p3AM0AFMBaACmAZoAM0xC5ouAZouAuadwFzRcD71xXyJ9EGKBi0AFAC4oAMUAFIBaYC0CCgAoAKACgCG8uoLGznu7yZILWBDLLK5wqKBkk/hQk27ITfKrs+Qfih41uPGXiF7s74rGEGKzgb/lnHnqR/fbAJ/Adq+jw1BYany/ae/wDkeDXrOvO/RbHFE1qZjCaQ7DCaTYxppXGNzRcBDSuMKLgJRcBaLgFMAzQAuaYBmgBc0wFzQAZoAKAFpgFAgoAM0AGaAFzTAXNAgpgLmi4BmmAtABQAUwDNAC5oEffFfJn0YuKACgBaQBigAxTAWgAoAKQBTAKQBTEABJwBk0AfOHx5+Ig1W4k8O6NLu023f/SpkPFzKp+6D3RSPxYeijPs4DC8i9vPfp/meRjMRzv2Udup4qzc5PWu9u5x2IyalsYwmlcoQmlcBhOaVwCkMTNAxKACgQU7gLmi4BmncAouAtMAouAuaADNO4C5oAM0wFzQAZoAKYC0AFAgoAM0ALmgAzTAXNAgp3AXNABmmAtAH33ivkz6IMUALTAKACgApAFABQAUAFMAxSCwuKAPIvjp8RF0Gxl0DR5yNXuExcSxnm1iYdAe0jDp6A56la9DA4X2r9pP4V+PkefjMVyLkhuz5idsn0A4AHavZlK7ueWlbQjJqLlDCaVwGE1NxiE0XGITSuA3NABQMM0AGaAFzQAUwCgAoELmmAUXAKYC0XAKdwCgBc07gGaLgLmgApgLmgAoAWmAUBYKBBQAZoAXNABmmAuaBH39Xyx9EFIAoAKACgAxQAYoAXFABQAUCCkBwnxa8ew+CtFC25STWrtSLWJuRGOhlcf3R2H8R46A468JhnXnbot2cuKxCox03Z8jX13Ne3UtxcyyTTSuZJJJG3M7E5LE9yTXvOySjHRI8bVu73ZVJqGyhhNK4xhNSA0mi4xpNK4CZoGFABQAUAFABmgBc0AFMBc0AFABTAKACgQU7gLmi4BmncBaACmAZouAuadwAGgBc0AFMBc0AGaAFpgFAgoAKAP0Ar5Y+hCmAYpAGKAFoAKACkIKAFxQAUwOY+IXjGx8FaC19eYlupMpaWobBnfH6KOrN2HuQDtQoSrz5YmNevGjG7PjzxLrt94g1e51HVJzPdTtudug9lUdlA4A7D8TXvxjGlH2cNvzPEcpTlzz3MljSbAYTSGNJpDGk0gGk0AJSGJmi4CUALmgAzTAKAFoAKACgAzRcAzRcBaYBmgBc0AGaACmAUAFAhc07gGaLgFMBaLgFMAzQAoNO4BmgBc0wDNAC5oAWgD9Aa+XPoAoAKQgoAMUALigAoGFMQUgMbxf4k07wpoc2qatIREnyxxrjfNIeiIPU/kACTwK0pUpVZKEFqzKrVjSjzSPjzx14s1Dxdrs2pakwDN8kcSE7IYweEX27k9Sck+30FKlGhDkj82eJUqSqy5pHNE02xDCakY0mlcY0mgBpNIYUgEJoASgAoAKACmAUALQAZpgGaACgBaACgAoAM0XAXNABTAXNABmi4BTAKACgQtMAzRcAzTuAtFwCncAzQAZpgLmi4C5oA/QOvlz3xcUAGKYwpCCgAoAWgAxQBQ17V7HQdJudT1WcQWduu53PJPYKo7sTwB3NVCDnJRitWROahHmkfH3xL8b3vjTXmu7jMVpFlLW13ZECZ/VzgFj9AOAK+goUVhocq+J7v8AQ8OtVdeXM9uhxpNVckYTSuA0mkMaTSGNNK4xDQAhNIBKACgAzQAuaACmAUAFABQAUwCgBaADNMAoAWgAoAKADNABmgBc0XAKYC5oAKACmAUAFAhaYBmi4BTuAtFwCncD9Bq+YPfCgBcUAGKADFABii4C0AV9QvLbTrG4vb+eO3tLdDJLLIcKijqTQk5OyJlJRV2fI3xb+Id1401cLF5kGj2zH7LbNwfQyP8A7ZHb+EcDuT7+Gw6w0bv4n+B4teu68v7p54TWrZkMJqbjGk0gGk0XGNzSuAhNIYhNIBM0AFAC0AFMAoAKACgBc0AFMAouAUAFABTAKACi4C5oAM0wCgBaACgAoAM0AGaLgLTAKAFzQAUAFMAoAKBC5p3A/QjFfNHvhQAtABSAKACgBkskcMUks0iRxRqXd3YKqqBkkk9AB3pibsrs+VPjP8TJPFt7/Z+lO8eg275jHQ3Lj/lqw9P7qnp1PJGPbwmG9gvaT+L8v+CeNia/tnyx+H8zyknJzXS2YDCakY0mkMYTSAQ0rgIaVxiGkAlABQAUwCgAoAKAFoAKACmAUAFABQAuaACmAUAFABQAUwCgAoAWgAzTAWgAoAKACgAzQAuaACmAuaADNABmgD9Cq+bPeCgAoAKACgAAJIAGSaAPmz47/E1dVkl8OaDMG0yNtt3cIeLpwfuKe8akcn+Ij0HPsYLC8i9tU+S/U8nF4jnfs4bdTw5mLHJOTXa3fVnGlYYTUFDSaQxhNIBCaVxjSaQCZpAFABQMKBBTAKACgAoAKdwCgAoAM0ALmgAoAKYBQAUAGaAFzTAKACgAoAKACmAUALQAZoAKYC0AFABQAUAGaLgLQB+hdfOHvBQAUAFABigDxH4+fEn+zYZ/DOhT4vJF239wh5hUj/VKf75B+Y/wg46nj0sFhef97U2X4nnYzE2/dw3Pmp2LHJ/Idq9OUru7POSsrEZNQ2MYTSuUNpAITSbGNzUgJQAUhhQAUwCgAoAKACgQUwCgAoAKACgApgFABQAZoAXNABTAKACgAoAKAFpgFABQAUAFMAoAKAFoAM0wCgBaACgD9DMV84e8LQAUAFAHnHxn+IS+DNHFrp8iHXbxD5IOD9nToZSPXOQoPU5PRTXXhMN7eWvwrc5MViPZRst2fItzO88rySuzuxLMztksSckknqSeSa9qTWy0SPIS6vcgJqRjCakoaaQDSakY0mkAUhhQAUAFABQAUAFABTAKACgAoAKACgQUwCgAoAKACmAUAFABQAZoAWgApgFABQAUAGaAFpgFABQAUAFMAoAKAFoA/Q6vnT3goAKAMHxx4osvCHhy41a/+fZ8kMAbDTykHag/IknsAT2rWjSlWmoRMq1VUo8zPi3xPrl74h1q71PU5fNu7l97sOAOwVR2UDAA7AV78YxpRVOGy/E8Nyc5Oct2ZBNJgMJqRjCaQxpqWMaaQBSAKBhQAUAFABQAUAFABQAUAFABQAUwCgAoAKACgQUwCgAoAKACgApgFABQAZoAM0ALmgApgFABQAUALmgApgFABQAUAfofXzx7wYoAjuZorW3luLmVIYIkMkkjnCooGSxPYADNPcTdldnx38XfHc3jTxE00RePS7bMdlC3BCd3Yf3nwCfQADsc+9h6H1eFn8T3/wAjw69b2879FscATWjMxhNS2MYTSGNJqQEJpDEpAJQAlAxaADNABQAUALQAUAFABQAUAFABQAUAFABQAUwCgAoAKACgApiCgAoAKACgApgFABQAZoAXNABQAUwCgAoAKAFzQB+iFfPnvBSA+f8A9pDx3tz4T0yXgbX1F17nhkh/kzf8BHqK9XL8P/y+l029f+AeZja937KPzPnhmJJJ6mvQbucJGTUMdhpNIY0mpGNJpAJSASkAlABQAUAFABTGFAC5oAM0AFABQAtABQAUAFABQAUAFABQAUAFMAoAKACgAoEFABTAKACgAoAKACmAUAFABmgBc0AFMAoA/RGvnj3jl/iV4si8GeE7nU22Ndt+5tIm6STEHGf9kAFj7KfUVth6LrVFBGGIqqlByPii/upr27muLmV5p5XaSSRzlnYnLMfckk19A7JKMdkeIr7vdlQmoKGGpGNJqWxjSaVwEpAJSASgAoAKACgAoAKACgAoAKACmMKAFzQAZoAKAFoAKACgAoAKACgAoAKACgAoAKYBQAUAFABQAUCCmAUAFABQAUwCgAoAM0AfomBk4HWvnz3z5A+OPjX/AIS3xZIlnLu0mw3W9rg8Pz88v/AiBj/ZVfU17uEo+xpXe8vyPDxNX2tTyR5oxrZmQwmpGMJpMBpqRiGkAhpAJQAUAFABQAUAFABQAUAFABQAUAFABRcAoAKYxaADNABmgBaAEoAWgAoAKACgAoAKACgAoAKLgFMAoAKACgAoAKBBTAKACgAoA+y/jx4t/wCEZ8FSW1rIU1LVN1tEVOCkeP3rj6AhR7uD2ry8FQ9tU12WrPTxlb2cLLdnyA7ZYnGB2HpXtyfM7nkJWViI1myhpNSMYTUjG0gENIBKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgApgFABQMWgAoAM0ALQAUAFABQAUAFABQAUAFABQAUAFMAoAKACgAoEFAHpnxu8UHxN47vpIpN1jZn7HbYPGxCQzD/efcfpt9KMLT9lRXeWvy6FYmp7Sq30R54xrVmYw1IxhqWMaakBppAJQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFMAoGLQAZoAKAFoAKACgAoAKACgAoAKACgAoAKYBQAUAXZW3OTXRN3ZlFWRCTWbKGGpGNJqRjaQCUgEoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgApjFoAM0AFAC0AFABQAUAFABQAUAFABQAUAWWrZkDDUjGGpYxpqQGmkAlABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUxhQAtABmgAoAWgAoAKACgAoAsE1qyBhqRjDUjGmkAhpAJQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFMAoGLQAZoAKAFoAKAJzWjIGGkxjDUjGmpASgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgApjFoAKAJzWhIw1LGNNSA00gEoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAouBO1aMQ00hjDUsBKQCUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUATmtBDDUsY2pAbQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUATmtBDDUjGVICUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFAExrQQ01LGMNSAlABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQBMa0ENNSxjDUgJQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUATGtBDDUsY01ICUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFAE57VoIjNSxjTUgJQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAf/2Q==","e":1}],"fonts":{"list":[{"origin":1,"fPath":"","fClass":"","fFamily":"SF Pro","fWeight":"","fStyle":"Semibold","fName":"SFPro-Semibold","ascent":70.458984375},{"origin":1,"fPath":"","fClass":"","fFamily":"SF Pro","fWeight":"","fStyle":"Regular","fName":"SFPro-Regular","ascent":70.458984375}]},"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Layer 9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[41.865,243.139,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[2.197,1.269],[2.537,-0.001],[2.196,-1.271],[1.265,-2.2],[0,0],[0,0],[0.003,1.27],[-0.634,1.1],[-1.1,0.635],[-1.27,-0.002]],"o":[[0,0],[-1.267,-2.199],[-2.197,-1.269],[-2.537,0.001],[-2.196,1.271],[0,0],[0,0],[-0.639,-1.098],[-0.003,-1.27],[0.634,-1.101],[1.1,-0.635],[0,0]],"v":[[0.004,-1.81],[12.512,-1.81],[7.224,-7.104],[-0.003,-9.039],[-7.229,-7.097],[-12.512,-1.799],[-6.257,9.034],[-6.247,9.039],[-7.226,5.425],[-6.262,1.806],[-3.615,-0.844],[0.004,-1.81]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.886274516582,0.227450981736,0.176470592618,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Layer 8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[41.869,248.558,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-3.161,0],[0,3.161],[3.161,0],[0,-3.161]],"o":[[3.161,0],[0,-3.161],[-3.161,0],[0,3.161]],"v":[[0,5.723],[5.723,0],[0,-5.723],[-5.723,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Layer 7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[37.772,252.173,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,-2.537],[-1.27,-2.197],[-2.199,-1.266],[-2.537,0.005],[0,0],[0,0],[1.099,-0.637],[1.27,-0.001],[1.1,0.635],[0.634,1.101]],"o":[[0,0],[-1.27,2.196],[-0.001,2.537],[1.27,2.197],[2.199,1.266],[0,0],[0,0],[-0.632,1.102],[-1.099,0.637],[-1.27,0.001],[-1.1,-0.635],[0,0]],"v":[[-2.164,-0.001],[-8.418,-10.832],[-10.358,-3.607],[-8.42,3.62],[-3.126,8.906],[4.104,10.832],[10.358,-0.001],[10.358,-0.013],[7.717,2.643],[4.101,3.618],[0.483,2.65],[-2.164,-0.001]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.156862750649,0.603921592236,0.282352954149,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Layer 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[49.091,252.167,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-2.198,1.268],[-1.268,2.198],[0.003,2.537],[1.273,2.195],[0,0],[0,0],[-1.101,-0.633],[-0.636,-1.099],[0,-1.27],[0.637,-1.099]],"o":[[0,0],[2.537,0.002],[2.198,-1.268],[1.268,-2.198],[-0.003,-2.537],[0,0],[0,0],[1.27,-0.004],[1.101,0.633],[0.636,1.099],[0,1.27],[0,0]],"v":[[-0.962,0.006],[-7.216,10.838],[0.012,8.906],[5.302,3.615],[7.233,-3.614],[5.286,-10.838],[-7.222,-10.838],[-7.233,-10.832],[-3.613,-9.872],[-0.961,-7.228],[0.01,-3.611],[-0.962,0.006]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.984313726425,0.75686275959,0.086274512112,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[42.055,248.744,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.971,0],[0,0],[0,-4.971],[0,0],[4.971,0],[0,0],[0,4.971],[0,0]],"o":[[0,0],[4.971,0],[0,0],[0,4.971],[0,0],[-4.971,0],[0,0],[0,-4.971]],"v":[[-9.5,-18.5],[9.5,-18.5],[18.5,-9.5],[18.5,9.5],[9.5,18.5],[-9.5,18.5],[-18.5,9.5],[-18.5,-9.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"cdds.sys.color.illo-neutral-min","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[187,90.306,0],"ix":2,"l":2},"a":{"a":0,"k":[-2.308,-117.694,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-6.796,0],[0,0],[0,-6.796],[0,0],[6.797,0],[0,0],[0,6.797]],"o":[[0,-6.796],[0,0],[6.797,0],[0,0],[0,6.797],[0,0],[-6.796,0],[0,0]],"v":[[-44.5,-117.694],[-32.194,-130],[27.578,-130],[39.884,-117.694],[39.884,-117.694],[27.578,-105.388],[-32.194,-105.388],[-44.5,-117.694]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"cdds.sys.color.illo-neutral-max","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":5,"nm":"Your item is now 15% off","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[68.544,263.417,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"t":{"d":{"k":[{"s":{"s":15,"f":"SFPro-Regular","t":"Your item is now 15% off","ca":0,"j":0,"tr":0,"lh":18,"ls":0,"fc":[0,0,0]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[]},"ip":0,"op":1,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":9,"ty":5,"nm":"Price drop on your tracked product","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[68.544,243.417,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"t":{"d":{"k":[{"s":{"s":15,"f":"SFPro-Semibold","t":"Price drop on your tracked product","ca":0,"j":0,"tr":0,"lh":18,"ls":0,"fc":[0,0,0]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[]},"ip":0,"op":1,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[187,249.447,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-12.776,0],[0,0],[0,-12.776],[0,0],[12.774,0],[0,0],[0,12.775]],"o":[[0,-12.776],[0,0],[12.776,0],[0,0],[0,12.775],[0,0],[-12.774,0],[0,0]],"v":[[-173.5,-18.314],[-150.367,-41.447],[150.367,-41.447],[173.5,-18.314],[173.5,18.317],[150.37,41.447],[-150.37,41.447],[-173.5,18.317]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.891911764706,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Layer 57","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[187,241.22,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[23.102,0],[0,0],[0,-23.102],[0,0]],"o":[[0,0],[0,-23.102],[0,0],[-23.102,0],[0,0],[0,0]],"v":[[136.411,174.78],[136.411,-132.95],[94.581,-174.78],[-94.581,-174.78],[-136.411,-132.95],[-136.411,174.78]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":9.12,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"cdds.sys.color.illo-neutral-max","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Shape Layer 1","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[187.5,208,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[270.881,411.306],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":41,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.192156862745,0.525490196078,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[3.517,65.335],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":14,"ty":2,"nm":"d0c5042ee809ecee02014d5ef422c58b 1.png","cl":"png","tt":1,"tp":13,"refId":"image_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[191.492,343.365,0],"ix":2,"l":2},"a":{"a":0,"k":[187.5,406.5,0],"ix":1,"l":2},"s":{"a":0,"k":[74,74,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":1,"st":0,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"Shadow","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[89,252.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-20.434,0],[0,0],[0,0],[0,0]],"o":[[0,-20.435],[0,0],[0,0],[0,0],[0,0]],"v":[[-61,-126.5],[-24,-163.5],[61,-163.5],[61,163.5],[-61,163.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.850980401039,0.858823537827,0.86274510622,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1,"st":0,"ct":1,"bm":0}],"markers":[],"props":{}}
diff --git a/ios/chrome/browser/toolbar/coordinator/toolbar_coordinator.mm b/ios/chrome/browser/toolbar/coordinator/toolbar_coordinator.mm index b50b9762..61d233c 100644 --- a/ios/chrome/browser/toolbar/coordinator/toolbar_coordinator.mm +++ b/ios/chrome/browser/toolbar/coordinator/toolbar_coordinator.mm
@@ -161,7 +161,7 @@ startDispatchingToTarget:self forProtocol:@protocol(FakeboxFocuser)]; - if (IsBestOfAppGuidedTourEnabled()) { + if (IsBestOfAppGuidedTourEnabled() && !IsChromeNextIaEnabled()) { [self.browser->GetCommandDispatcher() startDispatchingToTarget:self forProtocol:@protocol(GuidedTourCommands)]; @@ -763,20 +763,14 @@ #pragma mark - GuidedTourCommands - (void)highlightViewInStep:(GuidedTourStep)step { - if (IsChromeNextIaEnabled()) { - // TODO(crbug.com/483995303): implement this. - NOTREACHED() << "Not implemented yet"; - } + CHECK(!IsChromeNextIaEnabled()); for (id<GuidedTourCommands> coordinator in self.coordinators) { [coordinator highlightViewInStep:step]; } } - (void)stepCompleted:(GuidedTourStep)step { - if (IsChromeNextIaEnabled()) { - // TODO(crbug.com/483995303): implement this. - NOTREACHED() << "Not implemented yet"; - } + CHECK(!IsChromeNextIaEnabled()); for (id<GuidedTourCommands> coordinator in self.coordinators) { [coordinator stepCompleted:step]; }
diff --git a/media/gpu/chromeos/gl_image_processor_backend.cc b/media/gpu/chromeos/gl_image_processor_backend.cc index a20c62c..6b3e52ed 100644 --- a/media/gpu/chromeos/gl_image_processor_backend.cc +++ b/media/gpu/chromeos/gl_image_processor_backend.cc
@@ -15,7 +15,6 @@ #include "media/gpu/chromeos/frame_resource.h" #include "media/gpu/chromeos/platform_video_frame_utils.h" #include "media/gpu/macros.h" -#include "ui/gfx/buffer_types.h" #include "ui/gfx/geometry/size.h" #include "ui/gfx/gpu_memory_buffer_handle.h" #include "ui/gl/gl_bindings.h" @@ -107,10 +106,12 @@ std::move(gpu_memory_buffer_handle).native_pixmap_handle()); DCHECK(native_pixmap->AreDmaBufFdsValid()); - // Import the NativePixmap into GL. + // Import the NativePixmap into GL. The imported pixmap can be externally + // sampled i.e. does not provide per-plane textures but provides a unified + // single texture object. return GetCurrentGLOzone().ImportNativePixmap( std::move(native_pixmap), viz::MultiPlaneFormat::kNV12, - gfx::BufferPlane::DEFAULT, frame->coded_size(), gfx::ColorSpace(), + /*plane_index=*/std::nullopt, frame->coded_size(), gfx::ColorSpace(), target, texture_id); } @@ -141,8 +142,7 @@ // Import the NativePixmap into GL. return GetCurrentGLOzone().ImportNativePixmap( - std::move(native_pixmap), plane_format, - plane ? gfx::BufferPlane::UV : gfx::BufferPlane::Y, plane_size, + std::move(native_pixmap), plane_format, plane, plane_size, gfx::ColorSpace(), target, texture_id); }
diff --git a/media/gpu/chromeos/vulkan_overlay_adaptor_test.cc b/media/gpu/chromeos/vulkan_overlay_adaptor_test.cc index 1203d87..4e91d106 100644 --- a/media/gpu/chromeos/vulkan_overlay_adaptor_test.cc +++ b/media/gpu/chromeos/vulkan_overlay_adaptor_test.cc
@@ -2,11 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifdef UNSAFE_BUFFERS_BUILD -// TODO(crbug.com/40285824): Remove this and spanify to fix the errors. -#pragma allow_unsafe_buffers -#endif - #include "media/gpu/chromeos/vulkan_overlay_adaptor.h" #include <linux/videodev2.h>
diff --git a/media/gpu/mac/video_toolbox_h265_accelerator.cc b/media/gpu/mac/video_toolbox_h265_accelerator.cc index 6d3941f..179a3a1 100644 --- a/media/gpu/mac/video_toolbox_h265_accelerator.cc +++ b/media/gpu/mac/video_toolbox_h265_accelerator.cc
@@ -2,16 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifdef UNSAFE_BUFFERS_BUILD -// TODO(crbug.com/40285824): Remove this and convert code to safer constructs. -#pragma allow_unsafe_buffers -#endif - #include "media/gpu/mac/video_toolbox_h265_accelerator.h" #include <array> #include <utility> +#include "base/compiler_specific.h" #include "base/numerics/byte_conversions.h" #include "media/base/media_log.h" @@ -214,7 +210,7 @@ frame_vps_ids_.insert(sps->sps_video_parameter_set_id); frame_sps_ids_.insert(pps->pps_seq_parameter_set_id); frame_pps_ids_.insert(pps->pps_pic_parameter_set_id); - frame_slice_data_.push_back(base::span(data, size)); + frame_slice_data_.push_back(UNSAFE_TODO(base::span(data, size))); return Status::kOk; }
diff --git a/media/gpu/macros.h b/media/gpu/macros.h index 1265c6f..7f9cd4da 100644 --- a/media/gpu/macros.h +++ b/media/gpu/macros.h
@@ -2,16 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifdef UNSAFE_BUFFERS_BUILD -// TODO(crbug.com/390223051): Remove C-library calls to fix the errors. -#pragma allow_unsafe_libc_calls -#endif - #ifndef MEDIA_GPU_MACROS_H_ #define MEDIA_GPU_MACROS_H_ #include <array> +#include "base/compiler_specific.h" #include "base/containers/auto_spanification_helper.h" #include "base/logging.h" @@ -34,20 +30,20 @@ // Copy the memory between arrays with checking the array size. template <typename T, size_t N> inline void SafeArrayMemcpy(T (&to)[N], const T (&from)[N]) { - memcpy(to, from, sizeof(T[N])); + UNSAFE_TODO(memcpy(to, from, sizeof(T[N]))); } // Copy the memory between arrays while ensuring the same size. template <typename T, size_t N> inline void SafeArrayMemcpy(T (&to)[N], const std::array<T, N>& from) { - memcpy(to, from.data(), sizeof(T[N])); + UNSAFE_TODO(memcpy(to, from.data(), sizeof(T[N]))); } // Copy the memory between arrays while ensuring the same size. template <typename T, size_t N, size_t M> inline void SafeArrayMemcpy(T (&to)[N][M], const std::array<std::array<T, M>, N>& from) { - memcpy(to, from.data(), sizeof(T[N][M])); + UNSAFE_TODO(memcpy(to, from.data(), sizeof(T[N][M]))); } } // namespace media
diff --git a/media/gpu/vaapi/fuzzers/jpeg_decoder/jpeg_decoder_fuzzertest.cc b/media/gpu/vaapi/fuzzers/jpeg_decoder/jpeg_decoder_fuzzertest.cc index 28e5914c..6eb0ab6e 100644 --- a/media/gpu/vaapi/fuzzers/jpeg_decoder/jpeg_decoder_fuzzertest.cc +++ b/media/gpu/vaapi/fuzzers/jpeg_decoder/jpeg_decoder_fuzzertest.cc
@@ -2,11 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifdef UNSAFE_BUFFERS_BUILD -// TODO(crbug.com/40285824): Remove this and convert code to safer constructs. -#pragma allow_unsafe_buffers -#endif - #include <stddef.h> #include <stdint.h> #include <string.h> @@ -107,14 +102,14 @@ std::min(std::size(parse_result.dc_table), base::checked_cast<size_t>(proto_parse_result.dc_table_size())); for (size_t i = 0; i < num_dc_tables; i++) { - parse_result.dc_table[i] = + UNSAFE_TODO(parse_result.dc_table[i]) = ConvertToJpegHuffmanTable(proto_parse_result.dc_table()[i]); } const size_t num_ac_tables = std::min(std::size(parse_result.ac_table), base::checked_cast<size_t>(proto_parse_result.ac_table_size())); for (size_t i = 0; i < num_ac_tables; i++) { - parse_result.ac_table[i] = + UNSAFE_TODO(parse_result.ac_table[i]) = ConvertToJpegHuffmanTable(proto_parse_result.ac_table()[i]); } @@ -125,10 +120,11 @@ for (size_t i = 0; i < num_q_tables; i++) { const media::fuzzing::JpegQuantizationTable& input_q_table = proto_parse_result.q_table()[i]; - parse_result.q_table[i].valid = input_q_table.valid(); - const size_t value_min_size = std::min( - std::size(parse_result.q_table[i].value), input_q_table.value().size()); - base::span(parse_result.q_table[i].value) + UNSAFE_TODO(parse_result.q_table[i]).valid = input_q_table.valid(); + const size_t value_min_size = + std::min(std::size(UNSAFE_TODO(parse_result.q_table[i].value)), + input_q_table.value().size()); + base::span(UNSAFE_TODO(parse_result.q_table[i]).value) .first(value_min_size) .copy_from_nonoverlapping( base::as_byte_span(input_q_table.value()).first(value_min_size));
diff --git a/media/gpu/vp8_decoder_fuzzer.cc b/media/gpu/vp8_decoder_fuzzer.cc index 0deaac6..d196ac79 100644 --- a/media/gpu/vp8_decoder_fuzzer.cc +++ b/media/gpu/vp8_decoder_fuzzer.cc
@@ -2,11 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifdef UNSAFE_BUFFERS_BUILD -// TODO(crbug.com/40285824): Remove this and convert code to safer constructs. -#pragma allow_unsafe_buffers -#endif - #include "media/gpu/vp8_decoder.h" #include <stddef.h> @@ -50,11 +45,12 @@ if (!size) { return 0; } + // SAFETY: LibFuzzer guarantees that `data` is valid for `size` bytes. + auto data_span = UNSAFE_BUFFERS(base::span(data, size)); media::VP8Decoder decoder(std::make_unique<FakeVP8Accelerator>()); auto external_memory = - std::make_unique<media::ExternalMemoryAdapterForTesting>( - base::span(data, size)); + std::make_unique<media::ExternalMemoryAdapterForTesting>(data_span); scoped_refptr<media::DecoderBuffer> decoder_buffer = media::DecoderBuffer::FromExternalMemory(std::move(external_memory)); decoder.SetStream(1, decoder_buffer);
diff --git a/media/mojo/clients/win/media_foundation_renderer_client.cc b/media/mojo/clients/win/media_foundation_renderer_client.cc index a0d07eb..18f622f 100644 --- a/media/mojo/clients/win/media_foundation_renderer_client.cc +++ b/media/mojo/clients/win/media_foundation_renderer_client.cc
@@ -320,7 +320,6 @@ // Fatal error or hardware context reset should've already been handled in // MediaFoundationRenderer. Ignore other errors as video can possibly be // seen but displayed incorrectly against the video output area. - // TODO(crbug.com/488378483): Add UMA to check how often this happens. return; }
diff --git a/media/renderers/win/media_foundation_renderer.cc b/media/renderers/win/media_foundation_renderer.cc index 91a2e73..2c4e2b6 100644 --- a/media/renderers/win/media_foundation_renderer.cc +++ b/media/renderers/win/media_foundation_renderer.cc
@@ -68,6 +68,9 @@ constexpr uint32_t kMakeGpuNonActive = 4; +static constexpr char kSetOutputRectHresultUmaName[] = + "Media.MediaFoundationRenderer.SetOutputRect.Hresult"; + // Reported to UMA. Do NOT change or reuse existing values. enum class GpuOrDisplayCount { kUnknown = 0, @@ -1089,8 +1092,9 @@ output_rect.height(), SWP_NOACTIVATE)) { // DRM_E_TEE_INVALID_HWDRM_STATE error is not expected here since the // SetWindowPos API itself does not interact with video and HWDRM state. - DLOG(ERROR) << "Failed to SetWindowPos: " - << PrintHr(HRESULT_FROM_WIN32(GetLastError())); + HRESULT hr = HRESULT_FROM_WIN32(GetLastError()); + DLOG(ERROR) << "Failed to SetWindowPos: " << PrintHr(hr); + base::UmaHistogramSparse(kSetOutputRectHresultUmaName, hr); std::move(callback).Run(false); return; } @@ -1109,6 +1113,7 @@ HRESULT hr = UpdateVideoStream(output_rect.size()); if (FAILED(hr)) { DVLOG_FUNC(1) << "Failed to update video stream: " << PrintHr(hr); + base::UmaHistogramSparse(kSetOutputRectHresultUmaName, hr); if (hr == DRM_E_TEE_INVALID_HWDRM_STATE) { // Fail early to handle hardware context reset cases, which can cause // MediaEngine to enter a bad state and fail all subsequent calls. @@ -1119,6 +1124,7 @@ return; } + base::UmaHistogramSparse(kSetOutputRectHresultUmaName, S_OK); std::move(callback).Run(true); }
diff --git a/media/renderers/win/media_foundation_renderer_unittest.cc b/media/renderers/win/media_foundation_renderer_unittest.cc index 0e40c93d..504b75e 100644 --- a/media/renderers/win/media_foundation_renderer_unittest.cc +++ b/media/renderers/win/media_foundation_renderer_unittest.cc
@@ -13,6 +13,7 @@ #include "base/functional/callback_helpers.h" #include "base/memory/scoped_refptr.h" #include "base/task/single_thread_task_runner.h" +#include "base/test/metrics/histogram_tester.h" #include "base/test/mock_callback.h" #include "base/test/scoped_feature_list.h" #include "base/test/task_environment.h" @@ -229,6 +230,30 @@ task_environment_.RunUntilIdle(); } +TEST_F(MediaFoundationRendererTest, SetOutputRect_ErrorCases) { + base::HistogramTester histogram_tester; + base::MockCallback<MediaFoundationRendererExtension::SetOutputRectCB> + set_output_rect_cb; + + AddStream(DemuxerStream::AUDIO, /*encrypted=*/true); + AddStream(DemuxerStream::VIDEO, /*encrypted=*/true); + + EXPECT_CALL(set_cdm_cb_, Run(true)); + EXPECT_CALL(renderer_init_cb_, Run(HasStatusCode(PIPELINE_OK))); + EXPECT_CALL(set_output_rect_cb, Run(true)); + + mf_renderer_->Initialize(&media_resource_, &renderer_client_, + renderer_init_cb_.Get()); + mf_renderer_->SetCdm(&cdm_context_, set_cdm_cb_.Get()); + mf_renderer_->SetOutputRect(gfx::Rect(0, 0, 100, 100), + set_output_rect_cb.Get()); + + task_environment_.RunUntilIdle(); + + histogram_tester.ExpectBucketCount( + "Media.MediaFoundationRenderer.SetOutputRect.Hresult", S_OK, 1); +} + TEST_F(MediaFoundationRendererTest, IgnoreSubsequentErrorsAfterFirstError) { AddStream(DemuxerStream::AUDIO, /*encrypted=*/false); AddStream(DemuxerStream::VIDEO, /*encrypted=*/false);
diff --git a/media/webrtc/BUILD.gn b/media/webrtc/BUILD.gn index db6c2ae..850fd3e 100644 --- a/media/webrtc/BUILD.gn +++ b/media/webrtc/BUILD.gn
@@ -2,6 +2,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//components/optimization_guide/features.gni") import("//media/media_options.gni") import("//third_party/webrtc/webrtc.gni") @@ -26,11 +27,15 @@ deps = [ "//base", "//build:chromecast_buildflags", - "//components/optimization_guide/core/inference:op_resolver", + "//components/optimization_guide:machine_learning_tflite_buildflags", "//media", "//third_party/webrtc_overrides:webrtc_component", ] + if (build_with_tflite_lib) { + deps += [ "//components/optimization_guide/core/inference:op_resolver" ] + } + public_deps = [ "//third_party/tflite" ] }
diff --git a/media/webrtc/helpers.cc b/media/webrtc/helpers.cc index b3b6780..1308908 100644 --- a/media/webrtc/helpers.cc +++ b/media/webrtc/helpers.cc
@@ -13,7 +13,7 @@ #include "base/time/time.h" #include "build/build_config.h" #include "build/chromecast_buildflags.h" -#include "components/optimization_guide/core/tflite_op_resolver.h" +#include "components/optimization_guide/machine_learning_tflite_buildflags.h" #include "media/base/media_switches.h" #include "media/webrtc/webrtc_features.h" #include "third_party/webrtc/api/audio/audio_processing.h" @@ -23,6 +23,10 @@ #include "third_party/webrtc/modules/audio_processing/aec_dump/aec_dump_factory.h" #include "third_party/webrtc_overrides/environment.h" +#if BUILDFLAG(BUILD_WITH_TFLITE_LIB) +#include "components/optimization_guide/core/tflite_op_resolver.h" +#endif + namespace media { namespace { @@ -150,7 +154,7 @@ // Fuchsia does not use the optimization guide. // Avoid linking the op resolver to keep Fuchsia binary size down. // TODO(crbug.com/450466837): Investigate if this build guard can be avoided. -#if !BUILDFLAG(IS_FUCHSIA) +#if !BUILDFLAG(IS_FUCHSIA) && BUILDFLAG(BUILD_WITH_TFLITE_LIB) if (residual_echo_estimator_model) { optimization_guide::TFLiteOpResolver op_resolver; echo_estimator = webrtc::CreateNeuralResidualEchoEstimator( @@ -163,7 +167,7 @@ LOG(ERROR) << "Failed to initialize neural residual echo estimator."; } } -#endif // !BUILDFLAG(IS_FUCHSIA) +#endif // !BUILDFLAG(IS_FUCHSIA) && BUILDFLAG(BUILD_WITH_TFLITE_LIB) #if BUILDFLAG(SYSTEM_LOOPBACK_AS_AEC_REFERENCE) if (settings.use_loopback_aec_reference) {
diff --git a/mojo/public/rust/bindings/test/BUILD.gn b/mojo/public/rust/bindings/test/BUILD.gn index 95fd20db..398f3da 100644 --- a/mojo/public/rust/bindings/test/BUILD.gn +++ b/mojo/public/rust/bindings/test/BUILD.gn
@@ -37,6 +37,7 @@ ] deps = [ ":bindings_unittests_mojom", + "../../system", "//mojo/public/cpp/bindings", "//testing/gtest", ]
diff --git a/mojo/public/rust/bindings/test/cpp/cxx_shim.cc b/mojo/public/rust/bindings/test/cpp/cxx_shim.cc index 58ee413..af99f2f 100644 --- a/mojo/public/rust/bindings/test/cpp/cxx_shim.cc +++ b/mojo/public/rust/bindings/test/cpp/cxx_shim.cc
@@ -14,18 +14,18 @@ namespace bindings_unittests::mojom { std::unique_ptr<PlusSevenMathService> CreatePlusSevenMathService( - MojoHandle handle) { - mojo::PendingReceiver<MathService> receiver{ - mojo::ScopedMessagePipeHandle(mojo::MessagePipeHandle(handle))}; + std::unique_ptr<mojo::rust::ScopedMessagePipeHandleWrapper> wrapper) { + mojo::PendingReceiver<MathService> receiver(wrapper->take_handle()); return std::make_unique<PlusSevenMathService>(std::move(receiver)); } // To be called by the Rust testing code. Constructs a math service remote from // the given handle, and sends several messages through to ensure basic // functionality. -void TestRemoteFromCpp(MojoHandle handle) { - mojo::Remote<MathService> remote(mojo::PendingRemote<MathService>( - mojo::ScopedMessagePipeHandle(mojo::MessagePipeHandle(handle)), 0)); +void TestRemoteFromCpp( + std::unique_ptr<mojo::rust::ScopedMessagePipeHandleWrapper> wrapper) { + mojo::Remote<MathService> remote( + mojo::PendingRemote<MathService>(wrapper->take_handle(), 0)); base::RunLoop run_loop; @@ -43,4 +43,14 @@ run_loop.Run(); } +void CreatePlusSevenMathServiceAndRemote( + std::unique_ptr<PlusSevenMathService>& service_out, + std::unique_ptr<mojo::rust::ScopedMessagePipeHandleWrapper>& remote_out) { + mojo::PendingRemote<MathService> remote; + service_out = std::make_unique<PlusSevenMathService>( + remote.InitWithNewPipeAndPassReceiver()); + remote_out = std::make_unique<mojo::rust::ScopedMessagePipeHandleWrapper>( + remote.PassPipe()); +} + } // namespace bindings_unittests::mojom
diff --git a/mojo/public/rust/bindings/test/cpp/cxx_shim.h b/mojo/public/rust/bindings/test/cpp/cxx_shim.h index 719113a8..d1b3d4e0 100644 --- a/mojo/public/rust/bindings/test/cpp/cxx_shim.h +++ b/mojo/public/rust/bindings/test/cpp/cxx_shim.h
@@ -7,12 +7,21 @@ #include "mojo/public/c/system/types.h" #include "mojo/public/rust/bindings/test/cpp/add_seven_service.h" +#include "mojo/public/rust/system/scoped_handle_interop.h" namespace bindings_unittests::mojom { std::unique_ptr<PlusSevenMathService> CreatePlusSevenMathService( - MojoHandle handle); -void TestRemoteFromCpp(MojoHandle handle); + std::unique_ptr<mojo::rust::ScopedMessagePipeHandleWrapper> handle); +void TestRemoteFromCpp( + std::unique_ptr<mojo::rust::ScopedMessagePipeHandleWrapper> handle); + +// Creates a PlusSevenMathService, binds it to a new pipe, and returns both +// the service (to keep it alive) and the remote end (wrapped for Rust) +// via out-parameters. +void CreatePlusSevenMathServiceAndRemote( + std::unique_ptr<PlusSevenMathService>& service_out, + std::unique_ptr<mojo::rust::ScopedMessagePipeHandleWrapper>& remote_out); } // namespace bindings_unittests::mojom
diff --git a/mojo/public/rust/bindings/test/cxx.rs b/mojo/public/rust/bindings/test/cxx.rs index 63a6d5f7..0aa2758 100644 --- a/mojo/public/rust/bindings/test/cxx.rs +++ b/mojo/public/rust/bindings/test/cxx.rs
@@ -2,15 +2,35 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +chromium::import! { + "//mojo/public/rust/system"; +} + #[cxx::bridge(namespace = "bindings_unittests::mojom")] pub mod ffi { unsafe extern "C++" { include!("mojo/public/rust/bindings/test/cpp/cxx_shim.h"); include!("mojo/public/rust/bindings/test/cpp/add_seven_service.h"); + include!("mojo/public/rust/system/scoped_handle_interop.h"); type PlusSevenMathService; - fn CreatePlusSevenMathService(handle: usize) -> UniquePtr<PlusSevenMathService>; - fn TestRemoteFromCpp(handle: usize); + #[namespace = "mojo::rust"] + type ScopedMessagePipeHandleWrapper = + super::system::scoped_handle_interop::ScopedMessagePipeHandleWrapper; + + #[namespace = "mojo::rust"] + type ScopedHandleWrapper = crate::cxx::system::scoped_handle_interop::ScopedHandleWrapper; + + fn CreatePlusSevenMathService( + handle: UniquePtr<ScopedMessagePipeHandleWrapper>, + ) -> UniquePtr<PlusSevenMathService>; + + fn TestRemoteFromCpp(handle: UniquePtr<ScopedMessagePipeHandleWrapper>); + + fn CreatePlusSevenMathServiceAndRemote( + service_out: &mut UniquePtr<PlusSevenMathService>, + remote_out: &mut UniquePtr<ScopedMessagePipeHandleWrapper>, + ); } }
diff --git a/mojo/public/rust/bindings/test/tests.rs b/mojo/public/rust/bindings/test/tests.rs index 113c4dca..ee62019 100644 --- a/mojo/public/rust/bindings/test/tests.rs +++ b/mojo/public/rust/bindings/test/tests.rs
@@ -237,8 +237,11 @@ let quit = run_loop.get_quit_closure(); // Pass the receiver handle to C++ and bind it there - let receiver_handle = UntypedHandle::from(pending_receiver.into_endpoint()).into_raw_value(); - let _cpp_receiver = crate::cxx::ffi::CreatePlusSevenMathService(receiver_handle); + let receiver_wrapper = + system::scoped_handle_interop::ScopedMessagePipeHandleWrapper::from_message_endpoint( + pending_receiver.into_endpoint(), + ); + let _cpp_receiver = crate::cxx::ffi::CreatePlusSevenMathService(receiver_wrapper); let mut remote = pending_remote.bind(); @@ -275,8 +278,11 @@ // Pass the remote handle to C++ and have it send messages. // This call blocks until all responses are received. - let remote_handle = UntypedHandle::from(pending_remote.into_endpoint()).into_raw_value(); - crate::cxx::ffi::TestRemoteFromCpp(remote_handle); + let remote_wrapper = + system::scoped_handle_interop::ScopedMessagePipeHandleWrapper::from_message_endpoint( + pending_remote.into_endpoint(), + ); + crate::cxx::ffi::TestRemoteFromCpp(remote_wrapper); // These message must have come from C++ because `TestFromRemote` i // the only testing function that adds things to a total of 22! @@ -434,3 +440,35 @@ expect_true!(*dropped_clone.lock().unwrap()); expect_true!(self_owned.upgrade().is_none()); } + +#[gtest(RustBindingsAPI, CppToRustHandoverTest)] +fn test_cpp_to_rust_handover() { + let _task_env = test_cxx::ffi::CreateTaskEnvironment(); + + // Create a PlusSevenMathService and bind it, all in C++. + let mut _service = cxx::UniquePtr::null(); + let mut remote_wrapper = cxx::UniquePtr::null(); + crate::cxx::ffi::CreatePlusSevenMathServiceAndRemote(&mut _service, &mut remote_wrapper); + + // Convert the C++ endpoint to the equivalent Rust version. + // Since we use the scoped_handle_interop types, this doesn't require `unsafe`! + let remote_endpoint = + system::scoped_handle_interop::ScopedMessagePipeHandleWrapper::into_message_endpoint( + remote_wrapper, + ) + .unwrap(); + let pending_remote = PendingRemote::<dyn MathService>::new(remote_endpoint); + + let run_loop = RunLoop::new(); + let quit = run_loop.get_quit_closure(); + + let mut remote = pending_remote.bind(); + + // Simple test message + remote.Add(5, 10, move |n| { + expect_eq!(n, 22); // PlusSevenMathService adds 7 to all its operations. + quit(); + }); + + run_loop.run(); +}
diff --git a/mojo/public/rust/system/BUILD.gn b/mojo/public/rust/system/BUILD.gn index ad7f0eb3..1d63422a 100644 --- a/mojo/public/rust/system/BUILD.gn +++ b/mojo/public/rust/system/BUILD.gn
@@ -15,12 +15,28 @@ "message_pipe.rs", "mojo_types.rs", "raw_trap.rs", + "scoped_handle_interop.rs", "trap.rs", ] + cxx_bindings = [ "scoped_handle_interop.rs" ] allow_unsafe = true deps = [ ":ffi_bindings", + "//base", "//third_party/rust/bitflags/v2:lib", + "//third_party/rust/cxx/v1:lib", + ] + public_deps = [ ":scoped_handle_interop_cc" ] +} + +source_set("scoped_handle_interop_cc") { + sources = [ + "scoped_handle_interop.cc", + "scoped_handle_interop.h", + ] + deps = [ + "//base", + "//mojo/public/cpp/system", ] }
diff --git a/mojo/public/rust/system/ffi/handles.rs b/mojo/public/rust/system/ffi/handles.rs index 6f5e95b16..b5427d1 100644 --- a/mojo/public/rust/system/ffi/handles.rs +++ b/mojo/public/rust/system/ffi/handles.rs
@@ -60,6 +60,7 @@ /// /// # Safety /// The value must represent a live, unonwned handle. + /// Passing a value of 0 will panic, but will not cause undefined behavior. pub unsafe fn wrap_raw_value(raw_value: raw_ffi::MojoHandle) -> Self { // FOR_RELEASE: There are apparently other types of handle ("Pseudohandles") // that should not be representable by this type. Look into these and check for
diff --git a/mojo/public/rust/system/lib.rs b/mojo/public/rust/system/lib.rs index de3a88f..7bb952c3d 100644 --- a/mojo/public/rust/system/lib.rs +++ b/mojo/public/rust/system/lib.rs
@@ -9,4 +9,5 @@ // FOR_RELEASE: Get rid of raw_trap completely and build `trap` directly on the // ffi bindings. pub mod raw_trap; +pub mod scoped_handle_interop; pub mod trap;
diff --git a/mojo/public/rust/system/scoped_handle_interop.cc b/mojo/public/rust/system/scoped_handle_interop.cc new file mode 100644 index 0000000..0a048cc4 --- /dev/null +++ b/mojo/public/rust/system/scoped_handle_interop.cc
@@ -0,0 +1,42 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/rust/system/scoped_handle_interop.h" + +namespace mojo::rust { + +ScopedHandleWrapper::ScopedHandleWrapper(mojo::ScopedHandle handle) + : handle_(std::move(handle)) {} + +ScopedHandleWrapper::~ScopedHandleWrapper() = default; + +uintptr_t ScopedHandleWrapper::Release( + std::unique_ptr<ScopedHandleWrapper> wrapper) { + return wrapper->handle_.release().value(); +} + +std::unique_ptr<ScopedHandleWrapper> ScopedHandleWrapper::Create( + uintptr_t handle) { + return std::make_unique<ScopedHandleWrapper>( + mojo::ScopedHandle(mojo::Handle(handle))); +} + +ScopedMessagePipeHandleWrapper::ScopedMessagePipeHandleWrapper( + mojo::ScopedMessagePipeHandle handle) + : handle_(std::move(handle)) {} + +ScopedMessagePipeHandleWrapper::~ScopedMessagePipeHandleWrapper() = default; + +uintptr_t ScopedMessagePipeHandleWrapper::Release( + std::unique_ptr<ScopedMessagePipeHandleWrapper> wrapper) { + return wrapper->handle_.release().value(); +} + +std::unique_ptr<ScopedMessagePipeHandleWrapper> +ScopedMessagePipeHandleWrapper::Create(uintptr_t handle) { + return std::make_unique<ScopedMessagePipeHandleWrapper>( + mojo::ScopedMessagePipeHandle(mojo::MessagePipeHandle(handle))); +} + +} // namespace mojo::rust
diff --git a/mojo/public/rust/system/scoped_handle_interop.h b/mojo/public/rust/system/scoped_handle_interop.h new file mode 100644 index 0000000..8785a37 --- /dev/null +++ b/mojo/public/rust/system/scoped_handle_interop.h
@@ -0,0 +1,69 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_RUST_SYSTEM_SCOPED_HANDLE_INTEROP_H_ +#define MOJO_PUBLIC_RUST_SYSTEM_SCOPED_HANDLE_INTEROP_H_ + +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/message_pipe.h" + +// This file defines wrapper types that expose the various Scoped*Handle types +// to Rust, so they can be safely converted into Rust's handle types. +// +// Unfortunately, cxx doesn't handle templates, so we need one wrapper type for +// each handle type we expose. +// +// Each wrapper type provides a static member function `Release` which takes +// a unique pointer. Passing a unique pointer makes the ownership transfer +// obvious via the type system. Similarly, they provide a way to create a +// unique pointer given a raw handle. +// +// The ScopedHandle type guarantees that its contents are either a live, valid +// handle, or the invalid handle value 0 (if e.g. it has been moved from). + +namespace mojo::rust { + +class ScopedHandleWrapper { + public: + explicit ScopedHandleWrapper(mojo::ScopedHandle handle); + ~ScopedHandleWrapper(); + + ScopedHandleWrapper(const ScopedHandleWrapper&) = delete; + ScopedHandleWrapper& operator=(const ScopedHandleWrapper&) = delete; + + static uintptr_t Release(std::unique_ptr<ScopedHandleWrapper> wrapper); + + static std::unique_ptr<ScopedHandleWrapper> Create(uintptr_t handle); + + mojo::ScopedHandle take_handle() { return std::move(handle_); } + + private: + mojo::ScopedHandle handle_; +}; + +class ScopedMessagePipeHandleWrapper { + public: + explicit ScopedMessagePipeHandleWrapper(mojo::ScopedMessagePipeHandle handle); + ~ScopedMessagePipeHandleWrapper(); + + ScopedMessagePipeHandleWrapper(const ScopedMessagePipeHandleWrapper&) = + delete; + ScopedMessagePipeHandleWrapper& operator=( + const ScopedMessagePipeHandleWrapper&) = delete; + + static uintptr_t Release( + std::unique_ptr<ScopedMessagePipeHandleWrapper> wrapper); + + static std::unique_ptr<ScopedMessagePipeHandleWrapper> Create( + uintptr_t handle); + + mojo::ScopedMessagePipeHandle take_handle() { return std::move(handle_); } + + private: + mojo::ScopedMessagePipeHandle handle_; +}; + +} // namespace mojo::rust + +#endif // MOJO_PUBLIC_RUST_SYSTEM_SCOPED_HANDLE_INTEROP_H_
diff --git a/mojo/public/rust/system/scoped_handle_interop.rs b/mojo/public/rust/system/scoped_handle_interop.rs new file mode 100644 index 0000000..464523b --- /dev/null +++ b/mojo/public/rust/system/scoped_handle_interop.rs
@@ -0,0 +1,92 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//! This module (and its corresponding C++ files) defines a way to convert +//! safely between C++ and Rust handle types. +//! +//! TRY TO AVOID USING THESE TYPES! It is vastly preferable to get your mojo +//! handles via an existing message pipe. These types are meant to be used +//! over FFI in situations where that isn't feasible. +//! +//! For each type (untyped, message pipe, data pipe, etc), the C++ side defines +//! a wrapper for that ScopedHandle type. We need separate wrapper types for +//! each because cxx doesn't handle templated types. +//! +//! To give ownership of a handle from C++ to Rust, wrap the scoped handle in +//! the corresponding wrapper type, create a unique pointer, and pass it into +//! Rust (via an FFI function that you define yourself). Then you can simply +//! call that wrapper's `into_whatever_handle` method to convert it to the Rust +//! type. +//! +//! To give ownership of a handle from Rust to C++, call that wrapper's +//! `from_whatever_handle` method, then hand it to C++ (again, via your own FFI +//! function). +//! +//! For an example of doing this, see //mojo/public/rust/bindings/test/cxx.rs. + +use crate::message_pipe::MessageEndpoint; +use crate::mojo_types::UntypedHandle; + +#[cxx::bridge(namespace = "mojo::rust")] +mod ffi { + unsafe extern "C++" { + include!("mojo/public/rust/system/scoped_handle_interop.h"); + + pub type ScopedHandleWrapper; + #[Self = ScopedHandleWrapper] + fn Release(wrapper: UniquePtr<ScopedHandleWrapper>) -> usize; + #[Self = ScopedHandleWrapper] + fn Create(handle: usize) -> UniquePtr<ScopedHandleWrapper>; + + pub type ScopedMessagePipeHandleWrapper; + #[Self = ScopedMessagePipeHandleWrapper] + fn Release(wrapper: UniquePtr<ScopedMessagePipeHandleWrapper>) -> usize; + #[Self = ScopedMessagePipeHandleWrapper] + fn Create(handle: usize) -> UniquePtr<ScopedMessagePipeHandleWrapper>; + } +} + +// Re-export all the types in the bridge, since they're the only thing in this +// file so there's no point having them in a sub-module. +pub use ffi::*; + +// We have to use inherent implementations rather than `From` due to the +// orphaning rule: this crate doesn't define `UniquePtr` or `UntypedHandle`. + +impl ScopedHandleWrapper { + /// Returns None if the given handle is 0. + pub fn into_untyped_handle(handle: cxx::UniquePtr<Self>) -> Option<UntypedHandle> { + let raw = Self::Release(handle); + if raw == 0 { + return None; + } + // SAFETY: The ScopedHandle type owns its underlying handle value, which + // is either live or 0, and we know it's not 0. `Release` gives up that + // ownership. + return Some(unsafe { UntypedHandle::wrap_raw_value(raw) }); + } + + pub fn from_untyped_handle(handle: UntypedHandle) -> cxx::UniquePtr<Self> { + Self::Create(handle.into_raw_value()) + } +} + +impl ScopedMessagePipeHandleWrapper { + /// Returns None if the given handle is 0. + pub fn into_message_endpoint(handle: cxx::UniquePtr<Self>) -> Option<MessageEndpoint> { + let raw = Self::Release(handle); + if raw == 0 { + return None; + } + // SAFETY: The ScopedHandle type owns its underlying handle value, which + // is either live or 0, and we know it's not 0. `Release` gives up that + // ownership. + let untyped = unsafe { UntypedHandle::wrap_raw_value(raw) }; + Some(MessageEndpoint::from(untyped)) + } + + pub fn from_message_endpoint(endpoint: MessageEndpoint) -> cxx::UniquePtr<Self> { + Self::Create(UntypedHandle::from(endpoint).into_raw_value()) + } +}
diff --git a/net/base/features.cc b/net/base/features.cc index b06dc5c..d0a3d06 100644 --- a/net/base/features.cc +++ b/net/base/features.cc
@@ -756,6 +756,13 @@ BASE_FEATURE(kUseLockFreeX509Verification, base::FEATURE_DISABLED_BY_DEFAULT); +BASE_FEATURE(kProbeSecureDnsCanaryDomain, base::FEATURE_DISABLED_BY_DEFAULT); +BASE_FEATURE_PARAM(std::string, + kSecureDnsCanaryDomainHost, + &kProbeSecureDnsCanaryDomain, + /*name=*/"canary_domain_host", + /*default_value=*/""); + #if BUILDFLAG(IS_APPLE) BASE_FEATURE(kUseNSURLDataForGURLConversion, base::FEATURE_ENABLED_BY_DEFAULT); #endif // BUILDFLAG(IS_APPLE)
diff --git a/net/base/features.h b/net/base/features.h index 9399adb..a844c88 100644 --- a/net/base/features.h +++ b/net/base/features.h
@@ -775,6 +775,11 @@ // lock-free certificate verification mechanism. NET_EXPORT BASE_DECLARE_FEATURE(kUseLockFreeX509Verification); +// When enabled, at the same time that DoH probes are started, a canary domain +// will be probed to check whether Secure DNS is allowed by the network. +NET_EXPORT BASE_DECLARE_FEATURE(kProbeSecureDnsCanaryDomain); +NET_EXPORT BASE_DECLARE_FEATURE_PARAM(std::string, kSecureDnsCanaryDomainHost); + #if BUILDFLAG(IS_APPLE) // If enabled, the GURL conversion for NSURLs will use the data representation // of the URL if it differs from the absolute string.
diff --git a/net/dns/BUILD.gn b/net/dns/BUILD.gn index 348287f..d850911 100644 --- a/net/dns/BUILD.gn +++ b/net/dns/BUILD.gn
@@ -34,6 +34,8 @@ "address_info.cc", "address_info.h", "address_sorter.h", + "canary_domain_service.cc", + "canary_domain_service.h", "context_host_resolver.cc", "context_host_resolver.h", "dns_alias_utility.cc", @@ -427,6 +429,7 @@ testonly = true sources = [ "address_info_unittest.cc", + "canary_domain_service_unittest.cc", "context_host_resolver_unittest.cc", "dns_alias_utility_unittest.cc", "dns_config_service_unittest.cc",
diff --git a/net/dns/canary_domain_service.cc b/net/dns/canary_domain_service.cc new file mode 100644 index 0000000..7c4515c91 --- /dev/null +++ b/net/dns/canary_domain_service.cc
@@ -0,0 +1,164 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/dns/canary_domain_service.h" + +#include <memory> +#include <string> +#include <utility> + +#include "base/check.h" +#include "base/functional/bind.h" +#include "base/metrics/histogram_functions.h" +#include "base/task/sequenced_task_runner.h" +#include "net/base/features.h" +#include "net/base/host_port_pair.h" +#include "net/base/net_errors.h" +#include "net/base/network_anonymization_key.h" +#include "net/dns/dns_response.h" +#include "net/dns/dns_transaction.h" +#include "net/dns/public/dns_protocol.h" +#include "net/dns/public/dns_query_type.h" +#include "net/dns/public/host_resolver_source.h" +#include "net/dns/public/secure_dns_mode.h" +#include "net/dns/public/secure_dns_policy.h" +#include "net/log/net_log_with_source.h" + +namespace net { + +namespace { + +HostResolver::ResolveHostParameters CreateResolveHostParameters() { + // A canary domain check should not use the cache, and it should not allow + // Secure DNS. Since canary domain check is not related to user navigation, + // and is a probe of the system DNS provider, DoH is not needed, even when + // supported. Also, for example, in the case of a canary domain check for + // whether to allow DoH, then an insecure check is required. + HostResolver::ResolveHostParameters params; + params.cache_usage = + HostResolver::ResolveHostParameters::CacheUsage::DISALLOWED; + params.secure_dns_policy = SecureDnsPolicy::kDisable; + return params; +} + +// Note: HostResolver::SquashErrorCode() squashes most errors into +// ERR_NAME_NOT_RESOLVED, so it's not possible to disambiguate NXDOMAIN from +// other errors, such as SERVFAIL. +CanaryDomainResult GetCanaryDomainResult( + const HostResolver::ResolveHostRequest& request, + int net_error) { + switch (net_error) { + case OK: // NOERROR + // Return negative if the response contains no A or AAAA records. + if (request.GetAddressResults().endpoints().empty()) { + return CanaryDomainResult::kNegativeNoErrorWithoutRecords; + } + return CanaryDomainResult::kPositive; + case ERR_NAME_NOT_RESOLVED: + return CanaryDomainResult::kNegativeNxDomainOrOtherError; + default: + return CanaryDomainResult::kNegativeOtherError; + } +} + +} // namespace + +CanaryDomainService::CanaryDomainService( + base::SafeRef<ResolveContext> resolve_context, + base::SafeRef<HostResolver> host_resolver) + : host_resolver_(std::move(host_resolver)), + resolve_context_(std::move(resolve_context)), + canary_domain_host_(features::kSecureDnsCanaryDomainHost.Get(), 0), + net_log_( + NetLogWithSource::Make(NetLog::Get(), + NetLogSourceType::CANARY_DOMAIN_SERVICE)) { + CHECK(base::FeatureList::IsEnabled(features::kProbeSecureDnsCanaryDomain)); + CHECK(!features::kSecureDnsCanaryDomainHost.Get().empty()); + resolve_context_->set_doh_fallback_canary_domain_check_status( + CanaryDomainCheckStatus::kNotStarted); +} + +CanaryDomainService::~CanaryDomainService() { + resolve_context_->UnregisterDohStatusObserver(this); +} + +void CanaryDomainService::Start() { + resolve_context_->RegisterDohStatusObserver(this); + ProbeSecureDnsDomain(); +} + +// Called when the current session has changed. This should reset back to +// the initial state of the canary domain check not having started yet. +void CanaryDomainService::OnSessionChanged() { + // Reset any previous result. Secure DNS to the fallback provider should only + // be allowed if a new probe is positive. + pending_probe_request_.reset(); + + // Cancel any asynchronous ProbeSecureDnsDomain() calls. + weak_ptr_factory_.InvalidateWeakPtrs(); + + resolve_context_->set_doh_fallback_canary_domain_check_status( + CanaryDomainCheckStatus::kNotStarted); +} + +// Called when a DoH server has been marked as unavailable and is ready for +// availability probes. This should trigger a new probe of the canary domain. +void CanaryDomainService::OnDohServerUnavailable(bool network_change) { + // Start the probe asynchronously, as this may trigger reentrant calls into + // HostResolverManager, which are not allowed during notification handling. + base::SequencedTaskRunner::GetCurrentDefault()->PostTask( + FROM_HERE, base::BindOnce(&CanaryDomainService::ProbeSecureDnsDomain, + weak_ptr_factory_.GetWeakPtr())); +} + +void CanaryDomainService::ProbeSecureDnsDomain() { + if (!resolve_context_->IsDohFallbackProbeEnabled()) { + if (on_probe_complete_callback_for_testing_) { + std::move(on_probe_complete_callback_for_testing_).Run(); + } + return; + } + + resolve_context_->set_doh_fallback_canary_domain_check_status( + CanaryDomainCheckStatus::kStarted); + + pending_probe_request_ = host_resolver_->CreateRequest( + canary_domain_host_, NetworkAnonymizationKey::CreateTransient(), net_log_, + CreateResolveHostParameters()); + + int result = pending_probe_request_->Start( + base::BindOnce(&CanaryDomainService::OnSecureDnsProbeComplete, + weak_ptr_factory_.GetWeakPtr())); + + if (result != ERR_IO_PENDING) { + OnSecureDnsProbeComplete(result); + } +} + +void CanaryDomainService::OnSecureDnsProbeComplete(int net_error) { + // pending_probe_request_ should be non-null, since if it is destroyed, + // this callback is not called. + CanaryDomainResult result = + GetCanaryDomainResult(*pending_probe_request_.get(), net_error); + + resolve_context_->set_doh_fallback_canary_domain_check_status( + result == CanaryDomainResult::kPositive + ? CanaryDomainCheckStatus::kPositive + : CanaryDomainCheckStatus::kNegative); + + base::UmaHistogramEnumeration("Net.DNS.CanaryDomainService.SecureDns.Result", + result); + + if (on_probe_complete_callback_for_testing_) { + std::move(on_probe_complete_callback_for_testing_).Run(); + } +} + +void CanaryDomainService::SetOnProbeCompleteCallbackForTesting( + base::OnceClosure callback) { + CHECK(on_probe_complete_callback_for_testing_.is_null()); + on_probe_complete_callback_for_testing_ = std::move(callback); +} + +} // namespace net
diff --git a/net/dns/canary_domain_service.h b/net/dns/canary_domain_service.h new file mode 100644 index 0000000..47a14a21 --- /dev/null +++ b/net/dns/canary_domain_service.h
@@ -0,0 +1,97 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_DNS_CANARY_DOMAIN_SERVICE_H_ +#define NET_DNS_CANARY_DOMAIN_SERVICE_H_ + +#include <memory> +#include <string> + +#include "base/functional/callback.h" +#include "base/memory/safe_ref.h" +#include "base/memory/weak_ptr.h" +#include "net/base/net_export.h" +#include "net/dns/host_resolver.h" +#include "net/dns/resolve_context.h" +#include "net/log/net_log_with_source.h" + +namespace net { + +class DnsResponse; +class DnsTransaction; +class DnsTransactionFactory; +class NetLogWithSource; + +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. +// LINT.IfChange(CanaryDomainResult) +enum class CanaryDomainResult { + kUnknownResult = 0, + kPositive = 1, + kNegativeNoErrorWithoutRecords = 2, + kNegativeNxDomainOrOtherError = 3, + kNegativeOtherError = 4, + kMaxValue = kNegativeOtherError, +}; +// LINT.ThenChange(//tools/metrics/histograms/metadata/net/enums.xml:CanaryDomainResult) + +// Service for probing canary domains. It is owned by a HostResolverManager. +class NET_EXPORT_PRIVATE CanaryDomainService + : public ResolveContext::DohStatusObserver { + public: + // `resolve_context` is expected to be owned by `host_resolver`. + // Both must outlive this object. + // Also, the kProbeSecureDnsCanaryDomain feature must be enabled, with + // a non-empty canary domain host specified. + CanaryDomainService(base::SafeRef<ResolveContext> resolve_context, + base::SafeRef<HostResolver> host_resolver); + ~CanaryDomainService() override; + + CanaryDomainService(const CanaryDomainService&) = delete; + CanaryDomainService& operator=(const CanaryDomainService&) = delete; + + // Starts observing DoH status changes and probes the canary domain. + // Should only be called once. + void Start(); + + // ResolveContext::DohStatusObserver implementation: + void OnSessionChanged() override; + void OnDohServerUnavailable(bool network_change) override; + + void SetOnProbeCompleteCallbackForTesting(base::OnceClosure callback); + + private: + // Probes a canary domain that reports whether Secure DNS (DoH) fallback is + // allowed, and then runs `OnSecureDnsProbeComplete()` with the result. + // Should cancel any previous probe that is still pending. + void ProbeSecureDnsDomain(); + + // Called with the result of a Secure DNS probe request. Sets the canary + // domain status in ResolveContext. + void OnSecureDnsProbeComplete(int net_error); + + // The HostResolver that this service uses to perform probes. + // Must outlive this object. + base::SafeRef<HostResolver> host_resolver_; + + // This context is used to observe DoH status changes and update the canary + // domain check status. + // Must outlive this object, and is expected to be owned by `host_resolver_`. + base::SafeRef<ResolveContext> resolve_context_; + + // The canary domain host-port pair to probe. + const HostPortPair canary_domain_host_; + + NetLogWithSource net_log_; + + std::unique_ptr<HostResolver::ResolveHostRequest> pending_probe_request_; + + base::OnceClosure on_probe_complete_callback_for_testing_; + + base::WeakPtrFactory<CanaryDomainService> weak_ptr_factory_{this}; +}; + +} // namespace net + +#endif // NET_DNS_CANARY_DOMAIN_SERVICE_H_
diff --git a/net/dns/canary_domain_service_unittest.cc b/net/dns/canary_domain_service_unittest.cc new file mode 100644 index 0000000..5e27fbed --- /dev/null +++ b/net/dns/canary_domain_service_unittest.cc
@@ -0,0 +1,203 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/dns/canary_domain_service.h" + +#include <memory> +#include <string> +#include <utility> + +#include "base/functional/bind.h" +#include "base/metrics/statistics_recorder.h" +#include "base/run_loop.h" +#include "base/test/metrics/histogram_tester.h" +#include "base/test/scoped_feature_list.h" +#include "base/test/task_environment.h" +#include "base/test/test_future.h" +#include "net/base/features.h" +#include "net/base/ip_address.h" +#include "net/base/ip_endpoint.h" +#include "net/dns/dns_config.h" +#include "net/dns/dns_session.h" +#include "net/dns/dns_test_util.h" +#include "net/dns/dns_util.h" +#include "net/dns/mock_host_resolver.h" +#include "net/dns/public/dns_protocol.h" +#include "net/dns/resolve_context.h" +#include "net/log/net_log_with_source.h" +#include "net/test/gtest_util.h" +#include "net/test/test_with_task_environment.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_builder.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { +const char kHost[] = "canary-domain.com"; +} // namespace + +class CanaryDomainServiceTest : public ::testing::TestWithParam<bool>, + public WithTaskEnvironment { + protected: + CanaryDomainServiceTest() + : url_request_context_(CreateTestURLRequestContextBuilder()->Build()), + resolve_context_(url_request_context_.get(), /*enable_caching=*/false) { + } + + bool synchronous_mode() const { return GetParam(); } + + void SetUp() override { + feature_list_.InitAndEnableFeatureWithParameters( + features::kProbeSecureDnsCanaryDomain, + {{features::kSecureDnsCanaryDomainHost.name, kHost}}); + + // Set up a session with AUTOMATIC mode and fallback upgrade enabled. + DnsConfig config; + config.secure_dns_mode = SecureDnsMode::kAutomatic; + config.should_perform_doh_fallback_upgrade = true; + session_ = base::MakeRefCounted<DnsSession>( + config, base::BindRepeating([](int, int) -> int { return 0; }), + /*net_log=*/nullptr); + resolve_context_.InvalidateCachesAndPerSessionData(session_.get(), false); + + // Must be created after enabling the feature. + canary_domain_service_ = std::make_unique<CanaryDomainService>( + resolve_context_.AsSafeRef(), host_resolver_.AsSafeRef()); + + // Parameterizes whether the probe request will complete synchronously or + // asynchronously. + host_resolver_.set_synchronous_mode(synchronous_mode()); + } + + void StartAndWaitForProbeComplete() { + canary_domain_service_->SetOnProbeCompleteCallbackForTesting( + probe_complete_future_.GetCallback()); + canary_domain_service_->Start(); + EXPECT_TRUE(probe_complete_future_.Wait()); + probe_complete_future_.Clear(); + } + + void OnDohServerUnavailableAndWaitForProbeComplete() { + canary_domain_service_->SetOnProbeCompleteCallbackForTesting( + probe_complete_future_.GetCallback()); + canary_domain_service_->OnDohServerUnavailable(/*network_change=*/false); + EXPECT_TRUE(probe_complete_future_.Wait()); + probe_complete_future_.Clear(); + } + + void ExpectLoggedResult(CanaryDomainResult result) { + histogram_tester_.ExpectUniqueSample( + "Net.DNS.CanaryDomainService.SecureDns.Result", result, 1); + } + + std::unique_ptr<URLRequestContext> url_request_context_; + ResolveContext resolve_context_; + MockHostResolver host_resolver_; + base::test::TestFuture<void> probe_complete_future_; + scoped_refptr<DnsSession> session_; + std::unique_ptr<CanaryDomainService> canary_domain_service_; + net::NetLogWithSource net_log_; + base::test::ScopedFeatureList feature_list_; + base::HistogramTester histogram_tester_; +}; + +INSTANTIATE_TEST_SUITE_P( + All, + CanaryDomainServiceTest, + ::testing::Bool(), + [](const ::testing::TestParamInfo<CanaryDomainServiceTest::ParamType>& + info) { + return info.param ? "synchronous_mode_true" : "synchronous_mode_false"; + }); + +TEST_P(CanaryDomainServiceTest, ProbeSecureDnsDomain_PositiveResult) { + host_resolver_.rules()->AddRule(kHost, "1.2.3.4"); + StartAndWaitForProbeComplete(); + EXPECT_EQ(resolve_context_.doh_fallback_canary_domain_check_status(), + CanaryDomainCheckStatus::kPositive); + ExpectLoggedResult(CanaryDomainResult::kPositive); +} + +TEST_P(CanaryDomainServiceTest, + ProbeSecureDnsDomain_NegativeResultNoErrorWithNoRecords) { + host_resolver_.rules()->AddRule(kHost, ""); + StartAndWaitForProbeComplete(); + EXPECT_EQ(resolve_context_.doh_fallback_canary_domain_check_status(), + CanaryDomainCheckStatus::kNegative); + ExpectLoggedResult(CanaryDomainResult::kNegativeNoErrorWithoutRecords); +} + +TEST_P(CanaryDomainServiceTest, ProbeSecureDnsDomain_NegativeResultNxDomain) { + host_resolver_.rules()->AddRule(kHost, net::ERR_NAME_NOT_RESOLVED); + StartAndWaitForProbeComplete(); + EXPECT_EQ(resolve_context_.doh_fallback_canary_domain_check_status(), + CanaryDomainCheckStatus::kNegative); + ExpectLoggedResult(CanaryDomainResult::kNegativeNxDomainOrOtherError); +} + +TEST_P(CanaryDomainServiceTest, ProbeSecureDnsDomain_NegativeResultServfail) { + host_resolver_.rules()->AddRule(kHost, net::ERR_DNS_SERVER_FAILURE); + StartAndWaitForProbeComplete(); + EXPECT_EQ(resolve_context_.doh_fallback_canary_domain_check_status(), + CanaryDomainCheckStatus::kNegative); + // MockHostResolver squashes ERR_ABORTED to ERR_NAME_NOT_RESOLVED. + ExpectLoggedResult(CanaryDomainResult::kNegativeNxDomainOrOtherError); +} + +TEST_P(CanaryDomainServiceTest, ProbeSecureDnsDomain_NegativeResultOtherError) { + host_resolver_.rules()->AddRule(kHost, net::ERR_DNS_NAME_HTTPS_ONLY); + StartAndWaitForProbeComplete(); + EXPECT_EQ(resolve_context_.doh_fallback_canary_domain_check_status(), + CanaryDomainCheckStatus::kNegative); + ExpectLoggedResult(CanaryDomainResult::kNegativeOtherError); +} + +TEST_P(CanaryDomainServiceTest, OnSessionChangedResetsStatus) { + host_resolver_.rules()->AddRule(kHost, "1.2.3.4"); + StartAndWaitForProbeComplete(); + + EXPECT_EQ(resolve_context_.doh_fallback_canary_domain_check_status(), + CanaryDomainCheckStatus::kPositive); + + canary_domain_service_->OnSessionChanged(); + + EXPECT_EQ(resolve_context_.doh_fallback_canary_domain_check_status(), + CanaryDomainCheckStatus::kNotStarted); +} + +TEST_P(CanaryDomainServiceTest, OnDohServerUnavailableTriggersProbe) { + host_resolver_.rules()->AddRule(kHost, "1.2.3.4"); + StartAndWaitForProbeComplete(); + canary_domain_service_->OnSessionChanged(); + + EXPECT_EQ(resolve_context_.doh_fallback_canary_domain_check_status(), + CanaryDomainCheckStatus::kNotStarted); + + OnDohServerUnavailableAndWaitForProbeComplete(); + + EXPECT_EQ(resolve_context_.doh_fallback_canary_domain_check_status(), + CanaryDomainCheckStatus::kPositive); +} + +TEST_P(CanaryDomainServiceTest, CanaryDomainResolutionIsNotCached) { + host_resolver_.rules()->AddRule(kHost, "1.2.3.4"); + StartAndWaitForProbeComplete(); + + EXPECT_EQ(resolve_context_.doh_fallback_canary_domain_check_status(), + CanaryDomainCheckStatus::kPositive); + + host_resolver_.rules()->ClearRules(); + host_resolver_.rules()->AddRule(kHost, net::ERR_NAME_NOT_RESOLVED); + + canary_domain_service_->OnSessionChanged(); + OnDohServerUnavailableAndWaitForProbeComplete(); + + EXPECT_EQ(resolve_context_.doh_fallback_canary_domain_check_status(), + CanaryDomainCheckStatus::kNegative); +} + +} // namespace net
diff --git a/net/dns/context_host_resolver.cc b/net/dns/context_host_resolver.cc index 9450f93e..e50da53 100644 --- a/net/dns/context_host_resolver.cc +++ b/net/dns/context_host_resolver.cc
@@ -13,6 +13,7 @@ #include "base/time/tick_clock.h" #include "net/base/net_errors.h" #include "net/base/network_anonymization_key.h" +#include "net/dns/canary_domain_service.h" #include "net/dns/dns_config.h" #include "net/dns/host_cache.h" #include "net/dns/host_resolver.h" @@ -90,6 +91,17 @@ resolve_context_.get()); } +std::unique_ptr<CanaryDomainService> +ContextHostResolver::CreateCanaryDomainService() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (shutting_down_) { + return nullptr; + } + + return std::make_unique<CanaryDomainService>(resolve_context_->AsSafeRef(), + weak_ptr_factory_.GetSafeRef()); +} + std::unique_ptr<HostResolver::ResolveHostRequest> ContextHostResolver::CreateRequest( url::SchemeHostPort host,
diff --git a/net/dns/context_host_resolver.h b/net/dns/context_host_resolver.h index ee2fdf6..3da56bf 100644 --- a/net/dns/context_host_resolver.h +++ b/net/dns/context_host_resolver.h
@@ -26,6 +26,7 @@ namespace net { +class CanaryDomainService; class HostCache; class HostResolverManager; class ResolveContext; @@ -79,6 +80,7 @@ HostResolverManager* GetManagerForTesting() override; const URLRequestContext* GetContextForTesting() const override; handles::NetworkHandle GetTargetNetworkForTesting() const override; + std::unique_ptr<CanaryDomainService> CreateCanaryDomainService() override; // Returns the number of host cache entries that were restored, or 0 if there // is no cache. @@ -111,6 +113,8 @@ bool shutting_down_ = false; SEQUENCE_CHECKER(sequence_checker_); + + base::WeakPtrFactory<ContextHostResolver> weak_ptr_factory_{this}; }; } // namespace net
diff --git a/net/dns/context_host_resolver_unittest.cc b/net/dns/context_host_resolver_unittest.cc index eebd0768..f7ff790 100644 --- a/net/dns/context_host_resolver_unittest.cc +++ b/net/dns/context_host_resolver_unittest.cc
@@ -26,7 +26,9 @@ #include "net/base/network_isolation_key.h" #include "net/base/schemeful_site.h" #include "net/base/test_completion_callback.h" +#include "net/dns/canary_domain_service.h" #include "net/dns/dns_config.h" +#include "net/dns/dns_session.h" #include "net/dns/dns_test_util.h" #include "net/dns/dns_util.h" #include "net/dns/host_cache.h" @@ -139,6 +141,70 @@ EXPECT_THAT(request->GetAddressResults(), testing::ElementsAre(kEndpoint)); } +TEST_F(ContextHostResolverTest, CreateCanaryDomainService) { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndEnableFeatureWithParameters( + features::kProbeSecureDnsCanaryDomain, + {{features::kSecureDnsCanaryDomainHost.name, "example.com"}}); + + auto context = CreateTestURLRequestContextBuilder()->Build(); + auto resolve_context_ptr = + std::make_unique<ResolveContext>(context.get(), /*enable_caching=*/false); + ResolveContext* resolve_context = resolve_context_ptr.get(); + auto resolver = std::make_unique<ContextHostResolver>( + manager_.get(), std::move(resolve_context_ptr)); + + // Initially, no session. CreateCanaryDomainService should still return a + // service. + std::unique_ptr<CanaryDomainService> service = + resolver->CreateCanaryDomainService(); + EXPECT_TRUE(service); + + // But starting it should not trigger a probe if disabled. + service->Start(); + EXPECT_EQ(CanaryDomainCheckStatus::kNotStarted, + resolve_context->doh_fallback_canary_domain_check_status()); + + // Set up a session with AUTOMATIC mode and fallback upgrade enabled. + DnsConfig config; + config.secure_dns_mode = SecureDnsMode::kAutomatic; + config.should_perform_doh_fallback_upgrade = true; + auto session = base::MakeRefCounted<DnsSession>( + config, base::BindRepeating([](int, int) -> int { return 0; }), + /*net_log=*/nullptr); + resolve_context->InvalidateCachesAndPerSessionData(session.get(), false); + + service = resolver->CreateCanaryDomainService(); + EXPECT_TRUE(service); + + // Disable fallback upgrade. + config.should_perform_doh_fallback_upgrade = false; + session = base::MakeRefCounted<DnsSession>( + config, base::BindRepeating([](int, int) -> int { return 0; }), + /*net_log=*/nullptr); + resolve_context->InvalidateCachesAndPerSessionData(session.get(), false); + + service = resolver->CreateCanaryDomainService(); + EXPECT_TRUE(service); + service->Start(); + EXPECT_EQ(CanaryDomainCheckStatus::kNotStarted, + resolve_context->doh_fallback_canary_domain_check_status()); + + // Change mode to SECURE. + config.secure_dns_mode = SecureDnsMode::kSecure; + config.should_perform_doh_fallback_upgrade = true; + session = base::MakeRefCounted<DnsSession>( + config, base::BindRepeating([](int, int) -> int { return 0; }), + /*net_log=*/nullptr); + resolve_context->InvalidateCachesAndPerSessionData(session.get(), false); + + service = resolver->CreateCanaryDomainService(); + EXPECT_TRUE(service); + service->Start(); + EXPECT_EQ(CanaryDomainCheckStatus::kNotStarted, + resolve_context->doh_fallback_canary_domain_check_status()); +} + TEST_F(ContextHostResolverTest, ResolveWithScheme) { auto context = CreateTestURLRequestContextBuilder()->Build();
diff --git a/net/dns/dns_client.cc b/net/dns/dns_client.cc index 481df61..ce08ff4 100644 --- a/net/dns/dns_client.cc +++ b/net/dns/dns_client.cc
@@ -14,6 +14,7 @@ #include "base/functional/bind.h" #include "base/logging.h" #include "base/memory/raw_ptr.h" +#include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" #include "base/notimplemented.h" #include "base/rand_util.h" @@ -40,6 +41,19 @@ namespace { +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. +// LINT.IfChange(FallbackFromSecureTransactionPreferredReason) +enum class FallbackFromSecureTransactionPreferredReason { + kFallbackNotPreferred = 0, + kFallbackPreferredCannotUseSecureDns = 1, + kFallbackPreferredCanaryDomainCheckPending = 2, + kFallbackPreferredCanaryDomainCheckNegative = 3, + kFallbackPreferredNoAvailableDohServers = 4, + kMaxValue = kFallbackPreferredNoAvailableDohServers, +}; +// LINT.ThenChange(//tools/metrics/histograms/metadata/net/enums.xml:FallbackFromSecureTransactionPreferredReason) + DnsConfigLocalNameserverState GetDnsConfigLocalNameserverState( bool has_loopback_nameserver, bool has_local_non_loopback_nameserver) { @@ -66,6 +80,7 @@ } void UpdateConfigForDohUpgrade(DnsConfig* config) { + config->should_perform_doh_fallback_upgrade = false; bool has_doh_servers = !config->doh_config.servers().empty(); // Do not attempt upgrade when there are already DoH servers specified or // when there are aspects of the system DNS config that are unhandled. @@ -98,7 +113,6 @@ // Note: we don't apply this upgrade if the DNS config has a local // nameserver to give local resolvers priority over fallback DoH. has_doh_servers = !config->doh_config.servers().empty(); - bool upgraded_config_using_fallback = false; if (!has_doh_servers) { bool fallback_doh_nameservers_provided = !config->fallback_doh_nameservers.empty(); @@ -124,14 +138,14 @@ config->doh_config = DnsOverHttpsConfig(GetDohUpgradeServersFromNameservers( config->fallback_doh_nameservers)); - upgraded_config_using_fallback = + config->should_perform_doh_fallback_upgrade = !config->doh_config.servers().empty(); } has_doh_servers = !config->doh_config.servers().empty(); } UMA_HISTOGRAM_BOOLEAN( "Net.DNS.UpgradeConfig.InsecureUpgradeWithFallbackSucceeded", - upgraded_config_using_fallback); + config->should_perform_doh_fallback_upgrade); UMA_HISTOGRAM_BOOLEAN("Net.DNS.UpgradeConfig.InsecureUpgradeSucceeded", has_doh_servers); } @@ -178,13 +192,58 @@ can_query_additional_types_via_insecure_ = additional_types_enabled; } + void RecordFallbackFromSecureTransactionPreferred( + FallbackFromSecureTransactionPreferredReason reason) const { + base::UmaHistogramEnumeration( + "Net.DNS.FallbackFromSecureTransactionPreferred", reason); + } + bool FallbackFromSecureTransactionPreferred( ResolveContext* context) const override { - if (!CanUseSecureDnsTransactions()) + if (!CanUseSecureDnsTransactions()) { + RecordFallbackFromSecureTransactionPreferred( + FallbackFromSecureTransactionPreferredReason:: + kFallbackPreferredCannotUseSecureDns); return true; + } DCHECK(session_); // Should be true if CanUseSecureDnsTransactions() true. - return context->NumAvailableDohServers(session_.get()) == 0; + + // If the canary domain check is enabled and the canary domain is not + // successfully resolved, fall back to insecure DNS. + if (base::FeatureList::IsEnabled(features::kProbeSecureDnsCanaryDomain) && + session_->config().should_perform_doh_fallback_upgrade) { + switch (context->doh_fallback_canary_domain_check_status()) { + case CanaryDomainCheckStatus::kPositive: + // On a positive canary domain check, no fallback to insecure DNS. + break; + case CanaryDomainCheckStatus::kNegative: + RecordFallbackFromSecureTransactionPreferred( + FallbackFromSecureTransactionPreferredReason:: + kFallbackPreferredCanaryDomainCheckNegative); + return true; + case CanaryDomainCheckStatus::kUnknown: + case CanaryDomainCheckStatus::kNotStarted: + case CanaryDomainCheckStatus::kStarted: + RecordFallbackFromSecureTransactionPreferred( + FallbackFromSecureTransactionPreferredReason:: + kFallbackPreferredCanaryDomainCheckPending); + return true; + } + } + + // Otherwise, fall back to insecure DNS if there are no available DoH + // servers. + if (context->NumAvailableDohServers(session_.get()) == 0) { + RecordFallbackFromSecureTransactionPreferred( + FallbackFromSecureTransactionPreferredReason:: + kFallbackPreferredNoAvailableDohServers); + return true; + } + + RecordFallbackFromSecureTransactionPreferred( + FallbackFromSecureTransactionPreferredReason::kFallbackNotPreferred); + return false; } bool FallbackFromInsecureTransactionPreferred() const override { @@ -334,7 +393,6 @@ bool UpdateDnsConfig() { std::optional<DnsConfig> new_effective_config = BuildEffectiveConfig(); - if (IsEqual(new_effective_config, GetEffectiveConfig())) return false;
diff --git a/net/dns/dns_client_unittest.cc b/net/dns/dns_client_unittest.cc index 4470d5fe..de30355 100644 --- a/net/dns/dns_client_unittest.cc +++ b/net/dns/dns_client_unittest.cc
@@ -19,6 +19,7 @@ #include "net/dns/dns_test_util.h" #include "net/dns/public/dns_over_https_config.h" #include "net/dns/public/dns_protocol.h" +#include "net/dns/public/doh_provider_entry.h" #include "net/dns/public/secure_dns_mode.h" #include "net/dns/resolve_context.h" #include "net/socket/socket_test_util.h" @@ -265,7 +266,7 @@ ValidConfigWithDoh(false /* doh_only */)); } -TEST_F(DnsClientTest, FallbackFromInsecureTransactionPreferred_Failures) { +TEST_F(DnsClientTest, FallbackFromSecureTransactionPreferred_Failures) { client_->SetInsecureEnabled(/*enabled=*/true, /*additional_types_enabled=*/true); client_->SetSystemConfig(ValidConfigWithDoh(false /* doh_only */)); @@ -298,6 +299,54 @@ EXPECT_FALSE(client_->FallbackFromInsecureTransactionPreferred()); } +TEST_F(DnsClientTest, + FallbackFromSecureTransactionPreferred_CanaryDomainCheck) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitWithFeatures( + {net::features::kProbeSecureDnsCanaryDomain, + net::features::kAddAutomaticWithDohFallbackMode}, + {}); + + // 1. Set a config that has DoH fallback servers. + DnsConfig config = BasicValidConfig(); + config.secure_dns_mode = SecureDnsMode::kAutomatic; + config.allow_dns_over_https_upgrade = true; + config.fallback_doh_nameservers = {IPEndPoint(GooglePublicDnsIp(), 53)}; + client_->SetSystemConfig(config); + + resolve_context_->InvalidateCachesAndPerSessionData( + client_->GetCurrentSession(), /*network_change=*/false); + + // Canary domain check status is kNotStarted by default. + // Should prefer fallback since it's not kPositive. + EXPECT_TRUE( + client_->FallbackFromSecureTransactionPreferred(resolve_context_.get())); + + // Now record a positive canary domain check. + resolve_context_->set_doh_fallback_canary_domain_check_status( + CanaryDomainCheckStatus::kPositive); + + // Still prefers fallback because no DoH servers are available (successful). + EXPECT_TRUE( + client_->FallbackFromSecureTransactionPreferred(resolve_context_.get())); + + // Record DoH probe success to make it "available". + resolve_context_->RecordServerSuccess(/*server_index=*/0u, + /*is_doh_server=*/true, + client_->GetCurrentSession()); + + // Now that it's positive AND available, it should NOT prefer fallback. + EXPECT_FALSE( + client_->FallbackFromSecureTransactionPreferred(resolve_context_.get())); + + // If status becomes negative, it should prefer fallback again even if DoH is + // available. + resolve_context_->set_doh_fallback_canary_domain_check_status( + CanaryDomainCheckStatus::kNegative); + EXPECT_TRUE( + client_->FallbackFromSecureTransactionPreferred(resolve_context_.get())); +} + TEST_F(DnsClientTest, GetPresetAddrs) { DnsConfig config; config.doh_config = *net::DnsOverHttpsConfig::FromString(R"(
diff --git a/net/dns/dns_config.cc b/net/dns/dns_config.cc index 56ba951..2c075f1 100644 --- a/net/dns/dns_config.cc +++ b/net/dns/dns_config.cc
@@ -49,7 +49,9 @@ (doh_config == d.doh_config) && (secure_dns_mode == d.secure_dns_mode) && (allow_dns_over_https_upgrade == d.allow_dns_over_https_upgrade) && - (fallback_doh_nameservers == d.fallback_doh_nameservers); + (fallback_doh_nameservers == d.fallback_doh_nameservers) && + (should_perform_doh_fallback_upgrade == + d.should_perform_doh_fallback_upgrade); } void DnsConfig::CopyIgnoreHosts(const DnsConfig& d) { @@ -69,6 +71,7 @@ secure_dns_mode = d.secure_dns_mode; allow_dns_over_https_upgrade = d.allow_dns_over_https_upgrade; fallback_doh_nameservers = d.fallback_doh_nameservers; + should_perform_doh_fallback_upgrade = d.should_perform_doh_fallback_upgrade; } base::DictValue DnsConfig::ToDict() const { @@ -104,6 +107,8 @@ fallback_doh_nameserver_list.Append(nameserver.ToString()); } dict.Set("fallback_doh_nameservers", std::move(fallback_doh_nameserver_list)); + dict.Set("should_perform_doh_fallback_upgrade", + should_perform_doh_fallback_upgrade); return dict; }
diff --git a/net/dns/dns_config.h b/net/dns/dns_config.h index f45d863..add1443 100644 --- a/net/dns/dns_config.h +++ b/net/dns/dns_config.h
@@ -110,6 +110,10 @@ // to DoH they are used in the order defined by // `net::ResolveContext::GetDohIterator`. std::vector<IPEndPoint> fallback_doh_nameservers; + + // If true, DoH fallback servers are copied into `doh_config` should be used + // to send DNS queries over HTTPS. + bool should_perform_doh_fallback_upgrade = false; }; } // namespace net
diff --git a/net/dns/host_resolver.cc b/net/dns/host_resolver.cc index a3876f4..708b6cb 100644 --- a/net/dns/host_resolver.cc +++ b/net/dns/host_resolver.cc
@@ -27,6 +27,7 @@ #include "net/base/host_port_pair.h" #include "net/base/net_errors.h" #include "net/base/network_change_notifier.h" +#include "net/dns/canary_domain_service.h" #include "net/dns/context_host_resolver.h" #include "net/dns/dns_client.h" #include "net/dns/dns_util.h" @@ -385,6 +386,12 @@ return handles::kInvalidNetworkHandle; } +std::unique_ptr<CanaryDomainService> HostResolver::CreateCanaryDomainService() { + // Should be overridden in any HostResolver implementation where this method + // may be called. + NOTREACHED(); +} + // static std::unique_ptr<HostResolver> HostResolver::CreateResolver( HostResolverManager* manager,
diff --git a/net/dns/host_resolver.h b/net/dns/host_resolver.h index 1c48432..c26712ce 100644 --- a/net/dns/host_resolver.h +++ b/net/dns/host_resolver.h
@@ -41,6 +41,7 @@ namespace net { class AddressList; +class CanaryDomainService; class ContextHostResolver; class DnsClient; struct DnsConfigOverrides; @@ -566,6 +567,10 @@ virtual const URLRequestContext* GetContextForTesting() const; virtual handles::NetworkHandle GetTargetNetworkForTesting() const; + // Creates a CanaryDomainService that uses this resolver. Can return nullptr, + // for example, if the resolver is shutting down. + virtual std::unique_ptr<CanaryDomainService> CreateCanaryDomainService(); + // Creates a new HostResolver. `manager` must outlive the returned resolver. // // If `mapping_rules` is non-empty, the mapping rules will be applied to
diff --git a/net/dns/mapped_host_resolver.cc b/net/dns/mapped_host_resolver.cc index 246ede8..1df6a9fa 100644 --- a/net/dns/mapped_host_resolver.cc +++ b/net/dns/mapped_host_resolver.cc
@@ -14,6 +14,7 @@ #include "net/base/net_errors.h" #include "net/base/network_anonymization_key.h" #include "net/base/url_util.h" +#include "net/dns/canary_domain_service.h" #include "net/dns/host_resolver.h" #include "net/log/net_log_with_source.h" #include "url/gurl.h" @@ -141,4 +142,9 @@ return impl_->GetManagerForTesting(); } +std::unique_ptr<CanaryDomainService> +MappedHostResolver::CreateCanaryDomainService() { + return impl_->CreateCanaryDomainService(); +} + } // namespace net
diff --git a/net/dns/mapped_host_resolver.h b/net/dns/mapped_host_resolver.h index 5327b84..be7f0b3 100644 --- a/net/dns/mapped_host_resolver.h +++ b/net/dns/mapped_host_resolver.h
@@ -76,6 +76,7 @@ void SetRequestContext(URLRequestContext* request_context) override; bool IsHappyEyeballsV3Enabled() const override; HostResolverManager* GetManagerForTesting() override; + std::unique_ptr<CanaryDomainService> CreateCanaryDomainService() override; private: std::unique_ptr<HostResolver> impl_;
diff --git a/net/dns/mock_host_resolver.cc b/net/dns/mock_host_resolver.cc index 31158d3..b05fc068 100644 --- a/net/dns/mock_host_resolver.cc +++ b/net/dns/mock_host_resolver.cc
@@ -886,6 +886,16 @@ return base::FeatureList::IsEnabled(features::kHappyEyeballsV3); } +std::unique_ptr<CanaryDomainService> +MockHostResolverBase::CreateCanaryDomainService() { + if (!resolve_context_) { + return nullptr; + } + + return std::make_unique<CanaryDomainService>(resolve_context_->AsSafeRef(), + weak_ptr_factory_.GetSafeRef()); +} + int MockHostResolverBase::LoadIntoCache( std::variant<url::SchemeHostPort, HostPortPair> endpoint, const NetworkAnonymizationKey& network_anonymization_key,
diff --git a/net/dns/mock_host_resolver.h b/net/dns/mock_host_resolver.h index 45595a9a..9756ef77 100644 --- a/net/dns/mock_host_resolver.h +++ b/net/dns/mock_host_resolver.h
@@ -20,6 +20,7 @@ #include "base/memory/raw_ptr.h" #include "base/memory/ref_counted.h" +#include "base/memory/safe_ref.h" #include "base/memory/weak_ptr.h" #include "base/synchronization/lock.h" #include "base/synchronization/waitable_event.h" @@ -31,6 +32,7 @@ #include "net/base/host_port_pair.h" #include "net/base/net_errors.h" #include "net/base/network_anonymization_key.h" +#include "net/dns/canary_domain_service.h" #include "net/dns/host_resolver.h" #include "net/dns/host_resolver_proc.h" #include "net/dns/public/dns_query_type.h" @@ -300,6 +302,7 @@ HostCache* GetHostCache() override; void SetRequestContext(URLRequestContext* request_context) override {} bool IsHappyEyeballsV3Enabled() const override; + std::unique_ptr<CanaryDomainService> CreateCanaryDomainService() override; // Preloads the cache with what would currently be the result of a request // with the given parameters. Returns the net error of the cached result. @@ -413,6 +416,14 @@ tick_clock_ = tick_clock; } + base::SafeRef<MockHostResolverBase> AsSafeRef() { + return weak_ptr_factory_.GetSafeRef(); + } + + void SetResolveContextForTesting(ResolveContext* resolve_context) { + resolve_context_ = resolve_context; + } + private: friend class MockHostResolver; friend class MockCachingHostResolver; @@ -470,6 +481,8 @@ raw_ptr<const base::TickClock> tick_clock_; + raw_ptr<ResolveContext> resolve_context_; + scoped_refptr<State> state_; THREAD_CHECKER(thread_checker_);
diff --git a/net/dns/resolve_context.cc b/net/dns/resolve_context.cc index 642cd650..4344e8c 100644 --- a/net/dns/resolve_context.cc +++ b/net/dns/resolve_context.cc
@@ -684,6 +684,15 @@ } } +bool ResolveContext::IsDohFallbackProbeEnabled() const { + if (!current_session_) { + return false; + } + return current_session_->config().secure_dns_mode == + SecureDnsMode::kAutomatic && + current_session_->config().should_perform_doh_fallback_upgrade; +} + // static bool ResolveContext::ServerStatsToDohAvailability( const ResolveContext::ServerStats& stats) {
diff --git a/net/dns/resolve_context.h b/net/dns/resolve_context.h index 5b5c4211..178563a 100644 --- a/net/dns/resolve_context.h +++ b/net/dns/resolve_context.h
@@ -50,6 +50,23 @@ kMaxValue = kFailureWithNoPriorSuccesses }; +// Status of a canary domain check being used to check for whether a +// particular behavior is allowed. +enum class CanaryDomainCheckStatus { + // Unknown status, also when canary domain check is not enabled. + kUnknown, + // The canary domain check has not yet started. + kNotStarted, + // The canary domain check has started but not yet completed. + kStarted, + // The canary domain check has completed and was positive. + // A positive result indicates that the behavior is allowed. + kPositive, + // The canary domain check has completed and was negative. + // A positive result indicates that the behavior is not allowed. + kNegative, +}; + // Per-URLRequestContext data used by HostResolver. Expected to be owned by the // ContextHostResolver, and all usage/references are expected to be cleaned up // or cancelled before the URLRequestContext goes out of service. @@ -240,6 +257,22 @@ return weak_ptr_factory_.GetWeakPtr(); } + // Sets the status of a canary domain check for allowing DoH fallback. + void set_doh_fallback_canary_domain_check_status( + CanaryDomainCheckStatus status) { + doh_fallback_canary_domain_check_status_ = status; + } + + // Gets the status of a canary domain check for allowing DoH fallback. + // A positive status indicates that Secure DNS DoH fallback is allowed. + CanaryDomainCheckStatus doh_fallback_canary_domain_check_status() const { + return doh_fallback_canary_domain_check_status_; + } + + // Returns true if a canary domain probe should be performed for the purpose + // of determining whether to allow DoH fallback. + bool IsDohFallbackProbeEnabled() const; + private: friend DohDnsServerIterator; friend ClassicDnsServerIterator; @@ -350,6 +383,10 @@ base::OneShotTimer doh_autoupgrade_success_metric_timer_; + // Status of a canary domain check to allow DoH fallback for Secure DNS. + CanaryDomainCheckStatus doh_fallback_canary_domain_check_status_ = + CanaryDomainCheckStatus::kNotStarted; + base::WeakPtrFactory<ResolveContext> weak_ptr_factory_{this}; };
diff --git a/net/dns/stale_host_resolver.cc b/net/dns/stale_host_resolver.cc index 66f40eb8..dccff530 100644 --- a/net/dns/stale_host_resolver.cc +++ b/net/dns/stale_host_resolver.cc
@@ -21,6 +21,7 @@ #include "net/base/host_port_pair.h" #include "net/base/net_errors.h" #include "net/base/network_anonymization_key.h" +#include "net/dns/canary_domain_service.h" #include "net/dns/context_host_resolver.h" #include "net/dns/dns_util.h" #include "net/dns/host_resolver.h" @@ -393,6 +394,11 @@ return inner_resolver_->CreateDohProbeRequest(); } +std::unique_ptr<CanaryDomainService> +StaleHostResolver::CreateCanaryDomainService() { + return inner_resolver_->CreateCanaryDomainService(); +} + void StaleHostResolver::SetRequestContext(URLRequestContext* request_context) { inner_resolver_->SetRequestContext(request_context); }
diff --git a/net/dns/stale_host_resolver.h b/net/dns/stale_host_resolver.h index 1bdc5019..f51ed3833 100644 --- a/net/dns/stale_host_resolver.h +++ b/net/dns/stale_host_resolver.h
@@ -110,6 +110,7 @@ void SetRequestContext(URLRequestContext* request_context) override; bool IsHappyEyeballsV3Enabled() const override; std::unique_ptr<HostResolver::ProbeRequest> CreateDohProbeRequest() override; + std::unique_ptr<CanaryDomainService> CreateCanaryDomainService() override; // Set `tick_clock_` for testing. Must be set before issuing any requests. NET_EXPORT void SetTickClockForTesting(const base::TickClock* tick_clock);
diff --git a/net/log/net_log_source_type_list.h b/net/log/net_log_source_type_list.h index c146d3c..24b549c3 100644 --- a/net/log/net_log_source_type_list.h +++ b/net/log/net_log_source_type_list.h
@@ -61,3 +61,4 @@ SOURCE_TYPE(UDP_CLIENT_SOCKET) SOURCE_TYPE(QUIC_PROXY_DATAGRAM_CLIENT_SOCKET) SOURCE_TYPE(PROXY_OVERRIDE_HOST_RESOLUTION) +SOURCE_TYPE(CANARY_DOMAIN_SERVICE)
diff --git a/remoting/host/chromeos/clipboard_aura.cc b/remoting/host/chromeos/clipboard_aura.cc index 9980148..351e3d3 100644 --- a/remoting/host/chromeos/clipboard_aura.cc +++ b/remoting/host/chromeos/clipboard_aura.cc
@@ -80,13 +80,19 @@ current_change_token_ = change_token; - protocol::ClipboardEvent event; - std::string data; ui::DataTransferEndpoint data_dst = ui::DataTransferEndpoint( ui::EndpointType::kDefault, {.notify_if_restricted = false}); - clipboard->ReadAsciiText(ui::ClipboardBuffer::kCopyPaste, &data_dst, &data); + clipboard->ReadAsciiText(ui::ClipboardBuffer::kCopyPaste, std::move(data_dst), + base::BindOnce(&ClipboardAura::OnReadAsciiText, + weak_factory_.GetWeakPtr())); +} + +void ClipboardAura::OnReadAsciiText(std::string data) { + DCHECK(thread_checker_.CalledOnValidThread()); + + protocol::ClipboardEvent event; event.set_mime_type(kMimeTypeTextUtf8); - event.set_data(data); + event.set_data(std::move(data)); client_clipboard_->InjectClipboardEvent(event); }
diff --git a/remoting/host/chromeos/clipboard_aura.h b/remoting/host/chromeos/clipboard_aura.h index 7bbe9ca..d4e561a 100644 --- a/remoting/host/chromeos/clipboard_aura.h +++ b/remoting/host/chromeos/clipboard_aura.h
@@ -9,6 +9,7 @@ #include <memory> +#include "base/memory/weak_ptr.h" #include "base/threading/thread_checker.h" #include "base/time/time.h" #include "base/timer/timer.h" @@ -50,12 +51,15 @@ private: void CheckClipboardForChanges(); + void OnReadAsciiText(std::string data); base::ThreadChecker thread_checker_; std::unique_ptr<protocol::ClipboardStub> client_clipboard_; base::RepeatingTimer clipboard_polling_timer_; ui::ClipboardSequenceNumberToken current_change_token_; base::TimeDelta polling_interval_; + + base::WeakPtrFactory<ClipboardAura> weak_factory_{this}; }; } // namespace remoting
diff --git a/services/network/network_context.cc b/services/network/network_context.cc index 90e788d..8959f8c 100644 --- a/services/network/network_context.cc +++ b/services/network/network_context.cc
@@ -1022,6 +1022,15 @@ doh_probes_request_ = url_request_context_->host_resolver()->CreateDohProbeRequest(); doh_probes_request_->Start(); + + if (base::FeatureList::IsEnabled( + net::features::kProbeSecureDnsCanaryDomain)) { + net::HostResolver* primary_resolver = url_request_context_->host_resolver(); + canary_domain_service_ = primary_resolver->CreateCanaryDomainService(); + if (canary_domain_service_) { + canary_domain_service_->Start(); + } + } } void NetworkContext::SetClient(
diff --git a/services/network/network_context.h b/services/network/network_context.h index 0a668970..b0584e0 100644 --- a/services/network/network_context.h +++ b/services/network/network_context.h
@@ -38,6 +38,7 @@ #include "net/cert/cert_verifier.h" #include "net/cert/cert_verify_result.h" #include "net/cookies/cookie_setting_override.h" +#include "net/dns/canary_domain_service.h" #include "net/dns/host_resolver.h" #include "net/dns/public/dns_config_overrides.h" #include "net/first_party_sets/first_party_set_metadata.h" @@ -655,6 +656,10 @@ return url_loader_factories_.size(); } + net::CanaryDomainService* canary_domain_service_for_testing() { + return canary_domain_service_.get(); + } + // Returns whether all URLLoaderFactories owned by `this` are bound to // `bound_network`. bool AllURLLoaderFactoriesAreBoundToNetworkForTesting( @@ -970,6 +975,10 @@ host_resolvers_; std::unique_ptr<net::HostResolver::ProbeRequest> doh_probes_request_; + // Created on-demand. Null if unused. + // Must be destroyed before `url_request_context_owner_`; + std::unique_ptr<net::CanaryDomainService> canary_domain_service_; + // Used for certificate verification. uint64_t next_cert_verify_id_ = 0; struct PendingCertVerify {
diff --git a/services/network/network_context_unittest.cc b/services/network/network_context_unittest.cc index 415f00d5..3005b6c 100644 --- a/services/network/network_context_unittest.cc +++ b/services/network/network_context_unittest.cc
@@ -107,7 +107,10 @@ #include "net/disk_cache/cache_util.h" #include "net/disk_cache/disk_cache.h" #include "net/disk_cache/memory/mem_backend_impl.h" +#include "net/dns/canary_domain_service.h" #include "net/dns/context_host_resolver.h" +#include "net/dns/dns_config.h" +#include "net/dns/dns_session.h" #include "net/dns/dns_test_util.h" #include "net/dns/host_resolver_manager.h" #include "net/dns/mock_host_resolver.h" @@ -5510,6 +5513,153 @@ EXPECT_FALSE(state->IsDohProbeRunning()); } +class NetworkContextCanaryDomainTest + : public NetworkContextTest, + public testing::WithParamInterface</*is_positive=*/bool> { + public: + bool is_positive() const { return GetParam(); } +}; + +INSTANTIATE_TEST_SUITE_P(All, NetworkContextCanaryDomainTest, testing::Bool()); + +TEST_P(NetworkContextCanaryDomainTest, CanaryDomainServiceProbe) { + base::test::ScopedFeatureList feature_list; + std::string host = "example.test"; + feature_list.InitAndEnableFeatureWithParameters( + net::features::kProbeSecureDnsCanaryDomain, + {{"canary_domain_host", host}}); + + network_service_->set_host_resolver_factory_for_testing( + std::make_unique<net::MockHostResolverFactory>()); + + mojom::NetworkContextParamsPtr params = + CreateNetworkContextParamsForTesting(); + net::ResolveContext resolve_context(/*url_request_context=*/nullptr, + /*enable_caching=*/false); + + // Set up a DnsSession that allows DoH fallback probes. + net::DnsConfig config; + config.secure_dns_mode = net::SecureDnsMode::kAutomatic; + config.should_perform_doh_fallback_upgrade = true; + auto session = base::MakeRefCounted<net::DnsSession>( + config, base::BindRepeating([](int min, int max) -> int { return 0; }), + nullptr); + resolve_context.InvalidateCachesAndPerSessionData(session.get(), + /*network_change=*/false); + + std::unique_ptr<NetworkContext> network_context = + CreateContextWithParams(std::move(params)); + + auto* mock_resolver = static_cast<net::MockHostResolverBase*>( + network_context->url_request_context()->host_resolver()); + mock_resolver->set_ondemand_mode(true); + if (is_positive()) { + mock_resolver->rules()->AddRule(host, "1.2.3.4"); + } else { + mock_resolver->rules()->AddRule(host, net::ERR_NAME_NOT_RESOLVED); + } + mock_resolver->SetResolveContextForTesting(&resolve_context); + + // Creates CanaryDomainService and calls CanaryDomainService::Start(). + network_context->ActivateDohProbes(); + + net::CanaryDomainService* canary_domain_service = + network_context->canary_domain_service_for_testing(); + ASSERT_TRUE(canary_domain_service); + + // Wait for canary domain probe to complete. + base::RunLoop run_loop; + canary_domain_service->SetOnProbeCompleteCallbackForTesting( + run_loop.QuitClosure()); + mock_resolver->ResolveAllPending(); + run_loop.Run(); + + // Verify that the expected canary domain probe result was recorded in the + // ResolveContext. + net::CanaryDomainCheckStatus expected_status = + is_positive() ? net::CanaryDomainCheckStatus::kPositive + : net::CanaryDomainCheckStatus::kNegative; + EXPECT_EQ(expected_status, + resolve_context.doh_fallback_canary_domain_check_status()); +} + +TEST_F(NetworkContextTest, CanaryDomainServiceProbe_FeatureDisabled) { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndDisableFeature( + net::features::kProbeSecureDnsCanaryDomain); + + network_service_->set_host_resolver_factory_for_testing( + std::make_unique<net::MockHostResolverFactory>()); + + mojom::NetworkContextParamsPtr params = + CreateNetworkContextParamsForTesting(); + std::unique_ptr<NetworkContext> network_context = + CreateContextWithParams(std::move(params)); + + network_context->ActivateDohProbes(); + + net::CanaryDomainService* canary_domain_service = + network_context->canary_domain_service_for_testing(); + ASSERT_FALSE(canary_domain_service); +} + +TEST_F(NetworkContextTest, + CanaryDomainServiceProbe_NotStartedIfConfigDisallows) { + base::test::ScopedFeatureList feature_list; + feature_list.InitAndEnableFeatureWithParameters( + net::features::kProbeSecureDnsCanaryDomain, + {{"canary_domain_host", "test.test"}}); + + network_service_->set_host_resolver_factory_for_testing( + std::make_unique<net::MockHostResolverFactory>()); + + // Use a separate ResolveContext to control the DnsSession. + // Must outlive `network_context` because CanaryDomainService will hold a + // SafeRef to it. + net::ResolveContext resolve_context(nullptr, false); + + mojom::NetworkContextParamsPtr params = + CreateNetworkContextParamsForTesting(); + std::unique_ptr<NetworkContext> network_context = + CreateContextWithParams(std::move(params)); + + auto* mock_resolver = static_cast<net::MockHostResolverBase*>( + network_context->url_request_context()->host_resolver()); + mock_resolver->SetResolveContextForTesting(&resolve_context); + + // Case 1: Mode is not AUTOMATIC. + { + net::DnsConfig config; + config.secure_dns_mode = net::SecureDnsMode::kSecure; + config.should_perform_doh_fallback_upgrade = true; + auto session = base::MakeRefCounted<net::DnsSession>( + config, base::BindRepeating([](int min, int max) -> int { return 0; }), + nullptr); + resolve_context.InvalidateCachesAndPerSessionData(session.get(), false); + + network_context->ActivateDohProbes(); + EXPECT_TRUE(network_context->canary_domain_service_for_testing()); + EXPECT_EQ(net::CanaryDomainCheckStatus::kNotStarted, + resolve_context.doh_fallback_canary_domain_check_status()); + } + + // Case 2: should_perform_doh_fallback_upgrade is false. + { + net::DnsConfig config; + config.secure_dns_mode = net::SecureDnsMode::kAutomatic; + config.should_perform_doh_fallback_upgrade = false; + auto session = base::MakeRefCounted<net::DnsSession>( + config, base::BindRepeating([](int min, int max) -> int { return 0; }), + nullptr); + resolve_context.InvalidateCachesAndPerSessionData(session.get(), false); + + network_context->ActivateDohProbes(); + EXPECT_TRUE(network_context->canary_domain_service_for_testing()); + EXPECT_EQ(net::CanaryDomainCheckStatus::kNotStarted, + resolve_context.doh_fallback_canary_domain_check_status()); + } +} + TEST_F(NetworkContextTest, PrivacyModeDisabledByDefault) { const GURL kURL("http://foo.com"); const GURL kOtherURL("http://other.com");
diff --git a/services/proxy_resolver_win/windows_system_proxy_resolver_impl_unittest.cc b/services/proxy_resolver_win/windows_system_proxy_resolver_impl_unittest.cc index ab400514..453ada6 100644 --- a/services/proxy_resolver_win/windows_system_proxy_resolver_impl_unittest.cc +++ b/services/proxy_resolver_win/windows_system_proxy_resolver_impl_unittest.cc
@@ -2,11 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifdef UNSAFE_BUFFERS_BUILD -// TODO(crbug.com/351564777): Remove this and convert code to safer constructs. -#pragma allow_unsafe_buffers -#endif - #include "services/proxy_resolver_win/windows_system_proxy_resolver_impl.h" #include <windows.h> @@ -18,6 +13,7 @@ #include <string> #include <vector> +#include "base/compiler_specific.h" #include "base/functional/bind.h" #include "base/logging.h" #include "base/memory/ptr_util.h" @@ -109,8 +105,8 @@ // zero-initializes the memory when using the GPTR flag. *ie_proxy_config_string = static_cast<LPWSTR>( GlobalAlloc(GPTR, sizeof(wchar_t) * (setting.length() + 1))); - memcpy(*ie_proxy_config_string, setting.data(), - sizeof(wchar_t) * setting.length()); + UNSAFE_TODO(memcpy(*ie_proxy_config_string, setting.data(), + sizeof(wchar_t) * setting.length())); } // This class will internally validate behavior that MUST be present in the code @@ -282,7 +278,7 @@ void AddBypassToProxyResults() { ASSERT_LT(proxy_result_.cEntries, kMaxProxyEntryLimit - 1); AllocateProxyResultEntriesIfNeeded(); - proxy_result_.pEntries[proxy_result_.cEntries].fBypass = TRUE; + UNSAFE_TODO(proxy_result_.pEntries[proxy_result_.cEntries]).fBypass = TRUE; proxy_result_.cEntries++; } void AddDirectToProxyResults() { @@ -299,10 +295,13 @@ proxy_list_.push_back(std::move(proxy_host)); wchar_t* proxy_host_raw = const_cast<wchar_t*>(proxy_list_.back().data()); - proxy_result_.pEntries[proxy_result_.cEntries].fProxy = TRUE; - proxy_result_.pEntries[proxy_result_.cEntries].ProxyScheme = scheme; - proxy_result_.pEntries[proxy_result_.cEntries].pwszProxy = proxy_host_raw; - proxy_result_.pEntries[proxy_result_.cEntries].ProxyPort = port; + UNSAFE_TODO(proxy_result_.pEntries[proxy_result_.cEntries]).fProxy = TRUE; + UNSAFE_TODO(proxy_result_.pEntries[proxy_result_.cEntries]).ProxyScheme = + scheme; + UNSAFE_TODO(proxy_result_.pEntries[proxy_result_.cEntries]).pwszProxy = + proxy_host_raw; + UNSAFE_TODO(proxy_result_.pEntries[proxy_result_.cEntries]).ProxyPort = + port; proxy_result_.cEntries++; } @@ -366,8 +365,9 @@ proxy_result_.pEntries = new WINHTTP_PROXY_RESULT_ENTRY[kMaxProxyEntryLimit]; - std::memset(proxy_result_.pEntries, 0, - kMaxProxyEntryLimit * sizeof(WINHTTP_PROXY_RESULT_ENTRY)); + UNSAFE_TODO( + std::memset(proxy_result_.pEntries, 0, + kMaxProxyEntryLimit * sizeof(WINHTTP_PROXY_RESULT_ENTRY))); // The memory of the strings above will be backed by a vector of strings. proxy_list_.reserve(kMaxProxyEntryLimit);
diff --git a/services/shape_detection/face_detection_impl_mac_vision.mm b/services/shape_detection/face_detection_impl_mac_vision.mm index 568d70b..effc2baf 100644 --- a/services/shape_detection/face_detection_impl_mac_vision.mm +++ b/services/shape_detection/face_detection_impl_mac_vision.mm
@@ -2,15 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifdef UNSAFE_BUFFERS_BUILD -// TODO(crbug.com/351564777): Remove this and convert code to safer constructs. -#pragma allow_unsafe_buffers -#endif - #include "services/shape_detection/face_detection_impl_mac_vision.h" #include <vector> +#include "base/compiler_specific.h" #include "base/functional/bind.h" #include "base/logging.h" #include "base/memory/ptr_util.h" @@ -29,9 +25,11 @@ for (NSUInteger i = 0; i < landmark_region.pointCount; ++i) { // The points are normalized to the bounding box of the detected face. landmark->locations.emplace_back( - landmark_region.normalizedPoints[i].x * bounding_box.width() + + UNSAFE_TODO(landmark_region.normalizedPoints[i]).x * + bounding_box.width() + bounding_box.x(), - (1 - landmark_region.normalizedPoints[i].y) * bounding_box.height() + + (1 - UNSAFE_TODO(landmark_region.normalizedPoints[i]).y) * + bounding_box.height() + bounding_box.y()); } return landmark;
diff --git a/services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_parser_android_unittest.cc b/services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_parser_android_unittest.cc index 5886572..2d82eb4 100644 --- a/services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_parser_android_unittest.cc +++ b/services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_parser_android_unittest.cc
@@ -2,16 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifdef UNSAFE_BUFFERS_BUILD -// TODO(crbug.com/351564777): Remove this and convert code to safer constructs. -#pragma allow_unsafe_buffers -#endif - #include "services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_parser_android.h" #include <string_view> #include "base/android/java_heap_dump_generator.h" +#include "base/compiler_specific.h" #include "base/files/scoped_temp_dir.h" #include "base/logging.h" #include "base/strings/strcat.h" @@ -92,7 +88,7 @@ parser.strings_.emplace( 1, std::make_unique<HprofParser::StringReference>( - reinterpret_cast<const char*>(file_data + 16), 4)); + reinterpret_cast<const char*>(UNSAFE_TODO(file_data + 16)), 4)); parser.ParseClassTag(); auto it = parser.class_objects_.find(2); @@ -126,7 +122,7 @@ parser.strings_.emplace( 1, std::make_unique<HprofParser::StringReference>( - reinterpret_cast<const char*>(file_data + 60), 4)); + reinterpret_cast<const char*>(UNSAFE_TODO(file_data + 60)), 4)); parser.class_objects_.emplace( 3, std::make_unique<ClassObject>(3, "class_obj_dummy")); @@ -315,7 +311,7 @@ ASSERT_EQ(parser.class_objects().size(), std::size(expected)); for (unsigned i = 1; i < std::size(expected); ++i) { - const auto& expected_class = expected[i - 1]; + const auto& expected_class = UNSAFE_TODO(expected[i - 1]); auto it = parser.class_objects().find(i); ASSERT_TRUE(it != parser.class_objects().end()) << "missing object id " << i;
diff --git a/services/webnn/coreml/graph_builder_coreml.cc b/services/webnn/coreml/graph_builder_coreml.cc index 8a70657..e858686 100644 --- a/services/webnn/coreml/graph_builder_coreml.cc +++ b/services/webnn/coreml/graph_builder_coreml.cc
@@ -2,11 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifdef UNSAFE_BUFFERS_BUILD -// TODO(crbug.com/349653202): Remove this and spanify to fix the errors. -#pragma allow_unsafe_buffers -#endif - #include "services/webnn/coreml/graph_builder_coreml.h" #include <algorithm> @@ -22,6 +17,7 @@ #include <type_traits> #include "base/bits.h" +#include "base/compiler_specific.h" #include "base/containers/fixed_flat_set.h" #include "base/containers/span.h" #include "base/containers/span_reader.h" @@ -634,7 +630,8 @@ requires internal::IsSupportedTensorType<DataType> CoreML::Specification::MILSpec::Value CreateScalarImmediateValue( const DataType& value) { - return CreateTensorImmediateValue(/*dimensions=*/{}, base::span(&value, 1u)); + return CreateTensorImmediateValue(/*dimensions=*/{}, + UNSAFE_TODO(base::span(&value, 1u))); } // `Operation` input can bind to a `Value` or name, when binding to a name it @@ -5973,9 +5970,10 @@ float16s[i].data = fp16_ieee_from_fp32_value( data.ValueOrDefault(std::numeric_limits<float>::infinity())); } - RETURN_IF_ERROR(weight_item->WriteBytes(base::span<const uint8_t>( - reinterpret_cast<const uint8_t*>(float16s.data()), - subspan_byte_size))); + RETURN_IF_ERROR( + weight_item->WriteBytes(UNSAFE_TODO(base::span<const uint8_t>( + reinterpret_cast<const uint8_t*>(float16s.data()), + subspan_byte_size)))); break; } case OperandDataType::kFloat32: { @@ -5988,9 +5986,10 @@ floats[i] = data.ValueOrDefault(std::numeric_limits<float>::infinity()); } - RETURN_IF_ERROR(weight_item->WriteBytes(base::span<const uint8_t>( - reinterpret_cast<const uint8_t*>(floats.data()), - subspan_byte_size))); + RETURN_IF_ERROR( + weight_item->WriteBytes(UNSAFE_TODO(base::span<const uint8_t>( + reinterpret_cast<const uint8_t*>(floats.data()), + subspan_byte_size)))); break; } case OperandDataType::kUint8: { @@ -6003,9 +6002,10 @@ uints[i] = data.ValueOrDefault(std::numeric_limits<uint8_t>::infinity()); } - RETURN_IF_ERROR(weight_item->WriteBytes(base::span<const uint8_t>( - reinterpret_cast<const uint8_t*>(uints.data()), - subspan_byte_size))); + RETURN_IF_ERROR( + weight_item->WriteBytes(UNSAFE_TODO(base::span<const uint8_t>( + reinterpret_cast<const uint8_t*>(uints.data()), + subspan_byte_size)))); break; } case OperandDataType::kInt8: { @@ -6018,8 +6018,10 @@ ints[i] = data.ValueOrDefault(std::numeric_limits<int8_t>::infinity()); } - RETURN_IF_ERROR(weight_item->WriteBytes(base::span<const uint8_t>( - reinterpret_cast<const uint8_t*>(ints.data()), subspan_byte_size))); + RETURN_IF_ERROR( + weight_item->WriteBytes(UNSAFE_TODO(base::span<const uint8_t>( + reinterpret_cast<const uint8_t*>(ints.data()), + subspan_byte_size)))); break; } case OperandDataType::kInt32: @@ -6059,11 +6061,12 @@ sliced_dimensions)); RETURN_IF_ERROR(AddOperationForSlice(input_operand_id, sliced, beginnings, Ui32ToI32(endings), strides, block)); - ASSIGN_OR_RETURN(OperandId sliced_squeezed, - GenerateInternalOperandInfo( - input_operand_info.mil_data_type, - base::span<const uint32_t>(sliced_dimensions.begin() + 1, - sliced_dimensions.end()))); + ASSIGN_OR_RETURN( + OperandId sliced_squeezed, + GenerateInternalOperandInfo( + input_operand_info.mil_data_type, + UNSAFE_TODO(base::span<const uint32_t>(sliced_dimensions.begin() + 1, + sliced_dimensions.end())))); RETURN_IF_ERROR(AddOperationForReshape(sliced, sliced_squeezed, block)); return sliced_squeezed; }
diff --git a/services/webnn/dml/context_impl_dml.cc b/services/webnn/dml/context_impl_dml.cc index fb20176..5e7e9321f 100644 --- a/services/webnn/dml/context_impl_dml.cc +++ b/services/webnn/dml/context_impl_dml.cc
@@ -2,11 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifdef UNSAFE_BUFFERS_BUILD -// TODO(crbug.com/349653202): Remove this and spanify to fix the errors. -#pragma allow_unsafe_buffers -#endif - #include "services/webnn/dml/context_impl_dml.h" #include <limits> @@ -813,8 +808,9 @@ return; } - mojo_base::BigBuffer dst_buffer = WriteDataToDataPipeOrBigBuffer(base::span( - static_cast<const uint8_t*>(mapped_download_data), read_byte_size)); + mojo_base::BigBuffer dst_buffer = + WriteDataToDataPipeOrBigBuffer(UNSAFE_TODO(base::span( + static_cast<const uint8_t*>(mapped_download_data), read_byte_size))); download_buffer->Unmap(0, nullptr);
diff --git a/services/webnn/dml/graph_impl_dml.cc b/services/webnn/dml/graph_impl_dml.cc index 1b577808..8347dd8a 100644 --- a/services/webnn/dml/graph_impl_dml.cc +++ b/services/webnn/dml/graph_impl_dml.cc
@@ -4,11 +4,6 @@ #include "services/webnn/dml/graph_impl_dml.h" -#ifdef UNSAFE_BUFFERS_BUILD -// TODO(crbug.com/349653202): Remove this and spanify to fix the errors. -#pragma allow_unsafe_buffers -#endif - #include <winerror.h> #include <algorithm> @@ -20,6 +15,7 @@ #include "base/bits.h" #include "base/check.h" +#include "base/compiler_specific.h" #include "base/containers/fixed_flat_set.h" #include "base/containers/span.h" #include "base/feature_list.h" @@ -326,9 +322,9 @@ // Copy the input data to the upload heap with byte offset const auto& d3d12_range = aligned_byte_length.key_to_d3d12_range_map.at(operand_id); - auto mapped_buffer_span = + auto mapped_buffer_span = UNSAFE_TODO( base::span(static_cast<uint8_t*>(mapped_buffer) + d3d12_range.Begin, - constant_operand->descriptor().PackedByteLength()); + constant_operand->descriptor().PackedByteLength())); mapped_buffer_span.copy_from(constant_operand->ByteSpan()); // Create the buffer binding for each constant/input and push back into the // DML_BUFFER_BINDING array.
diff --git a/skia/ext/rgba_to_yuva.cc b/skia/ext/rgba_to_yuva.cc index 328136d..528362e 100644 --- a/skia/ext/rgba_to_yuva.cc +++ b/skia/ext/rgba_to_yuva.cc
@@ -167,31 +167,111 @@ // Permutation matrices to select the appropriate YUVA channels for each // output plane. - constexpr SkColorMatrix Yxx1(1, 0, 0, 0, 0, // + constexpr SkColorMatrix YxxA(1, 0, 0, 0, 0, // 0, 0, 0, 0, 0, // 0, 0, 0, 0, 0, // - 0, 0, 0, 0, 1); - constexpr SkColorMatrix UVx1(0, 1, 0, 0, 0, // + 0, 0, 0, 1, 0); + constexpr SkColorMatrix UxxA(0, 1, 0, 0, 0, // + 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, // + 0, 0, 0, 1, 0); + constexpr SkColorMatrix VxxA(0, 0, 1, 0, 0, // + 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, // + 0, 0, 0, 1, 0); + constexpr SkColorMatrix AxxA(0, 0, 0, 1, 0, // + 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, // + 0, 0, 0, 1, 0); + constexpr SkColorMatrix UVxA(0, 1, 0, 0, 0, // 0, 0, 1, 0, 0, // 0, 0, 0, 0, 0, // - 0, 0, 0, 0, 1); + 0, 0, 0, 1, 0); + constexpr SkColorMatrix VUxA(0, 0, 1, 0, 0, // + 0, 1, 0, 0, 0, // + 0, 0, 0, 0, 0, // + 0, 0, 0, 1, 0); + constexpr SkColorMatrix YUVA(1, 0, 0, 0, 0, // + 0, 1, 0, 0, 0, // + 0, 0, 1, 0, 0, // + 0, 0, 0, 1, 0); + constexpr SkColorMatrix UYVA(0, 1, 0, 0, 0, // + 1, 0, 0, 0, 0, // + 0, 0, 1, 0, 0, // + 0, 0, 0, 1, 0); // Only Y_UV has been tested. std::array<SkColorMatrix, SkYUVAInfo::kMaxPlanes> permutation_matrices; switch (dst_yuva_info.planeConfig()) { - case SkYUVAInfo::PlaneConfig::kY_UV: - permutation_matrices[0] = Yxx1; - permutation_matrices[1] = UVx1; + case SkYUVAInfo::PlaneConfig::kY_U_V: + permutation_matrices[0] = YxxA; + permutation_matrices[1] = UxxA; + permutation_matrices[2] = VxxA; break; - default: - DLOG(ERROR) << "Unsupported plane configuration."; - return; + case SkYUVAInfo::PlaneConfig::kY_V_U: + permutation_matrices[0] = YxxA; + permutation_matrices[1] = VxxA; + permutation_matrices[2] = UxxA; + break; + case SkYUVAInfo::PlaneConfig::kY_UV: + permutation_matrices[0] = YxxA; + permutation_matrices[1] = UVxA; + break; + case SkYUVAInfo::PlaneConfig::kY_VU: + permutation_matrices[0] = YxxA; + permutation_matrices[1] = VUxA; + break; + case SkYUVAInfo::PlaneConfig::kYUV: + permutation_matrices[0] = YUVA; + break; + case SkYUVAInfo::PlaneConfig::kUYV: + permutation_matrices[0] = UYVA; + break; + case SkYUVAInfo::PlaneConfig::kY_U_V_A: + permutation_matrices[0] = YxxA; + permutation_matrices[1] = UxxA; + permutation_matrices[2] = VxxA; + permutation_matrices[3] = AxxA; + break; + case SkYUVAInfo::PlaneConfig::kY_V_U_A: + permutation_matrices[0] = YxxA; + permutation_matrices[1] = VxxA; + permutation_matrices[2] = UxxA; + permutation_matrices[3] = AxxA; + break; + case SkYUVAInfo::PlaneConfig::kY_UV_A: + permutation_matrices[0] = YxxA; + permutation_matrices[1] = UVxA; + permutation_matrices[2] = AxxA; + break; + case SkYUVAInfo::PlaneConfig::kY_VU_A: + permutation_matrices[0] = YxxA; + permutation_matrices[1] = VUxA; + permutation_matrices[2] = AxxA; + break; + case SkYUVAInfo::PlaneConfig::kYUVA: + permutation_matrices[0] = YUVA; + break; + case SkYUVAInfo::PlaneConfig::kUYVA: + permutation_matrices[0] = UYVA; + break; + case SkYUVAInfo::PlaneConfig::kUnknown: + NOTREACHED(); } SkColorMatrix rgb_to_yuv_matrix = SkColorMatrix::RGBtoYUV(dst_yuva_info.yuvColorSpace()); // Blit each plane. for (int plane = 0; plane < dst_yuva_info.numPlanes(); ++plane) { + // If there exists a separate alpha plane, then, when rendering to the + // individual planes (which have R and RG pixel formats), ensure that + // those pixel values are unpremultiplied. + if (dst_yuva_info.hasAlpha() && dst_yuva_info.numPlanes() > 1 && + !src_image->isOpaque()) { + CHECK_EQ(dst_surfaces[plane]->imageInfo().alphaType(), + kUnpremul_SkAlphaType); + } + SkCanvas* plane_canvas = dst_surfaces[plane]->getCanvas(); SkColorMatrix color_matrix = rgb_to_yuv_matrix; @@ -202,11 +282,14 @@ SkPaint paint; paint.setBlendMode(SkBlendMode::kSrc); - // Blend the input image over black before performing RGB to YUV - // conversion, to match un-accelerated versions. - paint.setColorFilter(SkColorFilters::Compose( - SkColorFilters::Matrix(color_matrix), - SkColorFilters::Blend(SK_ColorBLACK, SkBlendMode::kDstOver))); + auto filter = SkColorFilters::Matrix(color_matrix); + if (!dst_yuva_info.hasAlpha()) { + // Blend the input image over black before performing RGB to YUV + // conversion, to match un-accelerated versions. + filter = SkColorFilters::Compose( + filter, SkColorFilters::Blend(SK_ColorBLACK, SkBlendMode::kDstOver)); + } + paint.setColorFilter(filter); auto [ssHoriz, ssVert] = dst_yuva_info.planeSubsamplingFactors(plane); const SkRect plane_dst_rect = SkRect::MakeXYWH( @@ -239,70 +322,37 @@ } } -void ConvertRGBAToOrFromYUVA(SkPixmap src_pm, - SkYUVColorSpace src_yuv_cs, - SkPixmap dst_pm, - SkYUVColorSpace dst_yuv_cs) { - CHECK(src_pm.dimensions() == dst_pm.dimensions()); - if (dst_pm.alphaType() == kOpaque_SkAlphaType) { - CHECK(src_pm.alphaType() == kOpaque_SkAlphaType); - } - const int h = src_pm.height(); - const int w = src_pm.width(); - const SkAlphaType alpha_type = src_pm.alphaType() == kOpaque_SkAlphaType - ? kOpaque_SkAlphaType - : kUnpremul_SkAlphaType; +void ConvertRGBAToYUVA(SkPixmap src_pm, + const SkYUVAInfo& dst_yuva_info, + const std::vector<SkPixmap>& dst_pixmaps) { + CHECK(src_pm.dimensions() == dst_pixmaps[0].dimensions()); + CHECK(dst_yuva_info.isValid()); + const auto* dst_color_space = dst_pixmaps[0].colorSpace(); - // If we need to do YUV to RGB conversion, we will do that in-place in - // `src_rgb_row`. - SkPixmap src_rgb_row; - SkBitmap src_rgb_row_bm; - std::array<float, 20> src_matrix; - if (src_yuv_cs != kIdentity_SkYUVColorSpace) { - src_rgb_row_bm.allocPixels(SkImageInfo::Make( - w, 1, kRGBA_F32_SkColorType, alpha_type, src_pm.refColorSpace())); - src_rgb_row = src_rgb_row_bm.pixmap(); - SkColorMatrix::YUVtoRGB(src_yuv_cs).getRowMajor(src_matrix.data()); - } - - // If we need to do RGB to YUV conversion, we will do that in-place in - // `dst_rgb_row`. - SkBitmap dst_rgb_row_bm; - SkPixmap dst_rgb_row; - std::array<float, 20> dst_matrix; - if (dst_yuv_cs != kIdentity_SkYUVColorSpace) { - dst_rgb_row_bm.allocPixels(SkImageInfo::Make( - w, 1, kRGBA_F32_SkColorType, alpha_type, dst_pm.refColorSpace())); - dst_rgb_row = dst_rgb_row_bm.pixmap(); - SkColorMatrix::RGBtoYUV(dst_yuv_cs).getRowMajor(dst_matrix.data()); - } - - // Process one row at a time. - for (int y = 0; y < h; ++y) { - // Extract the source and destination rows. - SkPixmap src_yuv_row; - SkPixmap dst_yuv_row; - const auto row_rect = SkIRect::MakeXYWH(0, y, w, 1); - CHECK(src_pm.extractSubset(&src_yuv_row, row_rect)); - CHECK(dst_pm.extractSubset(&dst_yuv_row, row_rect)); - - // Let `src_rgb_row` be the source row in full-range RGB. - if (src_yuv_cs == kIdentity_SkYUVColorSpace) { - src_rgb_row = src_yuv_row; - } else { - CHECK(src_yuv_row.readPixels(src_rgb_row)); - ApplyColorMatrix(src_rgb_row, src_matrix); + auto src_image = SkImages::RasterFromPixmap(src_pm, nullptr, nullptr); + std::array<sk_sp<SkSurface>, SkYUVAInfo::kMaxPlanes> dst_surfaces; + std::array<SkSurface*, SkYUVAInfo::kMaxPlanes> dst_surface_ptrs; + for (size_t p = 0; p < dst_pixmaps.size(); ++p) { + SkPixmap pm = dst_pixmaps[p]; + // Color space conversion is from `src_pm`'s color space to the space of + // `dst_pixmaps`. This is only coherent if all of `dst_pixmaps` have the + // same color space. + CHECK(SkColorSpace::Equals(dst_color_space, pm.colorSpace())); + // If `dst_pixmaps` has a separate alpha channel, then the alpha type + // of the individual planes for sampling isn't relevant (their alpha channel + // will always sample 1). For rendering to the plane's SkPixmap as an + // SkSurface, force its alpha type to unpremultiplied to ensure that + // the values written to it are unpremultiplied. + if (dst_yuva_info.hasAlpha() && dst_yuva_info.numPlanes() > 1 && + !src_pm.isOpaque()) { + pm = SkPixmap(pm.info().makeAlphaType(kUnpremul_SkAlphaType), pm.addr(), + pm.rowBytes()); } - - // Write `dst_yuv_row`. - if (dst_yuv_cs == kIdentity_SkYUVColorSpace) { - CHECK(src_rgb_row.readPixels(dst_yuv_row)); - } else { - src_rgb_row.readPixels(dst_rgb_row); - ApplyColorMatrix(dst_rgb_row, dst_matrix); - dst_rgb_row.readPixels(dst_yuv_row); - } + dst_surfaces[p] = SkSurfaces::WrapPixels(pm); + dst_surface_ptrs[p] = dst_surfaces[p].get(); } + BlitRGBAToYUVA(src_image.get(), + base::span<SkSurface* const>(dst_surface_ptrs), dst_yuva_info); } void ConvertYUVAToRGBA(const SkYUVAInfo& src_yuva_info,
diff --git a/skia/ext/rgba_to_yuva.h b/skia/ext/rgba_to_yuva.h index c848873..1f881d81 100644 --- a/skia/ext/rgba_to_yuva.h +++ b/skia/ext/rgba_to_yuva.h
@@ -31,18 +31,14 @@ bool clear_destination = false, const SkRect& src_region = SkRect::MakeEmpty()); -// Apply the following conversion pipeline: -// - Read the pixel from `src_pm` -// - Perform YUV to RGB conversion according to `src_yuv_cs` -// - Apply color space conversion to `dst_pm`'s color space -// - Perform RGB to YUV conversion according to `dst_yuv_cs`. -// It is allowed for `src_pm` and `dst_pm` to be the same. This function will -// CHECK if `src_pm` `dst_pm` differ in size, or if `dst_pm` is opaque but -// `src_pm` is not. -SK_API void ConvertRGBAToOrFromYUVA(SkPixmap src_pm, - SkYUVColorSpace src_yuv_cs, - SkPixmap dst_pm, - SkYUVColorSpace dst_yuv_cs); +// Perform RGB to YUV conversion and color space conversion. The SkColorSpace +// conversion will be from `src_pm`'s color space to `dst_pixmaps`' color +// spaces (which are CHECKed to be all the same). The `dst_yuva_info` must +// be valid. If `dst_yuva_info` is opaque but `src_pm` isn't, then `src_pm` +// will be blended against black. +SK_API void ConvertRGBAToYUVA(SkPixmap src_pm, + const SkYUVAInfo& dst_yuva_info, + const std::vector<SkPixmap>& dst_pixmaps); // Convert the planes in `src_pixmaps` to the RGBA `dst`. If `src_yuva_info` // is valid, then YUV to RGB conversion will be performed, and `src_bit_depth`
diff --git a/skia/ext/rgba_to_yuva_unittest.cc b/skia/ext/rgba_to_yuva_unittest.cc index be2a3f8..f3882bc 100644 --- a/skia/ext/rgba_to_yuva_unittest.cc +++ b/skia/ext/rgba_to_yuva_unittest.cc
@@ -23,7 +23,7 @@ }; // Several pairs of test points that will equal each other when called with - // ConvertRGBAToOrFromYUVA. + // ConvertRGBAToYUVA. constexpr size_t kNumTests = 2; std::array<std::array<TestPoint, 2>, kNumTests> test_cases; test_cases[0] = { @@ -71,7 +71,7 @@ &colors[i], sizeof(SkColor4f)); } - for (size_t test_config = 0; test_config < 3; ++test_config) { + for (size_t test_config = 0; test_config < 2; ++test_config) { size_t src_index = 0; size_t dst_index = 0; switch (test_config) { @@ -81,11 +81,6 @@ dst_index = 1; break; case 1: - // Copy from pixmaps[1] to pixmaps[0]. - src_index = 1; - dst_index = 0; - break; - case 2: // Copy from pixmaps[0] to pixmaps[0] to ensure that copying to // oneself works. src_index = 0; @@ -95,9 +90,12 @@ colors[dst_index] = SkColors::kTransparent; colors[src_index] = test_points[src_index].color; - ConvertRGBAToOrFromYUVA( - pixmaps[src_index], test_points[src_index].yuv_color_space, - pixmaps[dst_index], test_points[dst_index].yuv_color_space); + ConvertRGBAToYUVA(pixmaps[src_index], + SkYUVAInfo(pixmaps[dst_index].dimensions(), + SkYUVAInfo::PlaneConfig::kYUVA, + SkYUVAInfo::Subsampling::k444, + test_points[dst_index].yuv_color_space), + {pixmaps[dst_index]}); EXPECT_NEAR(colors[dst_index].fR, test_points[dst_index].color.fR, kEpsilon); EXPECT_NEAR(colors[dst_index].fG, test_points[dst_index].color.fG, @@ -110,6 +108,119 @@ } } +TEST(RGBAToYUVA, PlaneConfig) { + constexpr float kEpsilon = 0.01; + struct TestPoint { + SkColor4f color; + SkAlphaType alpha_type; + sk_sp<SkColorSpace> color_space; + SkYUVColorSpace yuv_color_space; + }; + + constexpr size_t kNumTestPoints = 2; + std::array<TestPoint, kNumTestPoints> src_points({ + { + .color = {0.91748756f, 0.20028681f, 0.13856059f, 1.f}, + .alpha_type = kPremul_SkAlphaType, + .color_space = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, + SkNamedGamut::kDisplayP3), + .yuv_color_space = kIdentity_SkYUVColorSpace, + }, + { + .color = {0.5f, 0.f, 0.f, 0.5f}, + .alpha_type = kPremul_SkAlphaType, + .color_space = SkColorSpace::MakeSRGB(), + .yuv_color_space = kIdentity_SkYUVColorSpace, + }, + }); + std::array<TestPoint, kNumTestPoints> dst_points({ + { + .color = {0.245331f, 0.401317f, 0.941177f, 1.f}, + .alpha_type = kPremul_SkAlphaType, + .color_space = SkColorSpace::MakeSRGB(), + .yuv_color_space = kRec709_SkYUVColorSpace, + }, + { + .color = {0.245331f, 0.401317f, 0.941177f, 0.5f}, + // This is a very important test configuration. The individual planes + // are opaque, but this verifies that they are internally changed to + // unpremultiplied when blitting. + .alpha_type = kOpaque_SkAlphaType, + .color_space = SkColorSpace::MakeSRGB(), + .yuv_color_space = kRec709_SkYUVColorSpace, + }, + }); + + struct Config { + SkYUVAInfo::PlaneConfig plane_config; + const char* name; + }; + std::array<std::vector<Config>, kNumTestPoints> configs({ + { + {SkYUVAInfo::PlaneConfig::kY_U_V, "Y_U_V"}, + {SkYUVAInfo::PlaneConfig::kY_UV, "Y_UV"}, + {SkYUVAInfo::PlaneConfig::kY_U_V_A, "Y_U_V_A"}, + {SkYUVAInfo::PlaneConfig::kY_UV_A, "Y_UV_A"}, + }, + { + {SkYUVAInfo::PlaneConfig::kY_U_V_A, "Y_U_V_A"}, + {SkYUVAInfo::PlaneConfig::kY_UV_A, "Y_UV_A"}, + }, + }); + + for (size_t t = 0; t < kNumTestPoints; ++t) { + const auto& src_point = src_points[t]; + const auto& dst_point = dst_points[t]; + + SkPixmap src_pixmap( + SkImageInfo::Make(1, 1, kRGBA_F32_SkColorType, src_point.alpha_type, + src_point.color_space), + &src_point.color, sizeof(SkColor4f)); + + for (const auto& config : configs[t]) { + SkYUVAInfo yuva_info(src_pixmap.dimensions(), config.plane_config, + SkYUVAInfo::Subsampling::k444, + dst_point.yuv_color_space); + int num_planes = yuva_info.numPlanes(); + std::vector<SkColor4f> plane_colors(num_planes, SkColors::kTransparent); + std::vector<SkPixmap> plane_pixmaps(num_planes); + for (int i = 0; i < num_planes; ++i) { + plane_pixmaps[i] = SkPixmap( + SkImageInfo::Make(1, 1, kRGBA_F32_SkColorType, dst_point.alpha_type, + dst_point.color_space), + &plane_colors[i], sizeof(SkColor4f)); + } + + ConvertRGBAToYUVA(src_pixmap, yuva_info, plane_pixmaps); + + const auto expected = dst_point.color; + EXPECT_NEAR(plane_colors[0].fR, expected.fR, kEpsilon) << config.name; + switch (config.plane_config) { + case SkYUVAInfo::PlaneConfig::kY_U_V: + EXPECT_NEAR(plane_colors[1].fR, expected.fG, kEpsilon) << config.name; + EXPECT_NEAR(plane_colors[2].fR, expected.fB, kEpsilon) << config.name; + break; + case SkYUVAInfo::PlaneConfig::kY_UV: + EXPECT_NEAR(plane_colors[1].fR, expected.fG, kEpsilon) << config.name; + EXPECT_NEAR(plane_colors[1].fG, expected.fB, kEpsilon) << config.name; + break; + case SkYUVAInfo::PlaneConfig::kY_U_V_A: + EXPECT_NEAR(plane_colors[1].fR, expected.fG, kEpsilon) << config.name; + EXPECT_NEAR(plane_colors[2].fR, expected.fB, kEpsilon) << config.name; + EXPECT_NEAR(plane_colors[3].fR, expected.fA, kEpsilon) << config.name; + break; + case SkYUVAInfo::PlaneConfig::kY_UV_A: + EXPECT_NEAR(plane_colors[1].fR, expected.fG, kEpsilon) << config.name; + EXPECT_NEAR(plane_colors[1].fG, expected.fB, kEpsilon) << config.name; + EXPECT_NEAR(plane_colors[2].fR, expected.fA, kEpsilon) << config.name; + break; + default: + FAIL() << "Unsupported plane config"; + } + } + } +} + TEST(RGBAToYUVATest, Basic) { // The test color is sRGB red.
diff --git a/storage/browser/blob/blob_reader.cc b/storage/browser/blob/blob_reader.cc index ce1139d..5560762 100644 --- a/storage/browser/blob/blob_reader.cc +++ b/storage/browser/blob/blob_reader.cc
@@ -18,6 +18,7 @@ #include "base/memory/ptr_util.h" #include "base/task/thread_pool.h" #include "base/trace_event/trace_event.h" +#include "base/types/expected.h" #include "components/file_access/scoped_file_access.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" @@ -424,15 +425,17 @@ return true; } -void BlobReader::DidGetFileItemLength(size_t index, int64_t result) { +void BlobReader::DidGetFileItemLength( + size_t index, + base::expected<int64_t, net::Error> result) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Do nothing if we have encountered an error. if (net_error_) return; - if (result < 0) { - InvalidateCallbacksAndDone(result, std::move(size_callback_)); + if (!result.has_value()) { + InvalidateCallbacksAndDone(result.error(), std::move(size_callback_)); return; } @@ -440,7 +443,7 @@ DCHECK_LT(index, items.size()); const BlobDataItem& item = *items.at(index); uint64_t length; - if (!ResolveFileItemLength(item, result, &length)) { + if (!ResolveFileItemLength(item, result.value(), &length)) { InvalidateCallbacksAndDone(net::ERR_FAILED, std::move(size_callback_)); return; }
diff --git a/storage/browser/blob/blob_reader.h b/storage/browser/blob/blob_reader.h index 32fb37c0..549a19a 100644 --- a/storage/browser/blob/blob_reader.h +++ b/storage/browser/blob/blob_reader.h
@@ -18,9 +18,11 @@ #include "base/gtest_prod_util.h" #include "base/memory/weak_ptr.h" #include "base/sequence_checker.h" +#include "base/types/expected.h" #include "mojo/public/cpp/base/big_buffer.h" #include "mojo/public/cpp/system/data_pipe.h" #include "net/base/completion_once_callback.h" +#include "net/base/net_errors.h" #include "services/network/public/cpp/data_pipe_to_source_stream.h" #include "storage/browser/blob/blob_storage_constants.h" @@ -204,7 +206,8 @@ bool ResolveFileItemLength(const BlobDataItem& item, int64_t total_length, uint64_t* output_length); - void DidGetFileItemLength(size_t index, int64_t result); + void DidGetFileItemLength(size_t index, + base::expected<int64_t, net::Error> result); void DidCountSize(); // For reading the blob.
diff --git a/storage/browser/blob/blob_reader_unittest.cc b/storage/browser/blob/blob_reader_unittest.cc index b9b43c7..1850cae 100644 --- a/storage/browser/blob/blob_reader_unittest.cc +++ b/storage/browser/blob/blob_reader_unittest.cc
@@ -129,7 +129,7 @@ return net::ERR_IO_PENDING; } - int64_t GetLength(net::Int64CompletionOnceCallback size_callback) override { + int64_t GetLength(GetLengthCallback size_callback) override { // When async_task_runner_ is not set, return synchronously. if (!async_task_runner_.get()) { if (net_error_ == net::OK) { @@ -144,7 +144,8 @@ } else { async_task_runner_->PostTask( FROM_HERE, base::BindOnce(std::move(size_callback), - static_cast<int64_t>(net_error_))); + base::unexpected( + static_cast<net::Error>(net_error_)))); } return net::ERR_IO_PENDING; }
diff --git a/storage/browser/file_system/file_stream_reader.h b/storage/browser/file_system/file_stream_reader.h index 20fc8e51..9d2e57ca3 100644 --- a/storage/browser/file_system/file_stream_reader.h +++ b/storage/browser/file_system/file_stream_reader.h
@@ -14,10 +14,12 @@ #include "base/files/file.h" #include "base/functional/callback_forward.h" #include "base/functional/callback_helpers.h" +#include "base/types/expected.h" #include "components/file_access/scoped_file_access.h" #include "components/file_access/scoped_file_access_delegate.h" #include "components/services/storage/public/cpp/filesystem/filesystem_proxy.h" #include "net/base/completion_once_callback.h" +#include "net/base/net_errors.h" namespace base { class FilePath; @@ -34,6 +36,10 @@ // A generic interface for reading a file-like object. class FileStreamReader { public: + // Callback type for GetLength(). + using GetLengthCallback = + base::OnceCallback<void(base::expected<int64_t, net::Error>)>; + // Creates a new FileReader for a local file |file_path|. // |initial_offset| specifies the offset in the file where the first read // should start. If the given offset is out of the file range any @@ -80,14 +86,12 @@ // Returns the length of the file if it could successfully retrieve the // file info *and* its last modification time equals to - // expected modification time (rv >= 0 cases). - // Otherwise, a negative error code is returned (rv < 0 cases). + // expected modification time. + // On success, the callback receives the file size (>= 0). + // On failure, the callback receives an error code. // If the stream is deleted while it has an in-flight GetLength operation // |callback| will not be called. - // Note that the return type is int64_t to return a larger file's size (a file - // larger than 2G) but an error code should fit in the int range (may be - // smaller than int64_t range). - virtual int64_t GetLength(net::Int64CompletionOnceCallback callback) = 0; + virtual int64_t GetLength(GetLengthCallback callback) = 0; }; } // namespace storage
diff --git a/storage/browser/file_system/file_stream_reader_test.h b/storage/browser/file_system/file_stream_reader_test.h index d5c3db3..e609119 100644 --- a/storage/browser/file_system/file_stream_reader_test.h +++ b/storage/browser/file_system/file_stream_reader_test.h
@@ -8,11 +8,14 @@ #include <string_view> #include "base/files/scoped_temp_dir.h" +#include "base/functional/bind.h" #include "base/functional/callback_helpers.h" +#include "base/run_loop.h" #include "base/task/single_thread_task_runner.h" #include "base/test/task_environment.h" #include "base/threading/thread.h" #include "base/time/time.h" +#include "base/types/expected.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/base/test_completion_callback.h" @@ -98,22 +101,18 @@ ASSERT_TRUE(data_or_error.has_value()); EXPECT_THAT(data_or_error.value(), testing::IsEmpty()); - net::TestInt64CompletionCallback callback; - int64_t length_result = reader->GetLength(callback.callback()); - if (length_result == net::ERR_IO_PENDING) - length_result = callback.WaitForResult(); - ASSERT_EQ(0, length_result); + auto length_result = GetLengthFromReader(reader.get()); + ASSERT_TRUE(length_result.has_value()); + ASSERT_EQ(0, length_result.value()); } TYPED_TEST_P(FileStreamReaderTypedTest, GetLengthNormal) { std::unique_ptr<FileStreamReader> reader( this->CreateFileReader(std::string(this->kTestFileName), 0, this->test_file_modification_time())); - net::TestInt64CompletionCallback callback; - int64_t result = reader->GetLength(callback.callback()); - if (result == net::ERR_IO_PENDING) - result = callback.WaitForResult(); - ASSERT_EQ(static_cast<int64_t>(this->kTestData.size()), result); + auto result = GetLengthFromReader(reader.get()); + ASSERT_TRUE(result.has_value()); + ASSERT_EQ(static_cast<int64_t>(this->kTestData.size()), result.value()); } TYPED_TEST_P(FileStreamReaderTypedTest, GetLengthAfterModified) { @@ -122,31 +121,25 @@ std::unique_ptr<FileStreamReader> reader( this->CreateFileReader(std::string(this->kTestFileName), 0, this->test_file_modification_time())); - net::TestInt64CompletionCallback callback1; - int64_t result = reader->GetLength(callback1.callback()); - if (result == net::ERR_IO_PENDING) - result = callback1.WaitForResult(); - ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result); + auto result = GetLengthFromReader(reader.get()); + ASSERT_FALSE(result.has_value()); + ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result.error()); // With nullptr expected modification time this should work. reader = this->CreateFileReader(std::string(this->kTestFileName), 0, base::Time()); - net::TestInt64CompletionCallback callback2; - result = reader->GetLength(callback2.callback()); - if (result == net::ERR_IO_PENDING) - result = callback2.WaitForResult(); - ASSERT_EQ(static_cast<int64_t>(this->kTestData.size()), result); + result = GetLengthFromReader(reader.get()); + ASSERT_TRUE(result.has_value()); + ASSERT_EQ(static_cast<int64_t>(this->kTestData.size()), result.value()); } TYPED_TEST_P(FileStreamReaderTypedTest, GetLengthWithOffset) { std::unique_ptr<FileStreamReader> reader(this->CreateFileReader( std::string(this->kTestFileName), 3, base::Time())); - net::TestInt64CompletionCallback callback; - int64_t result = reader->GetLength(callback.callback()); - if (result == net::ERR_IO_PENDING) - result = callback.WaitForResult(); + auto result = GetLengthFromReader(reader.get()); // Initial offset does not affect the result of GetLength. - ASSERT_EQ(static_cast<int64_t>(this->kTestData.size()), result); + ASSERT_TRUE(result.has_value()); + ASSERT_EQ(static_cast<int64_t>(this->kTestData.size()), result.value()); } TYPED_TEST_P(FileStreamReaderTypedTest, ReadNormal) {
diff --git a/storage/browser/file_system/file_stream_test_utils.cc b/storage/browser/file_system/file_stream_test_utils.cc index 0db5f33..ce7b446 100644 --- a/storage/browser/file_system/file_stream_test_utils.cc +++ b/storage/browser/file_system/file_stream_test_utils.cc
@@ -7,6 +7,9 @@ #include <string> #include <utility> +#include "base/functional/bind.h" +#include "base/functional/callback.h" +#include "base/run_loop.h" #include "base/types/expected.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" @@ -41,12 +44,28 @@ return result; } -int64_t GetLengthFromReader(FileStreamReader* reader) { +base::expected<int64_t, net::Error> GetLengthFromReader( + FileStreamReader* reader) { EXPECT_NE(nullptr, reader); - net::TestInt64CompletionCallback callback; - int rv = reader->GetLength(callback.callback()); - return callback.GetResult(rv); + base::expected<int64_t, net::Error> result; + base::RunLoop run_loop; + int64_t rv = reader->GetLength(base::BindOnce( + [](base::expected<int64_t, net::Error>* out_result, + base::OnceClosure quit_closure, + base::expected<int64_t, net::Error> length) { + *out_result = length; + std::move(quit_closure).Run(); + }, + &result, run_loop.QuitClosure())); + if (rv != net::ERR_IO_PENDING) { + if (rv < 0) { + return base::unexpected(static_cast<net::Error>(rv)); + } + return rv; + } + run_loop.Run(); + return result; } int WriteStringToWriter(FileStreamWriter* writer, const std::string& data) {
diff --git a/storage/browser/file_system/file_stream_test_utils.h b/storage/browser/file_system/file_stream_test_utils.h index ee702ce..99447b4a 100644 --- a/storage/browser/file_system/file_stream_test_utils.h +++ b/storage/browser/file_system/file_stream_test_utils.h
@@ -21,7 +21,8 @@ // Returns the length of the file if it could be successfully retrieved, // otherwise a net error. -int64_t GetLengthFromReader(FileStreamReader* reader); +base::expected<int64_t, net::Error> GetLengthFromReader( + FileStreamReader* reader); // Writes `data` to `writer`, an initialized FileStreamWriter. Returns net::OK // if successful, otherwise a net error.
diff --git a/storage/browser/file_system/local_file_stream_reader.cc b/storage/browser/file_system/local_file_stream_reader.cc index 3c18bec..5d8efc7 100644 --- a/storage/browser/file_system/local_file_stream_reader.cc +++ b/storage/browser/file_system/local_file_stream_reader.cc
@@ -91,8 +91,7 @@ return net::ERR_IO_PENDING; } -int64_t LocalFileStreamReader::GetLength( - net::Int64CompletionOnceCallback callback) { +int64_t LocalFileStreamReader::GetLength(GetLengthCallback callback) { bool posted = task_runner_->PostTaskAndReplyWithResult( FROM_HERE, base::BindOnce(&DoGetFileInfo, file_path_), base::BindOnce(&LocalFileStreamReader::DidGetFileInfoForGetLength, @@ -149,9 +148,9 @@ void LocalFileStreamReader::DidVerifyForOpen( net::CompletionOnceCallback callback, file_access::ScopedFileAccess scoped_file_access, - int64_t get_length_result) { - if (get_length_result < 0) { - std::move(callback).Run(static_cast<int>(get_length_result)); + base::expected<int64_t, net::Error> get_length_result) { + if (!get_length_result.has_value()) { + std::move(callback).Run(get_length_result.error()); return; } @@ -224,18 +223,23 @@ } void LocalFileStreamReader::DidGetFileInfoForGetLength( - net::Int64CompletionOnceCallback callback, + GetLengthCallback callback, base::FileErrorOr<base::File::Info> result) { - std::move(callback).Run([&]() -> int64_t { - ASSIGN_OR_RETURN(const auto& file_info, result, net::FileErrorToNetError); - if (file_info.is_directory) { - return net::ERR_FILE_NOT_FOUND; - } - if (!VerifySnapshotTime(expected_modification_time_, file_info)) { - return net::ERR_UPLOAD_FILE_CHANGED; - } - return file_info.size; - }()); + if (!result.has_value()) { + std::move(callback).Run( + base::unexpected(net::FileErrorToNetError(result.error()))); + return; + } + const auto& file_info = result.value(); + if (file_info.is_directory) { + std::move(callback).Run(base::unexpected(net::ERR_FILE_NOT_FOUND)); + return; + } + if (!VerifySnapshotTime(expected_modification_time_, file_info)) { + std::move(callback).Run(base::unexpected(net::ERR_UPLOAD_FILE_CHANGED)); + return; + } + std::move(callback).Run(file_info.size); } void LocalFileStreamReader::OnRead(
diff --git a/storage/browser/file_system/local_file_stream_reader.h b/storage/browser/file_system/local_file_stream_reader.h index dca27b0..11f3e08 100644 --- a/storage/browser/file_system/local_file_stream_reader.h +++ b/storage/browser/file_system/local_file_stream_reader.h
@@ -54,7 +54,7 @@ int Read(net::IOBuffer* buf, int buf_len, net::CompletionOnceCallback callback) override; - int64_t GetLength(net::Int64CompletionOnceCallback callback) override; + int64_t GetLength(GetLengthCallback callback) override; private: void Open(net::CompletionOnceCallback callback); @@ -65,8 +65,8 @@ file_access::ScopedFileAccess scoped_file_access); void DidVerifyForOpen(net::CompletionOnceCallback callback, file_access::ScopedFileAccess scoped_file_access, - int64_t get_length_result); - void DidOpenFileStream(file_access::ScopedFileAccess scoped_file_access, + base::expected<int64_t, net::Error> get_length_result); + void DidOpenFileStream(file_access::ScopedFileAccess /*scoped_file_access*/, net::Error result); void DidSeekFileStream(int64_t seek_result); void DidOpenForRead(net::IOBuffer* buf, @@ -75,7 +75,7 @@ int open_result); void OnRead(base::expected<base::ByteSize, net::Error> read_result); - void DidGetFileInfoForGetLength(net::Int64CompletionOnceCallback callback, + void DidGetFileInfoForGetLength(GetLengthCallback callback, base::FileErrorOr<base::File::Info> result); net::CompletionOnceCallback callback_;
diff --git a/storage/browser/file_system/memory_file_stream_reader.cc b/storage/browser/file_system/memory_file_stream_reader.cc index d786744..da8e1e11 100644 --- a/storage/browser/file_system/memory_file_stream_reader.cc +++ b/storage/browser/file_system/memory_file_stream_reader.cc
@@ -69,24 +69,23 @@ std::move(callback).Run(result); } -int64_t MemoryFileStreamReader::GetLength( - net::Int64CompletionOnceCallback callback) { +int64_t MemoryFileStreamReader::GetLength(GetLengthCallback callback) { task_runner_->PostTaskAndReplyWithResult( FROM_HERE, base::BindOnce( [](base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> util, - const base::FilePath& path, - base::Time expected_modification_time) -> int64_t { + const base::FilePath& path, base::Time expected_modification_time) + -> base::expected<int64_t, net::Error> { if (!util) - return net::ERR_FILE_NOT_FOUND; + return base::unexpected(net::ERR_FILE_NOT_FOUND); base::File::Info file_info; if (util->GetFileInfo(path, &file_info) != base::File::FILE_OK) { - return net::ERR_FILE_NOT_FOUND; + return base::unexpected(net::ERR_FILE_NOT_FOUND); } if (!FileStreamReader::VerifySnapshotTime( expected_modification_time, file_info)) { - return net::ERR_UPLOAD_FILE_CHANGED; + return base::unexpected(net::ERR_UPLOAD_FILE_CHANGED); } return file_info.size; @@ -101,9 +100,9 @@ } void MemoryFileStreamReader::OnGetLengthCompleted( - net::Int64CompletionOnceCallback callback, - int64_t result) { - std::move(callback).Run(result); + GetLengthCallback callback, + base::expected<int64_t, net::Error> result) { + std::move(callback).Run(std::move(result)); } } // namespace storage
diff --git a/storage/browser/file_system/memory_file_stream_reader.h b/storage/browser/file_system/memory_file_stream_reader.h index 5a6080a..60898598 100644 --- a/storage/browser/file_system/memory_file_stream_reader.h +++ b/storage/browser/file_system/memory_file_stream_reader.h
@@ -10,7 +10,9 @@ #include "base/files/file_path.h" #include "base/memory/weak_ptr.h" #include "base/time/time.h" +#include "base/types/expected.h" #include "net/base/completion_once_callback.h" +#include "net/base/net_errors.h" #include "storage/browser/file_system/file_stream_reader.h" #include "storage/browser/file_system/obfuscated_file_util_memory_delegate.h" @@ -45,12 +47,12 @@ int Read(net::IOBuffer* buf, int buf_len, net::CompletionOnceCallback callback) override; - int64_t GetLength(net::Int64CompletionOnceCallback callback) override; + int64_t GetLength(GetLengthCallback callback) override; private: void OnReadCompleted(net::CompletionOnceCallback callback, int result); - void OnGetLengthCompleted(net::Int64CompletionOnceCallback callback, - int64_t result); + void OnGetLengthCompleted(GetLengthCallback callback, + base::expected<int64_t, net::Error> result); base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util_;
diff --git a/storage/browser/file_system/sandbox_file_stream_reader.cc b/storage/browser/file_system/sandbox_file_stream_reader.cc index a1e044e..bf00a59 100644 --- a/storage/browser/file_system/sandbox_file_stream_reader.cc +++ b/storage/browser/file_system/sandbox_file_stream_reader.cc
@@ -50,8 +50,7 @@ std::move(callback))); } -int64_t SandboxFileStreamReader::GetLength( - net::Int64CompletionOnceCallback callback) { +int64_t SandboxFileStreamReader::GetLength(GetLengthCallback callback) { if (file_reader_) return file_reader_->GetLength(std::move(callback)); @@ -103,7 +102,7 @@ } void SandboxFileStreamReader::DidCreateSnapshotForGetLength( - net::Int64CompletionOnceCallback callback, + GetLengthCallback callback, base::File::Error file_error, const base::File::Info& file_info, const base::FilePath& platform_path, @@ -113,7 +112,8 @@ has_pending_create_snapshot_ = false; if (file_error != base::File::FILE_OK) { - std::move(callback).Run(net::FileErrorToNetError(file_error)); + std::move(callback).Run( + base::unexpected(net::FileErrorToNetError(file_error))); return; } @@ -128,7 +128,7 @@ &SandboxFileStreamReader::OnGetLength, weak_factory_.GetWeakPtr(), std::move(callback_pair.first))); if (rv != net::ERR_IO_PENDING) - std::move(callback_pair.second).Run(rv); + std::move(callback_pair.second).Run(base::unexpected(net::ERR_FAILED)); } void SandboxFileStreamReader::CreateFileReader( @@ -153,9 +153,9 @@ } void SandboxFileStreamReader::OnGetLength( - net::Int64CompletionOnceCallback callback, - int64_t rv) { - std::move(callback).Run(rv); + GetLengthCallback callback, + base::expected<int64_t, net::Error> rv) { + std::move(callback).Run(std::move(rv)); } } // namespace storage
diff --git a/storage/browser/file_system/sandbox_file_stream_reader.h b/storage/browser/file_system/sandbox_file_stream_reader.h index cedb000..9565ecb 100644 --- a/storage/browser/file_system/sandbox_file_stream_reader.h +++ b/storage/browser/file_system/sandbox_file_stream_reader.h
@@ -15,7 +15,9 @@ #include "base/memory/raw_ptr.h" #include "base/memory/scoped_refptr.h" #include "base/time/time.h" +#include "base/types/expected.h" #include "net/base/completion_once_callback.h" +#include "net/base/net_errors.h" #include "storage/browser/blob/shareable_file_reference.h" #include "storage/browser/file_system/file_stream_reader.h" #include "storage/browser/file_system/file_system_operation_runner.h" @@ -54,7 +56,7 @@ int Read(net::IOBuffer* buf, int buf_len, net::CompletionOnceCallback callback) override; - int64_t GetLength(net::Int64CompletionOnceCallback callback) override; + int64_t GetLength(GetLengthCallback callback) override; private: friend class FileStreamReader; @@ -69,7 +71,7 @@ const base::FilePath& platform_path, scoped_refptr<ShareableFileReference> file_ref); void DidCreateSnapshotForGetLength( - net::Int64CompletionOnceCallback callback, + GetLengthCallback callback, base::File::Error file_error, const base::File::Info& file_info, const base::FilePath& platform_path, @@ -77,7 +79,8 @@ void CreateFileReader(const base::FilePath& platform_path); void OnRead(net::CompletionOnceCallback callback, int rv); - void OnGetLength(net::Int64CompletionOnceCallback callback, int64_t rv); + void OnGetLength(GetLengthCallback callback, + base::expected<int64_t, net::Error> rv); scoped_refptr<FileSystemContext> file_system_context_; FileSystemURL url_;
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index 921d335..c3495874 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json
@@ -786,21 +786,6 @@ ] } ], - "AndroidBottomToolbar": [ - { - "platforms": [ - "android" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "AndroidBottomToolbar" - ] - } - ] - } - ], "AndroidBottomToolbarV2": [ { "platforms": [ @@ -1009,21 +994,6 @@ ] } ], - "AndroidOmniboxAimShortcut": [ - { - "platforms": [ - "android" - ], - "experiments": [ - { - "name": "Enabled_all_20250815", - "enable_features": [ - "OmniboxAimShortcutTypedState" - ] - } - ] - } - ], "AndroidOpenIncognitoAsWindow": [ { "platforms": [ @@ -1267,21 +1237,6 @@ ] } ], - "AndroidTabDeclutterPerformanceImprovements": [ - { - "platforms": [ - "android" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "AndroidTabDeclutterPerformanceImprovements" - ] - } - ] - } - ], "AndroidThreadPriority": [ { "platforms": [ @@ -1800,27 +1755,6 @@ ] } ], - "ArcVmIdleManagerV2": [ - { - "platforms": [ - "chromeos" - ], - "experiments": [ - { - "name": "EnabledAC_V2", - "params": { - "delay_ms": "420000", - "ignore_battery_for_test": "true", - "pending_idle_reactivate": "false" - }, - "enable_features": [ - "ArcIdleManager" - ], - "min_os_version": "15795.0.0" - } - ] - } - ], "ArcVmInitThrottle3": [ { "platforms": [ @@ -3637,22 +3571,6 @@ ] } ], - "BGPriorityWorkerThreadsWithPISupport": [ - { - "platforms": [ - "android" - ], - "experiments": [ - { - "name": "EnabledBoth_20250410", - "enable_features": [ - "UseBackgroundPriorityForWorkerThreads", - "UsePriorityInheritanceMutex" - ] - } - ] - } - ], "BackForwardCacheMemoryControls": [ { "platforms": [ @@ -3752,26 +3670,6 @@ ] } ], - "BackForwardCacheNonStickyDoubleFix": [ - { - "platforms": [ - "android", - "chromeos", - "ios", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Holdback", - "disable_features": [ - "BackForwardCacheNonStickyDoubleFix" - ] - } - ] - } - ], "BackForwardCacheNotRestoredReasons": [ { "platforms": [ @@ -4262,26 +4160,6 @@ ] } ], - "BookmarksUseBinaryTreeInTitledUrlIndex": [ - { - "platforms": [ - "android", - "chromeos", - "ios", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "BookmarksUseBinaryTreeInTitledUrlIndex" - ] - } - ] - } - ], "BoostClosingTabs": [ { "platforms": [ @@ -4578,21 +4456,6 @@ ] } ], - "CCTEphemeralMediaViewerExperiment": [ - { - "platforms": [ - "android" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "CCTEphemeralMediaViewerExperiment" - ] - } - ] - } - ], "CCTGoogleBottomBar": [ { "platforms": [ @@ -4790,26 +4653,6 @@ ] } ], - "CanvasTextNg": [ - { - "platforms": [ - "android_webview", - "android", - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "CanvasTextNg" - ] - } - ] - } - ], "CastStreamingExponentialVideoBitrateAlgorithm": [ { "platforms": [ @@ -5420,27 +5263,6 @@ ] } ], - "ChromeOSHoldingSpaceSuggestions": [ - { - "platforms": [ - "chromeos" - ], - "experiments": [ - { - "name": "Enabled_Dogfood", - "enable_features": [ - "HoldingSpaceSuggestions" - ] - }, - { - "name": "Enabled_Teamfood", - "enable_features": [ - "HoldingSpaceSuggestions" - ] - } - ] - } - ], "ChromeOSMaterialNextWaveOneWithTimeOfDayM117AndBeyond": [ { "platforms": [ @@ -6172,6 +5994,28 @@ ] } ], + "ClientSideDetectionNewObservers": [ + { + "platforms": [ + "android", + "chromeos", + "linux", + "mac", + "windows" + ], + "experiments": [ + { + "name": "Enabled", + "params": { + "ClassificationDelay": "0.0" + }, + "enable_features": [ + "ClientSideDetectionNewObservers" + ] + } + ] + } + ], "ClientSideDetectionOnDeviceModelLazyDownloadAndroid": [ { "platforms": [ @@ -6841,28 +6685,6 @@ ] } ], - "ConfigurableV8CodeCacheHotHours": [ - { - "platforms": [ - "android", - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "cache_72h_20230904", - "params": { - "V8CodeCacheHotHours": "72" - }, - "enable_features": [ - "ConfigurableV8CodeCacheHotHours" - ] - } - ] - } - ], "ConnectionKeepAliveForHttp2": [ { "platforms": [ @@ -7217,24 +7039,6 @@ ] } ], - "CrOSFederatedAutocorrectPhh": [ - { - "platforms": [ - "chromeos" - ], - "experiments": [ - { - "name": "Enabled", - "params": { - "launch_stage": "prod" - }, - "enable_features": [ - "AutocorrectFederatedPhh" - ] - } - ] - } - ], "CrOSFederatedLauncherQueryPhhVersion2": [ { "platforms": [ @@ -7662,25 +7466,6 @@ ] } ], - "CreateURLLoaderPipeAsync": [ - { - "platforms": [ - "android", - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "CreateURLLoaderPipeAsync" - ] - } - ] - } - ], "CredentialManagementThirdPartyWebApiRequestForwarding": [ { "platforms": [ @@ -7977,36 +7762,6 @@ ] } ], - "DTCAntivirusSignalEnabled": [ - { - "platforms": [ - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "DTCAntivirusSignalEnabled" - ] - } - ] - } - ], - "DTCKeyRotationUploadedBySharedAPIEnabled": [ - { - "platforms": [ - "mac" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "DTCKeyRotationUploadedBySharedAPIEnabled" - ] - } - ] - } - ], "DXGIWaitableSwapChain": [ { "platforms": [ @@ -8192,21 +7947,6 @@ ] } ], - "DeferConciergeStartup": [ - { - "platforms": [ - "chromeos" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "DeferConciergeStartup" - ] - } - ] - } - ], "DeferSpeculativeRFHCreation": [ { "platforms": [ @@ -8469,24 +8209,6 @@ ] } ], - "DesktopNtpMobilePromo": [ - { - "platforms": [ - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled_20241101", - "enable_features": [ - "NtpMobilePromo" - ] - } - ] - } - ], "DesktopNtpModules": [ { "platforms": [ @@ -8830,12 +8552,8 @@ "experiments": [ { "name": "Enabled", - "params": { - "Value": "l2_enabled" - }, "enable_features": [ "DeviceBoundSessionsForRestrictedSites", - "DeviceBoundSessionsForRestrictedSitesExperimentId", "EnableOAuthMultiloginStandardCookiesBinding", "UseUnexportableKeyServiceInBrowserProcess" ] @@ -9018,22 +8736,6 @@ ] } ], - "DisableMemoryReclaimerInBackground": [ - { - "platforms": [ - "android", - "android_webview" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "DisableMemoryReclaimerInBackground" - ] - } - ] - } - ], "DisableNonYUVOverlaysFromExo": [ { "platforms": [ @@ -9071,21 +8773,6 @@ ] } ], - "DisableSystemBlur": [ - { - "platforms": [ - "chromeos" - ], - "experiments": [ - { - "name": "DisableSystemBlur", - "enable_features": [ - "DisableSystemBlur" - ] - } - ] - } - ], "DisableUrgentPageDiscarding": [ { "platforms": [ @@ -9247,45 +8934,6 @@ ] } ], - "DnsOverHttpsCloudflareFamilies": [ - { - "platforms": [ - "android", - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled_20260220", - "enable_features": [ - "DohProviderCloudflareFamily", - "DohProviderCloudflareSecurity" - ] - } - ] - } - ], - "DnsOverHttpsQuad9Secure": [ - { - "platforms": [ - "android", - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "DohProviderQuad9Secure" - ] - } - ] - } - ], "DoNotEvictOnAXLocationChange": [ { "platforms": [ @@ -9622,27 +9270,6 @@ ] } ], - "EnableAsyncUploadAfterVerdict": [ - { - "platforms": [ - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "params": { - "max_parallel_requests": "15" - }, - "enable_features": [ - "EnableAsyncUploadAfterVerdict" - ] - } - ] - } - ], "EnableBestEffortTaskInhibitingPolicy": [ { "platforms": [ @@ -9887,21 +9514,6 @@ ] } ], - "EnableFallbackFontsCrashReporting": [ - { - "platforms": [ - "windows" - ], - "experiments": [ - { - "name": "Enabled_20230404", - "enable_features": [ - "EnableFallbackFontsCrashReporting" - ] - } - ] - } - ], "EnableFatalCrashEventsObserver": [ { "platforms": [ @@ -10179,24 +9791,6 @@ ] } ], - "EnablePrintWatermark": [ - { - "platforms": [ - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Disabled", - "enable_features": [ - "EnablePrintWatermark" - ] - } - ] - } - ], "EnableRootNsDnsProxy": [ { "platforms": [ @@ -10316,24 +9910,6 @@ ] } ], - "EndOfLifeIncentive": [ - { - "platforms": [ - "chromeos" - ], - "experiments": [ - { - "name": "EnabledOffer_20230324", - "params": { - "incentive_type": "offer" - }, - "enable_features": [ - "EolIncentive" - ] - } - ] - } - ], "EnhancedFieldsForSecOps": [ { "platforms": [ @@ -11084,24 +10660,6 @@ ] } ], - "FieldRankServerClassification": [ - { - "platforms": [ - "android", - "android_webview", - "chromeos", - "ios", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "FieldRankServerClassification_Experiment_20250422" - } - ] - } - ], "FlingSchedulingImprovements": [ { "platforms": [ @@ -11438,56 +10996,6 @@ ] } ], - "GamingPerksStudy": [ - { - "platforms": [ - "chromeos" - ], - "experiments": [ - { - "name": "CounterfactualControl_20240805", - "params": { - "IPH_ScalableIphGaming_availability": ">=0", - "IPH_ScalableIphGaming_event_1": "name:ScalableIphGameWindowOpened;comparator:>0;window:365;storage:365", - "IPH_ScalableIphGaming_event_trigger": "name:IphScalableIphGamingEventTrigger;comparator:==0;window:365;storage:365", - "IPH_ScalableIphGaming_event_used": "name:IphScalableIphGamingEventUsed;comparator:any;window:365;storage:365", - "IPH_ScalableIphGaming_session_rate": "any", - "IPH_ScalableIphGaming_tracking_only": "true", - "IPH_ScalableIphGaming_x_CustomConditionTriggerEvent": "ScalableIphUnlocked", - "IPH_ScalableIphGaming_x_CustomUiType": "None", - "IPH_ScalableIphGaming_x_CustomVersionNumber": "1" - }, - "enable_features": [ - "IPH_ScalableIphGaming" - ] - }, - { - "name": "Notification_20240805", - "params": { - "IPH_ScalableIphGaming_availability": ">=0", - "IPH_ScalableIphGaming_event_1": "name:ScalableIphGameWindowOpened;comparator:>0;window:365;storage:365", - "IPH_ScalableIphGaming_event_trigger": "name:IphScalableIphGamingEventTrigger;comparator:==0;window:365;storage:365", - "IPH_ScalableIphGaming_event_used": "name:IphScalableIphGamingEventUsed;comparator:any;window:365;storage:365", - "IPH_ScalableIphGaming_session_rate": "any", - "IPH_ScalableIphGaming_x_CustomButtonActionType": "PerksMinecraftRealms2023", - "IPH_ScalableIphGaming_x_CustomConditionTriggerEvent": "ScalableIphUnlocked", - "IPH_ScalableIphGaming_x_CustomNotificationBodyText": "", - "IPH_ScalableIphGaming_x_CustomNotificationButtonText": "Get perk", - "IPH_ScalableIphGaming_x_CustomNotificationIcon": "Redeem", - "IPH_ScalableIphGaming_x_CustomNotificationId": "scalable_iph_gaming", - "IPH_ScalableIphGaming_x_CustomNotificationImageType": "Minecraft", - "IPH_ScalableIphGaming_x_CustomNotificationSummaryText": "None", - "IPH_ScalableIphGaming_x_CustomNotificationTitle": "Get 3 months of Minecraft Realms Plus at no cost on your Chromebook", - "IPH_ScalableIphGaming_x_CustomUiType": "Notification", - "IPH_ScalableIphGaming_x_CustomVersionNumber": "1" - }, - "enable_features": [ - "IPH_ScalableIphGaming" - ] - } - ] - } - ], "GeminiAntiscamProtectionForMetricsCollection": [ { "platforms": [ @@ -11698,22 +11206,6 @@ ] } ], - "GlicClosedCaptioning": [ - { - "platforms": [ - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "GlicClosedCaptioning" - ] - } - ] - } - ], "GlicConfirmNavigationToNewOrigins": [ { "platforms": [ @@ -13025,21 +12517,6 @@ ] } ], - "HeadlessTabModel": [ - { - "platforms": [ - "android" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "HeadlessTabModel" - ] - } - ] - } - ], "HeatmapPalmDetectionStudy": [ { "platforms": [ @@ -13160,42 +12637,6 @@ ] } ], - "HistoryOptInEducationalTip": [ - { - "platforms": [ - "android" - ], - "experiments": [ - { - "name": "EnabledWithTurnOn", - "params": { - "history_opt_in_educational_tip_param": "0" - }, - "enable_features": [ - "HistoryOptInEducationalTip" - ] - }, - { - "name": "EnabledWithLetsGo", - "params": { - "history_opt_in_educational_tip_param": "1" - }, - "enable_features": [ - "HistoryOptInEducationalTip" - ] - }, - { - "name": "EnabledWithContinue", - "params": { - "history_opt_in_educational_tip_param": "2" - }, - "enable_features": [ - "HistoryOptInEducationalTip" - ] - } - ] - } - ], "HistoryQueryOnlyLocalFirst": [ { "platforms": [ @@ -13431,21 +12872,6 @@ ] } ], - "IOSBackgroundPasteboardAccess": [ - { - "platforms": [ - "ios" - ], - "experiments": [ - { - "name": "Enabled_CanaryDev", - "enable_features": [ - "OnlyAccessClipboardAsync" - ] - } - ] - } - ], "IOSBestOfAppFRE": [ { "platforms": [ @@ -13711,21 +13137,6 @@ ] } ], - "IOSEnablePasswordManagerTrustedVaultWidget": [ - { - "platforms": [ - "ios" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "IOSEnablePasswordManagerTrustedVaultWidget" - ] - } - ] - } - ], "IOSExpandedSetupList": [ { "platforms": [ @@ -13816,21 +13227,6 @@ ] } ], - "IOSFullscreenImprovement": [ - { - "platforms": [ - "ios" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "FullscreenImprovement" - ] - } - ] - } - ], "IOSFullscreenScrollThreshold": [ { "platforms": [ @@ -13911,21 +13307,6 @@ ] } ], - "IOSHomeMemoryImprovements": [ - { - "platforms": [ - "ios" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "HomeMemoryImprovements" - ] - } - ] - } - ], "IOSKeepsRenderProcessAlive": [ { "platforms": [ @@ -14010,21 +13391,6 @@ ] } ], - "IOSLensFetchSrp": [ - { - "platforms": [ - "ios" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "LensVsintParamEnabled" - ] - } - ] - } - ], "IOSLensGestureTextSelectionDisabled": [ { "platforms": [ @@ -14104,21 +13470,6 @@ ] } ], - "IOSManageAccountStorage": [ - { - "platforms": [ - "ios" - ], - "experiments": [ - { - "name": "Enabled_20250328", - "enable_features": [ - "IOSManageAccountStorage" - ] - } - ] - } - ], "IOSManualLogUploadsInTheFRE": [ { "platforms": [ @@ -14149,21 +13500,6 @@ ] } ], - "IOSMiniMapUniversalLink": [ - { - "platforms": [ - "ios" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "IOSMiniMapUniversalLink" - ] - } - ] - } - ], "IOSMostVisitedTilesCustomization": [ { "platforms": [ @@ -14209,39 +13545,6 @@ ] } ], - "IOSOmniboxAimShortcut": [ - { - "platforms": [ - "ios" - ], - "experiments": [ - { - "name": "Enabled1Char", - "params": { - "MinimumTypedCharactersToInvokeAimShortcut": "1" - }, - "enable_features": [ - "OmniboxAimShortcutTypedState" - ] - } - ] - } - ], - "IOSOneTapMiniMapRemoveSectionsBreaks": [ - { - "platforms": [ - "ios" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "IOSOneTapMiniMapRemoveSectionsBreaks" - ] - } - ] - } - ], "IOSOneTapMiniMapRestrictions": [ { "platforms": [ @@ -14429,21 +13732,6 @@ ] } ], - "IOSSaveToPhotosImprovements": [ - { - "platforms": [ - "ios" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "SaveToPhotosImprovements" - ] - } - ] - } - ], "IOSScopedCriticalActionSkipOnShutdown": [ { "platforms": [ @@ -14645,24 +13933,6 @@ ] } ], - "IOSTabResumptionThumbnails": [ - { - "platforms": [ - "ios" - ], - "experiments": [ - { - "name": "Enabled", - "params": { - "tr-images-type": "thumbnails" - }, - "enable_features": [ - "TabResumptionImages" - ] - } - ] - } - ], "IOSTipsNotificationsAlternativeStrings": [ { "platforms": [ @@ -16199,21 +15469,6 @@ ] } ], - "MacICloudKeychainRecoveryFactor": [ - { - "platforms": [ - "mac" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "EnableICloudKeychainRecoveryFactor" - ] - } - ] - } - ], "MainIdleBypassSchedulerExperiment": [ { "platforms": [ @@ -16269,21 +15524,6 @@ ] } ], - "MakeAccountsAvailableInIdentityManager": [ - { - "platforms": [ - "android" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "MakeAccountsAvailableInIdentityManager" - ] - } - ] - } - ], "MaliciousApkDownloadCheck": [ { "platforms": [ @@ -16718,21 +15958,6 @@ ] } ], - "MiniOriginBar": [ - { - "platforms": [ - "android" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "MiniOriginBar" - ] - } - ] - } - ], "MobilePromoOnDesktop": [ { "platforms": [ @@ -17386,24 +16611,6 @@ ] } ], - "NoSynchronizedScrolling": [ - { - "platforms": [ - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Disabled", - "disable_features": [ - "SynchronizedScrolling" - ] - } - ] - } - ], "NoThrottlingVisibleAgent": [ { "platforms": [ @@ -18181,21 +17388,6 @@ ] } ], - "OneGroupPerRenderer": [ - { - "platforms": [ - "chromeos" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "OneGroupPerRenderer" - ] - } - ] - } - ], "OngoingProcesses": [ { "platforms": [ @@ -18470,21 +17662,6 @@ ] } ], - "OzonePlatformAutoExternal": [ - { - "platforms": [ - "linux" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "OverrideDefaultOzonePlatformHintToAuto" - ] - } - ] - } - ], "PWAIconAndTitleInNativeNotificationsWin": [ { "platforms": [ @@ -18889,26 +18066,6 @@ ] } ], - "PartitionAllocShortMemoryReclaim": [ - { - "platforms": [ - "android", - "android_webview", - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "PartitionAllocShortMemoryReclaim" - ] - } - ] - } - ], "PartitionAllocUnretainedDanglingPtr": [ { "platforms": [ @@ -19081,21 +18238,6 @@ ] } ], - "PasswordlessSetup": [ - { - "platforms": [ - "chromeos" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "AllowPasswordlessSetup" - ] - } - ] - } - ], "PasswordsSyncErrorMessageAlternatives": [ { "platforms": [ @@ -19430,48 +18572,6 @@ ] } ], - "PerformanceControlsHatsStudy": [ - { - "platforms": [ - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "EnabledHighEfficiencyOptOut_20230223", - "params": { - "en_site_id": "hEedoxCS30ugnJ3q1cK0YKKzXjSm", - "probability": "1.0" - }, - "enable_features": [ - "PerformanceControlsHighEfficiencyOptOutSurvey" - ], - "disable_features": [ - "PerformanceControlsBatteryPerformanceSurvey", - "PerformanceControlsBatterySaverOptOutSurvey", - "PerformanceControlsPerformanceSurvey" - ] - }, - { - "name": "EnabledBatterySaverOptOut_20230223", - "params": { - "en_site_id": "gyuzHnE940ugnJ3q1cK0RyY65eG8", - "probability": "1.0" - }, - "enable_features": [ - "PerformanceControlsBatterySaverOptOutSurvey" - ], - "disable_features": [ - "PerformanceControlsBatteryPerformanceSurvey", - "PerformanceControlsHighEfficiencyOptOutSurvey", - "PerformanceControlsPerformanceSurvey" - ] - } - ] - } - ], "PerformanceControlsPPMSurvey": [ { "platforms": [ @@ -19557,24 +18657,6 @@ ] } ], - "PermissionPromiseLifetimeModulation": [ - { - "platforms": [ - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "PermissionPromiseLifetimeModulation" - ] - } - ] - } - ], "PermissionsAIP92": [ { "platforms": [ @@ -19594,24 +18676,6 @@ ] } ], - "PermissionsAIv3": [ - { - "platforms": [ - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "PermissionsAIv3" - ] - } - ] - } - ], "PermissionsAIv4": [ { "platforms": [ @@ -20074,26 +19138,6 @@ ] } ], - "PowerBookmarkBackend": [ - { - "platforms": [ - "android", - "chromeos", - "ios", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "PowerBookmarkBackend" - ] - } - ] - } - ], "PreconnectCreateNewTab": [ { "platforms": [ @@ -20280,27 +19324,6 @@ ] } ], - "PrefixCookieHttp": [ - { - "platforms": [ - "android", - "android_webview", - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "PrefixCookieHostHttp", - "PrefixCookieHttp" - ] - } - ] - } - ], "PreloadTopChromeWebUILessNavigations": [ { "platforms": [ @@ -20551,21 +19574,6 @@ ] } ], - "Printscanmgr": [ - { - "platforms": [ - "chromeos" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "AddPrinterViaPrintscanmgr" - ] - } - ] - } - ], "PrivacySandboxActivityTypeStorage": [ { "platforms": [ @@ -20716,21 +19724,6 @@ ] } ], - "ProcessBoundStringEncryption": [ - { - "platforms": [ - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "ProcessBoundStringEncryption" - ] - } - ] - } - ], "ProcessHtmlDataImmediately": [ { "platforms": [ @@ -21078,29 +20071,6 @@ ] } ], - "PushMessagingGcmEndpointEnvironment": [ - { - "platforms": [ - "android", - "chromeos", - "fuchsia", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled_Dogfood", - "params": { - "PushMessagingGcmEndpointUrl": "https://jmt17.google.com/fcm/send/" - }, - "enable_features": [ - "PushMessagingGcmEndpointEnvironment" - ] - } - ] - } - ], "PushMessagingGcmEndpointWebpushPath": [ { "platforms": [ @@ -21265,21 +20235,6 @@ ] } ], - "ReadAloudTapToSeek": [ - { - "platforms": [ - "android" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "ReadAloudTapToSeek" - ] - } - ] - } - ], "ReadAnythingIPHRollout": [ { "platforms": [ @@ -22048,22 +21003,6 @@ ] } ], - "RetrieveTrustedVaultKeyKeyboardAccessoryAction": [ - { - "platforms": [ - "android", - "android_webview" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "RetrieveTrustedVaultKeyKeyboardAccessoryAction" - ] - } - ] - } - ], "RetryGetVideoCaptureDeviceInfos": [ { "platforms": [ @@ -22099,25 +21038,6 @@ ] } ], - "RollBackModeB": [ - { - "platforms": [ - "android", - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "RollBackModeB" - ] - } - ] - } - ], "RoundedWindows": [ { "platforms": [ @@ -22150,30 +21070,6 @@ ] } ], - "RuntimeOnMessageWebExtensionPolyfillSupport": [ - { - "platforms": [ - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "RuntimeOnMessageWebExtensionPolyfillSupport" - ] - }, - { - "name": "Disabled", - "disable_features": [ - "RuntimeOnMessageWebExtensionPolyfillSupport" - ] - } - ] - } - ], "RustyBmpFeature": [ { "platforms": [ @@ -22359,52 +21255,6 @@ ] } ], - "SafetyHubOneOffHats": [ - { - "platforms": [ - "chromeos", - "fuchsia", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "SafetyHub_NoNotification", - "params": { - "probability": "0.0155", - "safety-hub-ab-control-trigger-id": "XxxasK3Zu0ugnJ3q1cK0Yv2LaPos", - "survey": "safety-hub-control" - }, - "enable_features": [ - "SafetyHubHaTSOneOffSurvey" - ] - }, - { - "name": "SafetyHub_Notification", - "params": { - "probability": "0.0155", - "safety-hub-ab-notification-trigger-id": "MVbs4W6AD0ugnJ3q1cK0Nc4CZvj2", - "survey": "safety-hub-notification" - }, - "enable_features": [ - "SafetyHubHaTSOneOffSurvey" - ] - }, - { - "name": "SafetyHub_Interaction", - "params": { - "probability": "0.1516", - "safety-hub-ab-interaction-trigger-id": "o6hoLZqjX0ugnJ3q1cK0XCht6ei6", - "survey": "safety-hub-interaction" - }, - "enable_features": [ - "SafetyHubHaTSOneOffSurvey" - ] - } - ] - } - ], "SafetyHubUnusedPermissionRevocationForAllSurfaces": [ { "platforms": [ @@ -22444,909 +21294,6 @@ ] } ], - "ScalableIphStudy": [ - { - "platforms": [ - "chromeos" - ], - "experiments": [ - { - "name": "CounterfactualControl_BETA_20231204", - "params": { - "IPH_ScalableIphHelpAppBasedNudge_availability": "any", - "IPH_ScalableIphHelpAppBasedNudge_blocked_by": "none", - "IPH_ScalableIphHelpAppBasedNudge_blocking": "none", - "IPH_ScalableIphHelpAppBasedNudge_event_trigger": "name:ScalableIphHelpAppBasedNudgeTrigger;comparator:==0;window:1;storage:7", - "IPH_ScalableIphHelpAppBasedNudge_event_used": "name:ScalableIphHelpAppBasedNudgeEventUsedNotUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedNudge_session_rate": "any", - "IPH_ScalableIphHelpAppBasedNudge_session_rate_impact": "none", - "IPH_ScalableIphHelpAppBasedNudge_tracking_only": "true", - "IPH_ScalableIphHelpAppBasedNudge_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphHelpAppBasedNudge_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphHelpAppBasedNudge_x_CustomUiType": "None", - "IPH_ScalableIphHelpAppBasedNudge_x_CustomVersionNumber": "1", - "IPH_ScalableIphTimerBasedEight_availability": "any", - "IPH_ScalableIphTimerBasedEight_blocked_by": "none", - "IPH_ScalableIphTimerBasedEight_blocking": "none", - "IPH_ScalableIphTimerBasedEight_event_1": "name:ScalableIphFiveMinTick;comparator:>=26;window:7;storage:8", - "IPH_ScalableIphTimerBasedEight_event_trigger": "name:ScalableIphTimerBasedEightTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedEight_event_used": "name:ScalableIphTimerBasedEightEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedEight_session_rate": "any", - "IPH_ScalableIphTimerBasedEight_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedEight_tracking_only": "true", - "IPH_ScalableIphTimerBasedEight_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedEight_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedEight_x_CustomConditionPhoneHubOnboardingEligible": "True", - "IPH_ScalableIphTimerBasedEight_x_CustomUiType": "None", - "IPH_ScalableIphTimerBasedEight_x_CustomVersionNumber": "1", - "IPH_ScalableIphTimerBasedFive_availability": "any", - "IPH_ScalableIphTimerBasedFive_blocked_by": "none", - "IPH_ScalableIphTimerBasedFive_blocking": "none", - "IPH_ScalableIphTimerBasedFive_event_1": "name:ScalableIphFiveMinTick;comparator:>=16;window:7;storage:8", - "IPH_ScalableIphTimerBasedFive_event_trigger": "name:ScalableIphTimerBasedFiveTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedFive_event_used": "name:ScalableIphTimerBasedFiveEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedFive_session_rate": "any", - "IPH_ScalableIphTimerBasedFive_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedFive_tracking_only": "true", - "IPH_ScalableIphTimerBasedFive_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedFive_x_CustomConditionHasSavedPrinter": "False", - "IPH_ScalableIphTimerBasedFive_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedFive_x_CustomUiType": "None", - "IPH_ScalableIphTimerBasedFive_x_CustomVersionNumber": "1", - "IPH_ScalableIphTimerBasedFour_availability": "any", - "IPH_ScalableIphTimerBasedFour_blocked_by": "none", - "IPH_ScalableIphTimerBasedFour_blocking": "none", - "IPH_ScalableIphTimerBasedFour_event_1": "name:ScalableIphFiveMinTick;comparator:>=12;window:7;storage:8", - "IPH_ScalableIphTimerBasedFour_event_precondition_personalization_app": "name:ScalableIphOpenPersonalizationApp;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedFour_event_trigger": "name:ScalableIphTimerBasedFourTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedFour_event_used": "name:ScalableIphTimerBasedFourEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedFour_session_rate": "any", - "IPH_ScalableIphTimerBasedFour_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedFour_tracking_only": "true", - "IPH_ScalableIphTimerBasedFour_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedFour_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedFour_x_CustomUiType": "None", - "IPH_ScalableIphTimerBasedFour_x_CustomVersionNumber": "1", - "IPH_ScalableIphTimerBasedNine_availability": "any", - "IPH_ScalableIphTimerBasedNine_blocked_by": "none", - "IPH_ScalableIphTimerBasedNine_blocking": "none", - "IPH_ScalableIphTimerBasedNine_event_1": "name:ScalableIphFiveMinTick;comparator:>=30;window:7;storage:8", - "IPH_ScalableIphTimerBasedNine_event_precondition_youtube_app_list": "name:ScalableIphAppListItemActivationYouTube;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedNine_event_precondition_youtube_shelf": "name:ScalableIphShelfItemActivationYouTube;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedNine_event_trigger": "name:ScalableIphTimerBasedNineTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedNine_event_used": "name:ScalableIphTimerBasedNineEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedNine_session_rate": "any", - "IPH_ScalableIphTimerBasedNine_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedNine_tracking_only": "true", - "IPH_ScalableIphTimerBasedNine_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedNine_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedNine_x_CustomUiType": "None", - "IPH_ScalableIphTimerBasedNine_x_CustomVersionNumber": "1", - "IPH_ScalableIphTimerBasedOne_availability": "any", - "IPH_ScalableIphTimerBasedOne_blocked_by": "none", - "IPH_ScalableIphTimerBasedOne_blocking": "none", - "IPH_ScalableIphTimerBasedOne_event_1": "name:ScalableIphFiveMinTick;comparator:>=0;window:7;storage:8", - "IPH_ScalableIphTimerBasedOne_event_trigger": "name:ScalableIphTimerBasedOneTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedOne_event_used": "name:ScalableIphTimerBasedOneEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedOne_session_rate": "any", - "IPH_ScalableIphTimerBasedOne_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedOne_tracking_only": "true", - "IPH_ScalableIphTimerBasedOne_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedOne_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedOne_x_CustomUiType": "None", - "IPH_ScalableIphTimerBasedOne_x_CustomVersionNumber": "1", - "IPH_ScalableIphTimerBasedSeven_availability": "any", - "IPH_ScalableIphTimerBasedSeven_blocked_by": "none", - "IPH_ScalableIphTimerBasedSeven_blocking": "none", - "IPH_ScalableIphTimerBasedSeven_event_1": "name:ScalableIphFiveMinTick;comparator:>=24;window:7;storage:8", - "IPH_ScalableIphTimerBasedSeven_event_preconditions_google_photos_android_app_list": "name:ScalableIphAppListItemActivationOpenGooglePhotosAndroid;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedSeven_event_preconditions_google_photos_android_shelf": "name:ScalableIphShelfItemActivationGooglePhotosAndroid;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedSeven_event_preconditions_google_photos_web_app_list": "name:ScalableIphAppListItemActivationGooglePhotosWeb;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedSeven_event_preconditions_google_photos_web_shelf": "name:ScalableIphShelfItemActivationGooglePhotosWeb;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedSeven_event_trigger": "name:ScalableIphTimerBasedSevenTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedSeven_event_used": "name:ScalableIphTimerBasedSevenEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedSeven_session_rate": "any", - "IPH_ScalableIphTimerBasedSeven_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedSeven_tracking_only": "true", - "IPH_ScalableIphTimerBasedSeven_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedSeven_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedSeven_x_CustomUiType": "None", - "IPH_ScalableIphTimerBasedSeven_x_CustomVersionNumber": "1", - "IPH_ScalableIphTimerBasedSix_availability": "any", - "IPH_ScalableIphTimerBasedSix_blocked_by": "none", - "IPH_ScalableIphTimerBasedSix_blocking": "none", - "IPH_ScalableIphTimerBasedSix_event_1": "name:ScalableIphFiveMinTick;comparator:>=18;window:7;storage:8", - "IPH_ScalableIphTimerBasedSix_event_precondition_google_play_app_list": "name:ScalableIphAppListItemActivationOpenGooglePlayStore;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedSix_event_precondition_google_play_shelf": "name:ScalableIphShelfItemActivationGooglePlay;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedSix_event_trigger": "name:ScalableIphTimerBasedSixTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedSix_event_used": "name:ScalableIphTimerBasedSixEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedSix_session_rate": "any", - "IPH_ScalableIphTimerBasedSix_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedSix_tracking_only": "true", - "IPH_ScalableIphTimerBasedSix_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedSix_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedSix_x_CustomUiType": "None", - "IPH_ScalableIphTimerBasedSix_x_CustomVersionNumber": "1", - "IPH_ScalableIphTimerBasedTen_availability": "any", - "IPH_ScalableIphTimerBasedTen_blocked_by": "none", - "IPH_ScalableIphTimerBasedTen_blocking": "none", - "IPH_ScalableIphTimerBasedTen_event_1": "name:ScalableIphFiveMinTick;comparator:>=48;window:7;storage:8", - "IPH_ScalableIphTimerBasedTen_event_precondition_print_job": "name:ScalableIphPrintJobCreated;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedTen_event_trigger": "name:ScalableIphTimerBasedTenTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedTen_event_used": "name:ScalableIphTimerBasedTenEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedTen_session_rate": "any", - "IPH_ScalableIphTimerBasedTen_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedTen_tracking_only": "true", - "IPH_ScalableIphTimerBasedTen_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedTen_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedTen_x_CustomUiType": "None", - "IPH_ScalableIphTimerBasedTen_x_CustomVersionNumber": "1", - "IPH_ScalableIphTimerBasedThree_availability": "any", - "IPH_ScalableIphTimerBasedThree_blocked_by": "none", - "IPH_ScalableIphTimerBasedThree_blocking": "none", - "IPH_ScalableIphTimerBasedThree_event_1": "name:ScalableIphFiveMinTick;comparator:>=2;window:7;storage:8", - "IPH_ScalableIphTimerBasedThree_event_precondition_google_docs_app_list": "name:ScalableIphAppListItemActivationGoogleDocs;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedThree_event_precondition_google_docs_shelf": "name:ScalableIphShelfItemActivationGoogleDocs;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedThree_event_trigger": "name:ScalableIphTimerBasedThreeTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedThree_event_used": "name:ScalableIphTimerBasedThreeEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedThree_session_rate": "any", - "IPH_ScalableIphTimerBasedThree_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedThree_tracking_only": "true", - "IPH_ScalableIphTimerBasedThree_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedThree_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedThree_x_CustomUiType": "None", - "IPH_ScalableIphTimerBasedThree_x_CustomVersionNumber": "1", - "IPH_ScalableIphTimerBasedTwo_availability": "any", - "IPH_ScalableIphTimerBasedTwo_blocked_by": "none", - "IPH_ScalableIphTimerBasedTwo_blocking": "none", - "IPH_ScalableIphTimerBasedTwo_event_1": "name:ScalableIphFiveMinTick;comparator:>=1;window:7;storage:8", - "IPH_ScalableIphTimerBasedTwo_event_precondition_launcher": "name:ScalableIphAppListShown;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedTwo_event_trigger": "name:ScalableIphTimerBasedTwoTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedTwo_event_used": "name:ScalableIphAppListShown;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedTwo_session_rate": "any", - "IPH_ScalableIphTimerBasedTwo_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedTwo_tracking_only": "true", - "IPH_ScalableIphTimerBasedTwo_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedTwo_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedTwo_x_CustomUiType": "None", - "IPH_ScalableIphTimerBasedTwo_x_CustomVersionNumber": "1", - "IPH_ScalableIphUnlockedBasedEight_availability": "any", - "IPH_ScalableIphUnlockedBasedEight_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedEight_blocking": "none", - "IPH_ScalableIphUnlockedBasedEight_event_1": "name:ScalableIphUnlocked;comparator:>=7;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedEight_event_trigger": "name:ScalableIphUnlockedBasedEightTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedEight_event_used": "name:ScalableIphUnlockedBasedEightEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedEight_session_rate": "any", - "IPH_ScalableIphUnlockedBasedEight_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedEight_tracking_only": "true", - "IPH_ScalableIphUnlockedBasedEight_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedEight_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedEight_x_CustomConditionPhoneHubOnboardingEligible": "True", - "IPH_ScalableIphUnlockedBasedEight_x_CustomUiType": "None", - "IPH_ScalableIphUnlockedBasedEight_x_CustomVersionNumber": "1", - "IPH_ScalableIphUnlockedBasedFive_availability": "any", - "IPH_ScalableIphUnlockedBasedFive_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedFive_blocking": "none", - "IPH_ScalableIphUnlockedBasedFive_event_1": "name:ScalableIphUnlocked;comparator:>=4;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFive_event_precondition_google_docs_app_list": "name:ScalableIphAppListItemActivationGoogleDocs;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFive_event_precondition_google_docs_shelf": "name:ScalableIphShelfItemActivationGoogleDocs;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFive_event_trigger": "name:ScalableIphUnlockedBasedFiveTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFive_event_used": "name:ScalableIphUnlockedBasedFiveEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFive_session_rate": "any", - "IPH_ScalableIphUnlockedBasedFive_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedFive_tracking_only": "true", - "IPH_ScalableIphUnlockedBasedFive_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedFive_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedFive_x_CustomUiType": "None", - "IPH_ScalableIphUnlockedBasedFive_x_CustomVersionNumber": "1", - "IPH_ScalableIphUnlockedBasedFour_availability": "any", - "IPH_ScalableIphUnlockedBasedFour_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedFour_blocking": "none", - "IPH_ScalableIphUnlockedBasedFour_event_1": "name:ScalableIphUnlocked;comparator:>=3;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFour_event_precondition_google_play_app_list": "name:ScalableIphAppListItemActivationOpenGooglePlayStore;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFour_event_precondition_google_play_shelf": "name:ScalableIphShelfItemActivationGooglePlay;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFour_event_trigger": "name:ScalableIphUnlockedBasedFourTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFour_event_used": "name:ScalableIphUnlockedBasedFourEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFour_session_rate": "any", - "IPH_ScalableIphUnlockedBasedFour_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedFour_tracking_only": "true", - "IPH_ScalableIphUnlockedBasedFour_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedFour_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedFour_x_CustomUiType": "None", - "IPH_ScalableIphUnlockedBasedFour_x_CustomVersionNumber": "1", - "IPH_ScalableIphUnlockedBasedNine_availability": "any", - "IPH_ScalableIphUnlockedBasedNine_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedNine_blocking": "none", - "IPH_ScalableIphUnlockedBasedNine_event_1": "name:ScalableIphUnlocked;comparator:>=8;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedNine_event_precondition_youtube_app_list": "name:ScalableIphAppListItemActivationYouTube;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedNine_event_precondition_youtube_shelf": "name:ScalableIphShelfItemActivationYouTube;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedNine_event_trigger": "name:ScalableIphUnlockedBasedNineTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedNine_event_used": "name:ScalableIphUnlockedBasedNineEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedNine_session_rate": "any", - "IPH_ScalableIphUnlockedBasedNine_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedNine_tracking_only": "true", - "IPH_ScalableIphUnlockedBasedNine_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedNine_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedNine_x_CustomUiType": "None", - "IPH_ScalableIphUnlockedBasedNine_x_CustomVersionNumber": "1", - "IPH_ScalableIphUnlockedBasedOne_availability": "any", - "IPH_ScalableIphUnlockedBasedOne_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedOne_blocking": "none", - "IPH_ScalableIphUnlockedBasedOne_event_1": "name:ScalableIphUnlocked;comparator:>=0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedOne_event_trigger": "name:ScalableIphUnlockedBasedOneTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedOne_event_used": "name:ScalableIphUnlockedBasedOneEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedOne_session_rate": "any", - "IPH_ScalableIphUnlockedBasedOne_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedOne_tracking_only": "true", - "IPH_ScalableIphUnlockedBasedOne_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedOne_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedOne_x_CustomUiType": "None", - "IPH_ScalableIphUnlockedBasedOne_x_CustomVersionNumber": "1", - "IPH_ScalableIphUnlockedBasedSeven_availability": "any", - "IPH_ScalableIphUnlockedBasedSeven_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedSeven_blocking": "none", - "IPH_ScalableIphUnlockedBasedSeven_event_1": "name:ScalableIphUnlocked;comparator:>=6;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSeven_event_trigger": "name:ScalableIphUnlockedBasedSevenTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSeven_event_used": "name:ScalableIphUnlockedBasedSevenEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSeven_session_rate": "any", - "IPH_ScalableIphUnlockedBasedSeven_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedSeven_tracking_only": "true", - "IPH_ScalableIphUnlockedBasedSeven_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedSeven_x_CustomConditionHasSavedPrinter": "False", - "IPH_ScalableIphUnlockedBasedSeven_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedSeven_x_CustomUiType": "None", - "IPH_ScalableIphUnlockedBasedSeven_x_CustomVersionNumber": "1", - "IPH_ScalableIphUnlockedBasedSix_availability": "any", - "IPH_ScalableIphUnlockedBasedSix_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedSix_blocking": "none", - "IPH_ScalableIphUnlockedBasedSix_event_1": "name:ScalableIphUnlocked;comparator:>=5;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSix_event_preconditions_google_photos_android_app_list": "name:ScalableIphAppListItemActivationOpenGooglePhotosAndroid;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSix_event_preconditions_google_photos_android_shelf": "name:ScalableIphShelfItemActivationGooglePhotosAndroid;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSix_event_preconditions_google_photos_web_app_list": "name:ScalableIphAppListItemActivationGooglePhotosWeb;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSix_event_preconditions_google_photos_web_shelf": "name:ScalableIphShelfItemActivationGooglePhotosWeb;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSix_event_trigger": "name:ScalableIphUnlockedBasedSixTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSix_event_used": "name:ScalableIphUnlockedBasedSixEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSix_session_rate": "any", - "IPH_ScalableIphUnlockedBasedSix_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedSix_tracking_only": "true", - "IPH_ScalableIphUnlockedBasedSix_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedSix_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedSix_x_CustomUiType": "None", - "IPH_ScalableIphUnlockedBasedSix_x_CustomVersionNumber": "1", - "IPH_ScalableIphUnlockedBasedTen_availability": "any", - "IPH_ScalableIphUnlockedBasedTen_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedTen_blocking": "none", - "IPH_ScalableIphUnlockedBasedTen_event_1": "name:ScalableIphUnlocked;comparator:>=9;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedTen_event_precondition_print_job": "name:ScalableIphPrintJobCreated;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedTen_event_trigger": "name:ScalableIphUnlockedBasedTenTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedTen_event_used": "name:ScalableIphUnlockedBasedTenEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedTen_session_rate": "any", - "IPH_ScalableIphUnlockedBasedTen_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedTen_tracking_only": "true", - "IPH_ScalableIphUnlockedBasedTen_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedTen_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedTen_x_CustomUiType": "None", - "IPH_ScalableIphUnlockedBasedTen_x_CustomVersionNumber": "1", - "IPH_ScalableIphUnlockedBasedThree_availability": "any", - "IPH_ScalableIphUnlockedBasedThree_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedThree_blocking": "none", - "IPH_ScalableIphUnlockedBasedThree_event_1": "name:ScalableIphUnlocked;comparator:>=2;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedThree_event_precondition_personalization_app": "name:ScalableIphOpenPersonalizationApp;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedThree_event_trigger": "name:ScalableIphUnlockedBasedThreeTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedThree_event_used": "name:ScalableIphUnlockedBasedThreeEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedThree_session_rate": "any", - "IPH_ScalableIphUnlockedBasedThree_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedThree_tracking_only": "true", - "IPH_ScalableIphUnlockedBasedThree_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedThree_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedThree_x_CustomUiType": "None", - "IPH_ScalableIphUnlockedBasedThree_x_CustomVersionNumber": "1", - "IPH_ScalableIphUnlockedBasedTwo_availability": "any", - "IPH_ScalableIphUnlockedBasedTwo_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedTwo_blocking": "none", - "IPH_ScalableIphUnlockedBasedTwo_event_1": "name:ScalableIphUnlocked;comparator:>=1;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedTwo_event_precondition_launcher": "name:ScalableIphAppListShown;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedTwo_event_trigger": "name:ScalableIphUnlockedBasedTwoTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedTwo_event_used": "name:ScalableIphAppListShown;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedTwo_session_rate": "any", - "IPH_ScalableIphUnlockedBasedTwo_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedTwo_tracking_only": "true", - "IPH_ScalableIphUnlockedBasedTwo_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedTwo_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedTwo_x_CustomUiType": "None", - "IPH_ScalableIphUnlockedBasedTwo_x_CustomVersionNumber": "1" - }, - "enable_features": [ - "IPH_ScalableIphHelpAppBasedNudge", - "IPH_ScalableIphTimerBasedEight", - "IPH_ScalableIphTimerBasedFive", - "IPH_ScalableIphTimerBasedFour", - "IPH_ScalableIphTimerBasedNine", - "IPH_ScalableIphTimerBasedOne", - "IPH_ScalableIphTimerBasedSeven", - "IPH_ScalableIphTimerBasedSix", - "IPH_ScalableIphTimerBasedTen", - "IPH_ScalableIphTimerBasedThree", - "IPH_ScalableIphTimerBasedTwo", - "IPH_ScalableIphUnlockedBasedEight", - "IPH_ScalableIphUnlockedBasedFive", - "IPH_ScalableIphUnlockedBasedFour", - "IPH_ScalableIphUnlockedBasedNine", - "IPH_ScalableIphUnlockedBasedOne", - "IPH_ScalableIphUnlockedBasedSeven", - "IPH_ScalableIphUnlockedBasedSix", - "IPH_ScalableIphUnlockedBasedTen", - "IPH_ScalableIphUnlockedBasedThree", - "IPH_ScalableIphUnlockedBasedTwo", - "ScalableIph" - ] - }, - { - "name": "HelpAppBased_BETA_20231204", - "params": { - "IPH_ScalableIphHelpAppBasedEight_availability": "any", - "IPH_ScalableIphHelpAppBasedEight_blocked_by": "none", - "IPH_ScalableIphHelpAppBasedEight_blocking": "none", - "IPH_ScalableIphHelpAppBasedEight_event_trigger": "name:ScalableIphHelpAppBasedEightTriggerNotUsed;comparator:==1;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedEight_event_used": "name:ScalableIphHelpAppActionOpenPhoneHub;comparator:any;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedEight_session_rate": "any", - "IPH_ScalableIphHelpAppBasedEight_session_rate_impact": "none", - "IPH_ScalableIphHelpAppBasedEight_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphHelpAppBasedEight_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphHelpAppBasedEight_x_CustomUiType": "None", - "IPH_ScalableIphHelpAppBasedEight_x_CustomVersionNumber": "1", - "IPH_ScalableIphHelpAppBasedFive_availability": "any", - "IPH_ScalableIphHelpAppBasedFive_blocked_by": "none", - "IPH_ScalableIphHelpAppBasedFive_blocking": "none", - "IPH_ScalableIphHelpAppBasedFive_event_trigger": "name:ScalableIphHelpAppBasedFiveTriggerNotUsed;comparator:==1;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedFive_event_used": "name:ScalableIphHelpAppActionOpenGoogleDocs;comparator:any;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedFive_session_rate": "any", - "IPH_ScalableIphHelpAppBasedFive_session_rate_impact": "none", - "IPH_ScalableIphHelpAppBasedFive_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphHelpAppBasedFive_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphHelpAppBasedFive_x_CustomUiType": "None", - "IPH_ScalableIphHelpAppBasedFive_x_CustomVersionNumber": "1", - "IPH_ScalableIphHelpAppBasedFour_availability": "any", - "IPH_ScalableIphHelpAppBasedFour_blocked_by": "none", - "IPH_ScalableIphHelpAppBasedFour_blocking": "none", - "IPH_ScalableIphHelpAppBasedFour_event_trigger": "name:ScalableIphHelpAppBasedFourTriggerNotUsed;comparator:==1;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedFour_event_used": "name:ScalableIphHelpAppActionOpenPlayStore;comparator:any;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedFour_session_rate": "any", - "IPH_ScalableIphHelpAppBasedFour_session_rate_impact": "none", - "IPH_ScalableIphHelpAppBasedFour_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphHelpAppBasedFour_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphHelpAppBasedFour_x_CustomUiType": "None", - "IPH_ScalableIphHelpAppBasedFour_x_CustomVersionNumber": "1", - "IPH_ScalableIphHelpAppBasedNine_availability": "any", - "IPH_ScalableIphHelpAppBasedNine_blocked_by": "none", - "IPH_ScalableIphHelpAppBasedNine_blocking": "none", - "IPH_ScalableIphHelpAppBasedNine_event_trigger": "name:ScalableIphHelpAppBasedNineTriggerNotUsed;comparator:==1;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedNine_event_used": "name:ScalableIphHelpAppActionOpenYouTube;comparator:any;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedNine_session_rate": "any", - "IPH_ScalableIphHelpAppBasedNine_session_rate_impact": "none", - "IPH_ScalableIphHelpAppBasedNine_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphHelpAppBasedNine_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphHelpAppBasedNine_x_CustomUiType": "None", - "IPH_ScalableIphHelpAppBasedNine_x_CustomVersionNumber": "1", - "IPH_ScalableIphHelpAppBasedNudge_availability": "any", - "IPH_ScalableIphHelpAppBasedNudge_blocked_by": "none", - "IPH_ScalableIphHelpAppBasedNudge_blocking": "none", - "IPH_ScalableIphHelpAppBasedNudge_event_trigger": "name:ScalableIphHelpAppBasedNudgeTrigger;comparator:==0;window:1;storage:7", - "IPH_ScalableIphHelpAppBasedNudge_event_used": "name:ScalableIphHelpAppBasedNudgeEventUsedNotUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedNudge_session_rate": "any", - "IPH_ScalableIphHelpAppBasedNudge_session_rate_impact": "none", - "IPH_ScalableIphHelpAppBasedNudge_x_CustomBubbleAnchorViewAppId": "nbljnnecbjbmifnoehiemkgefbnpoeak", - "IPH_ScalableIphHelpAppBasedNudge_x_CustomBubbleId": "scalable_iph_help_app_based_open_help_app", - "IPH_ScalableIphHelpAppBasedNudge_x_CustomBubbleText": "Continue learning about your Chromebook", - "IPH_ScalableIphHelpAppBasedNudge_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphHelpAppBasedNudge_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphHelpAppBasedNudge_x_CustomUiType": "Bubble", - "IPH_ScalableIphHelpAppBasedNudge_x_CustomVersionNumber": "1", - "IPH_ScalableIphHelpAppBasedOne_availability": "any", - "IPH_ScalableIphHelpAppBasedOne_blocked_by": "none", - "IPH_ScalableIphHelpAppBasedOne_blocking": "none", - "IPH_ScalableIphHelpAppBasedOne_event_trigger": "name:ScalableIphHelpAppBasedOneTriggerNotUsed;comparator:==1;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedOne_event_used": "name:ScalableIphHelpAppActionOpenChrome;comparator:any;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedOne_session_rate": "any", - "IPH_ScalableIphHelpAppBasedOne_session_rate_impact": "none", - "IPH_ScalableIphHelpAppBasedOne_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphHelpAppBasedOne_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphHelpAppBasedOne_x_CustomUiType": "None", - "IPH_ScalableIphHelpAppBasedOne_x_CustomVersionNumber": "1", - "IPH_ScalableIphHelpAppBasedSeven_availability": "any", - "IPH_ScalableIphHelpAppBasedSeven_blocked_by": "none", - "IPH_ScalableIphHelpAppBasedSeven_blocking": "none", - "IPH_ScalableIphHelpAppBasedSeven_event_trigger": "name:ScalableIphHelpAppBasedSevenTriggerNotUsed;comparator:==1;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedSeven_event_used": "name:ScalableIphHelpAppActionOpenSettingsPrinter;comparator:any;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedSeven_session_rate": "any", - "IPH_ScalableIphHelpAppBasedSeven_session_rate_impact": "none", - "IPH_ScalableIphHelpAppBasedSeven_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphHelpAppBasedSeven_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphHelpAppBasedSeven_x_CustomUiType": "None", - "IPH_ScalableIphHelpAppBasedSeven_x_CustomVersionNumber": "1", - "IPH_ScalableIphHelpAppBasedSix_availability": "any", - "IPH_ScalableIphHelpAppBasedSix_blocked_by": "none", - "IPH_ScalableIphHelpAppBasedSix_blocking": "none", - "IPH_ScalableIphHelpAppBasedSix_event_trigger": "name:ScalableIphHelpAppBasedSixTriggerNotUsed;comparator:==1;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedSix_event_used": "name:ScalableIphHelpAppActionOpenGooglePhotos;comparator:any;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedSix_session_rate": "any", - "IPH_ScalableIphHelpAppBasedSix_session_rate_impact": "none", - "IPH_ScalableIphHelpAppBasedSix_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphHelpAppBasedSix_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphHelpAppBasedSix_x_CustomUiType": "None", - "IPH_ScalableIphHelpAppBasedSix_x_CustomVersionNumber": "1", - "IPH_ScalableIphHelpAppBasedTen_availability": "any", - "IPH_ScalableIphHelpAppBasedTen_blocked_by": "none", - "IPH_ScalableIphHelpAppBasedTen_blocking": "none", - "IPH_ScalableIphHelpAppBasedTen_event_trigger": "name:ScalableIphHelpAppBasedTenTriggerNotUsed;comparator:==1;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedTen_event_used": "name:ScalableIphHelpAppActionOpenFileManager;comparator:any;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedTen_session_rate": "any", - "IPH_ScalableIphHelpAppBasedTen_session_rate_impact": "none", - "IPH_ScalableIphHelpAppBasedTen_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphHelpAppBasedTen_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphHelpAppBasedTen_x_CustomUiType": "None", - "IPH_ScalableIphHelpAppBasedTen_x_CustomVersionNumber": "1", - "IPH_ScalableIphHelpAppBasedThree_availability": "any", - "IPH_ScalableIphHelpAppBasedThree_blocked_by": "none", - "IPH_ScalableIphHelpAppBasedThree_blocking": "none", - "IPH_ScalableIphHelpAppBasedThree_event_trigger": "name:ScalableIphHelpAppBasedThreeTriggerNotUsed;comparator:==1;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedThree_event_used": "name:ScalableIphHelpAppActionOpenPersonalizationApp;comparator:any;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedThree_session_rate": "any", - "IPH_ScalableIphHelpAppBasedThree_session_rate_impact": "none", - "IPH_ScalableIphHelpAppBasedThree_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphHelpAppBasedThree_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphHelpAppBasedThree_x_CustomUiType": "None", - "IPH_ScalableIphHelpAppBasedThree_x_CustomVersionNumber": "1", - "IPH_ScalableIphHelpAppBasedTwo_availability": "any", - "IPH_ScalableIphHelpAppBasedTwo_blocked_by": "none", - "IPH_ScalableIphHelpAppBasedTwo_blocking": "none", - "IPH_ScalableIphHelpAppBasedTwo_event_trigger": "name:ScalableIphHelpAppBasedTwoTriggerNotUsed;comparator:==1;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedTwo_event_used": "name:ScalableIphAppListShown;comparator:any;window:7;storage:8", - "IPH_ScalableIphHelpAppBasedTwo_session_rate": "any", - "IPH_ScalableIphHelpAppBasedTwo_session_rate_impact": "none", - "IPH_ScalableIphHelpAppBasedTwo_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphHelpAppBasedTwo_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphHelpAppBasedTwo_x_CustomUiType": "None", - "IPH_ScalableIphHelpAppBasedTwo_x_CustomVersionNumber": "1" - }, - "enable_features": [ - "HelpAppWelcomeTips", - "IPH_ScalableIphHelpAppBasedEight", - "IPH_ScalableIphHelpAppBasedFive", - "IPH_ScalableIphHelpAppBasedFour", - "IPH_ScalableIphHelpAppBasedNine", - "IPH_ScalableIphHelpAppBasedNudge", - "IPH_ScalableIphHelpAppBasedOne", - "IPH_ScalableIphHelpAppBasedSeven", - "IPH_ScalableIphHelpAppBasedSix", - "IPH_ScalableIphHelpAppBasedTen", - "IPH_ScalableIphHelpAppBasedThree", - "IPH_ScalableIphHelpAppBasedTwo", - "ScalableIph" - ] - }, - { - "name": "TimerBased_BETA_20231204", - "params": { - "IPH_ScalableIphTimerBasedEight_availability": "any", - "IPH_ScalableIphTimerBasedEight_blocked_by": "none", - "IPH_ScalableIphTimerBasedEight_blocking": "none", - "IPH_ScalableIphTimerBasedEight_event_1": "name:ScalableIphFiveMinTick;comparator:>=26;window:7;storage:8", - "IPH_ScalableIphTimerBasedEight_event_trigger": "name:ScalableIphTimerBasedEightTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedEight_event_used": "name:ScalableIphTimerBasedEightEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedEight_session_rate": "any", - "IPH_ScalableIphTimerBasedEight_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedEight_x_CustomButtonActionType": "OpenPhoneHub", - "IPH_ScalableIphTimerBasedEight_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedEight_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedEight_x_CustomConditionPhoneHubOnboardingEligible": "True", - "IPH_ScalableIphTimerBasedEight_x_CustomNotificationBodyText": "You can quickly reply to messages from your Android phone, right from your Chromebook", - "IPH_ScalableIphTimerBasedEight_x_CustomNotificationButtonText": "Connect phone", - "IPH_ScalableIphTimerBasedEight_x_CustomNotificationId": "scalable_iph_timer_based_eight", - "IPH_ScalableIphTimerBasedEight_x_CustomNotificationSummaryText": "Welcome Tips", - "IPH_ScalableIphTimerBasedEight_x_CustomNotificationTitle": "Connect your Android phone", - "IPH_ScalableIphTimerBasedEight_x_CustomUiType": "Notification", - "IPH_ScalableIphTimerBasedEight_x_CustomVersionNumber": "1", - "IPH_ScalableIphTimerBasedFive_availability": "any", - "IPH_ScalableIphTimerBasedFive_blocked_by": "none", - "IPH_ScalableIphTimerBasedFive_blocking": "none", - "IPH_ScalableIphTimerBasedFive_event_1": "name:ScalableIphFiveMinTick;comparator:>=16;window:7;storage:8", - "IPH_ScalableIphTimerBasedFive_event_trigger": "name:ScalableIphTimerBasedFiveTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedFive_event_used": "name:ScalableIphTimerBasedFiveEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedFive_session_rate": "any", - "IPH_ScalableIphTimerBasedFive_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedFive_x_CustomButtonActionType": "OpenSettingsPrinter", - "IPH_ScalableIphTimerBasedFive_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedFive_x_CustomConditionHasSavedPrinter": "False", - "IPH_ScalableIphTimerBasedFive_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedFive_x_CustomNotificationBodyText": "Easily add a printer to your Chromebook so it will be ready to go", - "IPH_ScalableIphTimerBasedFive_x_CustomNotificationButtonText": "Add printer", - "IPH_ScalableIphTimerBasedFive_x_CustomNotificationId": "scalable_iph_timer_based_five", - "IPH_ScalableIphTimerBasedFive_x_CustomNotificationSummaryText": "Welcome Tips", - "IPH_ScalableIphTimerBasedFive_x_CustomNotificationTitle": "Connect a printer", - "IPH_ScalableIphTimerBasedFive_x_CustomUiType": "Notification", - "IPH_ScalableIphTimerBasedFive_x_CustomVersionNumber": "1", - "IPH_ScalableIphTimerBasedFour_availability": "any", - "IPH_ScalableIphTimerBasedFour_blocked_by": "none", - "IPH_ScalableIphTimerBasedFour_blocking": "none", - "IPH_ScalableIphTimerBasedFour_event_1": "name:ScalableIphFiveMinTick;comparator:>=12;window:7;storage:8", - "IPH_ScalableIphTimerBasedFour_event_precondition_personalization_app": "name:ScalableIphOpenPersonalizationApp;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedFour_event_trigger": "name:ScalableIphTimerBasedFourTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedFour_event_used": "name:ScalableIphTimerBasedFourEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedFour_session_rate": "any", - "IPH_ScalableIphTimerBasedFour_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedFour_x_CustomButtonActionType": "OpenPersonalizationApp", - "IPH_ScalableIphTimerBasedFour_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedFour_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedFour_x_CustomNotificationBodyText": "Make your Chromebook uniquely yours with a new wallpaper", - "IPH_ScalableIphTimerBasedFour_x_CustomNotificationButtonText": "Select wallpaper", - "IPH_ScalableIphTimerBasedFour_x_CustomNotificationId": "scalable_iph_timer_based_four", - "IPH_ScalableIphTimerBasedFour_x_CustomNotificationImageType": "Wallpaper", - "IPH_ScalableIphTimerBasedFour_x_CustomNotificationSummaryText": "Welcome Tips", - "IPH_ScalableIphTimerBasedFour_x_CustomNotificationTitle": "Customize your wallpaper", - "IPH_ScalableIphTimerBasedFour_x_CustomUiType": "Notification", - "IPH_ScalableIphTimerBasedFour_x_CustomVersionNumber": "1", - "IPH_ScalableIphTimerBasedNine_availability": "any", - "IPH_ScalableIphTimerBasedNine_blocked_by": "none", - "IPH_ScalableIphTimerBasedNine_blocking": "none", - "IPH_ScalableIphTimerBasedNine_event_1": "name:ScalableIphFiveMinTick;comparator:>=30;window:7;storage:8", - "IPH_ScalableIphTimerBasedNine_event_precondition_youtube_app_list": "name:ScalableIphAppListItemActivationYouTube;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedNine_event_precondition_youtube_shelf": "name:ScalableIphShelfItemActivationYouTube;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedNine_event_trigger": "name:ScalableIphTimerBasedNineTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedNine_event_used": "name:ScalableIphTimerBasedNineEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedNine_session_rate": "any", - "IPH_ScalableIphTimerBasedNine_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedNine_x_CustomBubbleButtonText": "Open YouTube", - "IPH_ScalableIphTimerBasedNine_x_CustomBubbleIcon": "YouTubeIcon", - "IPH_ScalableIphTimerBasedNine_x_CustomBubbleId": "scalable_iph_timer_based_nine", - "IPH_ScalableIphTimerBasedNine_x_CustomBubbleText": "Watch your favorite content on YouTube", - "IPH_ScalableIphTimerBasedNine_x_CustomBubbleTitle": "Welcome Tips", - "IPH_ScalableIphTimerBasedNine_x_CustomButtonActionType": "OpenYouTube", - "IPH_ScalableIphTimerBasedNine_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedNine_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedNine_x_CustomUiType": "Bubble", - "IPH_ScalableIphTimerBasedNine_x_CustomVersionNumber": "1", - "IPH_ScalableIphTimerBasedOne_availability": "any", - "IPH_ScalableIphTimerBasedOne_blocked_by": "none", - "IPH_ScalableIphTimerBasedOne_blocking": "none", - "IPH_ScalableIphTimerBasedOne_event_1": "name:ScalableIphFiveMinTick;comparator:>=0;window:7;storage:8", - "IPH_ScalableIphTimerBasedOne_event_trigger": "name:ScalableIphTimerBasedOneTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedOne_event_used": "name:ScalableIphTimerBasedOneEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedOne_session_rate": "any", - "IPH_ScalableIphTimerBasedOne_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedOne_x_CustomBubbleButtonText": "Open Chrome", - "IPH_ScalableIphTimerBasedOne_x_CustomBubbleIcon": "ChromeIcon", - "IPH_ScalableIphTimerBasedOne_x_CustomBubbleId": "scalable_iph_timer_based_one", - "IPH_ScalableIphTimerBasedOne_x_CustomBubbleText": "Connect to the world on your Chromebook with Chrome browser", - "IPH_ScalableIphTimerBasedOne_x_CustomBubbleTitle": "Welcome Tips", - "IPH_ScalableIphTimerBasedOne_x_CustomButtonActionType": "OpenChrome", - "IPH_ScalableIphTimerBasedOne_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedOne_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedOne_x_CustomUiType": "Bubble", - "IPH_ScalableIphTimerBasedOne_x_CustomVersionNumber": "1", - "IPH_ScalableIphTimerBasedSeven_availability": "any", - "IPH_ScalableIphTimerBasedSeven_blocked_by": "none", - "IPH_ScalableIphTimerBasedSeven_blocking": "none", - "IPH_ScalableIphTimerBasedSeven_event_1": "name:ScalableIphFiveMinTick;comparator:>=24;window:7;storage:8", - "IPH_ScalableIphTimerBasedSeven_event_preconditions_google_photos_android_app_list": "name:ScalableIphAppListItemActivationOpenGooglePhotosAndroid;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedSeven_event_preconditions_google_photos_android_shelf": "name:ScalableIphShelfItemActivationGooglePhotosAndroid;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedSeven_event_preconditions_google_photos_web_app_list": "name:ScalableIphAppListItemActivationGooglePhotosWeb;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedSeven_event_preconditions_google_photos_web_shelf": "name:ScalableIphShelfItemActivationGooglePhotosWeb;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedSeven_event_trigger": "name:ScalableIphTimerBasedSevenTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedSeven_event_used": "name:ScalableIphTimerBasedSevenEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedSeven_session_rate": "any", - "IPH_ScalableIphTimerBasedSeven_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedSeven_x_CustomBubbleButtonText": "Open Photos", - "IPH_ScalableIphTimerBasedSeven_x_CustomBubbleIcon": "GooglePhotosIcon", - "IPH_ScalableIphTimerBasedSeven_x_CustomBubbleId": "scalable_iph_timer_based_seven", - "IPH_ScalableIphTimerBasedSeven_x_CustomBubbleText": "Explore your favorite memories in Google Photos", - "IPH_ScalableIphTimerBasedSeven_x_CustomBubbleTitle": "Welcome Tips", - "IPH_ScalableIphTimerBasedSeven_x_CustomButtonActionType": "OpenGooglePhotos", - "IPH_ScalableIphTimerBasedSeven_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedSeven_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedSeven_x_CustomUiType": "Bubble", - "IPH_ScalableIphTimerBasedSeven_x_CustomVersionNumber": "1", - "IPH_ScalableIphTimerBasedSix_availability": "any", - "IPH_ScalableIphTimerBasedSix_blocked_by": "none", - "IPH_ScalableIphTimerBasedSix_blocking": "none", - "IPH_ScalableIphTimerBasedSix_event_1": "name:ScalableIphFiveMinTick;comparator:>=18;window:7;storage:8", - "IPH_ScalableIphTimerBasedSix_event_precondition_google_play_app_list": "name:ScalableIphAppListItemActivationOpenGooglePlayStore;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedSix_event_precondition_google_play_shelf": "name:ScalableIphShelfItemActivationGooglePlay;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedSix_event_trigger": "name:ScalableIphTimerBasedSixTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedSix_event_used": "name:ScalableIphTimerBasedSixEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedSix_session_rate": "any", - "IPH_ScalableIphTimerBasedSix_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedSix_x_CustomButtonActionType": "OpenPlayStore", - "IPH_ScalableIphTimerBasedSix_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedSix_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedSix_x_CustomNotificationBodyText": "Download your favorite Android apps from the Google Play Store and use them on your Chromebook", - "IPH_ScalableIphTimerBasedSix_x_CustomNotificationButtonText": "Open Play Store", - "IPH_ScalableIphTimerBasedSix_x_CustomNotificationId": "scalable_iph_timer_based_six", - "IPH_ScalableIphTimerBasedSix_x_CustomNotificationSummaryText": "Welcome Tips", - "IPH_ScalableIphTimerBasedSix_x_CustomNotificationTitle": "Install apps from the Play Store", - "IPH_ScalableIphTimerBasedSix_x_CustomUiType": "Notification", - "IPH_ScalableIphTimerBasedSix_x_CustomVersionNumber": "1", - "IPH_ScalableIphTimerBasedTen_availability": "any", - "IPH_ScalableIphTimerBasedTen_blocked_by": "none", - "IPH_ScalableIphTimerBasedTen_blocking": "none", - "IPH_ScalableIphTimerBasedTen_event_1": "name:ScalableIphFiveMinTick;comparator:>=48;window:7;storage:8", - "IPH_ScalableIphTimerBasedTen_event_precondition_print_job": "name:ScalableIphPrintJobCreated;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedTen_event_trigger": "name:ScalableIphTimerBasedTenTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedTen_event_used": "name:ScalableIphTimerBasedTenEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedTen_session_rate": "any", - "IPH_ScalableIphTimerBasedTen_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedTen_x_CustomBubbleButtonText": "Select file", - "IPH_ScalableIphTimerBasedTen_x_CustomBubbleIcon": "PrintJobsIcon", - "IPH_ScalableIphTimerBasedTen_x_CustomBubbleId": "scalable_iph_timer_based_ten", - "IPH_ScalableIphTimerBasedTen_x_CustomBubbleText": "Printing is easy with your Chromebook", - "IPH_ScalableIphTimerBasedTen_x_CustomBubbleTitle": "Welcome Tips", - "IPH_ScalableIphTimerBasedTen_x_CustomButtonActionType": "OpenFileManager", - "IPH_ScalableIphTimerBasedTen_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedTen_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedTen_x_CustomUiType": "Bubble", - "IPH_ScalableIphTimerBasedTen_x_CustomVersionNumber": "1", - "IPH_ScalableIphTimerBasedThree_availability": "any", - "IPH_ScalableIphTimerBasedThree_blocked_by": "none", - "IPH_ScalableIphTimerBasedThree_blocking": "none", - "IPH_ScalableIphTimerBasedThree_event_1": "name:ScalableIphFiveMinTick;comparator:>=2;window:7;storage:8", - "IPH_ScalableIphTimerBasedThree_event_precondition_google_docs_app_list": "name:ScalableIphAppListItemActivationGoogleDocs;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedThree_event_precondition_google_docs_shelf": "name:ScalableIphShelfItemActivationGoogleDocs;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedThree_event_trigger": "name:ScalableIphTimerBasedThreeTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedThree_event_used": "name:ScalableIphTimerBasedThreeEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedThree_session_rate": "any", - "IPH_ScalableIphTimerBasedThree_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedThree_x_CustomBubbleButtonText": "Open Docs", - "IPH_ScalableIphTimerBasedThree_x_CustomBubbleIcon": "GoogleDocsIcon", - "IPH_ScalableIphTimerBasedThree_x_CustomBubbleId": "scalable_iph_timer_based_three", - "IPH_ScalableIphTimerBasedThree_x_CustomBubbleText": "Create, edit, and collaborate with Google Docs", - "IPH_ScalableIphTimerBasedThree_x_CustomBubbleTitle": "Welcome Tips", - "IPH_ScalableIphTimerBasedThree_x_CustomButtonActionType": "OpenGoogleDocs", - "IPH_ScalableIphTimerBasedThree_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedThree_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedThree_x_CustomUiType": "Bubble", - "IPH_ScalableIphTimerBasedThree_x_CustomVersionNumber": "1", - "IPH_ScalableIphTimerBasedTwo_availability": "any", - "IPH_ScalableIphTimerBasedTwo_blocked_by": "none", - "IPH_ScalableIphTimerBasedTwo_blocking": "none", - "IPH_ScalableIphTimerBasedTwo_event_1": "name:ScalableIphFiveMinTick;comparator:>=1;window:7;storage:8", - "IPH_ScalableIphTimerBasedTwo_event_precondition_launcher": "name:ScalableIphAppListShown;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedTwo_event_trigger": "name:ScalableIphTimerBasedTwoTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphTimerBasedTwo_event_used": "name:ScalableIphAppListShown;comparator:any;window:7;storage:8", - "IPH_ScalableIphTimerBasedTwo_session_rate": "any", - "IPH_ScalableIphTimerBasedTwo_session_rate_impact": "none", - "IPH_ScalableIphTimerBasedTwo_x_CustomBubbleId": "scalable_iph_timer_based_two", - "IPH_ScalableIphTimerBasedTwo_x_CustomBubbleText": "Search and find your apps in the Launcher \u25c9", - "IPH_ScalableIphTimerBasedTwo_x_CustomBubbleTitle": "Welcome Tips", - "IPH_ScalableIphTimerBasedTwo_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphTimerBasedTwo_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphTimerBasedTwo_x_CustomUiType": "Bubble", - "IPH_ScalableIphTimerBasedTwo_x_CustomVersionNumber": "1" - }, - "enable_features": [ - "IPH_ScalableIphTimerBasedEight", - "IPH_ScalableIphTimerBasedFive", - "IPH_ScalableIphTimerBasedFour", - "IPH_ScalableIphTimerBasedNine", - "IPH_ScalableIphTimerBasedOne", - "IPH_ScalableIphTimerBasedSeven", - "IPH_ScalableIphTimerBasedSix", - "IPH_ScalableIphTimerBasedTen", - "IPH_ScalableIphTimerBasedThree", - "IPH_ScalableIphTimerBasedTwo", - "ScalableIph" - ] - }, - { - "name": "UnlockedBased_BETA_20231204", - "params": { - "IPH_ScalableIphUnlockedBasedEight_availability": "any", - "IPH_ScalableIphUnlockedBasedEight_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedEight_blocking": "none", - "IPH_ScalableIphUnlockedBasedEight_event_1": "name:ScalableIphUnlocked;comparator:>=7;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedEight_event_trigger": "name:ScalableIphUnlockedBasedEightTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedEight_event_used": "name:ScalableIphUnlockedBasedEightEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedEight_session_rate": "any", - "IPH_ScalableIphUnlockedBasedEight_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedEight_x_CustomBubbleButtonText": "Connect phone", - "IPH_ScalableIphUnlockedBasedEight_x_CustomBubbleId": "scalable_iph_unlocked_based_eight", - "IPH_ScalableIphUnlockedBasedEight_x_CustomBubbleText": "Quickly reply to your messages from your Android phone", - "IPH_ScalableIphUnlockedBasedEight_x_CustomBubbleTitle": "Welcome Tips", - "IPH_ScalableIphUnlockedBasedEight_x_CustomButtonActionType": "OpenPhoneHub", - "IPH_ScalableIphUnlockedBasedEight_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedEight_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedEight_x_CustomConditionPhoneHubOnboardingEligible": "True", - "IPH_ScalableIphUnlockedBasedEight_x_CustomUiType": "Bubble", - "IPH_ScalableIphUnlockedBasedEight_x_CustomVersionNumber": "1", - "IPH_ScalableIphUnlockedBasedFive_availability": "any", - "IPH_ScalableIphUnlockedBasedFive_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedFive_blocking": "none", - "IPH_ScalableIphUnlockedBasedFive_event_1": "name:ScalableIphUnlocked;comparator:>=4;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFive_event_precondition_google_docs_app_list": "name:ScalableIphAppListItemActivationGoogleDocs;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFive_event_precondition_google_docs_shelf": "name:ScalableIphShelfItemActivationGoogleDocs;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFive_event_trigger": "name:ScalableIphUnlockedBasedFiveTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFive_event_used": "name:ScalableIphUnlockedBasedFiveEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFive_session_rate": "any", - "IPH_ScalableIphUnlockedBasedFive_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedFive_x_CustomBubbleButtonText": "Open Docs", - "IPH_ScalableIphUnlockedBasedFive_x_CustomBubbleIcon": "GoogleDocsIcon", - "IPH_ScalableIphUnlockedBasedFive_x_CustomBubbleId": "scalable_iph_unlocked_based_five", - "IPH_ScalableIphUnlockedBasedFive_x_CustomBubbleText": "Create, edit, and collaborate with Google Docs", - "IPH_ScalableIphUnlockedBasedFive_x_CustomBubbleTitle": "Welcome Tips", - "IPH_ScalableIphUnlockedBasedFive_x_CustomButtonActionType": "OpenGoogleDocs", - "IPH_ScalableIphUnlockedBasedFive_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedFive_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedFive_x_CustomUiType": "Bubble", - "IPH_ScalableIphUnlockedBasedFive_x_CustomVersionNumber": "1", - "IPH_ScalableIphUnlockedBasedFour_availability": "any", - "IPH_ScalableIphUnlockedBasedFour_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedFour_blocking": "none", - "IPH_ScalableIphUnlockedBasedFour_event_1": "name:ScalableIphUnlocked;comparator:>=3;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFour_event_precondition_google_play_app_list": "name:ScalableIphAppListItemActivationOpenGooglePlayStore;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFour_event_precondition_google_play_shelf": "name:ScalableIphShelfItemActivationGooglePlay;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFour_event_trigger": "name:ScalableIphUnlockedBasedFourTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFour_event_used": "name:ScalableIphUnlockedBasedFourEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedFour_session_rate": "any", - "IPH_ScalableIphUnlockedBasedFour_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedFour_x_CustomBubbleButtonText": "Open Play Store", - "IPH_ScalableIphUnlockedBasedFour_x_CustomBubbleIcon": "PlayStoreIcon", - "IPH_ScalableIphUnlockedBasedFour_x_CustomBubbleId": "scalable_iph_unlocked_based_four", - "IPH_ScalableIphUnlockedBasedFour_x_CustomBubbleText": "Get your favorite apps from the Play Store", - "IPH_ScalableIphUnlockedBasedFour_x_CustomBubbleTitle": "Welcome Tips", - "IPH_ScalableIphUnlockedBasedFour_x_CustomButtonActionType": "OpenPlayStore", - "IPH_ScalableIphUnlockedBasedFour_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedFour_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedFour_x_CustomUiType": "Bubble", - "IPH_ScalableIphUnlockedBasedFour_x_CustomVersionNumber": "1", - "IPH_ScalableIphUnlockedBasedNine_availability": "any", - "IPH_ScalableIphUnlockedBasedNine_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedNine_blocking": "none", - "IPH_ScalableIphUnlockedBasedNine_event_1": "name:ScalableIphUnlocked;comparator:>=8;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedNine_event_precondition_youtube_app_list": "name:ScalableIphAppListItemActivationYouTube;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedNine_event_precondition_youtube_shelf": "name:ScalableIphShelfItemActivationYouTube;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedNine_event_trigger": "name:ScalableIphUnlockedBasedNineTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedNine_event_used": "name:ScalableIphUnlockedBasedNineEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedNine_session_rate": "any", - "IPH_ScalableIphUnlockedBasedNine_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedNine_x_CustomBubbleButtonText": "Open YouTube", - "IPH_ScalableIphUnlockedBasedNine_x_CustomBubbleIcon": "YouTubeIcon", - "IPH_ScalableIphUnlockedBasedNine_x_CustomBubbleId": "scalable_iph_unlocked_based_nine", - "IPH_ScalableIphUnlockedBasedNine_x_CustomBubbleText": "Watch your favorite content on YouTube", - "IPH_ScalableIphUnlockedBasedNine_x_CustomBubbleTitle": "Welcome Tips", - "IPH_ScalableIphUnlockedBasedNine_x_CustomButtonActionType": "OpenYouTube", - "IPH_ScalableIphUnlockedBasedNine_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedNine_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedNine_x_CustomUiType": "Bubble", - "IPH_ScalableIphUnlockedBasedNine_x_CustomVersionNumber": "1", - "IPH_ScalableIphUnlockedBasedOne_availability": "any", - "IPH_ScalableIphUnlockedBasedOne_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedOne_blocking": "none", - "IPH_ScalableIphUnlockedBasedOne_event_1": "name:ScalableIphUnlocked;comparator:>=0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedOne_event_trigger": "name:ScalableIphUnlockedBasedOneTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedOne_event_used": "name:ScalableIphUnlockedBasedOneEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedOne_session_rate": "any", - "IPH_ScalableIphUnlockedBasedOne_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedOne_x_CustomBubbleButtonText": "Open Chrome", - "IPH_ScalableIphUnlockedBasedOne_x_CustomBubbleIcon": "ChromeIcon", - "IPH_ScalableIphUnlockedBasedOne_x_CustomBubbleId": "scalable_iph_unlocked_based_one", - "IPH_ScalableIphUnlockedBasedOne_x_CustomBubbleText": "Connect to the world on your Chromebook with Chrome browser", - "IPH_ScalableIphUnlockedBasedOne_x_CustomBubbleTitle": "Welcome Tips", - "IPH_ScalableIphUnlockedBasedOne_x_CustomButtonActionType": "OpenChrome", - "IPH_ScalableIphUnlockedBasedOne_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedOne_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedOne_x_CustomUiType": "Bubble", - "IPH_ScalableIphUnlockedBasedOne_x_CustomVersionNumber": "1", - "IPH_ScalableIphUnlockedBasedSeven_availability": "any", - "IPH_ScalableIphUnlockedBasedSeven_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedSeven_blocking": "none", - "IPH_ScalableIphUnlockedBasedSeven_event_1": "name:ScalableIphUnlocked;comparator:>=6;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSeven_event_trigger": "name:ScalableIphUnlockedBasedSevenTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSeven_event_used": "name:ScalableIphUnlockedBasedSevenEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSeven_session_rate": "any", - "IPH_ScalableIphUnlockedBasedSeven_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedSeven_x_CustomBubbleButtonText": "Add printer", - "IPH_ScalableIphUnlockedBasedSeven_x_CustomBubbleIcon": "PrintJobsIcon", - "IPH_ScalableIphUnlockedBasedSeven_x_CustomBubbleId": "scalable_iph_unlocked_based_seven", - "IPH_ScalableIphUnlockedBasedSeven_x_CustomBubbleText": "Easily add a printer to your Chromebook", - "IPH_ScalableIphUnlockedBasedSeven_x_CustomBubbleTitle": "Welcome Tips", - "IPH_ScalableIphUnlockedBasedSeven_x_CustomButtonActionType": "OpenSettingsPrinter", - "IPH_ScalableIphUnlockedBasedSeven_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedSeven_x_CustomConditionHasSavedPrinter": "False", - "IPH_ScalableIphUnlockedBasedSeven_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedSeven_x_CustomUiType": "Bubble", - "IPH_ScalableIphUnlockedBasedSeven_x_CustomVersionNumber": "1", - "IPH_ScalableIphUnlockedBasedSix_availability": "any", - "IPH_ScalableIphUnlockedBasedSix_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedSix_blocking": "none", - "IPH_ScalableIphUnlockedBasedSix_event_1": "name:ScalableIphUnlocked;comparator:>=5;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSix_event_preconditions_google_photos_android_app_list": "name:ScalableIphAppListItemActivationOpenGooglePhotosAndroid;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSix_event_preconditions_google_photos_android_shelf": "name:ScalableIphShelfItemActivationGooglePhotosAndroid;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSix_event_preconditions_google_photos_web_app_list": "name:ScalableIphAppListItemActivationGooglePhotosWeb;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSix_event_preconditions_google_photos_web_shelf": "name:ScalableIphShelfItemActivationGooglePhotosWeb;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSix_event_trigger": "name:ScalableIphUnlockedBasedSixTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSix_event_used": "name:ScalableIphUnlockedBasedSixEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedSix_session_rate": "any", - "IPH_ScalableIphUnlockedBasedSix_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedSix_x_CustomBubbleButtonText": "Open Photos", - "IPH_ScalableIphUnlockedBasedSix_x_CustomBubbleIcon": "GooglePhotosIcon", - "IPH_ScalableIphUnlockedBasedSix_x_CustomBubbleId": "scalable_iph_unlocked_based_six", - "IPH_ScalableIphUnlockedBasedSix_x_CustomBubbleText": "Explore your favorite memories in Google Photos", - "IPH_ScalableIphUnlockedBasedSix_x_CustomBubbleTitle": "Welcome Tips", - "IPH_ScalableIphUnlockedBasedSix_x_CustomButtonActionType": "OpenGooglePhotos", - "IPH_ScalableIphUnlockedBasedSix_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedSix_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedSix_x_CustomUiType": "Bubble", - "IPH_ScalableIphUnlockedBasedSix_x_CustomVersionNumber": "1", - "IPH_ScalableIphUnlockedBasedTen_availability": "any", - "IPH_ScalableIphUnlockedBasedTen_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedTen_blocking": "none", - "IPH_ScalableIphUnlockedBasedTen_event_1": "name:ScalableIphUnlocked;comparator:>=9;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedTen_event_precondition_print_job": "name:ScalableIphPrintJobCreated;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedTen_event_trigger": "name:ScalableIphUnlockedBasedTenTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedTen_event_used": "name:ScalableIphUnlockedBasedTenEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedTen_session_rate": "any", - "IPH_ScalableIphUnlockedBasedTen_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedTen_x_CustomBubbleButtonText": "Select file", - "IPH_ScalableIphUnlockedBasedTen_x_CustomBubbleIcon": "PrintJobsIcon", - "IPH_ScalableIphUnlockedBasedTen_x_CustomBubbleId": "scalable_iph_unlocked_based_ten", - "IPH_ScalableIphUnlockedBasedTen_x_CustomBubbleText": "Printing is easy with your Chromebook", - "IPH_ScalableIphUnlockedBasedTen_x_CustomBubbleTitle": "Welcome Tips", - "IPH_ScalableIphUnlockedBasedTen_x_CustomButtonActionType": "OpenFileManager", - "IPH_ScalableIphUnlockedBasedTen_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedTen_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedTen_x_CustomUiType": "Bubble", - "IPH_ScalableIphUnlockedBasedTen_x_CustomVersionNumber": "1", - "IPH_ScalableIphUnlockedBasedThree_availability": "any", - "IPH_ScalableIphUnlockedBasedThree_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedThree_blocking": "none", - "IPH_ScalableIphUnlockedBasedThree_event_1": "name:ScalableIphUnlocked;comparator:>=2;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedThree_event_precondition_personalization_app": "name:ScalableIphOpenPersonalizationApp;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedThree_event_trigger": "name:ScalableIphUnlockedBasedThreeTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedThree_event_used": "name:ScalableIphUnlockedBasedThreeEventUsed;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedThree_session_rate": "any", - "IPH_ScalableIphUnlockedBasedThree_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedThree_x_CustomBubbleButtonText": "Select wallpaper", - "IPH_ScalableIphUnlockedBasedThree_x_CustomBubbleId": "scalable_iph_unlocked_based_three", - "IPH_ScalableIphUnlockedBasedThree_x_CustomBubbleText": "Make your Chromebook uniquely yours with a new wallpaper", - "IPH_ScalableIphUnlockedBasedThree_x_CustomBubbleTitle": "Welcome Tips", - "IPH_ScalableIphUnlockedBasedThree_x_CustomButtonActionType": "OpenPersonalizationApp", - "IPH_ScalableIphUnlockedBasedThree_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedThree_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedThree_x_CustomUiType": "Bubble", - "IPH_ScalableIphUnlockedBasedThree_x_CustomVersionNumber": "1", - "IPH_ScalableIphUnlockedBasedTwo_availability": "any", - "IPH_ScalableIphUnlockedBasedTwo_blocked_by": "none", - "IPH_ScalableIphUnlockedBasedTwo_blocking": "none", - "IPH_ScalableIphUnlockedBasedTwo_event_1": "name:ScalableIphUnlocked;comparator:>=1;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedTwo_event_precondition_launcher": "name:ScalableIphAppListShown;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedTwo_event_trigger": "name:ScalableIphUnlockedBasedTwoTriggered;comparator:==0;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedTwo_event_used": "name:ScalableIphAppListShown;comparator:any;window:7;storage:8", - "IPH_ScalableIphUnlockedBasedTwo_session_rate": "any", - "IPH_ScalableIphUnlockedBasedTwo_session_rate_impact": "none", - "IPH_ScalableIphUnlockedBasedTwo_x_CustomBubbleId": "scalable_iph_unlocked_based_two", - "IPH_ScalableIphUnlockedBasedTwo_x_CustomBubbleText": "Search and find your apps in the Launcher \u25c9", - "IPH_ScalableIphUnlockedBasedTwo_x_CustomBubbleTitle": "Welcome Tips", - "IPH_ScalableIphUnlockedBasedTwo_x_CustomConditionClientAgeInDays": "6", - "IPH_ScalableIphUnlockedBasedTwo_x_CustomConditionNetworkConnection": "Online", - "IPH_ScalableIphUnlockedBasedTwo_x_CustomUiType": "Bubble", - "IPH_ScalableIphUnlockedBasedTwo_x_CustomVersionNumber": "1" - }, - "enable_features": [ - "IPH_ScalableIphUnlockedBasedEight", - "IPH_ScalableIphUnlockedBasedFive", - "IPH_ScalableIphUnlockedBasedFour", - "IPH_ScalableIphUnlockedBasedNine", - "IPH_ScalableIphUnlockedBasedOne", - "IPH_ScalableIphUnlockedBasedSeven", - "IPH_ScalableIphUnlockedBasedSix", - "IPH_ScalableIphUnlockedBasedTen", - "IPH_ScalableIphUnlockedBasedThree", - "IPH_ScalableIphUnlockedBasedTwo", - "ScalableIph" - ] - } - ] - } - ], "ScaleScrollbarAnimationTiming": [ { "platforms": [ @@ -23670,26 +21617,6 @@ ] } ], - "SearchEngineChoiceClearInvalidPref": [ - { - "platforms": [ - "android", - "chromeos", - "ios", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "ClearCountryPrefForStoredUnknownCountry" - ] - } - ] - } - ], "SearchEngineExplicitChoiceDialog": [ { "platforms": [ @@ -24005,21 +21932,6 @@ ] } ], - "ServerBasedTranscriptionForScreencast": [ - { - "platforms": [ - "chromeos" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "InternalServerSideSpeechRecognition" - ] - } - ] - } - ], "ServiceWorkerAutoPreload": [ { "platforms": [ @@ -24277,21 +22189,6 @@ ] } ], - "ShareInWebContextMenuIOS": [ - { - "platforms": [ - "ios" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "ShareInWebContextMenuIOS" - ] - } - ] - } - ], "SharedHighlightingIphClank": [ { "platforms": [ @@ -24419,26 +22316,6 @@ ] } ], - "SharingDisableVapid": [ - { - "platforms": [ - "android", - "chromeos", - "ios", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "SharingDisableVapid" - ] - } - ] - } - ], "SharingHubDesktopScreenshots": [ { "platforms": [ @@ -24601,31 +22478,6 @@ ] } ], - "SideSearchInProductHelp": [ - { - "platforms": [ - "chromeos", - "fuchsia", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "params": { - "availability": "any", - "event_trigger": "name:side_search_iph_tgr;comparator:==0;window:90;storage:360", - "event_used": "name:side_search_opened;comparator:==0;window:90;storage:360", - "session_rate": "<3" - }, - "enable_features": [ - "IPH_SideSearch" - ] - } - ] - } - ], "SigninPromoLimitsExperiment": [ { "platforms": [ @@ -25042,46 +22894,6 @@ ] } ], - "SoftNavigationDetectionAdvancedPaintAttribution": [ - { - "platforms": [ - "android", - "android_webview", - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "SoftNavigationDetectionAdvancedPaintAttribution" - ] - } - ] - } - ], - "SoftNavigationDetectionPrePaintBasedAttribution": [ - { - "platforms": [ - "android", - "android_webview", - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "SoftNavigationDetectionPrePaintBasedAttribution" - ] - } - ] - } - ], "SonomaAccessibilityActivationRefinements": [ { "platforms": [ @@ -25264,41 +23076,6 @@ ] } ], - "StandardizedTimerClamping": [ - { - "platforms": [ - "android", - "android_webview", - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "StandardizedTimerClamping" - ] - } - ] - } - ], - "StartupImprovements": [ - { - "platforms": [ - "ios" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "EnableStartupImprovements" - ] - } - ] - } - ], "StaticStorageQuota": [ { "platforms": [ @@ -25400,25 +23177,6 @@ ] } ], - "SubresourceFilterPrewarm": [ - { - "platforms": [ - "android", - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "SubresourceFilterPrewarm" - ] - } - ] - } - ], "SupervisedUserBlockInterstitialV3": [ { "platforms": [ @@ -25650,21 +23408,6 @@ ] } ], - "SysPkJPandVKMv3": [ - { - "platforms": [ - "chromeos" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "ImeManifestV3" - ] - } - ] - } - ], "SysUiHoldbackStudy": [ { "platforms": [ @@ -25806,21 +23549,6 @@ ] } ], - "TabGridEmptyThumbnail": [ - { - "platforms": [ - "ios" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "TabGridEmptyThumbnail" - ] - } - ] - } - ], "TabGroupInteractionsDesktop": [ { "platforms": [ @@ -25844,22 +23572,6 @@ ] } ], - "TabGroupParityAndroidV2": [ - { - "platforms": [ - "android" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "TabGroupEntryPointsAndroid", - "TabGroupParityBottomSheetAndroid" - ] - } - ] - } - ], "TabGroupSuggestions": [ { "platforms": [ @@ -25975,28 +23687,6 @@ ] } ], - "TabstripComboButton": [ - { - "platforms": [ - "chromeos", - "fuchsia", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "params": { - "tab_search_toolbar_button": "true" - }, - "enable_features": [ - "TabstripComboButton" - ] - } - ] - } - ], "TabstripDeclutter": [ { "platforms": [ @@ -26068,24 +23758,6 @@ ] } ], - "TaskManagerDesktopRefresh": [ - { - "platforms": [ - "windows", - "mac", - "linux", - "chromeos" - ], - "experiments": [ - { - "name": "TaskManagerDesktopRefresh", - "enable_features": [ - "TaskManagerDesktopRefresh" - ] - } - ] - } - ], "TextInputClientNestedLoop": [ { "platforms": [ @@ -26256,25 +23928,6 @@ ] } ], - "ToolbarPinning": [ - { - "platforms": [ - "chromeos", - "fuchsia", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "ToolbarPinning" - ] - } - ] - } - ], "ToolbarSnapshotRefactor": [ { "platforms": [ @@ -26563,31 +24216,6 @@ ] } ], - "UMA-NonUniformity-Trial-1-Percent": [ - { - "platforms": [ - "android", - "android_webview", - "ios", - "chromeos", - "fuchsia", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "group_01", - "params": { - "delta": "0.01" - }, - "enable_features": [ - "UMANonUniformityLogNormal" - ] - } - ] - } - ], "UMA-Pseudo-Metrics-Effect-Injection-25-Percent": [ { "platforms": [ @@ -26952,36 +24580,6 @@ ] } ], - "UseFreedesktopSecretKeyProvider": [ - { - "platforms": [ - "linux" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "UseFreedesktopSecretKeyProvider" - ] - } - ] - } - ], - "UseFreedesktopSecretKeyProviderForEncryption": [ - { - "platforms": [ - "linux" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "UseFreedesktopSecretKeyProviderForEncryption" - ] - } - ] - } - ], "UseHeuristicForFindingEditor": [ { "platforms": [ @@ -27176,37 +24774,6 @@ ] } ], - "UserLevelMemoryPressureSignal": [ - { - "platforms": [ - "android" - ], - "experiments": [ - { - "name": "Disabled", - "disable_features": [ - "UserLevelMemoryPressureSignalOn4GbDevices", - "UserLevelMemoryPressureSignalOn6GbDevices" - ] - } - ] - } - ], - "UserLevelMemoryPressureSignalMetricsOnly": [ - { - "platforms": [ - "android" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "UserLevelMemoryPressureSignalMetricsOnly" - ] - } - ] - } - ], "UserValueDefaultBrowserStrings": [ { "platforms": [ @@ -28002,24 +25569,6 @@ ] } ], - "VisibilityAwareResourceScheduler": [ - { - "platforms": [ - "chromeos", - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled_20230807", - "enable_features": [ - "VisibilityAwareResourceScheduler" - ] - } - ] - } - ], "VisitedLinksOn404": [ { "platforms": [ @@ -29074,28 +26623,6 @@ ] } ], - "WebViewAutoGrantStorageAccessAPI": [ - { - "platforms": [ - "android_webview" - ], - "experiments": [ - { - "name": "EnabledWithIncludes", - "enable_features": [ - "WebViewAutoSAA", - "WebViewDigitalAssetLinksLoadIncludes" - ] - }, - { - "name": "EnabledWithoutIncludes", - "enable_features": [ - "WebViewAutoSAA" - ] - } - ] - } - ], "WebViewBypassProvisionalCookieManager": [ { "platforms": [ @@ -29141,21 +26668,6 @@ ] } ], - "WebViewDisableCHIPS": [ - { - "platforms": [ - "android_webview" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "WebViewDisableCHIPS" - ] - } - ] - } - ], "WebViewEarlyStartupTracing": [ { "platforms": [ @@ -29396,21 +26908,6 @@ ] } ], - "WebViewRecordAppDataDirectorySize": [ - { - "platforms": [ - "android_webview" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "WebViewRecordAppDataDirectorySize" - ] - } - ] - } - ], "WebViewReducedFinchTime": [ { "platforms": [ @@ -29457,36 +26954,6 @@ ] } ], - "WebViewSafeAreaIncludesSystemBars": [ - { - "platforms": [ - "android_webview" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "WebViewSafeAreaIncludesSystemBars" - ] - } - ] - } - ], - "WebViewSeparateResourceContext": [ - { - "platforms": [ - "android_webview" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "WebViewSeparateResourceContext" - ] - } - ] - } - ], "WebViewSharedStorageAPI": [ { "platforms": [ @@ -29604,36 +27071,6 @@ ] } ], - "WebViewThreadSafeMedia": [ - { - "platforms": [ - "android_webview" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "WebViewThreadSafeMedia" - ] - } - ] - } - ], - "WebViewTrackpadAndMouseTextSelectionImprovements": [ - { - "platforms": [ - "android_webview" - ], - "experiments": [ - { - "name": "Enabled_20240424", - "enable_features": [ - "MouseAndTrackpadDropdownMenu" - ] - } - ] - } - ], "WebViewUseMetricsUploadServiceOnlySdkRuntime": [ { "platforms": [ @@ -29664,21 +27101,6 @@ ] } ], - "WebViewXRequestedWithHeaderControl": [ - { - "platforms": [ - "android_webview" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "WebViewXRequestedWithHeaderControl" - ] - } - ] - } - ], "WebXrEnableCardboard": [ { "platforms": [ @@ -29801,37 +27223,6 @@ ] } ], - "WhatsNewSparkEdition": [ - { - "platforms": [ - "linux", - "mac", - "windows" - ], - "experiments": [ - { - "name": "Enabled_benefits", - "params": { - "whats_new_customization": "BENEFITS", - "whats_new_survey_id": "PsSZ5E6nP0ugnJ3q1cK0WcE1Zf8H" - }, - "enable_features": [ - "Spark" - ] - }, - { - "name": "Enabled_original", - "params": { - "whats_new_customization": "ORIGINAL", - "whats_new_survey_id": "nGcgtQoLx0ugnJ3q1cK0SbfqkdQd" - }, - "enable_features": [ - "Spark" - ] - } - ] - } - ], "WidevinePersistentLicenseSupport": [ { "platforms": [
diff --git a/third_party/blink/public/mojom/content_extraction/script_tools.mojom b/third_party/blink/public/mojom/content_extraction/script_tools.mojom index 34e1504..51c4fa3 100644 --- a/third_party/blink/public/mojom/content_extraction/script_tools.mojom +++ b/third_party/blink/public/mojom/content_extraction/script_tools.mojom
@@ -4,13 +4,13 @@ module blink.mojom; -// See AnnotationsDict in tool_registration_params.idl for details of this +// See ToolAnnotations in model_context_tool.idl for details of this // data structure. struct ScriptToolAnnotations { bool read_only; }; -// See ToolRegistrationParams in tool_registration_params.idl for details of +// See ModelContextTool in model_context_tool.idl for details of // this data structure. struct ScriptTool { // These directly map to the corresponding fields in the web API.
diff --git a/third_party/blink/public/mojom/payments/secure_payment_confirmation_service.mojom b/third_party/blink/public/mojom/payments/secure_payment_confirmation_service.mojom index 9c2fef7..58f6ebb 100644 --- a/third_party/blink/public/mojom/payments/secure_payment_confirmation_service.mojom +++ b/third_party/blink/public/mojom/payments/secure_payment_confirmation_service.mojom
@@ -20,6 +20,15 @@ kUnavailableNoUserVerifyingPlatformAuthenticator, }; +// Describes a single Secure Payment Confirmation capability and its support +// status. +// https://w3c.github.io/secure-payment-confirmation/#typedefdef-securepaymentconfirmationcapabilities +struct SecurePaymentConfirmationCapability { + string name; + bool supported; +}; + + // Interface providing browser services related to SecurePaymentConfirmation, // which are not tied to a specific PaymentRequest instantiation. This includes // services required at credential-creation time, which occurs outside of the @@ -33,6 +42,10 @@ // https://w3c.github.io/secure-payment-confirmation/#dom-paymentrequest-securepaymentconfirmationavailability SecurePaymentConfirmationAvailability() => (SecurePaymentConfirmationAvailabilityEnum availability); + // Returns an array of supported Secure Payment Confirmation capabilities. + // https://w3c.github.io/secure-payment-confirmation/#dom-paymentrequest-getsecurepaymentconfirmationcapabilities + GetSecurePaymentConfirmationCapabilities() => (array<SecurePaymentConfirmationCapability> capabilities); + // Store a new PublicKeyCredential with "payment" extension on disk. It can be // later used for PaymentRequests. StorePaymentCredential(array<uint8> credential_id, string rp_id, array<uint8> user_id)
diff --git a/third_party/blink/renderer/bindings/generated_in_core.gni b/third_party/blink/renderer/bindings/generated_in_core.gni index 0c8e053..1018afe3 100644 --- a/third_party/blink/renderer/bindings/generated_in_core.gni +++ b/third_party/blink/renderer/bindings/generated_in_core.gni
@@ -98,8 +98,8 @@ "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scheduler_post_task_callback.h", "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_subscribe_callback.cc", "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_subscribe_callback.h", - "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_tool_function.cc", - "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_tool_function.h", + "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_tool_execute_callback.cc", + "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_tool_execute_callback.h", "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_underlying_source_cancel_callback.cc", "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_underlying_source_cancel_callback.h", "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_underlying_source_pull_callback.cc", @@ -168,12 +168,12 @@ "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_clipboard_event_init.h", "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_close_watcher_options.cc", "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_close_watcher_options.h", - "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_tool_registration_params.cc", - "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_tool_registration_params.h", - "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_provide_context_params.cc", - "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_provide_context_params.h", - "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_annotations_dict.cc", - "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_annotations_dict.h", + "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_model_context_tool.cc", + "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_model_context_tool.h", + "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_model_context_options.cc", + "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_model_context_options.h", + "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_tool_annotations.cc", + "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_tool_annotations.h", "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_command_event_init.cc", "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_command_event_init.h", "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_composition_event_init.cc", @@ -452,6 +452,8 @@ "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scheduler_post_task_options.h", "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_into_view_options.cc", "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_into_view_options.h", + "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_result.cc", + "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_result.h", "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_options.cc", "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_options.h", "$root_gen_dir/third_party/blink/renderer/bindings/core/v8/v8_scroll_timeline_options.cc",
diff --git a/third_party/blink/renderer/bindings/generated_in_modules.gni b/third_party/blink/renderer/bindings/generated_in_modules.gni index 7afb6ff..0e89b69 100644 --- a/third_party/blink/renderer/bindings/generated_in_modules.gni +++ b/third_party/blink/renderer/bindings/generated_in_modules.gni
@@ -1882,6 +1882,8 @@ "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_screen_idle_state.h", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_secure_payment_confirmation_availability.cc", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_secure_payment_confirmation_availability.h", + "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_secure_payment_confirmation_capability.cc", + "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_secure_payment_confirmation_capability.h", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_service_worker_state.cc", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_service_worker_state.h", "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_service_worker_update_via_cache.cc",
diff --git a/third_party/blink/renderer/bindings/idl_in_core.gni b/third_party/blink/renderer/bindings/idl_in_core.gni index 01e4cca..9836bd8 100644 --- a/third_party/blink/renderer/bindings/idl_in_core.gni +++ b/third_party/blink/renderer/bindings/idl_in_core.gni
@@ -334,6 +334,7 @@ "//third_party/blink/renderer/core/frame/screen.idl", "//third_party/blink/renderer/core/frame/scroll_into_view_options.idl", "//third_party/blink/renderer/core/frame/scroll_options.idl", + "//third_party/blink/renderer/core/frame/scroll_result.idl", "//third_party/blink/renderer/core/frame/scroll_to_options.idl", "//third_party/blink/renderer/core/frame/selector_directive.idl", "//third_party/blink/renderer/core/frame/test_report_body.idl", @@ -816,9 +817,10 @@ "//third_party/blink/renderer/core/view_transition/view_transition_type_set.idl", "//third_party/blink/renderer/core/view_transition/view_transition_supplement.idl", "//third_party/blink/renderer/core/script_tools/model_context.idl", + "//third_party/blink/renderer/core/script_tools/model_context_options.idl", "//third_party/blink/renderer/core/script_tools/model_context_supplement.idl", "//third_party/blink/renderer/core/script_tools/model_context_testing.idl", - "//third_party/blink/renderer/core/script_tools/tool_registration_params.idl", + "//third_party/blink/renderer/core/script_tools/model_context_tool.idl", "//third_party/blink/renderer/core/workers/abstract_worker.idl", "//third_party/blink/renderer/core/workers/dedicated_worker_global_scope.idl", "//third_party/blink/renderer/core/workers/shared_worker.idl",
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc index 8bd4b2f..59940dc 100644 --- a/third_party/blink/renderer/core/dom/element.cc +++ b/third_party/blink/renderer/core/dom/element.cc
@@ -49,6 +49,7 @@ #include "third_party/blink/renderer/bindings/core/v8/v8_pointer_lock_options.h" #include "third_party/blink/renderer/bindings/core/v8/v8_scroll_container.h" #include "third_party/blink/renderer/bindings/core/v8/v8_scroll_into_view_options.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_scroll_result.h" #include "third_party/blink/renderer/bindings/core/v8/v8_scroll_to_options.h" #include "third_party/blink/renderer/bindings/core/v8/v8_set_html_options.h" #include "third_party/blink/renderer/bindings/core/v8/v8_set_html_unsafe_options.h" @@ -2165,7 +2166,7 @@ // TODO(https://crbug.com/41406914): Ad-hoc method until we hook up with scroll // animation end. -ScriptPromise<IDLUndefined> CreateScrollResolvedPromise( +ScriptPromise<ScrollResult> CreateScrollResolvedPromise( ScriptState* script_state) { // Legacy binary tests pass a null `script_state`. if (!script_state || @@ -2174,21 +2175,21 @@ } auto* resolver = - MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(script_state); - resolver->Resolve(); + MakeGarbageCollected<ScriptPromiseResolver<ScrollResult>>(script_state); + resolver->Resolve(ScrollResult::Create()); return resolver->Promise(); } } // namespace -ScriptPromise<IDLUndefined> Element::scrollIntoView(ScriptState* script_state, +ScriptPromise<ScrollResult> Element::scrollIntoView(ScriptState* script_state, bool align_to_top) { auto* arg = MakeGarbageCollected<V8UnionBooleanOrScrollIntoViewOptions>(align_to_top); return scrollIntoView(script_state, arg); } -ScriptPromise<IDLUndefined> Element::scrollIntoView( +ScriptPromise<ScrollResult> Element::scrollIntoView( ScriptState* script_state, const V8UnionBooleanOrScrollIntoViewOptions* arg) { ScrollIntoViewOptions* options = nullptr; @@ -2910,7 +2911,7 @@ return 0; } -ScriptPromise<IDLUndefined> Element::scrollBy(ScriptState* script_state, +ScriptPromise<ScrollResult> Element::scrollBy(ScriptState* script_state, double x, double y) { ScrollToOptions* scroll_to_options = ScrollToOptions::Create(); @@ -2919,7 +2920,7 @@ return scrollBy(script_state, scroll_to_options); } -ScriptPromise<IDLUndefined> Element::scrollBy( +ScriptPromise<ScrollResult> Element::scrollBy( ScriptState* script_state, const ScrollToOptions* scroll_to_options) { if (!InActiveDocument()) { @@ -2935,11 +2936,11 @@ GetDocument().UpdateStyleAndLayoutForNode(this, DocumentUpdateReason::kJavaScript); - ScriptPromiseResolver<IDLUndefined>* resolver = nullptr; + ScriptPromiseResolver<ScrollResult>* resolver = nullptr; if (script_state && RuntimeEnabledFeatures::ProgrammaticScrollPromiseEnabled()) { resolver = - MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(script_state); + MakeGarbageCollected<ScriptPromiseResolver<ScrollResult>>(script_state); } if (GetDocument().ScrollingElementNoLayout() == this) { @@ -2951,7 +2952,7 @@ return resolver ? resolver->Promise() : EmptyPromise(); } -ScriptPromise<IDLUndefined> Element::scrollTo(ScriptState* script_state, +ScriptPromise<ScrollResult> Element::scrollTo(ScriptState* script_state, double x, double y) { ScrollToOptions* scroll_to_options = ScrollToOptions::Create(); @@ -2960,14 +2961,14 @@ return scrollTo(script_state, scroll_to_options); } -ScriptPromise<IDLUndefined> Element::scrollTo( +ScriptPromise<ScrollResult> Element::scrollTo( ScriptState* script_state, const ScrollToOptions* scroll_to_options) { - ScriptPromiseResolver<IDLUndefined>* resolver = nullptr; + ScriptPromiseResolver<ScrollResult>* resolver = nullptr; if (script_state && RuntimeEnabledFeatures::ProgrammaticScrollPromiseEnabled()) { resolver = - MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(script_state); + MakeGarbageCollected<ScriptPromiseResolver<ScrollResult>>(script_state); } ScrollTo(scroll_to_options, resolver); @@ -2975,7 +2976,7 @@ } bool Element::ScrollTo(const ScrollToOptions* scroll_to_options, - ScriptPromiseResolver<IDLUndefined>* resolver) { + ScriptPromiseResolver<ScrollResult>* resolver) { if (!InActiveDocument()) { if (resolver) { resolver->Resolve(); @@ -3017,7 +3018,7 @@ } bool Element::ScrollLayoutBoxBy(const ScrollToOptions* scroll_to_options, - ScriptPromiseResolver<IDLUndefined>* resolver) { + ScriptPromiseResolver<ScrollResult>* resolver) { gfx::Vector2dF displacement; if (scroll_to_options->hasLeft()) { displacement.set_x( @@ -3060,7 +3061,7 @@ } bool Element::ScrollLayoutBoxTo(const ScrollToOptions* scroll_to_options, - ScriptPromiseResolver<IDLUndefined>* resolver) { + ScriptPromiseResolver<ScrollResult>* resolver) { mojom::blink::ScrollBehavior scroll_behavior = ScrollableArea::V8EnumToScrollBehavior( scroll_to_options->behavior().AsEnum()); @@ -3130,7 +3131,7 @@ } bool Element::ScrollFrameBy(const ScrollToOptions* scroll_to_options, - ScriptPromiseResolver<IDLUndefined>* resolver) { + ScriptPromiseResolver<ScrollResult>* resolver) { gfx::Vector2dF displacement; if (scroll_to_options->hasLeft()) { displacement.set_x( @@ -3171,7 +3172,7 @@ } bool Element::ScrollFrameTo(const ScrollToOptions* scroll_to_options, - ScriptPromiseResolver<IDLUndefined>* resolver) { + ScriptPromiseResolver<ScrollResult>* resolver) { mojom::blink::ScrollBehavior scroll_behavior = ScrollableArea::V8EnumToScrollBehavior( scroll_to_options->behavior().AsEnum());
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h index 0f463227..6a98c71 100644 --- a/third_party/blink/renderer/core/dom/element.h +++ b/third_party/blink/renderer/core/dom/element.h
@@ -136,6 +136,7 @@ class ScriptValue; class ScrollIntoViewOptions; class ScrollMarkerGroupData; +class ScrollResult; class ScrollMarkerPseudoElement; class ScrollToOptions; class SetHTMLOptions; @@ -562,20 +563,20 @@ virtual int scrollWidth(); virtual int scrollHeight(); - ScriptPromise<IDLUndefined> scrollIntoView( + ScriptPromise<ScrollResult> scrollIntoView( ScriptState* script_state, const V8UnionBooleanOrScrollIntoViewOptions* arg); - ScriptPromise<IDLUndefined> scrollIntoView(ScriptState* script_state, + ScriptPromise<ScrollResult> scrollIntoView(ScriptState* script_state, bool align_to_top = true); - ScriptPromise<IDLUndefined> scrollBy(ScriptState* script_state, + ScriptPromise<ScrollResult> scrollBy(ScriptState* script_state, double x, double y); - ScriptPromise<IDLUndefined> scrollBy(ScriptState* script_state, + ScriptPromise<ScrollResult> scrollBy(ScriptState* script_state, const ScrollToOptions*); - ScriptPromise<IDLUndefined> scrollTo(ScriptState* script_state, + ScriptPromise<ScrollResult> scrollTo(ScriptState* script_state, double x, double y); - ScriptPromise<IDLUndefined> scrollTo(ScriptState* script_state, + ScriptPromise<ScrollResult> scrollTo(ScriptState* script_state, const ScrollToOptions*); void scrollIntoViewForTesting( @@ -585,7 +586,7 @@ void scrollToForTesting(double x, double y); bool ScrollTo(const ScrollToOptions*, - ScriptPromiseResolver<IDLUndefined>* = nullptr); + ScriptPromiseResolver<ScrollResult>* = nullptr); // Returns the bounds of this Element, unclipped, in the coordinate space of // the local root's widget. That is, in the outermost main frame, this will @@ -2158,13 +2159,13 @@ bool HasSiblingBoxPseudoElements() const; bool ScrollLayoutBoxBy(const ScrollToOptions*, - ScriptPromiseResolver<IDLUndefined>*); + ScriptPromiseResolver<ScrollResult>*); bool ScrollLayoutBoxTo(const ScrollToOptions*, - ScriptPromiseResolver<IDLUndefined>*); + ScriptPromiseResolver<ScrollResult>*); bool ScrollFrameBy(const ScrollToOptions*, - ScriptPromiseResolver<IDLUndefined>*); + ScriptPromiseResolver<ScrollResult>*); bool ScrollFrameTo(const ScrollToOptions*, - ScriptPromiseResolver<IDLUndefined>*); + ScriptPromiseResolver<ScrollResult>*); bool HasElementFlag(ElementFlags mask) const; void SetElementFlag(ElementFlags, bool value = true);
diff --git a/third_party/blink/renderer/core/dom/element.idl b/third_party/blink/renderer/core/dom/element.idl index 5bfb87e39..6e22578 100644 --- a/third_party/blink/renderer/core/dom/element.idl +++ b/third_party/blink/renderer/core/dom/element.idl
@@ -150,13 +150,13 @@ // https://drafts.csswg.org/cssom-view/#dom-element-checkvisibility [MeasureAs=ElementCheckVisibility] boolean checkVisibility(optional CheckVisibilityOptions options = {}); - [CallWith=ScriptState] Promise<undefined> scrollIntoView(optional (ScrollIntoViewOptions or boolean) arg = {}); - [ImplementedAs=scrollTo, CallWith=ScriptState] Promise<undefined> scroll(optional ScrollToOptions options = {}); - [ImplementedAs=scrollTo, CallWith=ScriptState] Promise<undefined> scroll(unrestricted double x, unrestricted double y); - [CallWith=ScriptState] Promise<undefined> scrollTo(optional ScrollToOptions options = {}); - [CallWith=ScriptState] Promise<undefined> scrollTo(unrestricted double x, unrestricted double y); - [CallWith=ScriptState] Promise<undefined> scrollBy(optional ScrollToOptions options = {}); - [CallWith=ScriptState] Promise<undefined> scrollBy(unrestricted double x, unrestricted double y); + [CallWith=ScriptState] Promise<ScrollResult> scrollIntoView(optional (ScrollIntoViewOptions or boolean) arg = {}); + [ImplementedAs=scrollTo, CallWith=ScriptState] Promise<ScrollResult> scroll(optional ScrollToOptions options = {}); + [ImplementedAs=scrollTo, CallWith=ScriptState] Promise<ScrollResult> scroll(unrestricted double x, unrestricted double y); + [CallWith=ScriptState] Promise<ScrollResult> scrollTo(optional ScrollToOptions options = {}); + [CallWith=ScriptState] Promise<ScrollResult> scrollTo(unrestricted double x, unrestricted double y); + [CallWith=ScriptState] Promise<ScrollResult> scrollBy(optional ScrollToOptions options = {}); + [CallWith=ScriptState] Promise<ScrollResult> scrollBy(unrestricted double x, unrestricted double y); attribute unrestricted double scrollTop; attribute unrestricted double scrollLeft; readonly attribute long scrollWidth;
diff --git a/third_party/blink/renderer/core/frame/scroll_result.idl b/third_party/blink/renderer/core/frame/scroll_result.idl new file mode 100644 index 0000000..0f9223f --- /dev/null +++ b/third_party/blink/renderer/core/frame/scroll_result.idl
@@ -0,0 +1,8 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// TODO(crbug.com/41406914): Update this interface when spec'd. + +dictionary ScrollResult { +};
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc index becbdcb..265c843 100644 --- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc +++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -2574,9 +2574,12 @@ if (replaced.IsSVGRoot()) return To<LayoutSVGRoot>(replaced).ClipsToContentBox(); - // A replaced element with border-radius always clips the content. - if (replaced.StyleRef().HasBorderRadius()) + // A replaced element with border-radius or border-shape always clips the + // content to the inner shape. + if (replaced.StyleRef().HasBorderRadius() || + replaced.StyleRef().HasBorderShape()) { return true; + } // ImagePainter (but not painters for LayoutMedia whose IsImage is also true) // won't paint outside of the content box. @@ -2858,10 +2861,20 @@ PhysicalRect box_rect(context_.current.paint_offset, box.StitchedSize()); // Expand the reference rect by overflow-clip-margin if applicable, so // that the border-shape clip accounts for the additional visible - // overflow area allowed by the margin. + // overflow area allowed by the margin. Only expand outward (beyond the + // border box); never contract inward, since the inner border-shape clip + // must reference the border box, not the padding or content box. For + // example, the UA stylesheet sets `overflow-clip-margin: content-box` on + // replaced elements like <img>, and applying that contraction here would + // incorrectly shrink the inner border-shape clip. PhysicalRect expanded_box_rect = box_rect; if (box.ShouldApplyOverflowClipMargin()) { - expanded_box_rect.Expand(box.BorderOutsetsForClipping()); + PhysicalBoxStrut outsets = box.BorderOutsetsForClipping(); + outsets.top = std::max(outsets.top, LayoutUnit()); + outsets.right = std::max(outsets.right, LayoutUnit()); + outsets.bottom = std::max(outsets.bottom, LayoutUnit()); + outsets.left = std::max(outsets.left, LayoutUnit()); + expanded_box_rect.Expand(outsets); } std::optional<BorderShapeReferenceRects> border_shape_rects = ComputeBorderShapeReferenceRects(expanded_box_rect, box.StyleRef(),
diff --git a/third_party/blink/renderer/core/script_tools/model_context.cc b/third_party/blink/renderer/core/script_tools/model_context.cc index 66bfce9..881f8f5 100644 --- a/third_party/blink/renderer/core/script_tools/model_context.cc +++ b/third_party/blink/renderer/core/script_tools/model_context.cc
@@ -7,9 +7,10 @@ #include "base/task/single_thread_task_runner.h" #include "third_party/blink/public/platform/browser_interface_broker_proxy.h" #include "third_party/blink/renderer/bindings/core/v8/capture_source_location.h" -#include "third_party/blink/renderer/bindings/core/v8/v8_annotations_dict.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_model_context_options.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_model_context_tool.h" #include "third_party/blink/renderer/bindings/core/v8/v8_script_runner.h" -#include "third_party/blink/renderer/bindings/core/v8/v8_tool_function.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_tool_annotations.h" #include "third_party/blink/renderer/core/dom/abort_signal.h" #include "third_party/blink/renderer/core/dom/scoped_abort_state.h" #include "third_party/blink/renderer/core/event_type_names.h" @@ -168,9 +169,9 @@ } void ModelContext::registerTool(ScriptState* script_state, - ToolRegistrationParams* params, + ModelContextTool* tool, ExceptionState& exception_state) { - if (!RegisterTool(script_state, params, exception_state)) { + if (!RegisterTool(script_state, tool, exception_state)) { return; } } @@ -203,11 +204,11 @@ } void ModelContext::provideContext(ScriptState* script_state, - ProvideContextParams* params, + const ModelContextOptions* options, ExceptionState& exception_state) { auto prev_tool_map = std::move(tool_map_); - for (auto tool : params->tools()) { + for (auto tool : options->tools()) { if (!RegisterTool(script_state, tool, exception_state)) { tool_map_ = std::move(prev_tool_map); return; @@ -238,7 +239,8 @@ } std::optional<uint32_t> execution_id; - if (V8ToolFunction* v8_tool_function = it->value->GetV8ToolFunction()) { + if (V8ToolExecuteCallback* v8_tool_function = + it->value->GetV8ToolExecuteCallback()) { execution_id = ExecuteV8Tool(v8_tool_function, name, input_arguments, signal, std::move(tool_executed_cb)); } else { @@ -342,7 +344,7 @@ // waits for the promise to resolve, JSON-stringifies the result, and passes // it to OnToolExecuted(). std::optional<uint32_t> ModelContext::ExecuteV8Tool( - V8ToolFunction* tool_function, + V8ToolExecuteCallback* tool_function, const String& name, const String& input_arguments, AbortSignal* signal, @@ -418,30 +420,30 @@ } bool ModelContext::RegisterTool(ScriptState* script_state, - ToolRegistrationParams* params, + ModelContextTool* tool, ExceptionState& exception_state) { - if (tool_map_.find(params->name()) != tool_map_.end()) { + if (tool_map_.find(tool->name()) != tool_map_.end()) { exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, "Duplicate tool name"); return false; } - if (!params->name() || params->name().empty()) { + if (!tool->name() || tool->name().empty()) { exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, "Tool name is required"); return false; } - if (!params->description() || params->description().empty()) { + if (!tool->description() || tool->description().empty()) { exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, "Description is required"); return false; } String input_schema; - if (params->hasInputSchema()) { + if (tool->hasInputSchema()) { input_schema = - ValidateAndStringifyObject(script_state, params->inputSchema()); + ValidateAndStringifyObject(script_state, tool->inputSchema()); if (!input_schema) { exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, "Invalid input schema"); @@ -450,21 +452,21 @@ } auto script_tool = mojom::blink::ScriptTool::New(); - script_tool->name = params->name(); - script_tool->description = params->description(); + script_tool->name = tool->name(); + script_tool->description = tool->description(); script_tool->input_schema = input_schema; - if (params->hasAnnotations()) { + if (tool->hasAnnotations()) { script_tool->annotations = mojom::blink::ScriptToolAnnotations::New(); - script_tool->annotations->read_only = params->annotations()->readOnlyHint(); + script_tool->annotations->read_only = tool->annotations()->readOnlyHint(); } auto* tool_data = MakeGarbageCollected<ToolData>( base::PassKey<ModelContext>(), std::move(script_tool), - /*v8_tool_function=*/params->execute(), + /*v8_tool_function=*/tool->execute(), CaptureSourceLocation(ExecutionContext::From(script_state))); - tool_map_.insert(params->name(), tool_data); + tool_map_.insert(tool->name(), tool_data); OnToolsChanged(); UseCounter::Count(document_, WebFeature::kModelContextRegisterTool); return true;
diff --git a/third_party/blink/renderer/core/script_tools/model_context.h b/third_party/blink/renderer/core/script_tools/model_context.h index 9272fc94..2ee935b 100644 --- a/third_party/blink/renderer/core/script_tools/model_context.h +++ b/third_party/blink/renderer/core/script_tools/model_context.h
@@ -13,9 +13,7 @@ #include "third_party/blink/public/mojom/content_extraction/script_tools.mojom-blink.h" #include "third_party/blink/public/web/web_document.h" #include "third_party/blink/renderer/bindings/core/v8/v8_model_context.h" -#include "third_party/blink/renderer/bindings/core/v8/v8_provide_context_params.h" -#include "third_party/blink/renderer/bindings/core/v8/v8_tool_function.h" -#include "third_party/blink/renderer/bindings/core/v8/v8_tool_registration_params.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_tool_execute_callback.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/platform/allow_discouraged_type.h" @@ -27,6 +25,8 @@ class AbortSignal; class Element; class SourceLocation; +class ModelContextOptions; +class ModelContextTool; class DeclarativeWebMCPTool : public GarbageCollectedMixin { public: @@ -57,7 +57,7 @@ base::FunctionRef<void(const mojom::blink::ScriptTool&)>) const; void registerTool(ScriptState* state, - ToolRegistrationParams* params, + ModelContextTool* tool, ExceptionState& exception_state); void unregisterTool(const String& name, ExceptionState& exception_state); @@ -66,7 +66,7 @@ WebDocument::ScriptToolDeclaration* tool_declaration) const; void provideContext(ScriptState* state, - ProvideContextParams* params, + const ModelContextOptions* options, ExceptionState& exception_state); void clearContext(); @@ -101,7 +101,7 @@ // Creates a JS-backed tool. ToolData(base::PassKey<ModelContext>, mojo::StructPtr<mojom::blink::ScriptTool> script_tool, - V8ToolFunction* v8_tool_function, + V8ToolExecuteCallback* v8_tool_function, SourceLocation* source_location) : script_tool_(std::move(script_tool)), v8_tool_function_(v8_tool_function), @@ -131,14 +131,16 @@ private: friend class ModelContext; - V8ToolFunction* GetV8ToolFunction() const { return v8_tool_function_; } + V8ToolExecuteCallback* GetV8ToolExecuteCallback() const { + return v8_tool_function_; + } DeclarativeWebMCPTool* DeclarativeTool() const { return declarative_tool_; } void RefreshDeclarativeInputSchema(); mojo::StructPtr<mojom::blink::ScriptTool> script_tool_; // A JS-provided MCP tool: - Member<V8ToolFunction> v8_tool_function_; + Member<V8ToolExecuteCallback> v8_tool_function_; // Used for declarative (form-based) MCP tools only: Member<DeclarativeWebMCPTool> declarative_tool_; // For JS-provided MCP tools, the location of the registerTool() call. @@ -156,7 +158,7 @@ class ToolFunctionFinishedCallback; std::optional<uint32_t> ExecuteV8Tool( - V8ToolFunction* tool_function, + V8ToolExecuteCallback* tool_function, const String& name, const String& input_arguments, AbortSignal* signal, @@ -166,7 +168,7 @@ ScriptToolExecutedCallback tool_executed_cb); bool RegisterTool(ScriptState* script_state, - ToolRegistrationParams* params, + ModelContextTool* tool, ExceptionState& exception_state); void OnToolExecuted(uint32_t execution_id, std::optional<String> result);
diff --git a/third_party/blink/renderer/core/script_tools/model_context.idl b/third_party/blink/renderer/core/script_tools/model_context.idl index 249740c4..2df70f7 100644 --- a/third_party/blink/renderer/core/script_tools/model_context.idl +++ b/third_party/blink/renderer/core/script_tools/model_context.idl
@@ -2,13 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// https://webmachinelearning.github.io/webmcp/#model-context-container + [ Exposed=Window, + SecureContext, RuntimeEnabled=WebMCP ] interface ModelContext { - [CallWith=ScriptState, RaisesException] undefined registerTool(ToolRegistrationParams params); - [RaisesException] undefined unregisterTool(DOMString tool_name); - - [CallWith=ScriptState, RaisesException] undefined provideContext(ProvideContextParams params); + [CallWith=ScriptState, RaisesException] undefined provideContext(optional ModelContextOptions options = {}); undefined clearContext(); + [CallWith=ScriptState, RaisesException] undefined registerTool(ModelContextTool tool); + [RaisesException] undefined unregisterTool(DOMString name); };
diff --git a/third_party/blink/renderer/core/script_tools/model_context_options.idl b/third_party/blink/renderer/core/script_tools/model_context_options.idl new file mode 100644 index 0000000..953860e --- /dev/null +++ b/third_party/blink/renderer/core/script_tools/model_context_options.idl
@@ -0,0 +1,9 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// https://webmachinelearning.github.io/webmcp/#model-context-options + +dictionary ModelContextOptions { + sequence<ModelContextTool> tools = []; +};
diff --git a/third_party/blink/renderer/core/script_tools/model_context_supplement.idl b/third_party/blink/renderer/core/script_tools/model_context_supplement.idl index e46648b..cef492c 100644 --- a/third_party/blink/renderer/core/script_tools/model_context_supplement.idl +++ b/third_party/blink/renderer/core/script_tools/model_context_supplement.idl
@@ -2,10 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// https://webmachinelearning.github.io/webmcp/#navigator-extension + [ RuntimeEnabled=WebMCP, ImplementedAs=ModelContextSupplement ] partial interface Navigator { - [Replaceable] readonly attribute ModelContext modelContext; + [SecureContext, SameObject] readonly attribute ModelContext modelContext; [Replaceable, RuntimeEnabled=WebMCPTesting] readonly attribute ModelContextTesting modelContextTesting; };
diff --git a/third_party/blink/renderer/core/script_tools/model_context_tool.idl b/third_party/blink/renderer/core/script_tools/model_context_tool.idl new file mode 100644 index 0000000..187e792 --- /dev/null +++ b/third_party/blink/renderer/core/script_tools/model_context_tool.idl
@@ -0,0 +1,19 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// https://webmachinelearning.github.io/webmcp/#model-context-tool + +dictionary ModelContextTool { + required DOMString name; + required DOMString description; + object inputSchema; + required ToolExecuteCallback execute; + ToolAnnotations annotations; +}; + +dictionary ToolAnnotations { + boolean readOnlyHint; +}; + +callback ToolExecuteCallback = Promise<any>(any... parameters);
diff --git a/third_party/blink/renderer/core/script_tools/tool_registration_params.idl b/third_party/blink/renderer/core/script_tools/tool_registration_params.idl deleted file mode 100644 index 692c1915..0000000 --- a/third_party/blink/renderer/core/script_tools/tool_registration_params.idl +++ /dev/null
@@ -1,23 +0,0 @@ -// Copyright 2025 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// TODO(khushalsagar): Link to the explainer for details of the WebIDL. - -callback ToolFunction = Promise<any>(any... parameters); - -dictionary AnnotationsDict { - boolean readOnlyHint; -}; - -dictionary ToolRegistrationParams { - required ToolFunction execute; - required DOMString name; - required DOMString description; - object inputSchema; - AnnotationsDict annotations; -}; - -dictionary ProvideContextParams { - required sequence<ToolRegistrationParams> tools; -};
diff --git a/third_party/blink/renderer/core/scroll/scrollable_area.cc b/third_party/blink/renderer/core/scroll/scrollable_area.cc index d5be303..2f82ff8 100644 --- a/third_party/blink/renderer/core/scroll/scrollable_area.cc +++ b/third_party/blink/renderer/core/scroll/scrollable_area.cc
@@ -44,6 +44,7 @@ #include "cc/input/snap_selection_strategy.h" #include "third_party/blink/public/mojom/scroll/scroll_into_view_params.mojom-blink-forward.h" #include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_scroll_result.h" #include "third_party/blink/renderer/core/animation/scroll_timeline.h" #include "third_party/blink/renderer/core/css/properties/longhands.h" #include "third_party/blink/renderer/core/dom/scroll_marker_group_pseudo_element.h" @@ -411,7 +412,7 @@ const ScrollOffset& offset, cc::ScrollSourceType source_type, mojom::blink::ScrollBehavior behavior, - ScriptPromiseResolver<IDLUndefined>* resolver) { + ScriptPromiseResolver<ScrollResult>* resolver) { RegisterPromiseResolver(resolver); return SetScrollOffsetInternal(offset, mojom::blink::ScrollType::kProgrammatic, @@ -657,7 +658,7 @@ } void ScrollableArea::RegisterPromiseResolver( - ScriptPromiseResolver<IDLUndefined>* resolver) { + ScriptPromiseResolver<ScrollResult>* resolver) { if (promise_resolver_) { promise_resolver_->Resolve(); }
diff --git a/third_party/blink/renderer/core/scroll/scrollable_area.h b/third_party/blink/renderer/core/scroll/scrollable_area.h index c542cd8..5d921f2 100644 --- a/third_party/blink/renderer/core/scroll/scrollable_area.h +++ b/third_party/blink/renderer/core/scroll/scrollable_area.h
@@ -79,6 +79,7 @@ class ProgrammaticScrollAnimator; class ScrollAnchor; class ScrollAnimatorBase; +class ScrollResult; struct SerializedAnchor; class ScrollMarkerGroupPseudoElement; class TextOverflowPostLayoutSnapshot; @@ -141,7 +142,7 @@ bool SetProgrammaticScrollOffset(const ScrollOffset&, cc::ScrollSourceType, mojom::blink::ScrollBehavior, - ScriptPromiseResolver<IDLUndefined>*); + ScriptPromiseResolver<ScrollResult>*); void ScrollBy( const ScrollOffset&, @@ -172,7 +173,7 @@ // Register the promise resolver to handle it later at the end of the // requested scroll. This interrupts the pending resolver, if any, by // resolving it. - void RegisterPromiseResolver(ScriptPromiseResolver<IDLUndefined>*); + void RegisterPromiseResolver(ScriptPromiseResolver<ScrollResult>*); // Resolve the registered promise, if any. void SettlePendingPromiseResolver(ScrollCompletionMode); @@ -804,7 +805,7 @@ // Holds on to the `ScriptPromiseResolver` from a JS scroll request to be able // to resolve it at dtor if not resolved explicitly earlier. - Member<ScriptPromiseResolver<IDLUndefined>> promise_resolver_; + Member<ScriptPromiseResolver<ScrollResult>> promise_resolver_; ScrollOffset pending_scroll_anchor_adjustment_;
diff --git a/third_party/blink/renderer/modules/BUILD.gn b/third_party/blink/renderer/modules/BUILD.gn index 7683e09..a2f3898c 100644 --- a/third_party/blink/renderer/modules/BUILD.gn +++ b/third_party/blink/renderer/modules/BUILD.gn
@@ -550,6 +550,7 @@ "payments/payment_request_for_invalid_origin_or_ssl_test.cc", "payments/payment_request_optional_total_test.cc", "payments/payment_request_secure_payment_confirmation_availability_test.cc", + "payments/payment_request_secure_payment_confirmation_capabilities_test.cc", "payments/payment_request_test.cc", "payments/payment_request_update_event_test.cc", "payments/payment_response_test.cc",
diff --git a/third_party/blink/renderer/modules/payments/payment_request.cc b/third_party/blink/renderer/modules/payments/payment_request.cc index 772590a..3da09f4 100644 --- a/third_party/blink/renderer/modules/payments/payment_request.cc +++ b/third_party/blink/renderer/modules/payments/payment_request.cc
@@ -861,6 +861,29 @@ resolver->Resolve(V8SecurePaymentConfirmationAvailability( ToV8SecurePaymentConfirmationAvailabilityEnum(result))); } + +void OnGetSecurePaymentConfirmationCapabilitiesComplete( + std::unique_ptr<ScopedPromiseResolver> scoped_resolver, + const Vector<payments::mojom::blink::SecurePaymentConfirmationCapabilityPtr> + capabilities) { + auto* resolver = scoped_resolver->Release() + ->DowncastTo<IDLRecord<IDLString, IDLBoolean>>(); + + Vector<std::pair<String, bool>> results; + for (const auto& capability : capabilities) { + results.emplace_back(std::move(capability->name), capability->supported); + } + + // Results should be sorted lexicographically based on the keys. + std::sort( + results.begin(), results.end(), + [](const std::pair<String, bool>& a, const std::pair<String, bool>& b) { + return CodeUnitCompare(a.first, b.first) < 0; + }); + + resolver->Resolve(std::move(results)); +} + } // namespace // static @@ -898,6 +921,45 @@ return promise; } +// static +ScriptPromise<IDLRecord<IDLString, IDLBoolean>> +PaymentRequest::getSecurePaymentConfirmationCapabilities( + ScriptState* script_state) { + auto* resolver = MakeGarbageCollected< + ScriptPromiseResolver<IDLRecord<IDLString, IDLBoolean>>>(script_state); + ScriptPromise promise = resolver->Promise(); + + if (!RuntimeEnabledFeatures::SecurePaymentConfirmationEnabled( + ExecutionContext::From(script_state)) || + !RuntimeEnabledFeatures::SecurePaymentConfirmationCapabilitiesEnabled( + ExecutionContext::From(script_state))) { + return ScriptPromise<IDLRecord<IDLString, IDLBoolean>>:: + RejectWithDOMException(script_state, + MakeGarbageCollected<DOMException>( + DOMExceptionCode::kNotSupportedError, + "The feature is not enabled.")); + } + + if (!ExecutionContext::From(script_state) + ->IsFeatureEnabled( + network::mojom::PermissionsPolicyFeature::kPayment)) { + return ScriptPromise<IDLRecord<IDLString, IDLBoolean>>:: + RejectWithDOMException( + script_state, + MakeGarbageCollected<DOMException>( + DOMExceptionCode::kNotAllowedError, + "The \"payment\" permission policy is not enabled.")); + } + + CredentialManagerProxy::From(script_state) + ->SecurePaymentConfirmationService() + ->GetSecurePaymentConfirmationCapabilities( + BindOnce(&OnGetSecurePaymentConfirmationCapabilitiesComplete, + std::make_unique<ScopedPromiseResolver>(resolver))); + + return promise; +} + PaymentRequest* PaymentRequest::Create( ExecutionContext* execution_context, const HeapVector<Member<PaymentMethodData>>& method_data,
diff --git a/third_party/blink/renderer/modules/payments/payment_request.h b/third_party/blink/renderer/modules/payments/payment_request.h index 100c9e2..361f385f 100644 --- a/third_party/blink/renderer/modules/payments/payment_request.h +++ b/third_party/blink/renderer/modules/payments/payment_request.h
@@ -55,6 +55,9 @@ static ScriptPromise<V8SecurePaymentConfirmationAvailability> securePaymentConfirmationAvailability(ScriptState* script_state); + static ScriptPromise<IDLRecord<IDLString, IDLBoolean>> + getSecurePaymentConfirmationCapabilities(ScriptState* script_state); + static PaymentRequest* Create(ExecutionContext*, const HeapVector<Member<PaymentMethodData>>&, const PaymentDetailsInit*,
diff --git a/third_party/blink/renderer/modules/payments/payment_request.idl b/third_party/blink/renderer/modules/payments/payment_request.idl index c216a559..a4dfc87 100644 --- a/third_party/blink/renderer/modules/payments/payment_request.idl +++ b/third_party/blink/renderer/modules/payments/payment_request.idl
@@ -5,6 +5,9 @@ // https://w3c.github.io/payment-request/#paymentrequest-interface // http://crbug.com/587995 +// https://w3c.github.io/secure-payment-confirmation/#typedefdef-securepaymentconfirmationcapabilities +typedef record<DOMString, boolean> SecurePaymentConfirmationCapabilities; + // https://w3c.github.io/secure-payment-confirmation/#enumdef-securepaymentconfirmationavailability enum SecurePaymentConfirmationAvailability { "available", @@ -14,6 +17,11 @@ "unavailable-no-user-verifying-platform-authenticator", }; +// https://w3c.github.io/secure-payment-confirmation/#enumdef-securepaymentconfirmationcapability +enum SecurePaymentConfirmationCapability { + "browserBoundKeyHardware" +}; + [ RuntimeEnabled=PaymentRequest, SecureContext, @@ -40,4 +48,7 @@ // Note: This API is enabled/exposed on all Blink platforms except WebView // (which does not support Secure Payment Confirmation). [RuntimeEnabled=SecurePaymentConfirmationAvailabilityAPI, CallWith=ScriptState] static Promise<SecurePaymentConfirmationAvailability> securePaymentConfirmationAvailability(); + + // https://w3c.github.io/secure-payment-confirmation/#sctn-secure-payment-confirmation-capabilities + [RuntimeEnabled=SecurePaymentConfirmationCapabilities, CallWith=ScriptState] static Promise<SecurePaymentConfirmationCapabilities> getSecurePaymentConfirmationCapabilities(); };
diff --git a/third_party/blink/renderer/modules/payments/payment_request_secure_payment_confirmation_availability_test.cc b/third_party/blink/renderer/modules/payments/payment_request_secure_payment_confirmation_availability_test.cc index c5a20cf4..1d7abcbb 100644 --- a/third_party/blink/renderer/modules/payments/payment_request_secure_payment_confirmation_availability_test.cc +++ b/third_party/blink/renderer/modules/payments/payment_request_secure_payment_confirmation_availability_test.cc
@@ -44,6 +44,9 @@ std::move(callback).Run(spc_availability_); } + void GetSecurePaymentConfirmationCapabilities( + GetSecurePaymentConfirmationCapabilitiesCallback callback) override {} + void StorePaymentCredential( const Vector<uint8_t>& credential_id, const String& rp_id,
diff --git a/third_party/blink/renderer/modules/payments/payment_request_secure_payment_confirmation_capabilities_test.cc b/third_party/blink/renderer/modules/payments/payment_request_secure_payment_confirmation_capabilities_test.cc new file mode 100644 index 0000000..8894621 --- /dev/null +++ b/third_party/blink/renderer/modules/payments/payment_request_secure_payment_confirmation_capabilities_test.cc
@@ -0,0 +1,196 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <algorithm> +#include <memory> + +#include "services/network/public/cpp/permissions_policy/permissions_policy.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/mojom/payments/secure_payment_confirmation_service.mojom-blink.h" +#include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h" +#include "third_party/blink/renderer/core/frame/local_dom_window.h" +#include "third_party/blink/renderer/core/permissions_policy/permissions_policy_parser.h" +#include "third_party/blink/renderer/modules/payments/payment_request.h" +#include "third_party/blink/renderer/modules/payments/payment_test_helper.h" +#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" +#include "third_party/blink/renderer/platform/testing/task_environment.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +namespace blink { +namespace { + +using payments::mojom::blink::SecurePaymentConfirmationCapability; +using payments::mojom::blink::SecurePaymentConfirmationCapabilityPtr; +using payments::mojom::blink::SecurePaymentConfirmationService; + +class FakeSecurePaymentConfirmationService + : public SecurePaymentConfirmationService { + public: + explicit FakeSecurePaymentConfirmationService( + Vector<SecurePaymentConfirmationCapabilityPtr> spc_capabilities) + : spc_capabilities_(std::move(spc_capabilities)) {} + + FakeSecurePaymentConfirmationService( + const FakeSecurePaymentConfirmationService&) = delete; + FakeSecurePaymentConfirmationService& operator=( + const FakeSecurePaymentConfirmationService&) = delete; + + void Bind(mojo::PendingReceiver<SecurePaymentConfirmationService> receiver) { + receiver_.Bind(std::move(receiver)); + } + + void SecurePaymentConfirmationAvailability( + SecurePaymentConfirmationAvailabilityCallback callback) override {} + + void GetSecurePaymentConfirmationCapabilities( + GetSecurePaymentConfirmationCapabilitiesCallback callback) override { + std::move(callback).Run(std::move(spc_capabilities_)); + } + + void StorePaymentCredential( + const Vector<uint8_t>& credential_id, + const String& rp_id, + const Vector<uint8_t>& user_id, + StorePaymentCredentialCallback callback) override {} + + void MakePaymentCredential( + ::blink::mojom::blink::PublicKeyCredentialCreationOptionsPtr options, + MakePaymentCredentialCallback callback) override {} + + private: + mojo::Receiver<SecurePaymentConfirmationService> receiver_{this}; + Vector<SecurePaymentConfirmationCapabilityPtr> spc_capabilities_; +}; + +// A RAII class that creates and installs a mocked +// SecurePaymentConfirmationService on allocation, and uninstalls it on +// deletion. +class ScopedFakeSecurePaymentConfirmationService { + STACK_ALLOCATED(); + + public: + explicit ScopedFakeSecurePaymentConfirmationService( + PaymentRequestV8TestingScope* scope, + Vector<SecurePaymentConfirmationCapabilityPtr> spc_capabilities = {}) + : scope_(scope) { + mock_service_ = std::make_unique<FakeSecurePaymentConfirmationService>( + std::move(spc_capabilities)); + scope_->GetWindow().GetBrowserInterfaceBroker().SetBinderForTesting( + payments::mojom::blink::SecurePaymentConfirmationService::Name_, + BindRepeating( + [](FakeSecurePaymentConfirmationService* mock_service_ptr, + mojo::ScopedMessagePipeHandle handle) { + mock_service_ptr->Bind( + mojo::PendingReceiver< + payments::mojom::blink::SecurePaymentConfirmationService>( + std::move(handle))); + }, + Unretained(mock_service_.get()))); + } + + ~ScopedFakeSecurePaymentConfirmationService() { + scope_->GetWindow().GetBrowserInterfaceBroker().SetBinderForTesting( + payments::mojom::blink::SecurePaymentConfirmationService::Name_, {}); + } + + private: + raw_ptr<PaymentRequestV8TestingScope> scope_; + std::unique_ptr<FakeSecurePaymentConfirmationService> mock_service_; +}; + +TEST(PaymentRequestTest, SecurePaymentConfirmationCapabilities) { + ScopedSecurePaymentConfirmationForTest scoped_spc(true); + ScopedSecurePaymentConfirmationCapabilitiesForTest scoped_spc_capabilities( + true); + + test::TaskEnvironment task_environment; + PaymentRequestV8TestingScope scope; + // Capabilities should be out of alphabetical order to ensure that they get + // sorted by the `OnGetSecurePaymentConfirmationCapabilitiesComplete` method. + Vector<SecurePaymentConfirmationCapabilityPtr> spc_capabilities; + spc_capabilities.emplace_back( + SecurePaymentConfirmationCapability::New("capability_b", true)); + spc_capabilities.emplace_back( + SecurePaymentConfirmationCapability::New("capability_a", false)); + spc_capabilities.emplace_back( + SecurePaymentConfirmationCapability::New("capability_c", false)); + ScopedFakeSecurePaymentConfirmationService scoped_mock_service( + &scope, std::move(spc_capabilities)); + + ScriptPromise<IDLRecord<IDLString, IDLBoolean>> promise = + PaymentRequest::getSecurePaymentConfirmationCapabilities( + scope.GetScriptState()); + ScriptPromiseTester tester(scope.GetScriptState(), promise); + tester.WaitUntilSettled(); + + const auto& got_spc_capabilities = + NativeValueTraits<IDLRecord<IDLString, IDLBoolean>>::NativeValue( + scope.GetIsolate(), tester.Value().V8Value(), + scope.GetExceptionState()); + ASSERT_FALSE(scope.GetExceptionState().HadException()); + + // SPC capabilities should be sorted by name. + Vector<std::pair<String, bool>> want_spc_capabilities = { + {"capability_a", false}, {"capability_b", true}, {"capability_c", false}}; + EXPECT_EQ(got_spc_capabilities, want_spc_capabilities); +} + +TEST(PaymentRequestTest, + SecurePaymentConfirmationCapabilities_FeatureDisabled) { + ScopedSecurePaymentConfirmationForTest scoped_spc(true); + ScopedSecurePaymentConfirmationCapabilitiesForTest scoped_spc_capabilities( + false); + + test::TaskEnvironment task_environment; + PaymentRequestV8TestingScope scope; + ScopedFakeSecurePaymentConfirmationService scoped_mock_service(&scope); + + ScriptPromise<IDLRecord<IDLString, IDLBoolean>> promise = + PaymentRequest::getSecurePaymentConfirmationCapabilities( + scope.GetScriptState()); + ScriptPromiseTester tester(scope.GetScriptState(), promise); + tester.WaitUntilSettled(); + + EXPECT_TRUE(tester.IsRejected()); + auto* exception = V8DOMException::ToWrappable( + scope.GetScriptState()->GetIsolate(), tester.Value().V8Value()); + ASSERT_TRUE(exception); + EXPECT_EQ(exception->name(), "NotSupportedError"); +} + +TEST(PaymentRequestTest, + SecurePaymentConfirmationCapabilities_PaymentPermissionsPolicyDisabled) { + ScopedSecurePaymentConfirmationForTest scoped_spc(true); + ScopedSecurePaymentConfirmationCapabilitiesForTest scoped_spc_capabilities( + true); + + test::TaskEnvironment task_environment; + PaymentRequestV8TestingScope scope; + + network::ParsedPermissionsPolicy parsed_policy; + DisallowFeature(network::mojom::PermissionsPolicyFeature::kPayment, + parsed_policy); + auto origin = SecurityOrigin::CreateFromString("https://example.test"); + scope.GetExecutionContext()->GetSecurityContext().SetPermissionsPolicy( + network::PermissionsPolicy::CreateFromParsedPolicy( + parsed_policy, origin->ToUrlOrigin())); + + ScopedFakeSecurePaymentConfirmationService scoped_mock_service(&scope); + + ScriptPromise<IDLRecord<IDLString, IDLBoolean>> promise = + PaymentRequest::getSecurePaymentConfirmationCapabilities( + scope.GetScriptState()); + ScriptPromiseTester tester(scope.GetScriptState(), promise); + tester.WaitUntilSettled(); + + EXPECT_TRUE(tester.IsRejected()); + auto* exception = V8DOMException::ToWrappable( + scope.GetScriptState()->GetIsolate(), tester.Value().V8Value()); + ASSERT_TRUE(exception); + EXPECT_EQ(exception->name(), "NotAllowedError"); +} + +} // namespace +} // namespace blink
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 index 9751ef05..64175d64 100644 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -4859,9 +4859,11 @@ { name: "SecurePaymentConfirmationCapabilities", status: { - "Android": "test", - "Mac": "test", - "Win": "test", + "Android": "experimental", + "Mac": "experimental", + "Win": "experimental", + "iOS": "", + "default": "test", }, }, {
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations index 4a6b29f..fcfd498 100644 --- a/third_party/blink/web_tests/TestExpectations +++ b/third_party/blink/web_tests/TestExpectations
@@ -2796,6 +2796,8 @@ crbug.com/476344902 virtual/webui-browser/external/wpt/html/browsers/windows/nested-browsing-contexts/window-top.html [ Failure ] # ====== New tests from wpt-importer added here ====== +crbug.com/489022940 external/wpt/css/css-viewport/zoom/scroll-padding.html [ Failure ] +crbug.com/489043704 [ Linux ] external/wpt/wasm/core/gc/array_init_elem.wast.js.html [ Crash ] crbug.com/488651384 external/wpt/css/css-viewport/zoom/border-radius.html [ Failure ] crbug.com/488651384 external/wpt/css/css-viewport/zoom/scroll-margin.html [ Failure ] crbug.com/488651384 [ Win ] external/wpt/css/css-viewport/zoom/svg-computed-style.html [ Pass Timeout ]
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 9e6257e9..0f895ca 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
@@ -196264,6 +196264,19 @@ {} ] ], + "text-overflow-ellipsis-editable-div-with-caret.html": [ + "ecae2dcca11571cf516ed0e6267ad51b3aaae24c", + [ + null, + [ + [ + "/css/css-overflow/reference/text-overflow-ellipsis-editable-div-with-caret-ref.html", + "==" + ] + ], + {} + ] + ], "text-overflow-ellipsis-editing-input.html": [ "b235ca503a80c42d2083ba78195d9b28e1307c22", [ @@ -196290,6 +196303,19 @@ {} ] ], + "text-overflow-ellipsis-textarea-with-caret.html": [ + "ae0dd0521946bc8414744be419c6a8b5a711342c", + [ + null, + [ + [ + "/css/css-overflow/reference/text-overflow-ellipsis-textarea-with-caret-ref.html", + "==" + ] + ], + {} + ] + ], "text-overflow-ellipsis-vertical-001.html": [ "58c8f57dcf5ce328c42bc5552c608d83302af8d6", [ @@ -212199,6 +212225,58 @@ {} ] ], + "abspos-auto-sizing-fit-content-percentage-005.html": [ + "4c82ebe1de7a394a15a4cde750f0b813930fe67b", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square-only.html", + "==" + ] + ], + {} + ] + ], + "abspos-auto-sizing-fit-content-percentage-006.html": [ + "ca53769e2b93f2bff57563981277f90221d22a22", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square-only.html", + "==" + ] + ], + {} + ] + ], + "abspos-auto-sizing-fit-content-percentage-007.html": [ + "726c304980f256399782d956524bd777107896d4", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square-only.html", + "==" + ] + ], + {} + ] + ], + "abspos-auto-sizing-fit-content-percentage-008.html": [ + "2faf802e1a3e17be36bbf508de90cea32368aabd", + [ + null, + [ + [ + "/css/reference/ref-filled-green-100px-square-only.html", + "==" + ] + ], + {} + ] + ], "aspect-ratio": { "abspos-001.html": [ "e18a0a7c81dbef1b2526793bac31ab6640b33dd7", @@ -281922,6 +282000,19 @@ {} ] ], + "scroll-padding.html": [ + "90a5fafa65a3c98491509c238d61e6cf5c4d4227", + [ + null, + [ + [ + "/css/css-viewport/zoom/reference/scroll-padding-ref.html", + "==" + ] + ], + {} + ] + ], "stroke.html": [ "5af4eec10cce386eb5afce8d89ee4efb5a71cc85", [ @@ -389114,10 +389205,18 @@ "cf26b6b8c848521940936165002eb6aab46c068d", [] ], + "text-overflow-ellipsis-editable-div-with-caret-ref.html": [ + "1c728466535765f7ad53e0187a66cb0ff34cf166", + [] + ], "text-overflow-ellipsis-rtl-001-ref.html": [ "5c75e38d8308ac81dcfe5eef7e18fd7ad56d6b3b", [] ], + "text-overflow-ellipsis-textarea-with-caret-ref.html": [ + "1cc9c8df6ac94a9318845cb4668f62f900783a58", + [] + ], "text-overflow-ellipsis-vertical-001-ref.html": [ "18f8a1beaefc9ae6109d62555f1c5c61dccf4105", [] @@ -406749,6 +406848,10 @@ "d061c49165a111087225825fd8875db22de3990e", [] ], + "scroll-padding-ref.html": [ + "51a0ffb2ed15a82aca6866dbfada078019251a2f", + [] + ], "stroke-ref.html": [ "75742b1dc34e522a202bdedf7e4e66d472edc922", [] @@ -406809,7 +406912,7 @@ ] }, "svg-computed-style-expected.txt": [ - "884a97ffb45f614224ccf633d6c3c9f299d09032", + "80eabe24bee6421b99f6ae0010c5b0029e14f4dc", [] ], "svg-font-relative-units-ref.html": [ @@ -415137,150 +415240,6 @@ "97ecedbacb431d164ff4cfcce018bfac184c0588", [] ], - "join-different-white-space-style-paragraphs_method=backspace_left-white-space=normal_right-white-space=nowrap-expected.txt": [ - "e4381f3e3b134e16f0586cd6ad9e2a49b51d1888", - [] - ], - "join-different-white-space-style-paragraphs_method=backspace_left-white-space=normal_right-white-space=pre-line-expected.txt": [ - "3eea13eed2b4233c33e03147e94a00a00bf4a49a", - [] - ], - "join-different-white-space-style-paragraphs_method=backspace_left-white-space=normal_right-white-space=pre-wrap-expected.txt": [ - "67ba870cfcb51c2a129b3fac3a98fd96b0129cc7", - [] - ], - "join-different-white-space-style-paragraphs_method=backspace_left-white-space=nowrap_right-white-space=normal-expected.txt": [ - "3002d7ff0ee55c738613e012c8e2c2c19545fd78", - [] - ], - "join-different-white-space-style-paragraphs_method=backspace_left-white-space=nowrap_right-white-space=pre-expected.txt": [ - "54f7c088cea61627fe64ad986061f7d49d09b2a9", - [] - ], - "join-different-white-space-style-paragraphs_method=backspace_left-white-space=pre-line_right-white-space=normal-expected.txt": [ - "25eb53402838181cd316ac2387cafbe6fb8c92ae", - [] - ], - "join-different-white-space-style-paragraphs_method=backspace_left-white-space=pre-line_right-white-space=pre-wrap-expected.txt": [ - "4e693534e6045be83e95376ac1723ef5e55c49e1", - [] - ], - "join-different-white-space-style-paragraphs_method=backspace_left-white-space=pre-wrap_right-white-space=normal-expected.txt": [ - "e76c14662b7c70973299a94ea4ebdbd4afb79dce", - [] - ], - "join-different-white-space-style-paragraphs_method=backspace_left-white-space=pre-wrap_right-white-space=pre-expected.txt": [ - "cce03889b375872e116ab1ae8884a1a05ebcb2df", - [] - ], - "join-different-white-space-style-paragraphs_method=backspace_left-white-space=pre-wrap_right-white-space=pre-line-expected.txt": [ - "00bf30f5ce962708b6a714ecd6b13853803cebaf", - [] - ], - "join-different-white-space-style-paragraphs_method=backspace_left-white-space=pre_right-white-space=nowrap-expected.txt": [ - "5510240dd2b0c2011b8e3379d08b07a9d84af9f6", - [] - ], - "join-different-white-space-style-paragraphs_method=backspace_left-white-space=pre_right-white-space=pre-wrap-expected.txt": [ - "66e0eb138e4c7fd1b525010e1d23399969a2d63c", - [] - ], - "join-different-white-space-style-paragraphs_method=forwarddelete_left-white-space=normal_right-white-space=nowrap-expected.txt": [ - "ce8f9ec83c5215336592681b9ddeb5239dbee34b", - [] - ], - "join-different-white-space-style-paragraphs_method=forwarddelete_left-white-space=normal_right-white-space=pre-line-expected.txt": [ - "8aa6623bbc9e37e2cd753c8dfd117b55cf512b3f", - [] - ], - "join-different-white-space-style-paragraphs_method=forwarddelete_left-white-space=normal_right-white-space=pre-wrap-expected.txt": [ - "50b40d585c9239d5fa400d78fcb6372c46e12f50", - [] - ], - "join-different-white-space-style-paragraphs_method=forwarddelete_left-white-space=nowrap_right-white-space=normal-expected.txt": [ - "634a2ba4bdc152acc93e34e172005fc26a36d920", - [] - ], - "join-different-white-space-style-paragraphs_method=forwarddelete_left-white-space=nowrap_right-white-space=pre-expected.txt": [ - "6f15d1d82d9d659f6064c62e097798bd6c61854e", - [] - ], - "join-different-white-space-style-paragraphs_method=forwarddelete_left-white-space=pre-line_right-white-space=normal-expected.txt": [ - "473947d357d232df58b2504d62c67e117000ca7a", - [] - ], - "join-different-white-space-style-paragraphs_method=forwarddelete_left-white-space=pre-line_right-white-space=pre-wrap-expected.txt": [ - "7ce4daca3665ca0430ad7d6e417d3673d41a0dcc", - [] - ], - "join-different-white-space-style-paragraphs_method=forwarddelete_left-white-space=pre-wrap_right-white-space=normal-expected.txt": [ - "9ddfb481f4567fb6e560c9ef0d51aa029950981b", - [] - ], - "join-different-white-space-style-paragraphs_method=forwarddelete_left-white-space=pre-wrap_right-white-space=pre-expected.txt": [ - "8015d83e92dd11e6c1b11e00c2bada638ea6f7b8", - [] - ], - "join-different-white-space-style-paragraphs_method=forwarddelete_left-white-space=pre-wrap_right-white-space=pre-line-expected.txt": [ - "92a05727a7b5052b2d77fb49f67c2bc13f144c38", - [] - ], - "join-different-white-space-style-paragraphs_method=forwarddelete_left-white-space=pre_right-white-space=nowrap-expected.txt": [ - "f3e8d8648b3cd08ffbd401490085b6de395c4f78", - [] - ], - "join-different-white-space-style-paragraphs_method=forwarddelete_left-white-space=pre_right-white-space=pre-wrap-expected.txt": [ - "6f0dc6c68ea9b0071e34d994063ec73c6ca82499", - [] - ], - "join-different-white-space-style-paragraphs_method=select-boundary_left-white-space=normal_right-white-space=nowrap-expected.txt": [ - "fe6e6aefc9955ba82164f7c599fe6a62d3eff4ef", - [] - ], - "join-different-white-space-style-paragraphs_method=select-boundary_left-white-space=normal_right-white-space=pre-line-expected.txt": [ - "34ec1cf911ae4743057d74c0c3d0e8b73e7d18ee", - [] - ], - "join-different-white-space-style-paragraphs_method=select-boundary_left-white-space=normal_right-white-space=pre-wrap-expected.txt": [ - "ecfe001673a27a33b9dac8dbd3871bf27459827d", - [] - ], - "join-different-white-space-style-paragraphs_method=select-boundary_left-white-space=nowrap_right-white-space=normal-expected.txt": [ - "c4e077bac71592fbe6961c09e7aa02b611b077b6", - [] - ], - "join-different-white-space-style-paragraphs_method=select-boundary_left-white-space=nowrap_right-white-space=pre-expected.txt": [ - "3a72941b7a551205be63018eb6ad3b2be62940c7", - [] - ], - "join-different-white-space-style-paragraphs_method=select-boundary_left-white-space=pre-line_right-white-space=normal-expected.txt": [ - "e93598c8a5e76909a493bb6f84acf879a6681f39", - [] - ], - "join-different-white-space-style-paragraphs_method=select-boundary_left-white-space=pre-line_right-white-space=pre-wrap-expected.txt": [ - "1a97fc8584fdc33b6b178b9f69107a5803753dc4", - [] - ], - "join-different-white-space-style-paragraphs_method=select-boundary_left-white-space=pre-wrap_right-white-space=normal-expected.txt": [ - "9e1070447126a6e50b2989751f2ecddf226ff942", - [] - ], - "join-different-white-space-style-paragraphs_method=select-boundary_left-white-space=pre-wrap_right-white-space=pre-expected.txt": [ - "9a5ddd8aad5a7c9e9200ba666ca661db36af0d22", - [] - ], - "join-different-white-space-style-paragraphs_method=select-boundary_left-white-space=pre-wrap_right-white-space=pre-line-expected.txt": [ - "47e697dca05be3d849f7c78cce56ece06579c789", - [] - ], - "join-different-white-space-style-paragraphs_method=select-boundary_left-white-space=pre_right-white-space=nowrap-expected.txt": [ - "131bc3c34a9308d7a066d3f414e0334e1e56f4ba", - [] - ], - "join-different-white-space-style-paragraphs_method=select-boundary_left-white-space=pre_right-white-space=pre-wrap-expected.txt": [ - "b0fc4242f307e0cf84f93b6b9b2f8041b4a5c428", - [] - ], "link-boundaries-insertion-expected.txt": [ "d4eff9d116410742b7a62e406decbc75c8ffe492", [] @@ -415396,7 +415355,7 @@ [] ], "paste-when-nested-with-contenteditable-true.https-expected.txt": [ - "bc50cb74eb1313fdddfa9e9d94cc31521fbade7e", + "28e41bf69a88723b7e9b414f75ac6676669ec7b2", [] ], "plaintext-only-in-designMode-expected.txt": [ @@ -463368,6 +463327,10 @@ "ac5159143dbeaf1eec81d63127b215e6dd092309", [] ], + "SVGList-parse-invalid-clears-items-expected.txt": [ + "ee5224a6044b484a497fb0b430e7a02ffda478ff", + [] + ], "SVGMatrix-tentative-expected.txt": [ "cecf734d7dee6cf55d05cf568aabc022ab6d6c17", [] @@ -465310,7 +465273,7 @@ [] ], "try_table.wast.js": [ - "e2cdb5c102b36acce7e0669ac91c827b14370201", + "08a7015774bcc35c280a75d2646683e6f14c4277", [] ] }, @@ -465392,7 +465355,7 @@ [] ], "array_init_elem.wast.js": [ - "b39588ed2d4897e1fdd3a6d7c8370d3d1f24fc24", + "a2002f39d0160de5ce453ea8d7890221ccf0fdcf", [] ], "array_new_data.wast.js": [ @@ -546347,7 +546310,23 @@ null, {} ] - ] + ], + "parsing": { + "checkmark-pseudo-element.html": [ + "a89daf4f44d4a55efd912bf5853dbb4468d09b5f", + [ + null, + {} + ] + ], + "picker-icon-pseudo-element.html": [ + "9f95926e3b960d715dc5c0739fc6617015f92270", + [ + null, + {} + ] + ] + } }, "css-gaps": { "animation": { @@ -557917,20 +557896,6 @@ {} ] ], - "the-checkmark-pseudo-element.tentative.html": [ - "f50794a5128020361ccc4de5ff15731240e05844", - [ - null, - {} - ] - ], - "the-picker-icon-pseudo-element.tentative.html": [ - "79a5e10e93bc0097c175d4dc3f3f5c573700c2c0", - [ - null, - {} - ] - ], "tree-abiding-pseudo-elements.html": [ "7839a38049a190135149c0d3d3b35fbc2d29d207", [ @@ -574869,7 +574834,7 @@ ] ], "svg-computed-style.html": [ - "bc2b2915aaa63d9fe567d7f134f3c781adeae98b", + "7410ffa1a4bd4cbe512aae635c06089ba3f1756f", [ null, {} @@ -783353,6 +783318,13 @@ {} ] ], + "SVGList-parse-invalid-clears-items.html": [ + "a5b0d8d85070229137258cde55e47407c41cc937", + [ + null, + {} + ] + ], "SVGMatrix-tentative.html": [ "8b3382d6032ffca7a11f87b546465b11c3cb3132", [ @@ -783630,6 +783602,15 @@ } ] ], + "multi-touch-touchmove.html": [ + "0b7a055be9abf260055cc487e3f94bdc8da0c3e5", + [ + null, + { + "testdriver": true + } + ] + ], "pinch-zoom-change.html": [ "406dd315dc7118a14b10271e643360d05706dd17", [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-borders/tentative/border-shape/border-shape-overflow-replaced-canvas.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-borders/tentative/border-shape/border-shape-overflow-replaced-canvas.tentative.html new file mode 100644 index 0000000..6e91910 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-borders/tentative/border-shape/border-shape-overflow-replaced-canvas.tentative.html
@@ -0,0 +1,33 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Borders Test: border-shape overflow clips canvas child</title> +<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape"> +<link rel="match" href="border-shape-overflow-replaced-ref.html"> +<meta name="assert" content="When border-shape provides a single shape with overflow:hidden, a canvas child should be clipped to the inner edge of the border."> +<meta name="fuzzy" content="maxDifference=0-70;totalPixels=0-400"> +<style> +body { + margin: 0; + padding: 20px; +} +.target { + width: 100px; + height: 100px; + border: 10px solid black; + border-shape: circle(50%); + overflow: hidden; +} +.target > canvas { + display: block; + width: 100%; + height: 100%; +} +</style> +<div class="target"> + <canvas id="c" width="100" height="100"></canvas> +</div> +<script> +const ctx = document.getElementById('c').getContext('2d'); +ctx.fillStyle = 'lime'; +ctx.fillRect(0, 0, 100, 100); +</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-borders/tentative/border-shape/border-shape-overflow-replaced-iframe.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-borders/tentative/border-shape/border-shape-overflow-replaced-iframe.tentative.html new file mode 100644 index 0000000..198ce40 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-borders/tentative/border-shape/border-shape-overflow-replaced-iframe.tentative.html
@@ -0,0 +1,30 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Borders Test: border-shape overflow clips iframe child</title> +<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape"> +<link rel="match" href="border-shape-overflow-replaced-ref.html"> +<meta name="assert" content="When border-shape provides a single shape with overflow:hidden, an iframe child should be clipped to the inner edge of the border."> +<meta name="fuzzy" content="maxDifference=0-110;totalPixels=0-400"> +<style> +body { + margin: 0; + padding: 20px; +} +.target { + width: 100px; + height: 100px; + border: 10px solid black; + border-shape: circle(50%); + overflow: hidden; +} +.target > iframe { + display: block; + width: 100%; + height: 100%; + border: none; +} +</style> +<div class="target"> + <iframe srcdoc="<style>html,body{margin:0;width:100%;height:100%;background:lime}</style>" + scrolling="no"></iframe> +</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-borders/tentative/border-shape/border-shape-overflow-replaced-img.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-borders/tentative/border-shape/border-shape-overflow-replaced-img.tentative.html new file mode 100644 index 0000000..9cc7f9d --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-borders/tentative/border-shape/border-shape-overflow-replaced-img.tentative.html
@@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Borders Test: border-shape overflow clips img child</title> +<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape"> +<link rel="match" href="border-shape-overflow-replaced-ref.html"> +<meta name="assert" content="When border-shape provides a single shape with overflow:hidden, an img child should be clipped to the inner edge of the border."> +<meta name="fuzzy" content="maxDifference=0-70;totalPixels=0-400"> +<style> +body { + margin: 0; + padding: 20px; +} +.target { + width: 100px; + height: 100px; + border: 10px solid black; + border-shape: circle(50%); + overflow: hidden; +} +.target > img { + display: block; + width: 100%; + height: 100%; +} +</style> +<div class="target"> + <img src="/images/green-256x256.png" alt=""> +</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-borders/tentative/border-shape/border-shape-overflow-replaced-ref.html b/third_party/blink/web_tests/external/wpt/css/css-borders/tentative/border-shape/border-shape-overflow-replaced-ref.html new file mode 100644 index 0000000..b3094d6 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-borders/tentative/border-shape/border-shape-overflow-replaced-ref.html
@@ -0,0 +1,18 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Borders Test: border-shape overflow clipping of replaced elements reference</title> +<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape"> +<style> +body { + margin: 0; + padding: 20px; +} +.target { + width: 100px; + height: 100px; + border: 10px solid black; + border-shape: circle(50%); + background: lime; +} +</style> +<div class="target"></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-borders/tentative/border-shape/border-shape-overflow-replaced-self-ref.html b/third_party/blink/web_tests/external/wpt/css/css-borders/tentative/border-shape/border-shape-overflow-replaced-self-ref.html new file mode 100644 index 0000000..e45cdc8 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-borders/tentative/border-shape/border-shape-overflow-replaced-self-ref.html
@@ -0,0 +1,23 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Borders Test: border-shape on replaced element reference</title> +<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape"> +<style> +body { + margin: 0; + padding: 20px; +} +.outer { + width: 100px; + height: 100px; + border: 10px solid black; + border-shape: circle(50%); + overflow: hidden; +} +.inner { + width: 100px; + height: 100px; + background: lime; +} +</style> +<div class="outer"><div class="inner"></div></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-borders/tentative/border-shape/border-shape-overflow-replaced-self.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-borders/tentative/border-shape/border-shape-overflow-replaced-self.tentative.html new file mode 100644 index 0000000..8fb0d63 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-borders/tentative/border-shape/border-shape-overflow-replaced-self.tentative.html
@@ -0,0 +1,22 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Borders Test: border-shape clips replaced content when applied directly with overflow hidden</title> +<link rel="help" href="https://drafts.csswg.org/css-borders-4/#border-shape"> +<link rel="match" href="border-shape-overflow-replaced-self-ref.html"> +<meta name="assert" content="When border-shape is applied directly to a replaced element with overflow:hidden, the content should be clipped to the border-shape."> +<meta name="fuzzy" content="maxDifference=0-70;totalPixels=0-400"> +<style> +body { + margin: 0; + padding: 20px; +} +img { + display: block; + width: 100px; + height: 100px; + border: 10px solid black; + border-shape: circle(50%); + overflow: hidden; +} +</style> +<img src="/images/green-256x256.png" alt="">
diff --git a/third_party/blink/web_tests/external/wpt/css/css-color/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-color/WEB_FEATURES.yml index 1a7a8ae..9b2999c2 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-color/WEB_FEATURES.yml +++ b/third_party/blink/web_tests/external/wpt/css/css-color/WEB_FEATURES.yml
@@ -47,7 +47,7 @@ - "system-color-*" - name: contrast-color files: - - contrast-color-001.html + - contrast-color-*.html - name: hsl files: - background-color-hsl-*
diff --git a/third_party/blink/web_tests/external/wpt/css/css-color/parsing/WEB_FEATURES.yml b/third_party/blink/web_tests/external/wpt/css/css-color/parsing/WEB_FEATURES.yml index fa5d933..2ed0d9b9 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-color/parsing/WEB_FEATURES.yml +++ b/third_party/blink/web_tests/external/wpt/css/css-color/parsing/WEB_FEATURES.yml
@@ -12,7 +12,7 @@ - color-valid-system-color.html - name: contrast-color files: - - color-*-contrast-color-function.html + - "*contrast-color*.html" - name: hsl files: - color-computed-hsl.html
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/the-checkmark-pseudo-element.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-forms/parsing/checkmark-pseudo-element.html similarity index 94% rename from third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/the-checkmark-pseudo-element.tentative.html rename to third_party/blink/web_tests/external/wpt/css/css-forms/parsing/checkmark-pseudo-element.html index f50794a..a89daf4 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/the-checkmark-pseudo-element.tentative.html +++ b/third_party/blink/web_tests/external/wpt/css/css-forms/parsing/checkmark-pseudo-element.html
@@ -1,8 +1,8 @@ <!DOCTYPE html> <meta charset="utf-8"> <title>CSS ::checkmark Pseudo-Element Test</title> -<link rel="help" href="https://drafts.csswg.org/css-forms/#checkmark-element"> -<meta name="assert" content="This test checks the validity of the ::checkmark pseudo element selector." /> +<link rel="help" href="https://drafts.csswg.org/css-forms/#checkmark"> +<meta name="assert" content="This test checks the validity of the ::checkmark pseudo element selector."> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="/css/support/parsing-testcommon.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/the-picker-icon-pseudo-element.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-forms/parsing/picker-icon-pseudo-element.html similarity index 93% rename from third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/the-picker-icon-pseudo-element.tentative.html rename to third_party/blink/web_tests/external/wpt/css/css-forms/parsing/picker-icon-pseudo-element.html index 79a5e10..9f95926 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-pseudo/parsing/the-picker-icon-pseudo-element.tentative.html +++ b/third_party/blink/web_tests/external/wpt/css/css-forms/parsing/picker-icon-pseudo-element.html
@@ -1,8 +1,8 @@ <!DOCTYPE html> <meta charset="utf-8"> <title>CSS ::picker-icon Pseudo-Element Test</title> -<link rel="help" href="https://drafts.csswg.org/css-forms/#picker-icon-pseudo-element"> -<meta name="assert" content="This test checks the validity of the ::picker-icon pseudo element selector." /> +<link rel="help" href="https://drafts.csswg.org/css-forms/#picker-icon"> +<meta name="assert" content="This test checks the validity of the ::picker-icon pseudo element selector."> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="/css/support/parsing-testcommon.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/abspos-auto-sizing-fit-content-percentage-005.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/abspos-auto-sizing-fit-content-percentage-005.html new file mode 100644 index 0000000..4c82ebe --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/abspos-auto-sizing-fit-content-percentage-005.html
@@ -0,0 +1,55 @@ +<!DOCTYPE html> +<link rel="author" title="Brandon Stewart" href="mailto:brandonstewart@apple.com"> +<link rel="help" href="https://drafts.csswg.org/css-position-3/#abspos-auto-size"> +<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#valdef-width-fit-content"> +<link rel="match" href="../reference/ref-filled-green-100px-square-only.html"> +<meta name="assert" content="Absolutely positioned block with height: fit-content should treat percentage height children as indefinite, sizing to content rather than containing block height."> +<style> +/* + The containing block (.container) is 50px tall. The abs-pos element has + height: fit-content. Its child has height: 100%, which should resolve as + indefinite (auto) since the parent has an intrinsic height. The child then + sizes to its content (100px), making the abs-pos element 100px tall. + + If the bug exists, height: 100% resolves against the containing block (50px), + making the abs-pos element only 50px, exposing 50px of red below. +*/ +.container { + position: relative; + width: 100px; + height: 50px; +} + +.background { + width: 100px; + height: 100px; + background: red; +} + +.abs { + position: absolute; + top: 0; + left: 0; + height: fit-content; + width: 100px; + background: green; +} + +.child { + height: 100%; +} + +.content { + height: 100px; +} +</style> +<p>Test passes if there is a filled green square.</p> +<div class="background"> + <div class="container"> + <div class="abs"> + <div class="child"> + <div class="content"></div> + </div> + </div> + </div> +</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/abspos-auto-sizing-fit-content-percentage-006.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/abspos-auto-sizing-fit-content-percentage-006.html new file mode 100644 index 0000000..ca53769 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/abspos-auto-sizing-fit-content-percentage-006.html
@@ -0,0 +1,48 @@ +<!DOCTYPE html> +<link rel="author" title="Brandon Stewart" href="mailto:brandonstewart@apple.com"> +<link rel="help" href="https://drafts.csswg.org/css-position-3/#abspos-auto-size"> +<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#valdef-width-fit-content"> +<link rel="match" href="../reference/ref-filled-green-100px-square-only.html"> +<meta name="assert" content="Absolutely positioned flex container with height: fit-content should treat percentage height flex items as indefinite, sizing to content rather than containing block height."> +<style> +.container { + position: relative; + width: 100px; + height: 50px; +} + +.background { + width: 100px; + height: 100px; + background: red; +} + +.abs { + position: absolute; + top: 0; + left: 0; + display: flex; + height: fit-content; + width: 100px; + background: green; +} + +.child { + height: 100%; +} + +.content { + height: 100px; + width: 100px; +} +</style> +<p>Test passes if there is a filled green square.</p> +<div class="background"> + <div class="container"> + <div class="abs"> + <div class="child"> + <div class="content"></div> + </div> + </div> + </div> +</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/abspos-auto-sizing-fit-content-percentage-007.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/abspos-auto-sizing-fit-content-percentage-007.html new file mode 100644 index 0000000..726c304 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/abspos-auto-sizing-fit-content-percentage-007.html
@@ -0,0 +1,46 @@ +<!DOCTYPE html> +<link rel="author" title="Brandon Stewart" href="mailto:brandonstewart@apple.com"> +<link rel="help" href="https://drafts.csswg.org/css-position-3/#abspos-auto-size"> +<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#valdef-width-min-content"> +<link rel="match" href="../reference/ref-filled-green-100px-square-only.html"> +<meta name="assert" content="Absolutely positioned block with height: min-content should treat percentage height children as indefinite, sizing to content rather than containing block height."> +<style> +.container { + position: relative; + width: 100px; + height: 50px; +} + +.background { + width: 100px; + height: 100px; + background: red; +} + +.abs { + position: absolute; + top: 0; + left: 0; + height: min-content; + width: 100px; + background: green; +} + +.child { + height: 100%; +} + +.content { + height: 100px; +} +</style> +<p>Test passes if there is a filled green square.</p> +<div class="background"> + <div class="container"> + <div class="abs"> + <div class="child"> + <div class="content"></div> + </div> + </div> + </div> +</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/abspos-auto-sizing-fit-content-percentage-008.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/abspos-auto-sizing-fit-content-percentage-008.html new file mode 100644 index 0000000..2faf802 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/abspos-auto-sizing-fit-content-percentage-008.html
@@ -0,0 +1,46 @@ +<!DOCTYPE html> +<link rel="author" title="Brandon Stewart" href="mailto:brandonstewart@apple.com"> +<link rel="help" href="https://drafts.csswg.org/css-position-3/#abspos-auto-size"> +<link rel="help" href="https://drafts.csswg.org/css-sizing-3/#valdef-width-max-content"> +<link rel="match" href="../reference/ref-filled-green-100px-square-only.html"> +<meta name="assert" content="Absolutely positioned block with height: max-content should treat percentage height children as indefinite, sizing to content rather than containing block height."> +<style> +.container { + position: relative; + width: 100px; + height: 50px; +} + +.background { + width: 100px; + height: 100px; + background: red; +} + +.abs { + position: absolute; + top: 0; + left: 0; + height: max-content; + width: 100px; + background: green; +} + +.child { + height: 100%; +} + +.content { + height: 100px; +} +</style> +<p>Test passes if there is a filled green square.</p> +<div class="background"> + <div class="container"> + <div class="abs"> + <div class="child"> + <div class="content"></div> + </div> + </div> + </div> +</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/reference/scroll-padding-ref.html b/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/reference/scroll-padding-ref.html new file mode 100644 index 0000000..51a0ffb --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/reference/scroll-padding-ref.html
@@ -0,0 +1,86 @@ +<!DOCTYPE html> +<html> +<head> +<style> +#container { + display: inline-block; + width: 200px; + height: 100px; + border: solid black 1px; + overflow-x: hidden; + overflow-y: hidden; + scroll-snap-type: y mandatory; +} + +#container-zoomed { + display: inline-block; + width: 400px; + height: 200px; + border: solid black 2px; + overflow-x: hidden; + overflow-y: hidden; + scroll-snap-type: y mandatory; +} + +.buffer { + scroll-snap-align: start; + background-color: lightblue; + height: 1000px; + width: 200px; +} + +.buffer-zoomed { + scroll-snap-align: start; + background-color: lightblue; + height: 2000px; + width: 400px; +} + +.target { + scroll-snap-align: start; + background-color: black; + height: 20px; + width: 200px; +} + +.target-zoomed { + scroll-snap-align: start; + background-color: black; + height: 40px; + width: 400px; +} +</style> +</head> +<body> + +<div id="container" style="scroll-padding-top: 20px;"> + <div class="buffer"></div> + <div class="target" id="target1"></div> + <div class="buffer"></div> +</div> + +<div id="container" style="scroll-padding-top: 20px;"> + <div class="buffer"></div> + <div class="target" id="target3"></div> + <div class="buffer"></div> +</div> + +<div id="container-zoomed" style="scroll-padding-top: 40px;"> + <div class="buffer-zoomed"></div> + <div class="target-zoomed" id="target2"></div> + <div class="buffer-zoomed"></div> +</div> + +<div id="container-zoomed" style="scroll-padding-top: 40px;"> + <div class="buffer-zoomed"></div> + <div class="target-zoomed" id="target4"></div> + <div class="buffer-zoomed"></div> +</div> + +<script> + for (match of document.querySelectorAll(".target, .target-zoomed")) { + match.scrollIntoView(); + } +</script> +</body> +</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/scroll-padding.html b/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/scroll-padding.html new file mode 100644 index 0000000..90a5fafa --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/scroll-padding.html
@@ -0,0 +1,70 @@ +<!DOCTYPE html> +<html> +<head> +<title>CSS Zoom applies to scroll-padding</title> +<link rel="author" title="Sam Weinig" href="mailto:sam@webkit.org"> +<link rel="help" href="https://drafts.csswg.org/css-viewport/"> +<link rel="match" href="reference/scroll-padding-ref.html"> +<style> +#container { + display: inline-block; + width: 200px; + height: 100px; + border: solid black 1px; + overflow-x: hidden; + overflow-y: hidden; + scroll-snap-type: y mandatory; +} + +.buffer { + scroll-snap-align: start; + background-color: lightblue; + height: 1000px; + width: 200px; +} + +.target { + scroll-snap-align: start; + background-color: black; + height: 20px; + width: 200px; +} +</style> +</head> +<body> + +<div id="container" style="scroll-padding-top: 20px; zoom: 1;"> + <div class="buffer"></div> + <div class="target"></div> + <div class="buffer"></div> +</div> + +<div style="display: inline-block; scroll-padding-top: 20px;"> + <div id="container" style="scroll-padding-top: inherit; zoom: 1;"> + <div class="buffer"></div> + <div class="target"></div> + <div class="buffer"></div> + </div> +</div> + +<div id="container" style="scroll-padding-top: 20px; zoom: 2;"> + <div class="buffer"></div> + <div class="target"></div> + <div class="buffer"></div> +</div> + +<div style="display: inline-block; scroll-padding-top: 20px;"> + <div id="container" style="scroll-padding-top: inherit; zoom: 2;"> + <div class="buffer"></div> + <div class="target"></div> + <div class="buffer"></div> + </div> +</div> + +<script> + for (match of document.querySelectorAll(".target")) { + match.scrollIntoView(); + } +</script> +</body> +</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/svg-computed-style-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/svg-computed-style-expected.txt index 884a97ff..80eabe2 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/svg-computed-style-expected.txt +++ b/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/svg-computed-style-expected.txt
@@ -1,5 +1,5 @@ This is a testharness.js-based test. -Found 13 FAIL, 0 TIMEOUT, 0 NOTRUN. +Found 14 FAIL, 0 TIMEOUT, 0 NOTRUN. [FAIL] Property box-shadow value 'inherit' zoom: 2 assert_equals: expected "rgb(0, 0, 0) 5px 5px 5px 0px" but got "rgb(0, 0, 0) 2.5px 2.5px 2.5px 0px" [FAIL] Property filter value 'inherit' zoom: 2 @@ -8,6 +8,8 @@ assert_equals: expected "-10px 20px 30px -40px" but got "-5px 10px 15px -20px" [FAIL] Property padding value 'inherit' zoom: 2 assert_equals: expected "10px 20px 30px 40px" but got "5px 10px 15px 20px" +[FAIL] Property scroll-padding value 'inherit' zoom: 2 + assert_equals: expected "10px 20px 30px 40px" but got "5px 10px 15px 20px" [FAIL] Property scroll-margin value 'inherit' zoom: 2 assert_equals: expected "10px 20px 30px 40px" but got "5px 10px 15px 20px" [FAIL] Property text-decoration-thickness value '4px' no zoom
diff --git a/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/svg-computed-style.html b/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/svg-computed-style.html index bc2b291..7410ffa1 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/svg-computed-style.html +++ b/third_party/blink/web_tests/external/wpt/css/css-viewport/zoom/svg-computed-style.html
@@ -69,6 +69,10 @@ "value": "10px 20px 30px 40px", "otherValues": ["20px 40px 60px 80px"] }, + "scroll-padding": { + "value": "10px 20px 30px 40px", + "otherValues": ["20px 40px 60px 80px"] + }, "scroll-margin" : { "value": "10px 20px 30px 40px", "otherValues": ["20px 40px 60px 80px"]
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/permission-icon/icon-slotted-pseudo-element-ref.html b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/permission-icon/icon-slotted-pseudo-element-ref.html new file mode 100644 index 0000000..54b1dae --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/permission-icon/icon-slotted-pseudo-element-ref.html
@@ -0,0 +1,8 @@ +<!DOCTYPE html> +<title>CSS Test Reference</title> +<style> + usermedia::permission-icon { + fill: green; + } +</style> +<usermedia type="camera"></usermedia>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/permission-icon/icon-slotted-pseudo-element.html b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/permission-icon/icon-slotted-pseudo-element.html new file mode 100644 index 0000000..9df736d --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/html/semantics/permission-element/permission-icon/icon-slotted-pseudo-element.html
@@ -0,0 +1,15 @@ +<!DOCTYPE html> +<title>::permission-icon should match on ::slotted element</title> +<link rel="help" href="https://github.com/WICG/PEPC/blob/main/explainer.md"> +<link rel="match" href="icon-slotted-pseudo-element-ref.html"> +<div> + <template shadowrootmode="open"> + <style> + ::slotted(usermedia)::permission-icon { + fill: green; + } + </style> + <slot></slot> + </template> + <usermedia id="camera" type="camera"></usermedia> +</div>
diff --git a/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGList-parse-invalid-clears-items-expected.txt b/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGList-parse-invalid-clears-items-expected.txt new file mode 100644 index 0000000..ee5224a --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGList-parse-invalid-clears-items-expected.txt
@@ -0,0 +1,9 @@ +This is a testharness.js-based test. +[FAIL] SVGLengthList clears items when attribute value is invalid + assert_equals: SVGLengthList: partially invalid attribute value should result in 0 items expected 0 but got 2 +[FAIL] SVGNumberList clears items when attribute value is invalid + assert_equals: SVGNumberList: partially invalid attribute value should result in 0 items expected 0 but got 2 +[FAIL] SVGPointList clears items when attribute value is invalid + assert_equals: SVGPointList: partially invalid attribute value should result in 0 items expected 0 but got 2 +Harness: the test ran to completion. +
diff --git a/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGList-parse-invalid-clears-items.html b/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGList-parse-invalid-clears-items.html new file mode 100644 index 0000000..a5b0d8d --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/svg/types/scripted/SVGList-parse-invalid-clears-items.html
@@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<title>SVG list types clear items on invalid attribute parse</title> +<link rel="help" href="https://svgwg.org/svg2-draft/types.html#InterfaceSVGLengthList"> +<link rel="help" href="https://svgwg.org/svg2-draft/types.html#InterfaceSVGNumberList"> +<link rel="help" href="https://svgwg.org/svg2-draft/types.html#InterfaceSVGPointList"> +<link rel="help" href="https://svgwg.org/svg2-draft/types.html#InterfaceSVGTransformList"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + +test(function() { + const text = document.createElementNS("http://www.w3.org/2000/svg", "text"); + text.setAttribute("x", "10 20 30"); + + assert_equals(text.x.baseVal.numberOfItems, 3, "initial valid parse gives 3 items"); + + // Setting a partially invalid value: valid entries followed by an invalid one. + text.setAttribute("x", "10 20 INVALID"); + assert_equals(text.x.baseVal.numberOfItems, 0, + "SVGLengthList: partially invalid attribute value should result in 0 items"); +}, "SVGLengthList clears items when attribute value is invalid"); + +test(function() { + const text = document.createElementNS("http://www.w3.org/2000/svg", "text"); + text.setAttribute("rotate", "10 20 30"); + + assert_equals(text.rotate.baseVal.numberOfItems, 3, "initial valid parse gives 3 items"); + + // Setting a partially invalid value: valid entries followed by an invalid one. + text.setAttribute("rotate", "10 20 INVALID"); + assert_equals(text.rotate.baseVal.numberOfItems, 0, + "SVGNumberList: partially invalid attribute value should result in 0 items"); +}, "SVGNumberList clears items when attribute value is invalid"); + +test(function() { + const polygon = document.createElementNS("http://www.w3.org/2000/svg", "polygon"); + polygon.setAttribute("points", "0,0 100,0 100,100 0,100"); + + assert_equals(polygon.points.numberOfItems, 4, "initial valid parse gives 4 items"); + + // Setting a partially invalid value: valid point pairs followed by an incomplete pair. + polygon.setAttribute("points", "0,0 100,0 INVALID"); + assert_equals(polygon.points.numberOfItems, 0, + "SVGPointList: partially invalid attribute value should result in 0 items"); +}, "SVGPointList clears items when attribute value is invalid"); + +test(function() { + const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + rect.setAttribute("transform", "translate(10,20) rotate(45)"); + + assert_equals(rect.transform.baseVal.numberOfItems, 2, "initial valid parse gives 2 items"); + + // Setting a partially invalid value: valid transform followed by an invalid one. + rect.setAttribute("transform", "translate(10,20) INVALID"); + assert_equals(rect.transform.baseVal.numberOfItems, 0, + "SVGTransformList: partially invalid attribute value should result in 0 items"); +}, "SVGTransformList clears items when attribute value is invalid"); +</script>
diff --git a/third_party/blink/web_tests/external/wpt/touch-events/multi-touch-touchmove.html b/third_party/blink/web_tests/external/wpt/touch-events/multi-touch-touchmove.html new file mode 100644 index 0000000..0b7a055 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/touch-events/multi-touch-touchmove.html
@@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Simultaneous Touch Moves from multiple pointers</title> + <link rel=help href="https://github.com/servo/servo/issues/42921"> + <link rel="author" title="Euclid Ye" href="mailto:yezhizhenjiakang@gmail.com"> + <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> +</head> +<body> + <script> + promise_test(async () => { + const seenPointers = new Set(); + document.addEventListener("touchmove", (e) => { + for (let touch of e.changedTouches) { + seenPointers.add(touch.identifier); + } + }); + + await new test_driver.Actions() + .addPointer("f1", "touch") + .addPointer("f2", "touch") + .pointerMove(10, 10, { origin: "viewport", sourceName: "f1" }) + .pointerMove(15, 15, { origin: "viewport", sourceName: "f2" }) + .pointerDown({ sourceName: "f1" }) + .pointerDown({ sourceName: "f2" }) + .pointerMove(30, 30, { origin: "viewport", duration: 0, sourceName: "f1" }) + .pointerMove(40, 40, { origin: "viewport", duration: 0, sourceName: "f2" }) + .pointerUp({ sourceName: "f1" }) + .pointerUp({ sourceName: "f2" }) + .send(); + + assert_equals(seenPointers.size, 2, "Should have captured 2 distinct touch identifiers"); + }, "Touchmove events should track multiple pointers"); + </script> +</body> +</html>
diff --git a/third_party/blink/web_tests/external/wpt/wasm/core/js/exceptions/try_table.wast.js b/third_party/blink/web_tests/external/wpt/wasm/core/js/exceptions/try_table.wast.js index e2cdb5c..08a7015 100644 --- a/third_party/blink/web_tests/external/wpt/wasm/core/js/exceptions/try_table.wast.js +++ b/third_party/blink/web_tests/external/wpt/wasm/core/js/exceptions/try_table.wast.js
@@ -206,5 +206,17 @@ // try_table.wast:483 assert_invalid("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x95\x80\x80\x80\x00\x04\x60\x00\x00\x60\x01\x63\x00\x00\x60\x00\x01\x64\x00\x60\x00\x02\x64\x00\x64\x69\x03\x82\x80\x80\x80\x00\x01\x02\x0d\x83\x80\x80\x80\x00\x01\x00\x01\x07\x8d\x80\x80\x80\x00\x01\x09\x63\x61\x74\x63\x68\x5f\x72\x65\x66\x00\x00\x0a\x93\x80\x80\x80\x00\x01\x8d\x80\x80\x80\x00\x00\x02\x03\x1f\x40\x01\x01\x00\x00\x0b\x00\x0b\x0b", "try_table.wast:483"); + +// try_table.wast:499 +let $$6 = module("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x85\x80\x80\x80\x00\x01\x60\x00\x01\x7f\x03\x83\x80\x80\x80\x00\x02\x00\x00\x07\xa4\x80\x80\x80\x00\x02\x0c\x61\x73\x2d\x62\x72\x2d\x74\x61\x72\x67\x65\x74\x00\x00\x11\x61\x73\x2d\x76\x61\x6c\x75\x65\x2d\x70\x72\x6f\x76\x69\x64\x65\x72\x00\x01\x0a\xae\x80\x80\x80\x00\x02\x93\x80\x80\x80\x00\x00\x02\x40\x1f\x40\x00\x0c\x00\x00\x0b\x41\xef\x00\x0f\x0b\x41\xde\x01\x0b\x90\x80\x80\x80\x00\x00\x02\x40\x1f\x7f\x00\x41\xcd\x02\x0c\x00\x0b\x0f\x0b\x00\x0b", "try_table.wast:499"); + +// try_table.wast:499 +let $6 = instance($$6); + +// try_table.wast:522 +assert_return(() => call($6, "as-br-target", []), "try_table.wast:522", 111); + +// try_table.wast:523 +assert_return(() => call($6, "as-value-provider", []), "try_table.wast:523", 333); reinitializeRegistry(); })();
diff --git a/third_party/blink/web_tests/external/wpt/wasm/core/js/gc/array_init_elem.wast.js b/third_party/blink/web_tests/external/wpt/wasm/core/js/gc/array_init_elem.wast.js index b39588ed..a2002f39 100644 --- a/third_party/blink/web_tests/external/wpt/wasm/core/js/gc/array_init_elem.wast.js +++ b/third_party/blink/web_tests/external/wpt/wasm/core/js/gc/array_init_elem.wast.js
@@ -10,66 +10,102 @@ assert_invalid("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x89\x80\x80\x80\x00\x02\x5e\x70\x01\x60\x01\x64\x00\x00\x03\x82\x80\x80\x80\x00\x01\x01\x07\x9d\x80\x80\x80\x00\x01\x19\x61\x72\x72\x61\x79\x2e\x69\x6e\x69\x74\x5f\x65\x6c\x65\x6d\x2d\x69\x6e\x76\x61\x6c\x69\x64\x2d\x32\x00\x00\x09\x84\x80\x80\x80\x00\x01\x05\x6f\x00\x0a\x94\x80\x80\x80\x00\x01\x8e\x80\x80\x80\x00\x00\x20\x00\x41\x00\x41\x00\x41\x00\xfb\x13\x00\x00\x0b", "array_init_elem.wast:31"); // array_init_elem.wast:44 -let $$1 = module("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x95\x80\x80\x80\x00\x05\x60\x00\x00\x5e\x64\x00\x00\x5e\x70\x01\x60\x01\x7f\x00\x60\x03\x7f\x7f\x7f\x00\x03\x86\x80\x80\x80\x00\x05\x00\x03\x00\x04\x00\x04\x84\x80\x80\x80\x00\x01\x70\x00\x01\x06\x95\x80\x80\x80\x00\x02\x64\x01\x00\xd2\x00\x41\x0c\xfb\x06\x01\x0b\x64\x02\x00\x41\x0c\xfb\x07\x02\x0b\x07\xc7\x80\x80\x80\x00\x04\x0e\x61\x72\x72\x61\x79\x5f\x63\x61\x6c\x6c\x5f\x6e\x74\x68\x00\x01\x14\x61\x72\x72\x61\x79\x5f\x69\x6e\x69\x74\x5f\x65\x6c\x65\x6d\x2d\x6e\x75\x6c\x6c\x00\x02\x0f\x61\x72\x72\x61\x79\x5f\x69\x6e\x69\x74\x5f\x65\x6c\x65\x6d\x00\x03\x09\x64\x72\x6f\x70\x5f\x73\x65\x67\x73\x00\x04\x09\x90\x80\x80\x80\x00\x01\x01\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\xcf\x80\x80\x80\x00\x05\x82\x80\x80\x80\x00\x00\x0b\x92\x80\x80\x80\x00\x00\x41\x00\x23\x01\x20\x00\xfb\x0b\x02\x26\x00\x41\x00\x11\x00\x00\x0b\x8e\x80\x80\x80\x00\x00\xd0\x02\x41\x00\x41\x00\x41\x00\xfb\x13\x02\x00\x0b\x8e\x80\x80\x80\x00\x00\x23\x01\x20\x00\x20\x01\x20\x02\xfb\x13\x02\x00\x0b\x85\x80\x80\x80\x00\x00\xfc\x0d\x00\x0b", "array_init_elem.wast:44"); +let $$1 = module("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x96\x80\x80\x80\x00\x05\x5e\x70\x01\x60\x00\x01\x7f\x60\x01\x7f\x01\x7f\x60\x00\x00\x60\x03\x7f\x7f\x7f\x00\x03\x91\x80\x80\x80\x00\x10\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x02\x03\x04\x03\x04\x84\x80\x80\x80\x00\x01\x70\x00\x01\x06\x8a\x80\x80\x80\x00\x01\x64\x00\x00\x41\x0c\xfb\x07\x00\x0b\x07\xc7\x80\x80\x80\x00\x04\x0e\x61\x72\x72\x61\x79\x5f\x63\x61\x6c\x6c\x5f\x6e\x74\x68\x00\x0c\x14\x61\x72\x72\x61\x79\x5f\x69\x6e\x69\x74\x5f\x65\x6c\x65\x6d\x2d\x6e\x75\x6c\x6c\x00\x0d\x0f\x61\x72\x72\x61\x79\x5f\x69\x6e\x69\x74\x5f\x65\x6c\x65\x6d\x00\x0e\x09\x64\x72\x6f\x70\x5f\x73\x65\x67\x73\x00\x0f\x09\x90\x80\x80\x80\x00\x01\x01\x00\x0c\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0a\xb4\x81\x80\x80\x00\x10\x84\x80\x80\x80\x00\x00\x41\x00\x0b\x84\x80\x80\x80\x00\x00\x41\x01\x0b\x84\x80\x80\x80\x00\x00\x41\x02\x0b\x84\x80\x80\x80\x00\x00\x41\x03\x0b\x84\x80\x80\x80\x00\x00\x41\x04\x0b\x84\x80\x80\x80\x00\x00\x41\x05\x0b\x84\x80\x80\x80\x00\x00\x41\x06\x0b\x84\x80\x80\x80\x00\x00\x41\x07\x0b\x84\x80\x80\x80\x00\x00\x41\x08\x0b\x84\x80\x80\x80\x00\x00\x41\x09\x0b\x84\x80\x80\x80\x00\x00\x41\x0a\x0b\x84\x80\x80\x80\x00\x00\x41\x0b\x0b\x92\x80\x80\x80\x00\x00\x41\x00\x23\x00\x20\x00\xfb\x0b\x00\x26\x00\x41\x00\x11\x01\x00\x0b\x8e\x80\x80\x80\x00\x00\xd0\x00\x41\x00\x41\x00\x41\x00\xfb\x13\x00\x00\x0b\x8e\x80\x80\x80\x00\x00\x23\x00\x20\x00\x20\x01\x20\x02\xfb\x13\x00\x00\x0b\x85\x80\x80\x80\x00\x00\xfc\x0d\x00\x0b", "array_init_elem.wast:44"); // array_init_elem.wast:44 let $1 = instance($$1); -// array_init_elem.wast:78 -assert_trap(() => call($1, "array_init_elem-null", []), "array_init_elem.wast:78"); - -// array_init_elem.wast:81 -assert_trap(() => call($1, "array_init_elem", [13, 0, 0]), "array_init_elem.wast:81"); - -// array_init_elem.wast:82 -assert_trap(() => call($1, "array_init_elem", [0, 13, 0]), "array_init_elem.wast:82"); - // array_init_elem.wast:85 -assert_trap(() => call($1, "array_init_elem", [0, 0, 13]), "array_init_elem.wast:85"); +assert_trap(() => call($1, "array_init_elem-null", []), "array_init_elem.wast:85"); -// array_init_elem.wast:86 -assert_trap(() => call($1, "array_init_elem", [0, 0, 13]), "array_init_elem.wast:86"); +// array_init_elem.wast:88 +assert_trap(() => call($1, "array_init_elem", [13, 0, 0]), "array_init_elem.wast:88"); // array_init_elem.wast:89 -assert_return(() => call($1, "array_init_elem", [12, 0, 0]), "array_init_elem.wast:89"); +assert_trap(() => call($1, "array_init_elem", [0, 13, 0]), "array_init_elem.wast:89"); -// array_init_elem.wast:90 -assert_return(() => call($1, "array_init_elem", [0, 12, 0]), "array_init_elem.wast:90"); +// array_init_elem.wast:92 +assert_trap(() => call($1, "array_init_elem", [0, 0, 13]), "array_init_elem.wast:92"); // array_init_elem.wast:93 -assert_trap(() => call($1, "array_call_nth", [0]), "array_init_elem.wast:93"); - -// array_init_elem.wast:94 -assert_trap(() => call($1, "array_call_nth", [5]), "array_init_elem.wast:94"); - -// array_init_elem.wast:95 -assert_trap(() => call($1, "array_call_nth", [11]), "array_init_elem.wast:95"); +assert_trap(() => call($1, "array_init_elem", [0, 0, 13]), "array_init_elem.wast:93"); // array_init_elem.wast:96 -assert_trap(() => call($1, "array_call_nth", [12]), "array_init_elem.wast:96"); +assert_return(() => call($1, "array_init_elem", [12, 0, 0]), "array_init_elem.wast:96"); -// array_init_elem.wast:99 -assert_return(() => call($1, "array_init_elem", [2, 3, 2]), "array_init_elem.wast:99"); +// array_init_elem.wast:97 +assert_return(() => call($1, "array_init_elem", [0, 12, 0]), "array_init_elem.wast:97"); // array_init_elem.wast:100 -assert_trap(() => call($1, "array_call_nth", [1]), "array_init_elem.wast:100"); +assert_trap(() => call($1, "array_call_nth", [0]), "array_init_elem.wast:100"); // array_init_elem.wast:101 -assert_return(() => call($1, "array_call_nth", [2]), "array_init_elem.wast:101"); +assert_trap(() => call($1, "array_call_nth", [5]), "array_init_elem.wast:101"); // array_init_elem.wast:102 -assert_return(() => call($1, "array_call_nth", [3]), "array_init_elem.wast:102"); +assert_trap(() => call($1, "array_call_nth", [11]), "array_init_elem.wast:102"); // array_init_elem.wast:103 -assert_trap(() => call($1, "array_call_nth", [4]), "array_init_elem.wast:103"); +assert_trap(() => call($1, "array_call_nth", [12]), "array_init_elem.wast:103"); // array_init_elem.wast:106 -assert_return(() => call($1, "drop_segs", []), "array_init_elem.wast:106"); +assert_return(() => call($1, "array_init_elem", [2, 3, 2]), "array_init_elem.wast:106"); // array_init_elem.wast:107 -assert_return(() => call($1, "array_init_elem", [0, 0, 0]), "array_init_elem.wast:107"); +assert_trap(() => call($1, "array_call_nth", [1]), "array_init_elem.wast:107"); // array_init_elem.wast:108 -assert_trap(() => call($1, "array_init_elem", [0, 0, 1]), "array_init_elem.wast:108"); +assert_return(() => call($1, "array_call_nth", [2]), "array_init_elem.wast:108", 3); + +// array_init_elem.wast:109 +assert_return(() => call($1, "array_call_nth", [3]), "array_init_elem.wast:109", 4); + +// array_init_elem.wast:110 +assert_trap(() => call($1, "array_call_nth", [4]), "array_init_elem.wast:110"); + +// array_init_elem.wast:113 +assert_return(() => call($1, "drop_segs", []), "array_init_elem.wast:113"); + +// array_init_elem.wast:114 +assert_return(() => call($1, "array_init_elem", [0, 0, 0]), "array_init_elem.wast:114"); + +// array_init_elem.wast:115 +assert_trap(() => call($1, "array_init_elem", [0, 0, 1]), "array_init_elem.wast:115"); + +// array_init_elem.wast:117 +let $$2 = module("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x95\x80\x80\x80\x00\x04\x5e\x6a\x01\x60\x03\x7f\x7f\x7f\x00\x60\x01\x7f\x01\x7f\x60\x02\x7f\x7f\x01\x7f\x03\x84\x80\x80\x80\x00\x03\x01\x02\x03\x06\x8a\x80\x80\x80\x00\x01\x64\x00\x00\x41\x02\xfb\x07\x00\x0b\x07\xb4\x80\x80\x80\x00\x03\x0f\x61\x72\x72\x61\x79\x5f\x69\x6e\x69\x74\x5f\x65\x6c\x65\x6d\x00\x00\x0d\x61\x72\x72\x61\x79\x5f\x6c\x65\x6e\x5f\x6e\x74\x68\x00\x01\x0e\x61\x72\x72\x61\x79\x5f\x65\x71\x5f\x65\x6c\x65\x6d\x73\x00\x02\x09\x90\x80\x80\x80\x00\x01\x05\x6a\x02\x41\x01\xfb\x07\x00\x0b\x41\x02\xfb\x07\x00\x0b\x0a\xba\x80\x80\x80\x00\x03\x8e\x80\x80\x80\x00\x00\x23\x00\x20\x00\x20\x01\x20\x02\xfb\x13\x00\x00\x0b\x8b\x80\x80\x80\x00\x00\x23\x00\x20\x00\xfb\x0b\x00\xfb\x0f\x0b\x91\x80\x80\x80\x00\x00\x23\x00\x20\x00\xfb\x0b\x00\x23\x00\x20\x01\xfb\x0b\x00\xd3\x0b", "array_init_elem.wast:117"); + +// array_init_elem.wast:117 +let $2 = instance($$2); + +// array_init_elem.wast:144 +assert_trap(() => call($2, "array_len_nth", [0]), "array_init_elem.wast:144"); + +// array_init_elem.wast:145 +assert_trap(() => call($2, "array_len_nth", [1]), "array_init_elem.wast:145"); + +// array_init_elem.wast:148 +assert_return(() => call($2, "array_init_elem", [0, 0, 2]), "array_init_elem.wast:148"); + +// array_init_elem.wast:149 +assert_return(() => call($2, "array_len_nth", [0]), "array_init_elem.wast:149", 1); + +// array_init_elem.wast:150 +assert_return(() => call($2, "array_len_nth", [1]), "array_init_elem.wast:150", 2); + +// array_init_elem.wast:151 +assert_return(() => call($2, "array_eq_elems", [0, 1]), "array_init_elem.wast:151", 0); + +// array_init_elem.wast:154 +assert_return(() => call($2, "array_init_elem", [1, 0, 1]), "array_init_elem.wast:154"); + +// array_init_elem.wast:155 +assert_return(() => call($2, "array_len_nth", [0]), "array_init_elem.wast:155", 1); + +// array_init_elem.wast:156 +assert_return(() => call($2, "array_len_nth", [1]), "array_init_elem.wast:156", 1); + +// array_init_elem.wast:157 +assert_return(() => call($2, "array_eq_elems", [0, 1]), "array_init_elem.wast:157", 1); reinitializeRegistry(); })();
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt index db45087..818ba68f 100644 --- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt +++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -6977,7 +6977,6 @@ method updateAdInterestGroups method vibrate method webkitGetUserMedia - setter modelContext setter modelContextTesting interface NavigatorLogin attribute @@toStringTag @@ -7435,6 +7434,7 @@ getter methodName method constructor interface PaymentRequest : EventTarget + static method getSecurePaymentConfirmationCapabilities static method securePaymentConfirmationAvailability attribute @@toStringTag getter id
diff --git a/third_party/blink/web_tests/wpt_internal/script_tools/clear_context.html b/third_party/blink/web_tests/wpt_internal/script_tools/clear_context.https.html similarity index 100% rename from third_party/blink/web_tests/wpt_internal/script_tools/clear_context.html rename to third_party/blink/web_tests/wpt_internal/script_tools/clear_context.https.html
diff --git a/third_party/blink/web_tests/wpt_internal/script_tools/duplicate_tool_registration.html b/third_party/blink/web_tests/wpt_internal/script_tools/duplicate_tool_registration.https.html similarity index 100% rename from third_party/blink/web_tests/wpt_internal/script_tools/duplicate_tool_registration.html rename to third_party/blink/web_tests/wpt_internal/script_tools/duplicate_tool_registration.https.html
diff --git a/third_party/blink/web_tests/wpt_internal/script_tools/model_context.https.html b/third_party/blink/web_tests/wpt_internal/script_tools/model_context.https.html new file mode 100644 index 0000000..3e18d032 --- /dev/null +++ b/third_party/blink/web_tests/wpt_internal/script_tools/model_context.https.html
@@ -0,0 +1,15 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +'use strict'; + +test(function() { + assert_true(navigator.modelContext instanceof ModelContext); +}, "navigator.modelContext instanceof ModelContext"); + +test(function() { + assert_equals(navigator.modelContext, navigator.modelContext); +}, "navigator.modelContext SameObject"); + +</script>
diff --git a/third_party/blink/web_tests/wpt_internal/script_tools/provide_context_basic.html b/third_party/blink/web_tests/wpt_internal/script_tools/provide_context_basic.https.html similarity index 100% rename from third_party/blink/web_tests/wpt_internal/script_tools/provide_context_basic.html rename to third_party/blink/web_tests/wpt_internal/script_tools/provide_context_basic.https.html
diff --git a/third_party/blink/web_tests/wpt_internal/script_tools/provide_context_duplicates.html b/third_party/blink/web_tests/wpt_internal/script_tools/provide_context_duplicates.https.html similarity index 100% rename from third_party/blink/web_tests/wpt_internal/script_tools/provide_context_duplicates.html rename to third_party/blink/web_tests/wpt_internal/script_tools/provide_context_duplicates.https.html
diff --git a/third_party/blink/web_tests/wpt_internal/script_tools/provide_context_errors.html b/third_party/blink/web_tests/wpt_internal/script_tools/provide_context_errors.https.html similarity index 92% rename from third_party/blink/web_tests/wpt_internal/script_tools/provide_context_errors.html rename to third_party/blink/web_tests/wpt_internal/script_tools/provide_context_errors.https.html index 0a1a4ebf..438e7a4 100644 --- a/third_party/blink/web_tests/wpt_internal/script_tools/provide_context_errors.html +++ b/third_party/blink/web_tests/wpt_internal/script_tools/provide_context_errors.https.html
@@ -10,19 +10,6 @@ promise_test(async () => { const modelContext = navigator.modelContext; - // Missing required tools parameter - assert_throws_js( - TypeError, - () => { - modelContext.provideContext({}); - }, - "provideContext should throw when tools parameter is missing" - ); -}, "provideContext throws on missing tools parameter"); - -promise_test(async () => { - const modelContext = navigator.modelContext; - // Invalid tools parameter type assert_throws_js( TypeError,
diff --git a/third_party/blink/web_tests/wpt_internal/script_tools/provide_context_mixed_registration.html b/third_party/blink/web_tests/wpt_internal/script_tools/provide_context_mixed_registration.https.html similarity index 100% rename from third_party/blink/web_tests/wpt_internal/script_tools/provide_context_mixed_registration.html rename to third_party/blink/web_tests/wpt_internal/script_tools/provide_context_mixed_registration.https.html
diff --git a/third_party/blink/web_tests/wpt_internal/script_tools/register_tool_no_schema.html b/third_party/blink/web_tests/wpt_internal/script_tools/register_tool_no_schema.https.html similarity index 100% rename from third_party/blink/web_tests/wpt_internal/script_tools/register_tool_no_schema.html rename to third_party/blink/web_tests/wpt_internal/script_tools/register_tool_no_schema.https.html
diff --git a/third_party/blink/web_tests/wpt_internal/script_tools/register_tool_with_schema.html b/third_party/blink/web_tests/wpt_internal/script_tools/register_tool_with_schema.https.html similarity index 100% rename from third_party/blink/web_tests/wpt_internal/script_tools/register_tool_with_schema.html rename to third_party/blink/web_tests/wpt_internal/script_tools/register_tool_with_schema.https.html
diff --git a/third_party/blink/web_tests/wpt_internal/script_tools/unregister_invalid_tool.html b/third_party/blink/web_tests/wpt_internal/script_tools/unregister_invalid_tool.https.html similarity index 100% rename from third_party/blink/web_tests/wpt_internal/script_tools/unregister_invalid_tool.html rename to third_party/blink/web_tests/wpt_internal/script_tools/unregister_invalid_tool.https.html
diff --git a/third_party/crossbench b/third_party/crossbench index 0a6aad6..58b1752 160000 --- a/third_party/crossbench +++ b/third_party/crossbench
@@ -1 +1 @@ -Subproject commit 0a6aad6789b93b29db3637c43bcf53596f776e27 +Subproject commit 58b175282c85010daae2db859a574b37e7246163
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src index 2dbd599..194a8d9 160000 --- a/third_party/devtools-frontend/src +++ b/third_party/devtools-frontend/src
@@ -1 +1 @@ -Subproject commit 2dbd599b9ff902288d48e6bf5c9caa94e32c46f8 +Subproject commit 194a8d96af8d948fb6c0a42fb4f839d6e3531b53
diff --git a/third_party/fuzztest/src b/third_party/fuzztest/src index f67a6fe..6a217df 160000 --- a/third_party/fuzztest/src +++ b/third_party/fuzztest/src
@@ -1 +1 @@ -Subproject commit f67a6fe9ca3afc4d71997e75e8d87c571f0c30cb +Subproject commit 6a217df9edbe5705885457f10968ebe80aec9504
diff --git a/third_party/screen-ai/README.chromium b/third_party/screen-ai/README.chromium index 1320211..6c8c9c4 100644 --- a/third_party/screen-ai/README.chromium +++ b/third_party/screen-ai/README.chromium
@@ -1,7 +1,7 @@ Name: ScreenAI Library Short Name: screen-ai URL: https://source.corp.google.com/piper///depot/google3/chrome/accessibility/machine_intelligence/chrome_screen_ai -Version: 140.20 +Version: 140.21 Update Mechanism: Manual License: Apache-2.0,Apache-with-LLVM-Exception,BSD-2-Clause,BSD-3-Clause,BSD-3-Clause-Attribution,BSD-3-Clause-Open-MPI,BSD-4-Clause,BSD-4-Clause-UC,BSD-4.3RENO,BSD-Source-Code,BSL-1.0,Beerware,CC-BY-3.0,CC-BY-4.0,CC0-1.0,GPL-3.0-with-autoconf-exception,ICU,IJG,Libpng,LicenseRef-BLAS,LicenseRef-CERN,LicenseRef-Caffe,LicenseRef-FFT2D,LicenseRef-OpenGLUT,LicenseRef-PngSuite,LicenseRef-Public-Domain-Gutenberg,LicenseRef-Punycode,LicenseRef-WebM-Project-Patent,LicenseRef-base64-cpp,MIT,MIT-Khronos-old,MPL-2.0,Minpack,NAIST-2003,NCSA,SGI-B-2.0,SunPro,Unicode-3.0,Unicode-DFS-2015,Unicode-TOU,X11,Zlib,bcrypt-Solar-Designer,dtoa,libpng-2.0,libtiff
diff --git a/third_party/screen-ai/THIRD_PARTY_LICENSES b/third_party/screen-ai/THIRD_PARTY_LICENSES index ed596d10..6d34c4b 100644 --- a/third_party/screen-ai/THIRD_PARTY_LICENSES +++ b/third_party/screen-ai/THIRD_PARTY_LICENSES
@@ -551,7 +551,6 @@ == flatbuffers == gemmlowp == ml_dtypes -== openfst == rules_apple == ruy @@ -2945,7 +2944,6 @@ == flatbuffers == gemmlowp == ml_dtypes -== openfst == rules_rust == ruy == static_assertions @@ -3159,7 +3157,6 @@ == flatbuffers == gemmlowp == ml_dtypes -== openfst == ruy @@ -6712,7 +6709,6 @@ == flatbuffers == gemmlowp == ml_dtypes -== openfst == ruy @@ -7179,6 +7175,214 @@ ----------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------- +== gloop +== rules_cc + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +----------------------------------------------------------------------------------------- + +----------------------------------------------------------------------------------------- == half The MIT License @@ -10299,6 +10503,223 @@ ----------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------- +== openfst + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +## OpenFst examples use data from Project Gutenberg, which is subject to the following copyright notice: + +This ebook is for the use of anyone anywhere in the United States and +most other parts of the world at no cost and with almost no restrictions +whatsoever. You may copy it, give it away or re-use it under the terms +of the Project Gutenberg License included with this ebook or online +at www.gutenberg.org. If you are not located in the United States, +you will have to check the laws of the country where you are located +before using this eBook. +----------------------------------------------------------------------------------------- + +----------------------------------------------------------------------------------------- == openssl @@ -11452,213 +11873,6 @@ ----------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------- -== rules_cc - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ------------------------------------------------------------------------------------------ - ------------------------------------------------------------------------------------------ == scopeguard Apache License
diff --git a/third_party/vulkan-deps b/third_party/vulkan-deps index 473fd80..1458529 160000 --- a/third_party/vulkan-deps +++ b/third_party/vulkan-deps
@@ -1 +1 @@ -Subproject commit 473fd805b316f18182343fb6131cb5b9d98bc071 +Subproject commit 145852941ba897ac22dd6f569672f2d60d2b66d6
diff --git a/third_party/vulkan-validation-layers/src b/third_party/vulkan-validation-layers/src index d2769c7..860f51d 160000 --- a/third_party/vulkan-validation-layers/src +++ b/third_party/vulkan-validation-layers/src
@@ -1 +1 @@ -Subproject commit d2769c7598fdbbe769e74a12120773048a0b3a5b +Subproject commit 860f51d1f7df06432512ecb893bc5edeefd9deb1
diff --git a/tools/android/errorprone_plugin/src/org/chromium/tools/errorprone/plugin/NoAndroidLog.java b/tools/android/errorprone_plugin/src/org/chromium/tools/errorprone/plugin/NoAndroidLog.java index 2486554..2f46a6d 100644 --- a/tools/android/errorprone_plugin/src/org/chromium/tools/errorprone/plugin/NoAndroidLog.java +++ b/tools/android/errorprone_plugin/src/org/chromium/tools/errorprone/plugin/NoAndroidLog.java
@@ -9,13 +9,14 @@ import com.google.errorprone.BugPattern.SeverityLevel; import com.google.errorprone.VisitorState; import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.bugpatterns.BugChecker.IdentifierTreeMatcher; import com.google.errorprone.bugpatterns.BugChecker.MemberSelectTreeMatcher; -import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; import com.google.errorprone.matchers.Description; import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.ImportTree; import com.sun.source.tree.MemberSelectTree; -import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.Tree; import com.sun.tools.javac.code.Symbol; import org.chromium.build.annotations.ServiceImpl; @@ -23,9 +24,6 @@ /** * Checks that android.util.Log is not used directly. * - * <p>This check catches both: 1: Method invocations: {@code Log.d(TAG, "msg")} (imported or fully - * qualified) 2: Fully qualified references: {@code android.util.Log} (e.g. used as a type) - * * <p>Warnings are reported on usage sites (not imports), so * {@code @SuppressWarnings("NoAndroidLog")} can be used to suppress them at the method or class * level. @@ -38,50 +36,48 @@ linkType = LinkType.CUSTOM, link = "https://chromium.googlesource.com/chromium/src/+/HEAD/docs/android_logging.md") public class NoAndroidLog extends BugChecker - implements MethodInvocationTreeMatcher, MemberSelectTreeMatcher { + implements IdentifierTreeMatcher, MemberSelectTreeMatcher { private static final String ANDROID_LOG_CLASS = "android.util.Log"; private static final String ERROR_MESSAGE = "Do not use android.util.Log directly. Use org.chromium.base.Log instead."; @Override - public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { - Symbol.MethodSymbol method = ASTHelpers.getSymbol(tree); - if (method == null) { - return Description.NO_MATCH; + public Description matchIdentifier(IdentifierTree tree, VisitorState state) { + if (isImport(state)) return Description.NO_MATCH; + if (isAndroidLog(ASTHelpers.getSymbol(tree)) && !isParentAMatchingMemberSelect(state)) { + return buildDescription(tree).setMessage(ERROR_MESSAGE).build(); } - - // Check if the method belongs to android.util.Log. - String className = method.enclClass().fullname.toString(); - if (!ANDROID_LOG_CLASS.equals(className)) { - return Description.NO_MATCH; - } - - return buildDescription(tree).setMessage(ERROR_MESSAGE).build(); + return Description.NO_MATCH; } @Override public Description matchMemberSelect(MemberSelectTree tree, VisitorState state) { - // Skip if inside an import statement. - if (ASTHelpers.findEnclosingNode(state.getPath(), ImportTree.class) != null) { - return Description.NO_MATCH; + if (isImport(state)) return Description.NO_MATCH; + if (isAndroidLog(ASTHelpers.getSymbol(tree)) && !isParentAMatchingMemberSelect(state)) { + return buildDescription(tree).setMessage(ERROR_MESSAGE).build(); } + return Description.NO_MATCH; + } - // Skip if inside a method invocation. - if (ASTHelpers.findEnclosingNode(state.getPath(), MethodInvocationTree.class) != null) { - return Description.NO_MATCH; + private boolean isAndroidLog(Symbol symbol) { + if (symbol == null) return false; + if (symbol instanceof Symbol.ClassSymbol) { + return symbol.getQualifiedName().contentEquals(ANDROID_LOG_CLASS); } + return symbol.owner != null + && symbol.owner.getQualifiedName().contentEquals(ANDROID_LOG_CLASS); + } - // Check for fully qualified usage like android.util.Log as a type reference. - if (!tree.getIdentifier().contentEquals("Log")) { - return Description.NO_MATCH; + private boolean isImport(VisitorState state) { + return ASTHelpers.findEnclosingNode(state.getPath(), ImportTree.class) != null; + } + + private boolean isParentAMatchingMemberSelect(VisitorState state) { + Tree parent = state.getPath().getParentPath().getLeaf(); + if (parent instanceof MemberSelectTree) { + return isAndroidLog(ASTHelpers.getSymbol(parent)); } - - Symbol symbol = ASTHelpers.getSymbol(tree.getExpression()); - if (symbol == null || !symbol.getQualifiedName().contentEquals("android.util")) { - return Description.NO_MATCH; - } - - return buildDescription(tree).setMessage(ERROR_MESSAGE).build(); + return false; } }
diff --git a/tools/android/errorprone_plugin/test/BUILD.gn b/tools/android/errorprone_plugin/test/BUILD.gn index 00ac702d..5d0e3e9 100644 --- a/tools/android/errorprone_plugin/test/BUILD.gn +++ b/tools/android/errorprone_plugin/test/BUILD.gn
@@ -26,5 +26,13 @@ "nocompile_gn") expected_compile_output_regex = "warning: .TestClassNameCheck" }, + { + target = "nocompile_gn:no_android_log_test_java" + nocompile_sources = + rebase_path(no_android_log_test_nocompile_sources, + "", + "nocompile_gn") + expected_compile_output_regex = "warning: .NoAndroidLog" + }, ] }
diff --git a/tools/android/errorprone_plugin/test/nocompile_gn/BUILD.gn b/tools/android/errorprone_plugin/test/nocompile_gn/BUILD.gn index 483c3c04..29b010db5 100644 --- a/tools/android/errorprone_plugin/test/nocompile_gn/BUILD.gn +++ b/tools/android/errorprone_plugin/test/nocompile_gn/BUILD.gn
@@ -31,3 +31,12 @@ "//third_party/junit", ] } + +android_library("no_android_log_test_java") { + testonly = true + enable_errorprone = true + sources = [ empty_java ] + if (enable_android_nocompile_tests) { + sources += no_android_log_test_nocompile_sources + } +}
diff --git a/tools/android/errorprone_plugin/test/nocompile_gn/nocompile_sources.gni b/tools/android/errorprone_plugin/test/nocompile_gn/nocompile_sources.gni index ea97be6b..844372d 100644 --- a/tools/android/errorprone_plugin/test/nocompile_gn/nocompile_sources.gni +++ b/tools/android/errorprone_plugin/test/nocompile_gn/nocompile_sources.gni
@@ -7,3 +7,5 @@ test_class_name_check_test_nocompile_sources = [ "../src/org/chromium/tools/errorprone/plugin/TestClassNameCheckTesting.java", ] + +no_android_log_test_nocompile_sources = [ "../src/org/chromium/tools/errorprone/plugin/NoAndroidLogNoCompileSources.java" ]
diff --git a/tools/android/errorprone_plugin/test/src/org/chromium/tools/errorprone/plugin/NoAndroidLogNoCompileSources.java b/tools/android/errorprone_plugin/test/src/org/chromium/tools/errorprone/plugin/NoAndroidLogNoCompileSources.java new file mode 100644 index 0000000..e3ba361 --- /dev/null +++ b/tools/android/errorprone_plugin/test/src/org/chromium/tools/errorprone/plugin/NoAndroidLogNoCompileSources.java
@@ -0,0 +1,21 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.tools.errorprone.plugin; + +import static android.util.Log.ASSERT; + +import android.util.Log; + +/** Test for NoAndroidLog checker. */ +public class NoAndroidLogNoCompileSources { + public void testAll() { + Log.d("TAG", "msg"); + android.util.Log.e("TAG", "msg"); + int i = Log.VERBOSE; + int j = android.util.Log.INFO; + int k = ASSERT; + Class c = Log.class; + } +}
diff --git a/tools/binary_size/generate_milestone_reports.py b/tools/binary_size/generate_milestone_reports.py index 6cfb27be..5d36752 100755 --- a/tools/binary_size/generate_milestone_reports.py +++ b/tools/binary_size/generate_milestone_reports.py
@@ -131,6 +131,7 @@ '143.0.7499.26', '144.0.7559.34', '145.0.7632.50', + '146.0.7680.54', ]
diff --git a/tools/code_coverage/coverage_consts.py b/tools/code_coverage/coverage_consts.py index 7dee5fe..b34e9ec 100755 --- a/tools/code_coverage/coverage_consts.py +++ b/tools/code_coverage/coverage_consts.py
@@ -86,8 +86,6 @@ 'extension_web_request_form_data_parser_fuzzer', 'favicon_url_parser_fuzzer', 'feature_policy_fuzzer', - 'fido_ble_frames_fuzzer', - 'fido_cable_handshake_handler_fuzzer', 'fido_hid_message_fuzzer', 'field_trial_fuzzer', 'file_system_manager_mojolpm_fuzzer',
diff --git a/tools/metrics/PRESUBMIT.py b/tools/metrics/PRESUBMIT.py index 67f6c66..fc4b295 100644 --- a/tools/metrics/PRESUBMIT.py +++ b/tools/metrics/PRESUBMIT.py
@@ -10,11 +10,15 @@ import sys from pathlib import Path +# PRESUBMIT infrastructure doesn't guarantee that the cwd() will be on +# path requiring manual path manipulation to call setup_modules. +# TODO(crbug.com/482274154): Consider using subprocesses to run actual +# test as recommended by presubmit docs: +# https://www.chromium.org/developers/how-tos/depottools/presubmit-scripts/ sys.path.append('.') - import setup_modules -sys.path.pop() +sys.path.remove('.') import chromium_src.tools.metrics.python_support.tests_helpers as tests_helpers import chromium_src.tools.metrics.python_support.mypy_helpers as mypy_helpers
diff --git a/tools/metrics/actions/setup_modules.py b/tools/metrics/actions/setup_modules.py index f6477c7..c11c90c 100644 --- a/tools/metrics/actions/setup_modules.py +++ b/tools/metrics/actions/setup_modules.py
@@ -42,8 +42,9 @@ sys.path.append(str(setup_modules_path)) import setup_modules_lib -# Pop the path again to not interfere with actual modules setup. -sys.path.pop() +# Restore the path to an extent that it's possible, so +# that it doesn't interfere with actual modules setup. +sys.path.remove(str(setup_modules_path)) # Actually set up the modules using setup_modules_lib. setup_modules_lib.setup_modules(str(chromium_src_path))
diff --git a/tools/metrics/common/setup_modules.py b/tools/metrics/common/setup_modules.py index f6477c7..c11c90c 100644 --- a/tools/metrics/common/setup_modules.py +++ b/tools/metrics/common/setup_modules.py
@@ -42,8 +42,9 @@ sys.path.append(str(setup_modules_path)) import setup_modules_lib -# Pop the path again to not interfere with actual modules setup. -sys.path.pop() +# Restore the path to an extent that it's possible, so +# that it doesn't interfere with actual modules setup. +sys.path.remove(str(setup_modules_path)) # Actually set up the modules using setup_modules_lib. setup_modules_lib.setup_modules(str(chromium_src_path))
diff --git a/tools/metrics/histograms/PRESUBMIT.py b/tools/metrics/histograms/PRESUBMIT.py index edae148..e9ea8ff 100644 --- a/tools/metrics/histograms/PRESUBMIT.py +++ b/tools/metrics/histograms/PRESUBMIT.py
@@ -46,8 +46,8 @@ FORMATTING_VALIDATION = 3 -_CACHE_FILE_PATH = os.path.join(tempfile.gettempdir(), - 'histograms_presubmit_cache.json') +_CACHE_DIR_PATH = os.path.join(tempfile.gettempdir(), + 'histograms_presubmit_cache') def _RunCheckWithCache(check_method: Callable[[Type, Type, Any], List[Any]], @@ -222,7 +222,7 @@ def CheckHistogramFormatting(input_api, output_api, - cache_file_path=_CACHE_FILE_PATH, + cache_file_path=_CACHE_DIR_PATH, allow_test_paths=False, xml_paths_override=None): """Checks that histograms.xml is pretty-printed and well-formatted. @@ -274,7 +274,7 @@ def CheckWebViewHistogramsAllowlistOnUpload(input_api, output_api, - cache_file_path=_CACHE_FILE_PATH, + cache_file_path=_CACHE_DIR_PATH, allowlist_path_override=None, xml_paths_override=None): """Checks that HistogramsAllowlist.java contains valid histograms. @@ -326,7 +326,7 @@ def CheckBooleansAreEnums(input_api, output_api, - cache_file_path=_CACHE_FILE_PATH): + cache_file_path=_CACHE_DIR_PATH): """Checks that histograms that use Booleans do not use units. This function is a wrapper around ExecuteCheckBooleansAreEnums that adds
diff --git a/tools/metrics/histograms/PRESUBMIT_test.py b/tools/metrics/histograms/PRESUBMIT_test.py index 77841ba4..a11d6f2 100644 --- a/tools/metrics/histograms/PRESUBMIT_test.py +++ b/tools/metrics/histograms/PRESUBMIT_test.py
@@ -5,6 +5,7 @@ import os.path import tempfile import unittest +from typing import Tuple import setup_modules @@ -21,11 +22,8 @@ _MODIFIED_HISTOGRAMS_CONTENT = '<histogram name="Foo" units="Boolean" />' -def _TempCacheFile(): - file_handle, file_path = tempfile.mkstemp(suffix='.json', text=True) - os.close(file_handle) - return file_path - +def _TempCacheDir(): + return tempfile.mkdtemp() def _PrepareTestWorkingDirectory(): test_dir = tempfile.mkdtemp() @@ -35,7 +33,7 @@ return test_dir, histograms_path -def _MockInputFromTestFile(relative_path: str) -> (MockInputApi, str): +def _MockInputFromTestFile(relative_path: str) -> Tuple[MockInputApi, str]: """ Returns a MockInputApi that list a file relative to test_data/ as changed. The provided file is read and its contents are provided to the MockInputApi. @@ -200,7 +198,7 @@ def testSecondCheckOnTheSameDataReturnsSameResult(self): test_dir_path, _ = _PrepareTestWorkingDirectory() - test_cache_file = _TempCacheFile() + test_cache_file = _TempCacheDir() mock_input_api = _MockInputFromString( 'histograms.xml', '<histogram name="Foo" units="Boolean" />', @@ -234,7 +232,7 @@ def testSecondCheckOnTheSameDataReturnsSameEmptyResult(self): test_dir_path, _ = _PrepareTestWorkingDirectory() - test_cache_file = _TempCacheFile() + test_cache_file = _TempCacheDir() mock_input_api = _MockInputFromString( 'histograms.xml', '<histogram name="Foo" enum="Boolean" />', @@ -264,7 +262,7 @@ def testFailureInModifiedFileIsDetected(self): test_dir_path, histograms_path = _PrepareTestWorkingDirectory() - test_cache_file = _TempCacheFile() + test_cache_file = _TempCacheDir() mock_input_api = MockInputApi() mock_input_api.presubmit_local_path = test_dir_path mock_input_api.files = [
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index 1533a6ba..d4dad3c 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml
@@ -9027,6 +9027,7 @@ <int value="-1844445413" label="NotificationsIgnoreRequireInteraction:disabled"/> <int value="-1843677118" label="TwoPanesStartSurfaceAndroid:disabled"/> + <int value="-1843451369" label="NewTabPageCustomizationThemeSync:disabled"/> <int value="-1842511843" label="DiceWebSigninInterception:enabled"/> <int value="-1842145650" label="WaylandLinuxDrmSyncobj:enabled"/> <int value="-1841624903" label="FilesMaterializedViews:disabled"/> @@ -11994,6 +11995,7 @@ <int value="-832058463" label="LaunchWindowsNativeHostsDirectly:disabled"/> <int value="-831855839" label="DeprecateOldKeyboardShortcutsAccelerator:enabled"/> + <int value="-831789349" label="NewTabPageCustomizationThemeSync:enabled"/> <int value="-831303896" label="TabStripGroupIndicatorsAndroid:enabled"/> <int value="-831066457" label="IncognitoBrandConsistencyForDesktop:disabled"/> <int value="-829665817" label="AltClickAndSixPackCustomization:enabled"/>
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml index 76f067db..31f846ea 100644 --- a/tools/metrics/histograms/metadata/android/histograms.xml +++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -2026,16 +2026,6 @@ </summary> </histogram> -<histogram name="Android.DragDrop.Image.UriCreatedInterval" units="ms" - expires_after="2025-09-07"> - <owner>shuyng@google.com</owner> - <owner>clank-large-form-factors@google.com</owner> - <summary> - Records the interval between two back-to-back image drags. This is used to - evaluate the duration to preserve the image data. Android only. - </summary> -</histogram> - <histogram name="Android.DragDrop.TabOrGroup.MaxInstanceFailureCount" units="failures" expires_after="2026-05-05"> <owner>aishwaryarj@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml index 7ad72d0..feed6e0 100644 --- a/tools/metrics/histograms/metadata/media/histograms.xml +++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -5527,6 +5527,16 @@ <token key="ResultReason" variants="RenderedVideoFrameDetectionResultReason"/> </histogram> +<histogram name="Media.MediaFoundationRenderer.SetOutputRect.Hresult" + enum="Hresult" expires_after="2026-12-09"> + <owner>sangbaekpark@chromium.org</owner> + <owner>media-dev-uma@chromium.org</owner> + <summary> + Recorded when the MediaFoundationRenderer hits an error when trying to set + output rectangle. + </summary> +</histogram> + <histogram name="Media.MediaHistory.DatabaseExists" enum="Boolean" expires_after="2026-05-17"> <owner>evliu@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/memory/histograms.xml b/tools/metrics/histograms/metadata/memory/histograms.xml index 2e308f6..992b9fc 100644 --- a/tools/metrics/histograms/metadata/memory/histograms.xml +++ b/tools/metrics/histograms/metadata/memory/histograms.xml
@@ -3211,27 +3211,6 @@ </summary> </histogram> -<histogram - name="Memory.SystemMemoryLists.ExhaustedIntervalsPerThirtySeconds.{PageList}" - units="intervals" expires_after="2026-07-13"> - <owner>spvm@google.com</owner> - <owner>etienneb@google.com</owner> - <summary> - Recorded on Windows only. Every 100ms, the size of the system memory lists - are queried (and if this query fails, the entire process is cancelled and - Memory.SystemMemoryLists.QueryFailureStatus is recorded instead). This - metric is the total number of consecutive queries over the course of 30s (at - least two in a row to qualify, but several runs can occur disjointedly) that - returned that {PageList} were empty. Emitted every 30s in the browser - process after main message loop start. - </summary> - <token key="PageList"> - <variant name="FreeList" summary="free pages"/> - <variant name="ZeroAndFreeList" summary="both zero and free pages"/> - <variant name="ZeroList" summary="zero pages"/> - </token> -</histogram> - <histogram name="Memory.SystemMemoryLists.QueryFailureStatus" enum="NTSTATUS" expires_after="2026-07-13"> <owner>spvm@google.com</owner> @@ -3280,6 +3259,33 @@ </summary> </histogram> +<histogram + name="Memory.SystemMemoryLists.{Level}IntervalsPerThirtySeconds.{PageList}" + units="intervals" expires_after="2026-07-13"> + <owner>spvm@google.com</owner> + <owner>etienneb@google.com</owner> + <summary> + Recorded on Windows only. Every 100ms, the size of the system memory lists + are queried (and if this query fails, the entire process is cancelled and + Memory.SystemMemoryLists.QueryFailureStatus is recorded instead). This + metric is the total number of consecutive queries over the course of 30s (at + least two in a row to qualify, but several runs can occur disjointedly) that + returned that {PageList} had {Level} pages. Emitted every 30s in the browser + process after main message loop start. + </summary> + <token key="PageList"> + <variant name="AllLists" + summary="pages in the zero, free, and standby lists"/> + <variant name="FreeList" summary="free pages"/> + <variant name="StandbyList" summary="standby pages"/> + <variant name="ZeroList" summary="zero pages"/> + </token> + <token key="Level"> + <variant name="Exhausted" summary="zero"/> + <variant name="Low" summary="less than 2MiB of"/> + </token> +</histogram> + <histogram name="Memory.SystemMemoryLists.{PageCount}Count" units="pages" expires_after="2026-07-13"> <owner>spvm@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/net/enums.xml b/tools/metrics/histograms/metadata/net/enums.xml index 4ad023aa..8f0a2c5b 100644 --- a/tools/metrics/histograms/metadata/net/enums.xml +++ b/tools/metrics/histograms/metadata/net/enums.xml
@@ -138,6 +138,19 @@ </int> </enum> +<!-- LINT.IfChange(CanaryDomainResult) --> + +<enum name="CanaryDomainResult"> + <int value="0" label="Unknown result"/> + <int value="1" label="Cancelled"/> + <int value="2" label="Positive"/> + <int value="3" label="Negative: NOERROR without records"/> + <int value="4" label="Negative: NXDOMAIN or other error"/> + <int value="5" label="Negative: other error"/> +</enum> + +<!-- LINT.ThenChange(/net/dns/canary_domain_service.h:CanaryDomainResult) --> + <enum name="CertificateChangeNotificationType"> <int value="0" label="Trust"/> <int value="1" label="Client Cert"/> @@ -418,6 +431,19 @@ <!-- LINT.ThenChange(//net/dns/host_cache.h:EraseReason) --> +<!-- LINT.IfChange(FallbackFromSecureTransactionPreferredReason) --> + +<enum name="FallbackFromSecureTransactionPreferredReason"> + <int value="0" label="Fallback not preferred (secure DNS used)"/> + <int value="1" + label="Fallback preferred: cannot use secure DNS (empty DoH config)"/> + <int value="2" label="Fallback preferred: canary domain check pending"/> + <int value="3" label="Fallback preferred: canary domain check negative"/> + <int value="4" label="Fallback preferred: no available Doh servers"/> +</enum> + +<!-- LINT.ThenChange(//net/dns/dns_client.cc:FallbackFromSecureTransactionPreferredReason) --> + <enum name="HSTSRedirectUpgradeReason"> <int value="0" label="URL not HTTP"/> <int value="1" label="HSTS unavailable"/>
diff --git a/tools/metrics/histograms/metadata/net/histograms.xml b/tools/metrics/histograms/metadata/net/histograms.xml index 1b750aca..ad14f4a 100644 --- a/tools/metrics/histograms/metadata/net/histograms.xml +++ b/tools/metrics/histograms/metadata/net/histograms.xml
@@ -2395,6 +2395,16 @@ </token> </histogram> +<histogram name="Net.DNS.CanaryDomainService.SecureDns.Result" + enum="CanaryDomainResult" expires_after="2027-01-31"> + <owner>bcb@chromium.org</owner> + <owner>src/net/OWNERS</owner> + <summary> + Records the result of a canary domain probe used to determine whether Secure + DNS is allowed. + </summary> +</histogram> + <histogram name="Net.DNS.DnsConfig.AdditionalDnsQueryTypesEnabled" enum="BooleanEnabled" expires_after="2025-02-10"> <owner>horo@chromium.org</owner> @@ -2726,6 +2736,20 @@ </token> </histogram> +<histogram name="Net.DNS.FallbackFromSecureTransactionPreferred" + enum="FallbackFromSecureTransactionPreferredReason" + expires_after="2027-01-31"> + <owner>bcb@chromium.org</owner> + <owner>src/net/dns/OWNERS</owner> + <summary> + Records the reason for falling back from a secure DNS transaction to an + insecure one. This is recorded every time HostResolverManager::PushDnsTasks + is called while in AUTOMATIC secure DNS mode. A preferred reason indicates + that the SECURE_DNS task was prevented from being included to resolve a host + request. + </summary> +</histogram> + <histogram name="Net.DNS.HostCache.EraseStale.ExpiredBy{HostType}" units="ms" expires_after="2026-08-23"> <owner>suzukikeita@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml index 2f4113e5..2be4b3b3 100644 --- a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml +++ b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
@@ -1077,6 +1077,11 @@ Note: This is only recorded when the OmniboxAutofocusOnIncognitoNtp feature flag is enabled. + + Historical caveats: Starting from 146.0.7680.16 (for M146) and 147.0.7692.0 + (for M147+), measurements are considered valid and recorded only if the tab + height has decreased to a non-zero value (see + `NewTabPage.Incognito.OmniboxAutofocus.OnFocus.TabHeightChange`). </summary> </histogram> @@ -1094,6 +1099,11 @@ Note: This is only recorded when the OmniboxAutofocusOnIncognitoNtp feature flag is enabled. + + Historical caveats: Starting from 146.0.7680.16 (for M146) and 147.0.7692.0 + (for M147+), measurements are considered valid and recorded only if the tab + height has decreased to a non-zero value (see + `NewTabPage.Incognito.OmniboxAutofocus.OnFocus.TabHeightChange`). </summary> </histogram> @@ -1111,6 +1121,11 @@ Note: This is only recorded when the OmniboxAutofocusOnIncognitoNtp feature flag is enabled. + + Historical caveats: Starting from 146.0.7680.16 (for M146) and 147.0.7692.0 + (for M147+), measurements are considered valid and recorded only if the tab + height has decreased to a non-zero value (see + `NewTabPage.Incognito.OmniboxAutofocus.OnFocus.TabHeightChange`). </summary> </histogram> @@ -1187,6 +1202,11 @@ Note: This is only recorded when the OmniboxAutofocusOnIncognitoNtp feature flag is enabled. + + Historical caveats: Starting from 146.0.7680.16 (for M146) and 147.0.7692.0 + (for M147+), measurements are considered valid and recorded only if the tab + height has decreased to a non-zero value (see + `NewTabPage.Incognito.OmniboxAutofocus.OnFocus.TabHeightChange`). </summary> <token key="FocusEvent"> <variant name="OnAutofocus" @@ -1211,6 +1231,11 @@ Note: This is only recorded when the OmniboxAutofocusOnIncognitoNtp feature flag is enabled. + + Historical caveats: Starting from 146.0.7680.16 (for M146) and 147.0.7692.0 + (for M147+), measurements are considered valid and recorded only if the tab + height has decreased to a non-zero value (see + `NewTabPage.Incognito.OmniboxAutofocus.OnFocus.TabHeightChange`). </summary> <token key="FocusEvent"> <variant name="OnAutofocus"
diff --git a/tools/metrics/histograms/metadata/notifications/enums.xml b/tools/metrics/histograms/metadata/notifications/enums.xml index a1f2d51..00124607 100644 --- a/tools/metrics/histograms/metadata/notifications/enums.xml +++ b/tools/metrics/histograms/metadata/notifications/enums.xml
@@ -246,6 +246,11 @@ <int value="1" label="Quick Delete"/> <int value="2" label="Google Lens"/> <int value="3" label="Bottom Omnibox"/> + <int value="4" label="Password Autofill"/> + <int value="5" label="Signin"/> + <int value="6" label="Create Tab Groups"/> + <int value="7" label="Customize MVT"/> + <int value="8" label="Recent Tabs"/> </enum> <!-- LINT.IfChange(TipsNotificationsOptInPromoEventType) -->
diff --git a/tools/metrics/histograms/metadata/notifications/histograms.xml b/tools/metrics/histograms/metadata/notifications/histograms.xml index eed385ef..117b2c9d 100644 --- a/tools/metrics/histograms/metadata/notifications/histograms.xml +++ b/tools/metrics/histograms/metadata/notifications/histograms.xml
@@ -1072,9 +1072,14 @@ <token key="NotificationTipsFeatureType"> <variant name=""/> <variant name=".BottomOmnibox"/> + <variant name=".CreateTabGroups"/> + <variant name=".CustomizeMVT"/> <variant name=".EnhancedSafeBrowsing"/> <variant name=".GoogleLens"/> + <variant name=".PasswordAutofill"/> <variant name=".QuickDelete"/> + <variant name=".RecentTabs"/> + <variant name=".Signin"/> </token> </histogram> @@ -1103,9 +1108,14 @@ <token key="NotificationTipsFeatureType"> <variant name=""/> <variant name=".BottomOmnibox"/> + <variant name=".CreateTabGroups"/> + <variant name=".CustomizeMVT"/> <variant name=".EnhancedSafeBrowsing"/> <variant name=".GoogleLens"/> + <variant name=".PasswordAutofill"/> <variant name=".QuickDelete"/> + <variant name=".RecentTabs"/> + <variant name=".Signin"/> </token> </histogram>
diff --git a/tools/metrics/histograms/metadata/sb_client/enums.xml b/tools/metrics/histograms/metadata/sb_client/enums.xml index 10c357e..aac92b12 100644 --- a/tools/metrics/histograms/metadata/sb_client/enums.xml +++ b/tools/metrics/histograms/metadata/sb_client/enums.xml
@@ -457,6 +457,10 @@ label="Phishing classifier callback is empty on classification completion"/> <int value="9" label="Phishing classification request is responded"/> + <int value="10" label="Renderer page layout has finished loading"/> + <int value="11" + label="Renderer page layout has finished loading again in middle of + classification"/> </enum> <enum name="TailoredWarningType">
diff --git a/tools/metrics/histograms/metadata/session/histograms.xml b/tools/metrics/histograms/metadata/session/histograms.xml index 494c2dda..cbd83e2 100644 --- a/tools/metrics/histograms/metadata/session/histograms.xml +++ b/tools/metrics/histograms/metadata/session/histograms.xml
@@ -122,6 +122,26 @@ </summary> </histogram> +<histogram name="Session.LockedDuration" units="ms" expires_after="2026-10-01"> + <owner>victortan@chromium.org</owner> + <owner>chrome-analysis-team@google.com</owner> + <summary> + The duration the screen was locked (OS-level lock) while Chrome was running. + Recorded when the screen is unlocked or the session ends. + </summary> +</histogram> + +<histogram name="Session.LockedDuration.WithUpdate" units="ms" + expires_after="2026-10-01"> + <owner>victortan@chromium.org</owner> + <owner>chrome-analysis-team@google.com</owner> + <summary> + The duration the screen was locked (OS-level lock) while Chrome was running + AND an update was pending (UpgradeDetector). Recorded when the screen is + unlocked or the session ends. + </summary> +</histogram> + <histogram name="Session.OpenedTabCounts" units="operations" expires_after="2026-07-05"> <owner>rohitrao@chromium.org</owner> @@ -1203,6 +1223,31 @@ </summary> </histogram> +<histogram name="Session.ZeroWindowDuration" units="ms" + expires_after="2026-10-01"> + <owner>victortan@chromium.org</owner> + <owner>chrome-analysis-team@google.com</owner> + <summary> + The duration Chrome was running with zero open browser windows (specifically + TYPE_NORMAL). Recorded when a new window is opened or Chrome quits. + + This metric is only recorded on macOS. + </summary> +</histogram> + +<histogram name="Session.ZeroWindowDuration.WithUpdate" units="ms" + expires_after="2026-10-01"> + <owner>victortan@chromium.org</owner> + <owner>chrome-analysis-team@google.com</owner> + <summary> + The duration Chrome was running with zero open browser windows (specifically + TYPE_NORMAL) AND an update was pending. Recorded when a new window is opened + or Chrome quits. + + This metric is only recorded on macOS. + </summary> +</histogram> + <histogram name="SessionRestore.ForegroundTabFirstPaint4.FinishReason" enum="SessionRestoreFinishReason" expires_after="2026-06-28"> <owner>joenotcharles@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/stability/histograms.xml b/tools/metrics/histograms/metadata/stability/histograms.xml index 7c703db5..cc880e67 100644 --- a/tools/metrics/histograms/metadata/stability/histograms.xml +++ b/tools/metrics/histograms/metadata/stability/histograms.xml
@@ -224,13 +224,9 @@ {AndroidProcessExitReasons} </summary> <token key="AndroidProcessExitReasons"> - <variant name=".Browser" summary="Recorded for Browser crashes."/> - <variant name=".Browser.Background2" - summary="Recorded for background (application is not in running - state) Browser crashes."/> - <variant name=".Browser.Foreground2" - summary="Recorded for foregound (application is in running state) - Browser crashes."/> + <variant name=".Browser2" + summary="Recorded for all Browser process exits that launched a + ChromeActivity at some point."/> <variant name=".GpuForegroundOom" summary="Recorded for foreground GPU process OOM crashes."/> <variant name=".UtilityForegroundOom"
diff --git a/tools/metrics/histograms/presubmit_caching_support.py b/tools/metrics/histograms/presubmit_caching_support.py index b211b1d..936a36f 100644 --- a/tools/metrics/histograms/presubmit_caching_support.py +++ b/tools/metrics/histograms/presubmit_caching_support.py
@@ -7,6 +7,7 @@ import os import pickle from typing import Optional, Any, Dict +from pathlib import Path @dataclasses.dataclass(frozen=True) class _PresubmitCheckContext: @@ -14,7 +15,7 @@ This is used as a key to cache results of a presubmit check. The histograms directory hash is used to lower the probability of changes in one file - impacting health status of presubmit checks in the other. This still doesn't + impacing health status of presubmit checks in the other. This still doesn't eliminate the risk of changes from outside of this directory affecting the health status, but given how PRESUBMIT is triggered, this seems to be inline with the risk of that happening because of PRESUBMIT not being triggered. @@ -34,13 +35,12 @@ return f"{self.histograms_directory_hash}:{self.check_id}" -_CURRENT_CACHE_FILE_SCHEMA_VERSION = 1 +_CURRENT_CACHE_FILE_SCHEMA_VERSION = "v2" @dataclasses.dataclass(frozen=True) class CacheFileSchema: """Describes the schema of the cache file.""" - version: int data: Dict[str, Any] @@ -59,7 +59,6 @@ chunk = f.read(4096) return hasher.hexdigest() - class PresubmitCache: """Stores and retrieves results of a presubmit checks for presubmits.""" @@ -67,13 +66,14 @@ _storage_file_path: str _observed_directory: str - def __init__(self, storage_file_path: str, observed_directory_path: str): - self._storage_file_path = storage_file_path + def __init__(self, storage_directory_path: str, observed_directory_path: str): + base_dir_path = Path(storage_directory_path) + versioned_path = base_dir_path.joinpath(_CURRENT_CACHE_FILE_SCHEMA_VERSION) + versioned_path.mkdir(parents=True, exist_ok=True) + + self._storage_file_path = str(versioned_path.joinpath("cache.json")) self._observed_directory = observed_directory_path - self._cache_contents = CacheFileSchema( - version=_CURRENT_CACHE_FILE_SCHEMA_VERSION, - data={}, - ) + self._cache_contents = CacheFileSchema(data={}) if not os.path.exists(self._storage_file_path) or os.path.getsize( self._storage_file_path) == 0: @@ -83,14 +83,25 @@ # create a new, empty cache. with open(self._storage_file_path, "rb") as f: try: - loaded_cache = pickle.load(f) - if loaded_cache.version == _CURRENT_CACHE_FILE_SCHEMA_VERSION: - self._cache_contents = loaded_cache - except pickle.PickleError: - pass - except ModuleNotFoundError: - # If changes were made to modules used we should drop the cache as well - pass + self._cache_contents = pickle.load(f) + cache_needs_invalidation = False + except Exception as e: + # Blank exception - it's better to ignore cache then to break + # the presubmit because of cache failure. + print(f"Cache is being as it failed to finish reading: {e}") + cache_needs_invalidation = True + + if not cache_needs_invalidation: + return + + try: + os.remove(self._storage_file_path) + except Exception as e: + # Again using blank exception, just because failing here + # causes issues with presubmits. + print(f"Failed to delete the cache file ({e}). To invalidate cache," + f" please try to remove {self._storage_file_path} manually.") + def _GetForContext(self, context: _PresubmitCheckContext) -> Optional[str]: if context.key() not in self._cache_contents.data:
diff --git a/tools/metrics/histograms/presubmit_caching_support_test.py b/tools/metrics/histograms/presubmit_caching_support_test.py index ba8bf13..9bf75c29b 100644 --- a/tools/metrics/histograms/presubmit_caching_support_test.py +++ b/tools/metrics/histograms/presubmit_caching_support_test.py
@@ -6,6 +6,8 @@ import tempfile import unittest +from typing import Optional + import setup_modules import chromium_src.tools.metrics.histograms.presubmit_caching_support as presubmit_caching_support @@ -17,16 +19,28 @@ self.msg = msg -def _TempCacheFile(): - file_handle, file_path = tempfile.mkstemp(suffix='.json', text=True) - os.close(file_handle) - return file_path +def _TempCacheDir(): + return tempfile.mkdtemp() + + +def _prepend_text_to_file(file_path: str, new_text: str): + extra_bytes = bytes(new_text, encoding="utf-8") + + with open(file_path, 'rb') as file: + original_content = file.read() + + with open(file_path, 'wb') as file: + file.write(extra_bytes) + file.write(original_content) class PresubmitCachingSupportTest(unittest.TestCase): + checked_dir: str = "" + storage_path: str = "" + cache: Optional[presubmit_caching_support.PresubmitCache] = None def setUp(self): self.checked_dir = tempfile.mkdtemp() - self.storage_path = _TempCacheFile() + self.storage_path = _TempCacheDir() self.cache = presubmit_caching_support.PresubmitCache( self.storage_path, self.checked_dir) self.cache.StoreResultInCache(1, DummyResult('dummy result')) @@ -59,10 +73,15 @@ new_retrieved_result = self.cache.RetrieveResultFromCache(1) self.assertIsNone(new_retrieved_result) - checked_dir: str = "" - storage_path: str = "" - cache: presubmit_caching_support.PresubmitCache = None + def testBrokenCleansTheCache(self): + cache_storage_file = self.cache._storage_file_path + _prepend_text_to_file(cache_storage_file, "THIS_IS_INVALID_PICKLE") + restored_cache = presubmit_caching_support.PresubmitCache( + self.storage_path, self.checked_dir) + + self.assertIsNone(restored_cache.RetrieveResultFromCache(1)) + self.assertFalse(os.path.exists(self.cache._storage_file_path)) if __name__ == '__main__': unittest.main()
diff --git a/tools/metrics/histograms/setup_modules.py b/tools/metrics/histograms/setup_modules.py index f6477c7..c11c90c 100644 --- a/tools/metrics/histograms/setup_modules.py +++ b/tools/metrics/histograms/setup_modules.py
@@ -42,8 +42,9 @@ sys.path.append(str(setup_modules_path)) import setup_modules_lib -# Pop the path again to not interfere with actual modules setup. -sys.path.pop() +# Restore the path to an extent that it's possible, so +# that it doesn't interfere with actual modules setup. +sys.path.remove(str(setup_modules_path)) # Actually set up the modules using setup_modules_lib. setup_modules_lib.setup_modules(str(chromium_src_path))
diff --git a/tools/metrics/private_metrics/setup_modules.py b/tools/metrics/private_metrics/setup_modules.py index f6477c7..c11c90c 100644 --- a/tools/metrics/private_metrics/setup_modules.py +++ b/tools/metrics/private_metrics/setup_modules.py
@@ -42,8 +42,9 @@ sys.path.append(str(setup_modules_path)) import setup_modules_lib -# Pop the path again to not interfere with actual modules setup. -sys.path.pop() +# Restore the path to an extent that it's possible, so +# that it doesn't interfere with actual modules setup. +sys.path.remove(str(setup_modules_path)) # Actually set up the modules using setup_modules_lib. setup_modules_lib.setup_modules(str(chromium_src_path))
diff --git a/tools/metrics/python_support/generate_setup_modules.py b/tools/metrics/python_support/generate_setup_modules.py index cc09bb9..de08454 100644 --- a/tools/metrics/python_support/generate_setup_modules.py +++ b/tools/metrics/python_support/generate_setup_modules.py
@@ -66,8 +66,9 @@ sys.path.append(str(setup_modules_path)) import setup_modules_lib -# Pop the path again to not interfere with actual modules setup. -sys.path.pop() +# Restore the path to an extent that it's possible, so +# that it doesn't interfere with actual modules setup. +sys.path.remove(str(setup_modules_path)) # Actually set up the modules using setup_modules_lib. setup_modules_lib.setup_modules(str(chromium_src_path))
diff --git a/tools/metrics/python_support/setup_modules.py b/tools/metrics/python_support/setup_modules.py index f6477c7..c11c90c 100644 --- a/tools/metrics/python_support/setup_modules.py +++ b/tools/metrics/python_support/setup_modules.py
@@ -42,8 +42,9 @@ sys.path.append(str(setup_modules_path)) import setup_modules_lib -# Pop the path again to not interfere with actual modules setup. -sys.path.pop() +# Restore the path to an extent that it's possible, so +# that it doesn't interfere with actual modules setup. +sys.path.remove(str(setup_modules_path)) # Actually set up the modules using setup_modules_lib. setup_modules_lib.setup_modules(str(chromium_src_path))
diff --git a/tools/metrics/setup_modules.py b/tools/metrics/setup_modules.py index 515b1c2e..35c2a97c 100644 --- a/tools/metrics/setup_modules.py +++ b/tools/metrics/setup_modules.py
@@ -42,8 +42,9 @@ sys.path.append(str(setup_modules_path)) import setup_modules_lib -# Pop the path again to not interfere with actual modules setup. -sys.path.pop() +# Restore the path to an extent that it's possible, so +# that it doesn't interfere with actual modules setup. +sys.path.remove(str(setup_modules_path)) # Actually set up the modules using setup_modules_lib. setup_modules_lib.setup_modules(str(chromium_src_path))
diff --git a/tools/metrics/structured/setup_modules.py b/tools/metrics/structured/setup_modules.py index f6477c7..c11c90c 100644 --- a/tools/metrics/structured/setup_modules.py +++ b/tools/metrics/structured/setup_modules.py
@@ -42,8 +42,9 @@ sys.path.append(str(setup_modules_path)) import setup_modules_lib -# Pop the path again to not interfere with actual modules setup. -sys.path.pop() +# Restore the path to an extent that it's possible, so +# that it doesn't interfere with actual modules setup. +sys.path.remove(str(setup_modules_path)) # Actually set up the modules using setup_modules_lib. setup_modules_lib.setup_modules(str(chromium_src_path))
diff --git a/tools/metrics/structured/sync/setup_modules.py b/tools/metrics/structured/sync/setup_modules.py index 28fe0863..a22a178 100644 --- a/tools/metrics/structured/sync/setup_modules.py +++ b/tools/metrics/structured/sync/setup_modules.py
@@ -42,8 +42,9 @@ sys.path.append(str(setup_modules_path)) import setup_modules_lib -# Pop the path again to not interfere with actual modules setup. -sys.path.pop() +# Restore the path to an extent that it's possible, so +# that it doesn't interfere with actual modules setup. +sys.path.remove(str(setup_modules_path)) # Actually set up the modules using setup_modules_lib. setup_modules_lib.setup_modules(str(chromium_src_path))
diff --git a/tools/metrics/ukm/setup_modules.py b/tools/metrics/ukm/setup_modules.py index f6477c7..c11c90c 100644 --- a/tools/metrics/ukm/setup_modules.py +++ b/tools/metrics/ukm/setup_modules.py
@@ -42,8 +42,9 @@ sys.path.append(str(setup_modules_path)) import setup_modules_lib -# Pop the path again to not interfere with actual modules setup. -sys.path.pop() +# Restore the path to an extent that it's possible, so +# that it doesn't interfere with actual modules setup. +sys.path.remove(str(setup_modules_path)) # Actually set up the modules using setup_modules_lib. setup_modules_lib.setup_modules(str(chromium_src_path))
diff --git a/tools/perf/core/bot_platforms.py b/tools/perf/core/bot_platforms.py index 628364d..fcf940e0 100644 --- a/tools/perf/core/bot_platforms.py +++ b/tools/perf/core/bot_platforms.py
@@ -774,7 +774,7 @@ 'agsa_probe_config.hjson', '--repetitions=50', '--cool-down-threshold=moderate', - '--http-request-timeout=10s', + '--http-request-timeout=2s', '--ignore-partial-failures', '--embedder-process-name=googleapp', '--embedder-setup-command-config=../../clank/android_webview/tools/crossbench_config/'
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json index 934dade0..3655acd 100644 --- a/tools/perf/core/perfetto_binary_roller/binary_deps.json +++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -6,7 +6,7 @@ }, "win": { "hash": "4f09070783fc24804c6f4a32709f41745e663ebe", - "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/bd72f29183b6897c6d092985256fe4ba309ecdce/trace_processor_shell.exe" + "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/6391c1361787a9be611fc207447d0af2ddb00f5e/trace_processor_shell.exe" }, "linux_arm": { "hash": "46d798c1864490cbb2ee053d6eda436184470e69", @@ -22,7 +22,7 @@ }, "linux": { "hash": "d82f9ff056f8086fff3986984420dff890ebd3e9", - "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/bd72f29183b6897c6d092985256fe4ba309ecdce/trace_processor_shell" + "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/6391c1361787a9be611fc207447d0af2ddb00f5e/trace_processor_shell" } }, "power_profile.sql": {
diff --git a/tools/perf/core/schedule/embedder.crossbench.csv b/tools/perf/core/schedule/embedder.crossbench.csv index cef57932..c308af0 100644 --- a/tools/perf/core/schedule/embedder.crossbench.csv +++ b/tools/perf/core/schedule/embedder.crossbench.csv
@@ -1,3 +1,3 @@ bot,repeat,shard,flags -android-pixel4_webview-perf,1,1,--wpr=crossbench_android_embedder_000.wprgo --skip-wpr-script-injection --embedder=../../clank/android_webview/tools/crossbench_config/cipd/arm64/Velvet_arm64.apk --splashscreen=skip --cuj-config=../../third_party/crossbench/config/team/woa/embedder_cuj_config.hjson --probe-config=../../clank/android_webview/tools/crossbench_config/agsa_probe_config.hjson --repetitions=50 --cool-down-threshold=moderate --http-request-timeout=10s --ignore-partial-failures --embedder-process-name=googleapp --embedder-setup-command-config=../../clank/android_webview/tools/crossbench_config/agsa_setup_config.hjson --embedder-drop-caches -android-pixel4_webview-perf-pgo,1,1,--wpr=crossbench_android_embedder_000.wprgo --skip-wpr-script-injection --embedder=../../clank/android_webview/tools/crossbench_config/cipd/arm64/Velvet_arm64.apk --splashscreen=skip --cuj-config=../../third_party/crossbench/config/team/woa/embedder_cuj_config.hjson --probe-config=../../clank/android_webview/tools/crossbench_config/agsa_probe_config.hjson --repetitions=50 --cool-down-threshold=moderate --http-request-timeout=10s --ignore-partial-failures --embedder-process-name=googleapp --embedder-setup-command-config=../../clank/android_webview/tools/crossbench_config/agsa_setup_config.hjson --embedder-drop-caches +android-pixel4_webview-perf,1,1,--wpr=crossbench_android_embedder_000.wprgo --skip-wpr-script-injection --embedder=../../clank/android_webview/tools/crossbench_config/cipd/arm64/Velvet_arm64.apk --splashscreen=skip --cuj-config=../../third_party/crossbench/config/team/woa/embedder_cuj_config.hjson --probe-config=../../clank/android_webview/tools/crossbench_config/agsa_probe_config.hjson --repetitions=50 --cool-down-threshold=moderate --http-request-timeout=2s --ignore-partial-failures --embedder-process-name=googleapp --embedder-setup-command-config=../../clank/android_webview/tools/crossbench_config/agsa_setup_config.hjson --embedder-drop-caches +android-pixel4_webview-perf-pgo,1,1,--wpr=crossbench_android_embedder_000.wprgo --skip-wpr-script-injection --embedder=../../clank/android_webview/tools/crossbench_config/cipd/arm64/Velvet_arm64.apk --splashscreen=skip --cuj-config=../../third_party/crossbench/config/team/woa/embedder_cuj_config.hjson --probe-config=../../clank/android_webview/tools/crossbench_config/agsa_probe_config.hjson --repetitions=50 --cool-down-threshold=moderate --http-request-timeout=2s --ignore-partial-failures --embedder-process-name=googleapp --embedder-setup-command-config=../../clank/android_webview/tools/crossbench_config/agsa_setup_config.hjson --embedder-drop-caches
diff --git a/tools/perf/core/shard_maps/android-pixel4_webview-perf-pgo_map.json b/tools/perf/core/shard_maps/android-pixel4_webview-perf-pgo_map.json index 11ed822..3108aad1 100644 --- a/tools/perf/core/shard_maps/android-pixel4_webview-perf-pgo_map.json +++ b/tools/perf/core/shard_maps/android-pixel4_webview-perf-pgo_map.json
@@ -11,7 +11,7 @@ "abridged": false }, "blink_perf.css": { - "end": 10, + "end": 6, "abridged": false }, "speedometer3": { @@ -22,7 +22,7 @@ "1": { "benchmarks": { "blink_perf.css": { - "begin": 10, + "begin": 6, "abridged": false }, "blink_perf.dom": { @@ -35,7 +35,7 @@ "abridged": false }, "blink_perf.layout": { - "end": 9, + "end": 24, "abridged": false }, "speedometer3": { @@ -46,14 +46,14 @@ "2": { "benchmarks": { "blink_perf.layout": { - "begin": 9, + "begin": 24, "abridged": false }, "blink_perf.owp_storage": { "abridged": false }, "blink_perf.parser": { - "end": 14, + "end": 19, "abridged": false }, "speedometer3": { @@ -64,7 +64,7 @@ "3": { "benchmarks": { "blink_perf.parser": { - "begin": 14, + "begin": 19, "abridged": false }, "blink_perf.shadow_dom": { @@ -106,7 +106,7 @@ "--probe-config=../../clank/android_webview/tools/crossbench_config/agsa_probe_config.hjson", "--repetitions=50", "--cool-down-threshold=moderate", - "--http-request-timeout=10s", + "--http-request-timeout=2s", "--ignore-partial-failures", "--embedder-process-name=googleapp", "--embedder-setup-command-config=../../clank/android_webview/tools/crossbench_config/agsa_setup_config.hjson", @@ -153,7 +153,7 @@ "benchmarks": { "rendering.mobile": { "begin": 3, - "end": 57, + "end": 53, "abridged": false }, "speedometer3": { @@ -164,8 +164,8 @@ "7": { "benchmarks": { "rendering.mobile": { - "begin": 57, - "end": 114, + "begin": 53, + "end": 111, "abridged": false }, "speedometer3": { @@ -176,8 +176,8 @@ "8": { "benchmarks": { "rendering.mobile": { - "begin": 114, - "end": 161, + "begin": 111, + "end": 163, "abridged": false }, "speedometer3": { @@ -188,8 +188,8 @@ "9": { "benchmarks": { "rendering.mobile": { - "begin": 161, - "end": 211, + "begin": 163, + "end": 221, "abridged": false }, "speedometer3": { @@ -200,8 +200,8 @@ "10": { "benchmarks": { "rendering.mobile": { - "begin": 211, - "end": 266, + "begin": 221, + "end": 275, "abridged": false }, "speedometer3": { @@ -212,8 +212,8 @@ "11": { "benchmarks": { "rendering.mobile": { - "begin": 266, - "end": 321, + "begin": 275, + "end": 327, "abridged": false }, "speedometer3": { @@ -224,8 +224,8 @@ "12": { "benchmarks": { "rendering.mobile": { - "begin": 321, - "end": 364, + "begin": 327, + "end": 369, "abridged": false }, "speedometer3": { @@ -236,7 +236,7 @@ "13": { "benchmarks": { "rendering.mobile": { - "begin": 364, + "begin": 369, "abridged": false }, "rendering.mobile.notracing": { @@ -244,13 +244,6 @@ }, "speedometer3": { "abridged": false - } - } - }, - "14": { - "benchmarks": { - "speedometer3": { - "abridged": false }, "speedometer3-future": { "abridged": false @@ -259,10 +252,22 @@ "abridged": false }, "system_health.common_mobile": { + "end": 28, + "abridged": false + } + } + }, + "14": { + "benchmarks": { + "system_health.common_mobile": { + "begin": 28, "abridged": false }, "system_health.memory_mobile": { - "end": 6, + "end": 13, + "abridged": false + }, + "speedometer3": { "abridged": false } } @@ -270,8 +275,8 @@ "15": { "benchmarks": { "system_health.memory_mobile": { - "begin": 6, - "end": 24, + "begin": 13, + "end": 26, "abridged": false }, "speedometer3": { @@ -282,8 +287,8 @@ "16": { "benchmarks": { "system_health.memory_mobile": { - "begin": 24, - "end": 42, + "begin": 26, + "end": 46, "abridged": false }, "speedometer3": { @@ -294,8 +299,8 @@ "17": { "benchmarks": { "system_health.memory_mobile": { - "begin": 42, - "end": 62, + "begin": 46, + "end": 68, "abridged": false }, "speedometer3": { @@ -306,7 +311,7 @@ "18": { "benchmarks": { "system_health.memory_mobile": { - "begin": 62, + "begin": 68, "abridged": false }, "system_health.webview_startup": { @@ -334,30 +339,30 @@ } }, "extra_infos": { - "num_stories": 1133, - "predicted_min_shard_time": 1014.0, + "num_stories": 1098, + "predicted_min_shard_time": 875.0, "predicted_min_shard_index": 3, - "predicted_max_shard_time": 1469.0, - "predicted_max_shard_index": 16, - "shard #0": 1375.0, - "shard #1": 1378.0, - "shard #2": 1373.0, - "shard #3": 1014.0, - "shard #4": 1064.0, - "shard #5": 1421.0, - "shard #6": 1433.0, - "shard #7": 1415.0, - "shard #8": 1434.0, - "shard #9": 1424.0, - "shard #10": 1413.0, - "shard #11": 1435.0, - "shard #12": 1412.0, - "shard #13": 1400.0, - "shard #14": 1441.0, - "shard #15": 1355.0, - "shard #16": 1469.0, - "shard #17": 1448.0, - "shard #18": 1448.0, - "shard #19": 1384.0 + "predicted_max_shard_time": 1331.0, + "predicted_max_shard_index": 19, + "shard #0": 1248.0, + "shard #1": 1251.0, + "shard #2": 1251.0, + "shard #3": 875.0, + "shard #4": 994.0, + "shard #5": 1300.0, + "shard #6": 1291.0, + "shard #7": 1293.0, + "shard #8": 1302.0, + "shard #9": 1280.0, + "shard #10": 1304.0, + "shard #11": 1295.0, + "shard #12": 1287.0, + "shard #13": 1300.0, + "shard #14": 1327.0, + "shard #15": 1282.0, + "shard #16": 1273.0, + "shard #17": 1264.0, + "shard #18": 1314.0, + "shard #19": 1331.0 } }
diff --git a/tools/perf/core/shard_maps/android-pixel4_webview-perf_map.json b/tools/perf/core/shard_maps/android-pixel4_webview-perf_map.json index 2f27e73..8a9aad57 100644 --- a/tools/perf/core/shard_maps/android-pixel4_webview-perf_map.json +++ b/tools/perf/core/shard_maps/android-pixel4_webview-perf_map.json
@@ -8,7 +8,7 @@ "abridged": false }, "blink_perf.bindings": { - "end": 43, + "end": 37, "abridged": false }, "speedometer3": { @@ -19,14 +19,17 @@ "1": { "benchmarks": { "blink_perf.bindings": { - "begin": 43, + "begin": 37, "abridged": false }, "blink_perf.css": { "abridged": false }, "blink_perf.dom": { - "end": 15, + "abridged": false + }, + "blink_perf.events": { + "end": 2, "abridged": false }, "speedometer3": { @@ -36,18 +39,15 @@ }, "2": { "benchmarks": { - "blink_perf.dom": { - "begin": 15, - "abridged": false - }, "blink_perf.events": { + "begin": 2, "abridged": false }, "blink_perf.image_decoder": { "abridged": false }, "blink_perf.layout": { - "end": 69, + "end": 88, "abridged": false }, "speedometer3": { @@ -58,19 +58,7 @@ "3": { "benchmarks": { "blink_perf.layout": { - "begin": 69, - "end": 101, - "abridged": false - }, - "speedometer3": { - "abridged": false - } - } - }, - "4": { - "benchmarks": { - "blink_perf.layout": { - "begin": 101, + "begin": 88, "abridged": false }, "blink_perf.owp_storage": { @@ -80,7 +68,10 @@ "abridged": false }, "blink_perf.shadow_dom": { - "end": 30, + "abridged": false + }, + "blink_perf.webaudio": { + "end": 6, "abridged": false }, "speedometer3": { @@ -88,13 +79,10 @@ } } }, - "5": { + "4": { "benchmarks": { - "blink_perf.shadow_dom": { - "begin": 30, - "abridged": false - }, "blink_perf.webaudio": { + "begin": 6, "abridged": false }, "blink_perf.webcodecs": { @@ -128,7 +116,7 @@ "--probe-config=../../clank/android_webview/tools/crossbench_config/agsa_probe_config.hjson", "--repetitions=50", "--cool-down-threshold=moderate", - "--http-request-timeout=10s", + "--http-request-timeout=2s", "--ignore-partial-failures", "--embedder-process-name=googleapp", "--embedder-setup-command-config=../../clank/android_webview/tools/crossbench_config/agsa_setup_config.hjson", @@ -137,13 +125,13 @@ } } }, - "6": { + "5": { "benchmarks": { "media.mobile": { "abridged": false }, "rasterize_and_record_micro.top_25": { - "end": 1, + "end": 2, "abridged": false }, "speedometer3": { @@ -163,14 +151,26 @@ } } }, - "7": { + "6": { "benchmarks": { "rasterize_and_record_micro.top_25": { - "begin": 1, + "begin": 2, "abridged": false }, "rendering.mobile": { - "end": 39, + "end": 20, + "abridged": false + }, + "speedometer3": { + "abridged": false + } + } + }, + "7": { + "benchmarks": { + "rendering.mobile": { + "begin": 20, + "end": 62, "abridged": false }, "speedometer3": { @@ -181,8 +181,8 @@ "8": { "benchmarks": { "rendering.mobile": { - "begin": 39, - "end": 84, + "begin": 62, + "end": 110, "abridged": false }, "speedometer3": { @@ -193,8 +193,8 @@ "9": { "benchmarks": { "rendering.mobile": { - "begin": 84, - "end": 138, + "begin": 110, + "end": 153, "abridged": false }, "speedometer3": { @@ -205,8 +205,8 @@ "10": { "benchmarks": { "rendering.mobile": { - "begin": 138, - "end": 185, + "begin": 153, + "end": 194, "abridged": false }, "speedometer3": { @@ -217,8 +217,8 @@ "11": { "benchmarks": { "rendering.mobile": { - "begin": 185, - "end": 235, + "begin": 194, + "end": 238, "abridged": false }, "speedometer3": { @@ -229,8 +229,8 @@ "12": { "benchmarks": { "rendering.mobile": { - "begin": 235, - "end": 297, + "begin": 238, + "end": 309, "abridged": false }, "speedometer3": { @@ -241,8 +241,8 @@ "13": { "benchmarks": { "rendering.mobile": { - "begin": 297, - "end": 337, + "begin": 309, + "end": 344, "abridged": false }, "speedometer3": { @@ -253,8 +253,8 @@ "14": { "benchmarks": { "rendering.mobile": { - "begin": 337, - "end": 381, + "begin": 344, + "end": 389, "abridged": false }, "speedometer3": { @@ -265,7 +265,7 @@ "15": { "benchmarks": { "rendering.mobile": { - "begin": 381, + "begin": 389, "abridged": false }, "rendering.mobile.notracing": { @@ -276,19 +276,24 @@ }, "speedometer3-future": { "abridged": false + }, + "startup.mobile": { + "abridged": false + }, + "system_health.common_mobile": { + "end": 48, + "abridged": false } } }, "16": { "benchmarks": { - "startup.mobile": { - "abridged": false - }, "system_health.common_mobile": { + "begin": 48, "abridged": false }, "system_health.memory_mobile": { - "end": 4, + "end": 12, "abridged": false }, "speedometer3": { @@ -299,8 +304,8 @@ "17": { "benchmarks": { "system_health.memory_mobile": { - "begin": 4, - "end": 20, + "begin": 12, + "end": 24, "abridged": false }, "speedometer3": { @@ -311,8 +316,8 @@ "18": { "benchmarks": { "system_health.memory_mobile": { - "begin": 20, - "end": 29, + "begin": 24, + "end": 36, "abridged": false }, "speedometer3": { @@ -323,8 +328,8 @@ "19": { "benchmarks": { "system_health.memory_mobile": { - "begin": 29, - "end": 47, + "begin": 36, + "end": 53, "abridged": false }, "speedometer3": { @@ -335,8 +340,8 @@ "20": { "benchmarks": { "system_health.memory_mobile": { - "begin": 47, - "end": 69, + "begin": 53, + "end": 72, "abridged": false } } @@ -344,14 +349,14 @@ "21": { "benchmarks": { "system_health.memory_mobile": { - "begin": 69, + "begin": 72, "abridged": false }, "system_health.webview_startup": { "abridged": false }, "v8.browsing_mobile": { - "end": 2, + "end": 6, "abridged": false } } @@ -359,7 +364,7 @@ "22": { "benchmarks": { "v8.browsing_mobile": { - "begin": 2, + "begin": 6, "abridged": false }, "wasmpspdfkit": { @@ -371,33 +376,33 @@ } }, "extra_infos": { - "num_stories": 1133, - "predicted_min_shard_time": 1109.0, - "predicted_min_shard_index": 16, - "predicted_max_shard_time": 1507.0, - "predicted_max_shard_index": 5, - "shard #0": 1163.0, - "shard #1": 1173.0, - "shard #2": 1139.0, - "shard #3": 1163.0, - "shard #4": 1165.0, - "shard #5": 1507.0, - "shard #6": 1152.0, - "shard #7": 1139.0, - "shard #8": 1147.0, - "shard #9": 1141.0, - "shard #10": 1153.0, - "shard #11": 1135.0, - "shard #12": 1154.0, - "shard #13": 1137.0, - "shard #14": 1142.0, - "shard #15": 1110.0, - "shard #16": 1109.0, - "shard #17": 1151.0, - "shard #18": 1193.0, - "shard #19": 1184.0, - "shard #20": 1152.0, - "shard #21": 1166.0, - "shard #22": 1121.0 + "num_stories": 1098, + "predicted_min_shard_time": 1014.0, + "predicted_min_shard_index": 17, + "predicted_max_shard_time": 1174.0, + "predicted_max_shard_index": 4, + "shard #0": 1060.0, + "shard #1": 1063.0, + "shard #2": 1063.0, + "shard #3": 1052.0, + "shard #4": 1174.0, + "shard #5": 1056.0, + "shard #6": 1047.0, + "shard #7": 1061.0, + "shard #8": 1066.0, + "shard #9": 1055.0, + "shard #10": 1069.0, + "shard #11": 1051.0, + "shard #12": 1066.0, + "shard #13": 1050.0, + "shard #14": 1069.0, + "shard #15": 1061.0, + "shard #16": 1035.0, + "shard #17": 1014.0, + "shard #18": 1089.0, + "shard #19": 1056.0, + "shard #20": 1119.0, + "shard #21": 1047.0, + "shard #22": 1056.0 } }
diff --git a/ui/android/java/src/org/chromium/ui/base/ActivityWindowAndroid.java b/ui/android/java/src/org/chromium/ui/base/ActivityWindowAndroid.java index 1a130c7..f0e3b27 100644 --- a/ui/android/java/src/org/chromium/ui/base/ActivityWindowAndroid.java +++ b/ui/android/java/src/org/chromium/ui/base/ActivityWindowAndroid.java
@@ -5,7 +5,6 @@ package org.chromium.ui.base; import android.app.Activity; -import android.content.Context; import org.chromium.base.ActivityState; import org.chromium.base.ApplicationStatus; @@ -35,24 +34,23 @@ /** * Creates an Activity-specific WindowAndroid with associated intent functionality. * - * @param context Context wrapping an activity associated with the WindowAndroid. + * @param activity The activity associated with the WindowAndroid. * @param listenToActivityState Whether to listen to activity state changes. * @param intentRequestTracker The {@link IntentRequestTracker} of the current activity. + * @param insetObserver Observes window insets to track keyboard and layout changes. * @param trackOcclusion Whether to track occlusion of the window. */ - public ActivityWindowAndroid( - Context context, + public static ActivityWindowAndroid create( + Activity activity, boolean listenToActivityState, IntentRequestTracker intentRequestTracker, @Nullable InsetObserver insetObserver, boolean trackOcclusion) { - this( - context, + return new ActivityWindowAndroid( + activity, listenToActivityState, - new ActivityAndroidPermissionDelegate( - new WeakReference<>(ContextUtils.activityFromContext(context))), - new ActivityKeyboardVisibilityDelegate( - new WeakReference<>(ContextUtils.activityFromContext(context))), + new ActivityAndroidPermissionDelegate(new WeakReference<>(activity)), + new ActivityKeyboardVisibilityDelegate(new WeakReference<>(activity)), /* activityTopResumedSupported= */ false, intentRequestTracker, insetObserver, @@ -62,43 +60,18 @@ /** * Creates an Activity-specific WindowAndroid with associated intent functionality. * - * @param context Context wrapping an activity associated with the WindowAndroid. + * @param activity The activity associated with the WindowAndroid. * @param listenToActivityState Whether to listen to activity state changes. - * @param keyboardVisibilityDelegate Delegate which handles keyboard visibility. + * @param activityAndroidPermissionDelegate Delegates which handles android permissions. + * @param activityKeyboardVisibilityDelegate Delegate to handle keyboard visibility. + * @param activityTopResumedSupported If true, allows the activity to report top resumed state + * changes. * @param intentRequestTracker The {@link IntentRequestTracker} of the current activity. + * @param insetObserver Observes window insets to track keyboard and layout changes. * @param trackOcclusion Whether to track occlusion of the window. */ public ActivityWindowAndroid( - Context context, - boolean listenToActivityState, - ActivityKeyboardVisibilityDelegate keyboardVisibilityDelegate, - boolean activityTopResumedSupported, - IntentRequestTracker intentRequestTracker, - InsetObserver insetObserver, - boolean trackOcclusion) { - this( - context, - listenToActivityState, - new ActivityAndroidPermissionDelegate( - new WeakReference<>(ContextUtils.activityFromContext(context))), - keyboardVisibilityDelegate, - activityTopResumedSupported, - intentRequestTracker, - insetObserver, - trackOcclusion); - } - - /** - * Creates an Activity-specific WindowAndroid with associated intent functionality. - * - * @param context Context wrapping an activity associated with the WindowAndroid. - * @param listenToActivityState Whether to listen to activity state changes. - * @param activityAndroidPermissionDelegate Delegates which handles android permissions. - * @param intentRequestTracker The {@link IntentRequestTracker} of the current activity. - * @param trackOcclusion Whether to track occlusion of the window. - */ - private ActivityWindowAndroid( - Context context, + Activity activity, boolean listenToActivityState, ActivityAndroidPermissionDelegate activityAndroidPermissionDelegate, ActivityKeyboardVisibilityDelegate activityKeyboardVisibilityDelegate, @@ -107,15 +80,11 @@ @Nullable InsetObserver insetObserver, boolean trackOcclusion) { super( - context, + activity, activityTopResumedSupported, intentRequestTracker, insetObserver, trackOcclusion); - Activity activity = ContextUtils.activityFromContext(context); - if (activity == null) { - throw new IllegalArgumentException("Context is not and does not wrap an Activity"); - } mListenToActivityState = listenToActivityState; if (listenToActivityState) { ApplicationStatus.registerStateListenerForActivity(this, activity);
diff --git a/ui/android/java/src/org/chromium/ui/dragdrop/DropDataProviderImpl.java b/ui/android/java/src/org/chromium/ui/dragdrop/DropDataProviderImpl.java index 26ca86e2..cc9de8d 100644 --- a/ui/android/java/src/org/chromium/ui/dragdrop/DropDataProviderImpl.java +++ b/ui/android/java/src/org/chromium/ui/dragdrop/DropDataProviderImpl.java
@@ -107,7 +107,6 @@ private long mOpenFileLastAccessTime; private @Nullable Uri mLastUri; private long mLastUriClearedTimestamp; - private long mLastUriCreatedTimestamp; private boolean mLastUriRecorded; private final DropPipeDataWriter mDropPipeDataWriter = new DropPipeDataWriter(); @@ -133,8 +132,6 @@ * Cache the passed-in image data of Drag and Drop. It is expected for filename to be non-empty. */ public Uri cache(byte[] imageBytes, String encodingFormat, String filename) { - long elapsedRealtime = SystemClock.elapsedRealtime(); - long lastUriCreatedTimestamp = mLastUriCreatedTimestamp; String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(encodingFormat); Uri newUri = generateUri(); @@ -142,7 +139,6 @@ // Clear out any old data. clearCacheData(); // Set new data. - mLastUriCreatedTimestamp = elapsedRealtime; this.mImageBytes = imageBytes; mImageFilename = filename; mMimeType = mimeType; @@ -151,11 +147,6 @@ mContentProviderUri = newUri; } - if (lastUriCreatedTimestamp > 0) { - long duration = elapsedRealtime - lastUriCreatedTimestamp; - RecordHistogram.deprecatedRecordMediumTimesHistogram( - "Android.DragDrop.Image.UriCreatedInterval", duration); - } int sizeInKB = imageBytes.length / BYTES_PER_KILOBYTE; RecordHistogram.recordCustomCountHistogram( "Android.DragDrop.Image.Size", sizeInKB, 1, 100_000, 50); @@ -409,10 +400,4 @@ return mHandler; } } - - void clearLastUriCreatedTimestampForTesting() { - synchronized (LOCK) { - mLastUriCreatedTimestamp = 0; - } - } }
diff --git a/ui/android/java/src/org/chromium/ui/listmenu/BasicListMenu.java b/ui/android/java/src/org/chromium/ui/listmenu/BasicListMenu.java index a4eae82..883fa29 100644 --- a/ui/android/java/src/org/chromium/ui/listmenu/BasicListMenu.java +++ b/ui/android/java/src/org/chromium/ui/listmenu/BasicListMenu.java
@@ -106,7 +106,9 @@ R.style.TextAppearance_DensityAdaptive_ListMenuItem) .with( ListMenuItemProperties.ICON_TINT_COLOR_STATE_LIST_ID, - isIconTintable ? R.color.list_menu_item_icon_color_list : 0) + isIconTintable + ? R.color.list_menu_item_icon_color_list + : Resources.ID_NULL) .with(ListMenuItemProperties.ORDER, order); return new ListItem(ListItemType.MENU_ITEM, modelBuilder.build()); }
diff --git a/ui/android/java/src/org/chromium/ui/listmenu/ListMenuItemViewBinder.java b/ui/android/java/src/org/chromium/ui/listmenu/ListMenuItemViewBinder.java index c0638f9..7b4e796 100644 --- a/ui/android/java/src/org/chromium/ui/listmenu/ListMenuItemViewBinder.java +++ b/ui/android/java/src/org/chromium/ui/listmenu/ListMenuItemViewBinder.java
@@ -4,6 +4,7 @@ package org.chromium.ui.listmenu; +import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; @@ -126,17 +127,13 @@ } else if (propertyKey == ListMenuItemProperties.ICON_TINT_COLOR_STATE_LIST_ID) { @ColorRes int tintColorId = model.get(ListMenuItemProperties.ICON_TINT_COLOR_STATE_LIST_ID); - if (tintColorId != 0) { + if (tintColorId != Resources.ID_NULL) { ImageViewCompat.setImageTintList( startIcon, - AppCompatResources.getColorStateList( - view.getContext(), - model.get(ListMenuItemProperties.ICON_TINT_COLOR_STATE_LIST_ID))); + AppCompatResources.getColorStateList(view.getContext(), tintColorId)); ImageViewCompat.setImageTintList( endIcon, - AppCompatResources.getColorStateList( - view.getContext(), - model.get(ListMenuItemProperties.ICON_TINT_COLOR_STATE_LIST_ID))); + AppCompatResources.getColorStateList(view.getContext(), tintColorId)); } else { // No tint. ImageViewCompat.setImageTintList(startIcon, null);
diff --git a/ui/android/junit/src/org/chromium/ui/dragdrop/DropDataProviderImplTest.java b/ui/android/junit/src/org/chromium/ui/dragdrop/DropDataProviderImplTest.java index 93646e7..b4e0d9e 100644 --- a/ui/android/junit/src/org/chromium/ui/dragdrop/DropDataProviderImplTest.java +++ b/ui/android/junit/src/org/chromium/ui/dragdrop/DropDataProviderImplTest.java
@@ -53,7 +53,6 @@ @After public void tearDown() { mDropDataProviderImpl.clearCache(); - mDropDataProviderImpl.clearLastUriCreatedTimestampForTesting(); } @Test @@ -65,8 +64,6 @@ "image/jpeg", mDropDataProviderImpl.getType(uri)); assertImageSizeRecorded(/* expectedCnt= */ 1); - // Android.DragDrop.Image.UriCreatedInterval is not recorded for the first created Uri. - assertImageUriCreatedIntervalRecorded(/* expectedCnt= */ 0); uri = mDropDataProviderImpl.cache(IMAGE_DATA_B, EXTENSION_B, IMAGE_FILENAME_B); Assert.assertEquals( @@ -74,7 +71,6 @@ "image/gif", mDropDataProviderImpl.getType(uri)); assertImageSizeRecorded(/* expectedCnt= */ 2); - assertImageUriCreatedIntervalRecorded(/* expectedCnt= */ 1); uri = mDropDataProviderImpl.cache(IMAGE_DATA_C, EXTENSION_C, IMAGE_FILENAME_C); Assert.assertEquals( @@ -82,7 +78,6 @@ "image/png", mDropDataProviderImpl.getType(uri)); assertImageSizeRecorded(/* expectedCnt= */ 3); - assertImageUriCreatedIntervalRecorded(/* expectedCnt= */ 2); } @Test @@ -171,12 +166,9 @@ mDropDataProviderImpl.onDragEnd(true); ShadowLooper.idleMainLooper(1, TimeUnit.MILLISECONDS); mDropDataProviderImpl.openFile(new DropDataContentProvider(), uri); - // Android.DragDrop.Image.UriCreatedInterval is not recorded for the first created Uri. - assertImageUriCreatedIntervalRecorded(/* expectedCnt= */ 0); // Next image drag starts before the previous image expires. mDropDataProviderImpl.cache(IMAGE_DATA_B, EXTENSION_B, IMAGE_FILENAME_B); - assertImageUriCreatedIntervalRecorded(/* expectedCnt= */ 1); assertImageFirstExpiredOpenFileRecorded(/* expectedCnt= */ 0); // #openFile is called from the drop target app with the expired uri. @@ -205,13 +197,6 @@ errorMsg, expectedCnt, RecordHistogram.getHistogramTotalCountForTesting(histogram)); } - private void assertImageUriCreatedIntervalRecorded(int expectedCnt) { - final String histogram = "Android.DragDrop.Image.UriCreatedInterval"; - final String errorMsg = "<" + histogram + "> is not recorded properly."; - Assert.assertEquals( - errorMsg, expectedCnt, RecordHistogram.getHistogramTotalCountForTesting(histogram)); - } - private void assertImageFirstOpenFileRecorded(int expectedCnt) { final String histogram = "Android.DragDrop.Image.OpenFileTime.FirstAttempt"; final String errorMsg = "<" + histogram + "> is not recorded properly.";
diff --git a/ui/base/clipboard/clipboard.cc b/ui/base/clipboard/clipboard.cc index d1a3588..5fc203fa 100644 --- a/ui/base/clipboard/clipboard.cc +++ b/ui/base/clipboard/clipboard.cc
@@ -437,15 +437,6 @@ std::move(callback).Run(std::move(result)); } -void Clipboard::ReadAsciiText( - ClipboardBuffer buffer, - const std::optional<DataTransferEndpoint>& data_dst, - ReadAsciiTextCallback callback) const { - std::string result; - ReadAsciiText(buffer, base::OptionalToPtr(data_dst), &result); - std::move(callback).Run(std::move(result)); -} - void Clipboard::ReadBookmark( const std::optional<DataTransferEndpoint>& data_dst, ReadBookmarkCallback callback) const {
diff --git a/ui/base/clipboard/clipboard.h b/ui/base/clipboard/clipboard.h index ba25aee..d49030f 100644 --- a/ui/base/clipboard/clipboard.h +++ b/ui/base/clipboard/clipboard.h
@@ -204,7 +204,7 @@ virtual void ReadAsciiText( ClipboardBuffer buffer, const std::optional<DataTransferEndpoint>& data_dst, - ReadAsciiTextCallback callback) const; + ReadAsciiTextCallback callback) const = 0; // Reads HTML from the clipboard, if available. If the HTML fragment requires // context to parse, |fragment_start| and |fragment_end| are indexes into @@ -261,9 +261,6 @@ virtual void ReadText(ClipboardBuffer buffer, const DataTransferEndpoint* data_dst, std::u16string* result) const = 0; - virtual void ReadAsciiText(ClipboardBuffer buffer, - const DataTransferEndpoint* data_dst, - std::string* result) const = 0; virtual void ReadBookmark(const DataTransferEndpoint* data_dst, std::u16string* title, std::string* url) const = 0;
diff --git a/ui/base/clipboard/clipboard_android.cc b/ui/base/clipboard/clipboard_android.cc index 379ce1d..39c1892f 100644 --- a/ui/base/clipboard/clipboard_android.cc +++ b/ui/base/clipboard/clipboard_android.cc
@@ -604,20 +604,22 @@ std::u16string* result) const { DCHECK(CalledOnValidThread()); DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste); - std::string utf8; - ReadAsciiText(buffer, data_dst, &utf8); - *result = base::UTF8ToUTF16(utf8); + RecordRead(ClipboardFormatMetric::kText); + *result = base::UTF8ToUTF16( + GetClipboardMap().Get(ClipboardFormatType::PlainTextType())); } // |data_dst| is not used. It's only passed to be consistent with other // platforms. -void ClipboardAndroid::ReadAsciiText(ClipboardBuffer buffer, - const DataTransferEndpoint* data_dst, - std::string* result) const { +void ClipboardAndroid::ReadAsciiText( + ClipboardBuffer buffer, + const std::optional<DataTransferEndpoint>& data_dst, + ReadAsciiTextCallback callback) const { DCHECK(CalledOnValidThread()); DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste); RecordRead(ClipboardFormatMetric::kText); - *result = GetClipboardMap().Get(ClipboardFormatType::PlainTextType()); + std::move(callback).Run( + GetClipboardMap().Get(ClipboardFormatType::PlainTextType())); } // |src_url| isn't really used. It is only implemented in Windows.
diff --git a/ui/base/clipboard/clipboard_android.h b/ui/base/clipboard/clipboard_android.h index 86955ca..fd230367 100644 --- a/ui/base/clipboard/clipboard_android.h +++ b/ui/base/clipboard/clipboard_android.h
@@ -78,8 +78,8 @@ const DataTransferEndpoint* data_dst, std::u16string* result) const override; void ReadAsciiText(ClipboardBuffer buffer, - const DataTransferEndpoint* data_dst, - std::string* result) const override; + const std::optional<DataTransferEndpoint>& data_dst, + ReadAsciiTextCallback callback) const override; void ReadHTML(ClipboardBuffer buffer, const std::optional<DataTransferEndpoint>& data_dst, ReadHtmlCallback callback) const override;
diff --git a/ui/base/clipboard/clipboard_android_test_support.cc b/ui/base/clipboard/clipboard_android_test_support.cc index 761b773..c0f23ae 100644 --- a/ui/base/clipboard/clipboard_android_test_support.cc +++ b/ui/base/clipboard/clipboard_android_test_support.cc
@@ -13,6 +13,7 @@ #include "ui/base/clipboard/clipboard_monitor.h" #include "ui/base/clipboard/clipboard_observer.h" #include "ui/base/clipboard/scoped_clipboard_writer.h" +#include "ui/base/clipboard/test/clipboard_test_util.h" // Must come after all headers that specialize FromJniType() / ToJniType(). #include "ui/android/ui_javatest_jni_headers/ClipboardAndroidTestSupport_jni.h" @@ -65,9 +66,8 @@ std::string expected_text = base::android::ConvertJavaStringToUTF8(env, j_text); - std::string contents; - clipboard->ReadAsciiText(ClipboardBuffer::kCopyPaste, - /* data_dst = */ nullptr, &contents); + std::string contents = ui::clipboard_test_util::ReadAsciiText( + clipboard, ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr); if (expected_text != contents) { LOG(ERROR) << "Clipboard contents do not match. Expected: " << expected_text << " Actual: " << contents;
diff --git a/ui/base/clipboard/clipboard_ios.h b/ui/base/clipboard/clipboard_ios.h index b9034d4a..b22cd593 100644 --- a/ui/base/clipboard/clipboard_ios.h +++ b/ui/base/clipboard/clipboard_ios.h
@@ -47,8 +47,8 @@ const DataTransferEndpoint* data_dst, std::u16string* result) const override; void ReadAsciiText(ClipboardBuffer buffer, - const DataTransferEndpoint* data_dst, - std::string* result) const override; + const std::optional<DataTransferEndpoint>& data_dst, + ReadAsciiTextCallback callback) const override; void ReadHTML(ClipboardBuffer buffer, const std::optional<DataTransferEndpoint>& data_dst, ReadHtmlCallback callback) const override;
diff --git a/ui/base/clipboard/clipboard_ios.mm b/ui/base/clipboard/clipboard_ios.mm index 3290bbb..a19df95 100644 --- a/ui/base/clipboard/clipboard_ios.mm +++ b/ui/base/clipboard/clipboard_ios.mm
@@ -168,20 +168,23 @@ // |data_dst| is not used. It's only passed to be consistent with other // platforms. -void ClipboardIOS::ReadAsciiText(ClipboardBuffer buffer, - const DataTransferEndpoint* data_dst, - std::string* result) const { +void ClipboardIOS::ReadAsciiText( + ClipboardBuffer buffer, + const std::optional<DataTransferEndpoint>& data_dst, + ReadAsciiTextCallback callback) const { DCHECK(CalledOnValidThread()); DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste); RecordRead(ClipboardFormatMetric::kText); + std::string result; NSData* data = GetDataWithTypeFromPasteboard( GetPasteboard(), ClipboardFormatType::PlainTextType().ToNSString()); if (data) { NSString* contents = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - result->assign(base::SysNSStringToUTF8(contents)); + result.assign(base::SysNSStringToUTF8(contents)); } + std::move(callback).Run(std::move(result)); } // |data_dst| is not used. It's only passed to be consistent with other
diff --git a/ui/base/clipboard/clipboard_mac.h b/ui/base/clipboard/clipboard_mac.h index c785862f1..dae6a24 100644 --- a/ui/base/clipboard/clipboard_mac.h +++ b/ui/base/clipboard/clipboard_mac.h
@@ -63,8 +63,8 @@ const DataTransferEndpoint* data_dst, std::u16string* result) const override; void ReadAsciiText(ClipboardBuffer buffer, - const DataTransferEndpoint* data_dst, - std::string* result) const override; + const std::optional<DataTransferEndpoint>& data_dst, + ReadAsciiTextCallback callback) const override; void ReadHTML(ClipboardBuffer buffer, const std::optional<DataTransferEndpoint>& data_dst, ReadHtmlCallback callback) const override;
diff --git a/ui/base/clipboard/clipboard_mac.mm b/ui/base/clipboard/clipboard_mac.mm index ac4a43a..255d93e 100644 --- a/ui/base/clipboard/clipboard_mac.mm +++ b/ui/base/clipboard/clipboard_mac.mm
@@ -316,18 +316,20 @@ // |data_dst| is not used. It's only passed to be consistent with other // platforms. -void ClipboardMac::ReadAsciiText(ClipboardBuffer buffer, - const DataTransferEndpoint* data_dst, - std::string* result) const { +void ClipboardMac::ReadAsciiText( + ClipboardBuffer buffer, + const std::optional<DataTransferEndpoint>& data_dst, + ReadAsciiTextCallback callback) const { DCHECK(CalledOnValidThread()); DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste); RecordRead(ClipboardFormatMetric::kText); NSString* contents = [GetPasteboard() stringForType:NSPasteboardTypeString]; - if (!contents) - result->clear(); - else - result->assign(base::SysNSStringToUTF8(contents)); + std::string result; + if (contents) { + result.assign(base::SysNSStringToUTF8(contents)); + } + std::move(callback).Run(std::move(result)); } // |data_dst| is not used. It's only passed to be consistent with other
diff --git a/ui/base/clipboard/clipboard_non_backed.cc b/ui/base/clipboard/clipboard_non_backed.cc index 10734d8..b9411f0 100644 --- a/ui/base/clipboard/clipboard_non_backed.cc +++ b/ui/base/clipboard/clipboard_non_backed.cc
@@ -660,15 +660,17 @@ #endif } -void ClipboardNonBacked::ReadAsciiText(ClipboardBuffer buffer, - const DataTransferEndpoint* data_dst, - std::string* result) const { +void ClipboardNonBacked::ReadAsciiText( + ClipboardBuffer buffer, + const std::optional<DataTransferEndpoint>& data_dst, + ReadAsciiTextCallback callback) const { DCHECK(CalledOnValidThread()); const ClipboardInternal& clipboard_internal = GetInternalClipboard(buffer); - if (!clipboard_internal.IsReadAllowed(data_dst, + if (!clipboard_internal.IsReadAllowed(base::OptionalToPtr(data_dst), ClipboardInternalFormat::kText)) { + std::move(callback).Run(""); return; } @@ -677,7 +679,9 @@ #endif // BUILDFLAG(IS_CHROMEOS) RecordRead(ClipboardFormatMetric::kText); - clipboard_internal.ReadAsciiText(result); + std::string result; + clipboard_internal.ReadAsciiText(&result); + std::move(callback).Run(std::move(result)); #if BUILDFLAG(IS_CHROMEOS) ClipboardMonitor::GetInstance()->NotifyClipboardDataRead();
diff --git a/ui/base/clipboard/clipboard_non_backed.h b/ui/base/clipboard/clipboard_non_backed.h index 55b31818..48b74f2 100644 --- a/ui/base/clipboard/clipboard_non_backed.h +++ b/ui/base/clipboard/clipboard_non_backed.h
@@ -94,8 +94,8 @@ const DataTransferEndpoint* data_dst, std::u16string* result) const override; void ReadAsciiText(ClipboardBuffer buffer, - const DataTransferEndpoint* data_dst, - std::string* result) const override; + const std::optional<DataTransferEndpoint>& data_dst, + ReadAsciiTextCallback callback) const override; void ReadHTML(ClipboardBuffer buffer, const std::optional<DataTransferEndpoint>& data_dst, ReadHtmlCallback callback) const override;
diff --git a/ui/base/clipboard/clipboard_ozone.cc b/ui/base/clipboard/clipboard_ozone.cc index 04039e24..1365a5f 100644 --- a/ui/base/clipboard/clipboard_ozone.cc +++ b/ui/base/clipboard/clipboard_ozone.cc
@@ -794,22 +794,6 @@ reinterpret_cast<char*>(clipboard_data.data()), clipboard_data.size())); } -void ClipboardOzone::ReadAsciiText(ClipboardBuffer buffer, - const DataTransferEndpoint* data_dst, - std::string* result) const { - DCHECK(CalledOnValidThread()); - - auto clipboard_data = async_clipboard_ozone_->ReadClipboardDataAndWait( - buffer, kMimeTypePlainText); - - if (!IsReadAllowed(GetSource(buffer), data_dst, clipboard_data)) { - return; - } - - RecordRead(ClipboardFormatMetric::kText); - result->assign(clipboard_data.begin(), clipboard_data.end()); -} - void ClipboardOzone::ReadBookmark(const DataTransferEndpoint* data_dst, std::u16string* title, std::string* url) const {
diff --git a/ui/base/clipboard/clipboard_ozone.h b/ui/base/clipboard/clipboard_ozone.h index 3c8c7896..3a39b3d 100644 --- a/ui/base/clipboard/clipboard_ozone.h +++ b/ui/base/clipboard/clipboard_ozone.h
@@ -85,9 +85,6 @@ void ReadText(ClipboardBuffer buffer, const DataTransferEndpoint* data_dst, std::u16string* result) const override; - void ReadAsciiText(ClipboardBuffer buffer, - const DataTransferEndpoint* data_dst, - std::string* result) const override; void ReadBookmark(const DataTransferEndpoint* data_dst, std::u16string* title, std::string* url) const override;
diff --git a/ui/base/clipboard/clipboard_win.cc b/ui/base/clipboard/clipboard_win.cc index c73fd7d..13fdd1f 100644 --- a/ui/base/clipboard/clipboard_win.cc +++ b/ui/base/clipboard/clipboard_win.cc
@@ -606,16 +606,6 @@ return result; } -// |data_dst| is not used. It's only passed to be consistent with other -// platforms. -void ClipboardWin::ReadAsciiText(ClipboardBuffer buffer, - const DataTransferEndpoint* data_dst, - std::string* result) const { - CHECK(result); - *result = ReadAsciiTextInternal(buffer, base::OptionalFromPtr(data_dst), - GetClipboardWindow()); -} - // static // |data_dst| is not used, but is kept as it may be used in the future. std::string ClipboardWin::ReadAsciiTextInternal(
diff --git a/ui/base/clipboard/clipboard_win.h b/ui/base/clipboard/clipboard_win.h index 9980a8b..10d39d60 100644 --- a/ui/base/clipboard/clipboard_win.h +++ b/ui/base/clipboard/clipboard_win.h
@@ -94,9 +94,6 @@ void ReadText(ClipboardBuffer buffer, const DataTransferEndpoint* data_dst, std::u16string* result) const override; - void ReadAsciiText(ClipboardBuffer buffer, - const DataTransferEndpoint* data_dst, - std::string* result) const override; void ReadPng(ClipboardBuffer buffer, const std::optional<DataTransferEndpoint>& data_dst, ReadPngCallback callback) const override;
diff --git a/ui/base/clipboard/test/test_clipboard.cc b/ui/base/clipboard/test/test_clipboard.cc index 77d42cd..bb55b384 100644 --- a/ui/base/clipboard/test/test_clipboard.cc +++ b/ui/base/clipboard/test/test_clipboard.cc
@@ -150,28 +150,33 @@ void TestClipboard::ReadText(ClipboardBuffer buffer, const DataTransferEndpoint* data_dst, std::u16string* result) const { - if (!IsReadAllowed(GetStore(buffer).data_src, data_dst)) { - return; - } - - std::string result8; - ReadAsciiText(buffer, data_dst, &result8); - *result = base::UTF8ToUTF16(result8); -} - -// TODO(crbug.com/40704509): |data_dst| should be supported. -void TestClipboard::ReadAsciiText(ClipboardBuffer buffer, - const DataTransferEndpoint* data_dst, - std::string* result) const { const DataStore& store = GetStore(buffer); if (!IsReadAllowed(store.data_src, data_dst)) { return; } - result->clear(); auto it = store.data.find(ClipboardFormatType::PlainTextType()); - if (it != store.data.end()) - *result = it->second; + if (it != store.data.end()) { + *result = base::UTF8ToUTF16(it->second); + } +} + +void TestClipboard::ReadAsciiText( + ClipboardBuffer buffer, + const std::optional<DataTransferEndpoint>& data_dst, + ReadAsciiTextCallback callback) const { + const DataStore& store = GetStore(buffer); + if (!IsReadAllowed(store.data_src, base::OptionalToPtr(data_dst))) { + std::move(callback).Run(""); + return; + } + + std::string result; + auto it = store.data.find(ClipboardFormatType::PlainTextType()); + if (it != store.data.end()) { + result = it->second; + } + std::move(callback).Run(std::move(result)); } void TestClipboard::ReadHTML(
diff --git a/ui/base/clipboard/test/test_clipboard.h b/ui/base/clipboard/test/test_clipboard.h index 4b8fb7f..7a14189 100644 --- a/ui/base/clipboard/test/test_clipboard.h +++ b/ui/base/clipboard/test/test_clipboard.h
@@ -59,8 +59,8 @@ const DataTransferEndpoint* data_dst, std::u16string* result) const override; void ReadAsciiText(ClipboardBuffer buffer, - const DataTransferEndpoint* data_dst, - std::string* result) const override; + const std::optional<DataTransferEndpoint>& data_dst, + ReadAsciiTextCallback callback) const override; void ReadHTML(ClipboardBuffer buffer, const std::optional<DataTransferEndpoint>& data_dst, ReadHtmlCallback callback) const override;
diff --git a/ui/gfx/buffer_types.h b/ui/gfx/buffer_types.h index 5a728a1..ae21d9b 100644 --- a/ui/gfx/buffer_types.h +++ b/ui/gfx/buffer_types.h
@@ -39,28 +39,6 @@ LAST = VEA_READ_CAMERA_AND_CPU_READ_WRITE }; -// Used to identify the plane of a GpuMemoryBuffer to use when creating a -// SharedImage. -enum class BufferPlane { - // For single-plane GpuMemoryBuffer, this refers to that single plane. For - // YUV_420, YUV_420_BIPLANAR, YUVA_420_TRIPLANAR, and P010 GpuMemoryBuffers, - // this refers to an RGB representation of the planes (either bound directly - // as a texture or created through an extra copy). - DEFAULT, - // The Y plane for YUV_420, YUV_420_BIPLANAR, YUVA_420_TRIPLANAR, and P010. - Y, - // The UV plane for YUV_420_BIPLANAR, YUVA_420_TRIPLANAR and P010. - UV, - // The U plane for YUV_420. - U, - // The V plane for YUV_420. - V, - // The A plane for YUVA_420_TRIPLANAR. - A, - - LAST = A -}; - } // namespace gfx #endif // UI_GFX_BUFFER_TYPES_H_
diff --git a/ui/ozone/common/gl_ozone_egl.cc b/ui/ozone/common/gl_ozone_egl.cc index aeabc00..7244087 100644 --- a/ui/ozone/common/gl_ozone_egl.cc +++ b/ui/ozone/common/gl_ozone_egl.cc
@@ -66,7 +66,7 @@ std::unique_ptr<NativePixmapGLBinding> GLOzoneEGL::ImportNativePixmap( scoped_refptr<gfx::NativePixmap> pixmap, viz::SharedImageFormat plane_format, - gfx::BufferPlane plane, + std::optional<int> plane_index, gfx::Size plane_size, const gfx::ColorSpace& color_space, GLenum target,
diff --git a/ui/ozone/common/gl_ozone_egl.h b/ui/ozone/common/gl_ozone_egl.h index 7e0e7aed0..af9d46b 100644 --- a/ui/ozone/common/gl_ozone_egl.h +++ b/ui/ozone/common/gl_ozone_egl.h
@@ -5,6 +5,8 @@ #ifndef UI_OZONE_COMMON_GL_OZONE_EGL_H_ #define UI_OZONE_COMMON_GL_OZONE_EGL_H_ +#include <optional> + #include "components/viz/common/resources/shared_image_format.h" #include "third_party/khronos/EGL/eglplatform.h" #include "ui/gl/gl_implementation.h" @@ -39,7 +41,7 @@ std::unique_ptr<NativePixmapGLBinding> ImportNativePixmap( scoped_refptr<gfx::NativePixmap> pixmap, viz::SharedImageFormat plane_format, - gfx::BufferPlane plane, + std::optional<int> plane_index, gfx::Size plane_size, const gfx::ColorSpace& color_space, GLenum target,
diff --git a/ui/ozone/common/native_pixmap_egl_binding.cc b/ui/ozone/common/native_pixmap_egl_binding.cc index e2c9afef..49a11d4 100644 --- a/ui/ozone/common/native_pixmap_egl_binding.cc +++ b/ui/ozone/common/native_pixmap_egl_binding.cc
@@ -44,8 +44,8 @@ NativePixmapEGLBinding::NativePixmapEGLBinding(const gfx::Size& size, viz::SharedImageFormat format, - gfx::BufferPlane plane) - : size_(size), format_(format), plane_(plane) {} + std::optional<int> plane_index) + : size_(size), format_(format), plane_index_(plane_index) {} NativePixmapEGLBinding::~NativePixmapEGLBinding() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); @@ -60,15 +60,15 @@ std::unique_ptr<NativePixmapGLBinding> NativePixmapEGLBinding::Create( scoped_refptr<gfx::NativePixmap> pixmap, viz::SharedImageFormat plane_format, - gfx::BufferPlane plane, + std::optional<int> plane_index, gfx::Size plane_size, const gfx::ColorSpace& color_space, GLenum target, GLuint texture_id) { DCHECK_GT(texture_id, 0u); - auto binding = - std::make_unique<NativePixmapEGLBinding>(plane_size, plane_format, plane); + auto binding = std::make_unique<NativePixmapEGLBinding>( + plane_size, plane_format, plane_index); if (!binding->InitializeFromNativePixmap(std::move(pixmap), color_space, target, texture_id)) { @@ -141,7 +141,7 @@ } } - if (plane_ == gfx::BufferPlane::DEFAULT) { + if (!plane_index_.has_value()) { constexpr auto kPlaneFDAttrs = std::to_array<EGLint>({ EGL_DMA_BUF_PLANE0_FD_EXT, EGL_DMA_BUF_PLANE1_FD_EXT, @@ -202,8 +202,15 @@ } attrs.push_back(EGL_NONE); } else { - DCHECK(plane_ == gfx::BufferPlane::Y || plane_ == gfx::BufferPlane::UV); - size_t pixmap_plane = plane_ == gfx::BufferPlane::Y ? 0 : 1; + size_t pixmap_plane = plane_index_.value(); + // Gate the pixmap_plane to be either 0 or 1 to preserve historical + // behavior which assumed multiplanar format's plane config to be kY_UV + // always. + // TODO(b/482199461): Verify that flows other than kY_UV can reach here and + // eliminate this conditional. + if (pixmap_plane > 1) { + pixmap_plane = 1; + } attrs.push_back(EGL_DMA_BUF_PLANE0_FD_EXT); attrs.push_back(pixmap->GetDmaBufFd(pixmap_plane));
diff --git a/ui/ozone/common/native_pixmap_egl_binding.h b/ui/ozone/common/native_pixmap_egl_binding.h index 32c6a00..61fb0de 100644 --- a/ui/ozone/common/native_pixmap_egl_binding.h +++ b/ui/ozone/common/native_pixmap_egl_binding.h
@@ -6,6 +6,7 @@ #define UI_OZONE_COMMON_NATIVE_PIXMAP_EGL_BINDING_H_ #include <memory> +#include <optional> #include "base/threading/thread_checker.h" #include "components/viz/common/resources/shared_image_format.h" @@ -21,7 +22,7 @@ public: NativePixmapEGLBinding(const gfx::Size& size, viz::SharedImageFormat format, - gfx::BufferPlane plane); + std::optional<int> plane_index); ~NativePixmapEGLBinding() override; static bool IsSharedImageFormatSupported(viz::SharedImageFormat format); @@ -34,7 +35,7 @@ static std::unique_ptr<NativePixmapGLBinding> Create( scoped_refptr<gfx::NativePixmap> pixmap, viz::SharedImageFormat plane_format, - gfx::BufferPlane plane, + std::optional<int> plane_index, gfx::Size plane_size, const gfx::ColorSpace& color_space, GLenum target, @@ -54,7 +55,9 @@ THREAD_CHECKER(thread_checker_); viz::SharedImageFormat format_; scoped_refptr<gfx::NativePixmap> pixmap_; - gfx::BufferPlane plane_; + // Set only for multiplanar formats without external sampler (textures per + // plane). + std::optional<int> plane_index_; }; } // namespace ui
diff --git a/ui/ozone/demo/skia/skia_surfaceless_gl_renderer.cc b/ui/ozone/demo/skia/skia_surfaceless_gl_renderer.cc index 69db612..6226e6a 100644 --- a/ui/ozone/demo/skia/skia_surfaceless_gl_renderer.cc +++ b/ui/ozone/demo/skia/skia_surfaceless_gl_renderer.cc
@@ -123,11 +123,14 @@ ->CreateNativePixmap(widget, nullptr, size, format, gfx::BufferUsage::SCANOUT); + // The imported pixmap can be externally sampled i.e. does not provide + // per-plane textures but provides a unified single texture object. pixmap_gl_binding_ = OzonePlatform::GetInstance() ->GetSurfaceFactoryOzone() ->GetCurrentGLOzone() - ->ImportNativePixmap(pixmap_, format, gfx::BufferPlane::DEFAULT, size, + ->ImportNativePixmap(pixmap_, format, + /*plane_index=*/std::nullopt, size, gfx::ColorSpace(), GL_TEXTURE_2D, gl_tex_); if (!pixmap_gl_binding_) {
diff --git a/ui/ozone/demo/surfaceless_gl_renderer.cc b/ui/ozone/demo/surfaceless_gl_renderer.cc index 3cb84cff..bfb8b03 100644 --- a/ui/ozone/demo/surfaceless_gl_renderer.cc +++ b/ui/ozone/demo/surfaceless_gl_renderer.cc
@@ -101,11 +101,14 @@ glBindFramebufferEXT(GL_FRAMEBUFFER, gl_fb_); + // The imported pixmap can be externally sampled i.e. does not provide + // per-plane textures but provides a unified single texture object. pixmap_gl_binding_ = OzonePlatform::GetInstance() ->GetSurfaceFactoryOzone() ->GetCurrentGLOzone() - ->ImportNativePixmap(pixmap_, format, gfx::BufferPlane::DEFAULT, size, + ->ImportNativePixmap(pixmap_, format, + /*plane_index=*/std::nullopt, size, gfx::ColorSpace(), GL_TEXTURE_2D, gl_tex_); if (!pixmap_gl_binding_) {
diff --git a/ui/ozone/gl/native_pixmap_gl_binding_unittest.cc b/ui/ozone/gl/native_pixmap_gl_binding_unittest.cc index 76e6d484..9e9c0e97 100644 --- a/ui/ozone/gl/native_pixmap_gl_binding_unittest.cc +++ b/ui/ozone/gl/native_pixmap_gl_binding_unittest.cc
@@ -101,8 +101,10 @@ ->GetCurrentGLOzone(); EXPECT_TRUE(gl_ozone->CanImportNativePixmap(kFormat)); + // The imported pixmap can be externally sampled i.e. does not provide + // per-plane textures but provides a unified single texture object. auto binding = gl_ozone->ImportNativePixmap( - std::move(pixmap), kFormat, gfx::BufferPlane::DEFAULT, size, + std::move(pixmap), kFormat, /*plane_index=*/std::nullopt, size, gfx::ColorSpace(), GL_TEXTURE_EXTERNAL_OES, texture_id_); EXPECT_TRUE(binding); return binding;
diff --git a/ui/ozone/platform/drm/gpu/gbm_surface_factory.cc b/ui/ozone/platform/drm/gpu/gbm_surface_factory.cc index 1281eb8..9d8d94a 100644 --- a/ui/ozone/platform/drm/gpu/gbm_surface_factory.cc +++ b/ui/ozone/platform/drm/gpu/gbm_surface_factory.cc
@@ -190,12 +190,12 @@ std::unique_ptr<NativePixmapGLBinding> ImportNativePixmap( scoped_refptr<gfx::NativePixmap> pixmap, viz::SharedImageFormat plane_format, - gfx::BufferPlane plane, + std::optional<int> plane_index, gfx::Size plane_size, const gfx::ColorSpace& color_space, GLenum target, GLuint texture_id) override { - return NativePixmapEGLBinding::Create(pixmap, plane_format, plane, + return NativePixmapEGLBinding::Create(pixmap, plane_format, plane_index, plane_size, color_space, target, texture_id); }
diff --git a/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc b/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc index 613d736e..2be9495 100644 --- a/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc +++ b/ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc
@@ -77,7 +77,7 @@ std::unique_ptr<NativePixmapGLBinding> ImportNativePixmap( scoped_refptr<gfx::NativePixmap> pixmap, viz::SharedImageFormat plane_format, - gfx::BufferPlane plane, + std::optional<int> plane_index, gfx::Size plane_size, const gfx::ColorSpace& color_space, GLenum target, @@ -100,13 +100,14 @@ std::unique_ptr<NativePixmapGLBinding> GLOzoneEGLWayland::ImportNativePixmap( scoped_refptr<gfx::NativePixmap> pixmap, viz::SharedImageFormat plane_format, - gfx::BufferPlane plane, + std::optional<int> plane_index, gfx::Size plane_size, const gfx::ColorSpace& color_space, GLenum target, GLuint texture_id) { - return NativePixmapEGLBinding::Create(pixmap, plane_format, plane, plane_size, - color_space, target, texture_id); + return NativePixmapEGLBinding::Create(pixmap, plane_format, plane_index, + plane_size, color_space, target, + texture_id); } scoped_refptr<gl::GLSurface> GLOzoneEGLWayland::CreateViewGLSurface(
diff --git a/ui/ozone/platform/x11/x11_surface_factory.cc b/ui/ozone/platform/x11/x11_surface_factory.cc index ba53f20..07dd820 100644 --- a/ui/ozone/platform/x11/x11_surface_factory.cc +++ b/ui/ozone/platform/x11/x11_surface_factory.cc
@@ -140,14 +140,14 @@ std::unique_ptr<NativePixmapGLBinding> ImportNativePixmap( scoped_refptr<gfx::NativePixmap> pixmap, viz::SharedImageFormat plane_format, - gfx::BufferPlane plane, + std::optional<int> plane_index, gfx::Size plane_size, const gfx::ColorSpace& color_space, GLenum target, GLuint texture_id) override { switch (GetNativePixmapSupportType()) { case NativePixmapSupportType::kDMABuf: { - return NativePixmapEGLBinding::Create(pixmap, plane_format, plane, + return NativePixmapEGLBinding::Create(pixmap, plane_format, plane_index, plane_size, color_space, target, texture_id); }
diff --git a/ui/ozone/public/gl_ozone.h b/ui/ozone/public/gl_ozone.h index 2e5806f..71d8e2c 100644 --- a/ui/ozone/public/gl_ozone.h +++ b/ui/ozone/public/gl_ozone.h
@@ -5,6 +5,7 @@ #ifndef UI_OZONE_PUBLIC_GL_OZONE_H_ #define UI_OZONE_PUBLIC_GL_OZONE_H_ +#include <optional> #include <string> #include "base/component_export.h" @@ -78,7 +79,7 @@ virtual std::unique_ptr<NativePixmapGLBinding> ImportNativePixmap( scoped_refptr<gfx::NativePixmap> pixmap, viz::SharedImageFormat plane_format, - gfx::BufferPlane plane, + std::optional<int> plane_index, gfx::Size plane_size, const gfx::ColorSpace& color_space, GLenum target,
diff --git a/ui/ozone/public/native_pixmap_gl_binding.h b/ui/ozone/public/native_pixmap_gl_binding.h index f4fed1f..839c85a 100644 --- a/ui/ozone/public/native_pixmap_gl_binding.h +++ b/ui/ozone/public/native_pixmap_gl_binding.h
@@ -6,7 +6,6 @@ #define UI_OZONE_PUBLIC_NATIVE_PIXMAP_GL_BINDING_H_ #include "base/component_export.h" -#include "ui/gfx/buffer_types.h" typedef unsigned int GLuint; typedef unsigned int GLenum;
diff --git a/ui/views/view.h b/ui/views/view.h index d410372..97c8014 100644 --- a/ui/views/view.h +++ b/ui/views/view.h
@@ -66,7 +66,6 @@ class InfoBarView; class OmniboxPopupPresenter; class OmniboxPopupViewViews; -class SadTabView; class StatusIconButtonLinux; namespace arc { @@ -316,7 +315,6 @@ friend class ::InfoBarView; friend class ::OmniboxPopupPresenter; friend class ::OmniboxPopupViewViews; - friend class ::SadTabView; friend class ::StatusIconButtonLinux; friend class ::arc::CustomTab; friend class ::ash::ArcNotificationContentView;