diff --git a/.gemini/commands/cr/test/silence-test-ios.toml b/.gemini/commands/cr/test/silence-test-ios.toml index be9522263..1f02813 100644 --- a/.gemini/commands/cr/test/silence-test-ios.toml +++ b/.gemini/commands/cr/test/silence-test-ios.toml
@@ -18,53 +18,79 @@ * Identify the required `<test_name>`, required `<silencing_type>` (`DISABLED` or `FLAKY`), and optional `<conditions>` from the user's input. - * Example input: `/silence-test-ios testMyFeature FLAKY only on iphone - device` + * Example input: `/cr:test:silence-test-ios testMyFeature FLAKY only on + iphone device` * **If you can't parse all the required arguments:** Do not proceed with this task and print out the missing arguments to the user. 2. **Locate and read the Test File:** - * Use codesearch to find the file containing the `<test_name>`. - * Separate the test suite name from the test suite name when doing your - search. Example: TestSuite/testCase => TestSuite testCase. + * Use codesearch to find the file containing the `<test_name>`. Do not use + any language filter which are too strict. + * Separate the test suite name in 2 distinct search items from the test + suite name when doing your search. Example: TestSuite/testCase => + "TestSuite", "testCase". 3. **Determine and Apply Silencing Strategy:** * Based on the file content and user-provided `<conditions>`, choose the correct silencing method. * **For Objective-C/C++ (`.mm`, `.cc`):** + * **No Conditions:** Rename the test by adding the `<silencing_type>_` prefix. * `TEST_F(MySuite, testMyFeature)` -> `TEST_F(MySuite, <silencing_type>_testMyFeature)` * `- (void)testMyFeature` -> `- (void)<silencing_type>_testMyFeature` + * **Important for Objective-C instance methods:** If the test + method is referenced by a `@selector`, you MUST also update the + selector to match the new test name (e.g., + `@selector(testMyFeature)` -> + `@selector(<silencing_type>_testMyFeature)`). Search for usages + of the selector in the same file. * **With Conditions (e.g., "iphone device", "ipad simulator", "no multiwindow"):** + * **Compile-time conditions for tests on iOS (e.g., simulator vs. device):** Rename the test to `MAYBE_testMyFeature` and add a preprocessor block. For "iphone device": `objectivec #if !TARGET_OS_SIMULATOR #define MAYBE_testMyFeature DISABLED_testMyFeature #else #define MAYBE_testMyFeature - testMyFeature #endif` + testMyFeature #endif`. * **Runtime conditions (e.g., OS version, form factor, multi-window):** Add a guard at the beginning of the test method. + * **OS Version:** `if (@available(iOS <os_version>, *)) { EARL_GREY_TEST_SKIPPED(@"Reason"); }` + * **Starting from an iOS version and beyond:** + `base::ios::IsRunningOnIOS<os_version>OrLater() { + EARL_GREY_TEST_SKIPPED(@"Reason");}`. + + Pick one of the helpers in @base/ios/ios_util.mm. Replace + the <os_version> by the OS version to start silencing from + (e.g. IsRunningOnIOS26OrLater() for silencing the test when + running on iOS 26 or later). Commit + afc85ffeb6f263217e983d0c5cf2f4e2d4af50d5 shows an example of + that. + * **Form Factor:** `if ([ChromeEarlGrey isCompactWidth]) { EARL_GREY_TEST_SKIPPED(@"Reason"); }` + * **Multi-window:** `if (![ChromeEarlGrey areMultipleWindowsSupported]) { EARL_GREY_TEST_DISABLED(@"Reason"); }` + * **For Java (`.java`):** + * Add the `@DisabledTest(message = "crbug.com/...")` annotation above the test method. You will need to create a bug first to get the bug number. -4. **Create a Bug:** +4. **Create a Bug iff needed:** + * Search for an existing bug for the test case before creating a new one. * Create a bug base on this template: * **Title:** `[iOS Gardener] Test <test_name> failing on waterfall <bot_name>` @@ -91,6 +117,8 @@ freeze` to temporarily commit those changes in the current git branch before switching. * Apply the silencing change to the test file. + * Make sure that the needed headers are included. + * If there are missing dependencies in the BUILD.gn file, add them. * Add the file to git and commit the change. The commit message should be: ``` [iOS][Gardener] Disable flaky test <test_name>
diff --git a/DEPS b/DEPS index aa71c000..f90f1a3d 100644 --- a/DEPS +++ b/DEPS
@@ -240,7 +240,7 @@ # luci-go CIPD package version. # Make sure the revision is uploaded by infra-packagers builder. # https://ci.chromium.org/p/infra-internal/g/infra-packagers/console - 'luci_go': 'git_revision:0ff6e38043daec0a3e3bea265820a63eab6bf21d', + 'luci_go': 'git_revision:f58a984f19eeed333afe8ec2e167cb7197dca7f5', # This can be overridden, e.g. with custom_vars, to build clang from HEAD # instead of downloading the prebuilt pinned revision. @@ -305,11 +305,11 @@ # 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': '3ddd5788bd026e3ed8a3f674c273a6030f54e656', + 'src_internal_revision': '603b258ff4ab92ff387e07b3f47c447a52595968', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Skia # and whatever else without interference from each other. - 'skia_revision': '359f3d7cc7edfcb93e99ab5ed7e9f2f5fdd8ef85', + 'skia_revision': '7c2f502e3304f4c76413696a9035470c32ac6dde', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. @@ -397,7 +397,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling devtools-frontend # and whatever else without interference from each other. - 'devtools_frontend_revision': '5c0782b1ec316a185616cd76223d66c8b2b4dbbb', + 'devtools_frontend_revision': '6f55537aa2a782d9e8093896c857add3a88d01f3', # 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. @@ -457,7 +457,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling crabbyavif # and whatever else without interference from each other. - 'crabbyavif_revision': '4c70b98d1ebc8a210f2919be7ccabbcf23061cb5', + 'crabbyavif_revision': 'f96d484aa93776f076a2949805fd29bc4888b1c4', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Speedometer main # and whatever else without interference from each other. @@ -1212,7 +1212,7 @@ 'packages': [ { 'package': 'chromium/chrome/android/orderfiles/arm64', - 'version': 'Y0AWgaivgW6UpW6RIrsVLJG2p5zUsR7Hrx1g6tkWnN0C', + 'version': 'RoROg2UqfYdfWdYUSX5JU9ETgBOP1MPow11HloSRmwQC', }, ], 'condition': 'checkout_android', @@ -1618,7 +1618,7 @@ }, 'src/docs/website': { - 'url': Var('chromium_git') + '/website.git' + '@' + 'c6edf98e7fab2385c90caac6211f00b62e9b773d', + 'url': Var('chromium_git') + '/website.git' + '@' + '4f4404f68b21aefa24a86abad8fb0cf01a0db7fc', }, 'src/ios/third_party/earl_grey2/src': { @@ -2096,7 +2096,7 @@ Var('chromium_git') + '/chromium/web-tests.git' + '@' + Var('crossbench_web_tests_revision'), 'src/third_party/depot_tools': - Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '83ad19235f633ab5ac843b3e977d53f4b7b042a5', + Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '0e9c00fe9f9783104836bd68870b5253c8c76121', 'src/third_party/devtools-frontend/src': Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'), @@ -2641,7 +2641,7 @@ Var('pdfium_git') + '/pdfium.git' + '@' + Var('pdfium_revision'), 'src/third_party/perfetto': - Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + 'd17b40b3b5e36f3744f1d010fe3ba2d3c55559c0', + Var('chromium_git') + '/external/github.com/google/perfetto.git' + '@' + '49af9ecad055e597f1cc7d57677454b72606e96d', 'src/base/tracing/test/data': { 'bucket': 'perfetto', @@ -3012,7 +3012,7 @@ Var('chromium_git') + '/webpagereplay.git' + '@' + Var('webpagereplay_revision'), 'src/third_party/webrtc': - Var('webrtc_git') + '/src.git' + '@' + 'db0e64c5e80168839d74ae0cc6173ccd537ae211', + Var('webrtc_git') + '/src.git' + '@' + '35d9a8f86a90ddfebd9c8b5f6ddce14b68a1a879', # Wuffs' canonical repository is at github.com/google/wuffs, but we use # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file. @@ -3793,7 +3793,7 @@ 'src/ios_internal': { 'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' + - '684211821a75efa2da3fad3310385a55308a3da6', + '30018259e2511654b23013f465ac3d119a7e4a35', 'condition': 'checkout_ios and checkout_src_internal', },
diff --git a/ash/hud_display/fps_graph_page_view.cc b/ash/hud_display/fps_graph_page_view.cc index 85a5612..5bda265 100644 --- a/ash/hud_display/fps_graph_page_view.cc +++ b/ash/hud_display/fps_graph_page_view.cc
@@ -118,6 +118,7 @@ } void FPSGraphPageView::OnDidPresentCompositorFrame( + ui::Compositor* compositor, uint32_t frame_token, const gfx::PresentationFeedback& feedback) { UpdateStats(feedback);
diff --git a/ash/hud_display/fps_graph_page_view.h b/ash/hud_display/fps_graph_page_view.h index c9e7176..217d0e0 100644 --- a/ash/hud_display/fps_graph_page_view.h +++ b/ash/hud_display/fps_graph_page_view.h
@@ -47,6 +47,7 @@ // ui::CompositorObserver: void OnDidPresentCompositorFrame( + ui::Compositor* compositor, uint32_t frame_token, const gfx::PresentationFeedback& feedback) override;
diff --git a/ash/system/power/power_event_observer.cc b/ash/system/power/power_event_observer.cc index 8694320..7efb610 100644 --- a/ash/system/power/power_event_observer.cc +++ b/ash/system/power/power_event_observer.cc
@@ -32,6 +32,7 @@ #include "ui/compositor/compositor.h" #include "ui/compositor/compositor_observer.h" #include "ui/display/manager/display_configurator.h" +#include "ui/gfx/presentation_feedback.h" // TODO(b/248107965): Remove after figuring out the root cause of the bug #undef ENABLED_VLOG_LEVEL @@ -100,7 +101,10 @@ } pending_compositing_[compositor].state = CompositingState::kWaitingForEnded; } - void OnCompositingAckDeprecated(ui::Compositor* compositor) override { + void OnDidPresentCompositorFrame( + ui::Compositor* compositor, + uint32_t frame_token, + const gfx::PresentationFeedback& feedback) override { if (!pending_compositing_.count(compositor)) return; CompositorInfo& compositor_info = pending_compositing_[compositor]; @@ -119,6 +123,7 @@ RunCallbackIfAllCompositingEnded(); } + void OnCompositingShuttingDown(ui::Compositor* compositor) override { compositor_observations_.RemoveObservation(compositor); pending_compositing_.erase(compositor);
diff --git a/ash/system/power/power_event_observer_test_api.cc b/ash/system/power/power_event_observer_test_api.cc index 8f7b944a7..cad59faf 100644 --- a/ash/system/power/power_event_observer_test_api.cc +++ b/ash/system/power/power_event_observer_test_api.cc
@@ -7,6 +7,7 @@ #include "ash/system/power/power_event_observer.h" #include "base/time/time.h" #include "ui/compositor/compositor_observer.h" +#include "ui/gfx/presentation_feedback.h" namespace ash { @@ -40,8 +41,8 @@ ui::Compositor* compositor) { if (!power_event_observer_->compositor_watcher_.get()) return; - power_event_observer_->compositor_watcher_->OnCompositingAckDeprecated( - compositor); + power_event_observer_->compositor_watcher_->OnDidPresentCompositorFrame( + compositor, /*frame_token=*/0, gfx::PresentationFeedback()); } void PowerEventObserverTestApi::CompositeFrame(ui::Compositor* compositor) { @@ -51,8 +52,8 @@ compositor); power_event_observer_->compositor_watcher_->OnCompositingStarted( compositor, base::TimeTicks()); - power_event_observer_->compositor_watcher_->OnCompositingAckDeprecated( - compositor); + power_event_observer_->compositor_watcher_->OnDidPresentCompositorFrame( + compositor, /*frame_token=*/0, gfx::PresentationFeedback()); } bool PowerEventObserverTestApi::SimulateCompositorsReadyForSuspend() {
diff --git a/base/android/java/src/org/chromium/base/AconfigFlaggedApiDelegate.java b/base/android/java/src/org/chromium/base/AconfigFlaggedApiDelegate.java index 1d398ec..feb8b032 100644 --- a/base/android/java/src/org/chromium/base/AconfigFlaggedApiDelegate.java +++ b/base/android/java/src/org/chromium/base/AconfigFlaggedApiDelegate.java
@@ -50,16 +50,6 @@ } /** - * Checks if the display topology is available, based on the API level and Aconfig flags. - * - * @deprecated Use {@link #isDisplayTopologyAvailable(DisplayManager)} instead. - */ - @Deprecated - default boolean isDisplayTopologyAvailable() { - return false; - } - - /** * Checks if the display topology is available, based on the API level, Aconfig flags and * Display Topology state. *
diff --git a/build/android/gyp/lint.py b/build/android/gyp/lint.py index c718887..61986747e 100755 --- a/build/android/gyp/lint.py +++ b/build/android/gyp/lint.py
@@ -333,9 +333,6 @@ # If all the warnings are filtered, we should not fail on the final # summary line. r'\d+ errors?, \d+ warnings?', - # Not sure why, these were new starting 32.0.0-alpha01. Remove when - # https://crbug.com/439854682 is fixed. - 'WARNING: skipping method null', ] return build_utils.FilterLines(output, '|'.join(filter_patterns))
diff --git a/build/config/locales.gni b/build/config/locales.gni index 37632eb..4d503e1 100644 --- a/build/config/locales.gni +++ b/build/config/locales.gni
@@ -16,7 +16,7 @@ # pass the "--translate-genders" flag into GRIT to generate 4 translation # files per locale ("", "_FEMININE", "_MASCULINE", "_NEUTER") instead of # one. - translate_genders = true + translate_genders = !is_ios } # The following additional platform specific lists are created:
diff --git a/chrome/android/java/res/layout/incognito_indicator.xml b/chrome/android/java/res/layout/incognito_indicator.xml index e4a7a801..afa3a963 100644 --- a/chrome/android/java/res/layout/incognito_indicator.xml +++ b/chrome/android/java/res/layout/incognito_indicator.xml
@@ -4,27 +4,34 @@ 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" - android:orientation="horizontal" - android:layout_height="match_parent" +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" - android:clickable="false" - android:focusable="true" + android:layout_height="match_parent" android:visibility="gone" - android:gravity="center_vertical" - android:paddingStart="@dimen/incognito_indicator_lateral_start_padding" - android:paddingEnd="@dimen/incognito_indicator_lateral_end_padding" - android:background="@drawable/incognito_indicator_background"> - <ImageView + android:paddingVertical="@dimen/location_bar_vertical_margin" + android:paddingHorizontal="@dimen/incognito_indicator_lateral_margin"> + <LinearLayout + android:orientation="horizontal" + android:layout_height="match_parent" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:contentDescription="@string/accessibility_incognito_badge" - android:src="@drawable/ic_incognito"/> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/incognito_indicator_text_lateral_start_margin" - android:textAppearance="@style/TextAppearance.TextLarge.Secondary.Baseline.Light" - android:text="@string/incognito_badge" /> -</LinearLayout> + android:clickable="false" + android:focusable="true" + android:gravity="center_vertical" + android:paddingStart="@dimen/incognito_indicator_lateral_start_padding" + android:paddingEnd="@dimen/incognito_indicator_lateral_end_padding" + android:background="@drawable/incognito_indicator_background" + tools:ignore="UselessParent"> + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@string/accessibility_incognito_badge" + android:src="@drawable/ic_incognito"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/incognito_indicator_text_lateral_start_margin" + android:textAppearance="@style/TextAppearance.TextLarge.Secondary.Baseline.Light" + android:text="@string/incognito_badge" /> + </LinearLayout> +</FrameLayout> \ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/appearance/settings/AppearanceSettingsFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/appearance/settings/AppearanceSettingsFragment.java index 8aa952d8..efd54ab0 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/appearance/settings/AppearanceSettingsFragment.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/appearance/settings/AppearanceSettingsFragment.java
@@ -5,13 +5,17 @@ package org.chromium.chrome.browser.appearance.settings; import android.content.Context; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.os.Bundle; +import org.chromium.base.ContextUtils; +import org.chromium.base.DeviceInfo; import org.chromium.base.supplier.ObservableSupplier; import org.chromium.base.supplier.ObservableSupplierImpl; import org.chromium.build.annotations.NullMarked; import org.chromium.build.annotations.Nullable; import org.chromium.chrome.R; +import org.chromium.chrome.browser.bookmarks.bar.BookmarkBarConstants; import org.chromium.chrome.browser.bookmarks.bar.BookmarkBarUtils; import org.chromium.chrome.browser.night_mode.NightModeMetrics.ThemeSettingsEntry; import org.chromium.chrome.browser.night_mode.NightModeUtils; @@ -36,15 +40,20 @@ public static final String PREF_UI_THEME = "ui_theme"; private final ObservableSupplierImpl<String> mPageTitle = new ObservableSupplierImpl<>(); + private boolean mUseProfileUserPrefs; private @Nullable PrefChangeRegistrar mPrefChangeRegistrar; private @Nullable PrefObserver mPrefObserver; + private @Nullable OnSharedPreferenceChangeListener mDevicePrefsListener; @Override public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { mPageTitle.set(getTitle(getContext())); SettingsUtils.addPreferencesFromResource(this, R.xml.appearance_preferences); + // This fragment may be used on Desktop or tablets. For Desktop we use the current Profile's + // UserPrefs. For tablets, we use the local device preference. + mUseProfileUserPrefs = DeviceInfo.isDesktop(); initBookmarkBarPref(); initToolbarShortcutPref(); initUiThemePref(); @@ -59,6 +68,11 @@ mPrefChangeRegistrar.destroy(); mPrefChangeRegistrar = null; } + if (mDevicePrefsListener != null) { + ContextUtils.getAppSharedPreferences() + .unregisterOnSharedPreferenceChangeListener(mDevicePrefsListener); + mDevicePrefsListener = null; + } } @Override @@ -92,6 +106,14 @@ return; } + if (mUseProfileUserPrefs) { + initBookmarkBarPrefForUserPrefs(); + } else { + initBookmarkBarPrefForDevicePreference(); + } + } + + private void initBookmarkBarPrefForUserPrefs() { mPrefChangeRegistrar = PrefServiceUtil.createFor(getProfile()); mPrefObserver = new PrefObserver() { @@ -113,6 +135,28 @@ }); } + private void initBookmarkBarPrefForDevicePreference() { + // Similar to UserPrefs above, we must have both an observer of changes to the device prefs, + // as well as the ability to set the device prefs via the toggle, since the value can be + // toggled by another window. + mDevicePrefsListener = + (sharedPreferences, key) -> { + if (key != null + && key.equals(BookmarkBarConstants.BOOKMARK_BAR_SHOW_BOOKMARK_BAR)) { + updateBookmarkBarPref(); + } + }; + ContextUtils.getAppSharedPreferences() + .registerOnSharedPreferenceChangeListener(mDevicePrefsListener); + + ((ChromeSwitchPreference) findPreference(PREF_BOOKMARK_BAR)) + .setOnPreferenceChangeListener( + (pref, newValue) -> { + BookmarkBarUtils.setDevicePrefShowBookmarksBar((boolean) newValue); + return true; + }); + } + private void initToolbarShortcutPref() { // LINT.IfChange(InitPrefToolbarShortcut) new AdaptiveToolbarStatePredictor( @@ -143,9 +187,16 @@ } private void updateBookmarkBarPref() { - if (BookmarkBarUtils.isDeviceBookmarkBarCompatible(getContext())) { + if (!BookmarkBarUtils.isDeviceBookmarkBarCompatible(getContext())) { + return; + } + + if (mUseProfileUserPrefs) { ((ChromeSwitchPreference) findPreference(PREF_BOOKMARK_BAR)) .setChecked(BookmarkBarUtils.isUserPrefsShowBookmarksBarEnabled(getProfile())); + } else { + ((ChromeSwitchPreference) findPreference(PREF_BOOKMARK_BAR)) + .setChecked(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled()); } }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/TabStripDragHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/TabStripDragHandler.java index 8787a830..bd0f780a 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/TabStripDragHandler.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/reorder/TabStripDragHandler.java
@@ -37,6 +37,7 @@ import org.chromium.chrome.browser.dragdrop.ChromeDragDropUtils; import org.chromium.chrome.browser.dragdrop.ChromeDropDataAndroid; import org.chromium.chrome.browser.dragdrop.ChromeTabDropDataAndroid; +import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.multiwindow.MultiInstanceManager; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab_ui.TabContentManager; @@ -599,6 +600,8 @@ } public static boolean isDraggedItemPinned() { + if (!ChromeFeatureList.sAndroidPinnedTabs.isEnabled()) return false; + @Nullable Tab tab = ChromeDragDropUtils.getTabFromGlobalState( getDragDropGlobalState(/* dragEvent= */ null));
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/FragmentDependencyProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/FragmentDependencyProvider.java index 1e46a04..c04a6e21 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/settings/FragmentDependencyProvider.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/FragmentDependencyProvider.java
@@ -57,7 +57,6 @@ import org.chromium.components.privacy_sandbox.IncognitoTrackingProtectionsFragment; import org.chromium.components.privacy_sandbox.IpProtectionSettingsFragment; import org.chromium.components.privacy_sandbox.TrackingProtectionSettings; -import org.chromium.components.user_prefs.UserPrefs; import org.chromium.ui.modaldialog.ModalDialogManager; /** Provides dependencies to fragments in the settings activity. */ @@ -123,7 +122,6 @@ new SafetyCheckBridge(mProfile), mModalDialogManagerSupplier, SyncServiceFactory.getForProfile(mProfile), - UserPrefs.get(mProfile), new PasswordStoreBridge(mProfile), PasswordManagerHelper.getForProfile(mProfile), new SettingsCustomTabLauncherImpl());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/KeyboardFocusRowManager.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/KeyboardFocusRowManager.java index 7a636eca..5c24deb8 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/KeyboardFocusRowManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/KeyboardFocusRowManager.java
@@ -10,6 +10,7 @@ import org.chromium.chrome.browser.bookmarks.bar.BookmarkBarCoordinator; import org.chromium.chrome.browser.compositor.CompositorViewHolder; import org.chromium.chrome.browser.compositor.overlays.strip.StripLayoutHelperManager; +import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.tab.TabObscuringHandler; import org.chromium.chrome.browser.toolbar.ToolbarManager; import org.chromium.chrome.browser.toolbar.top.tab_strip.StripVisibilityState; @@ -150,9 +151,11 @@ } // The next item in the focus cycle order is BOOKMARKS_BAR, if it is present. - var bookmarkBarCoordinator = mBookmarkBarCoordinatorSupplier.get(); - if (bookmarkBarCoordinator != null && bookmarkBarCoordinator.isVisible()) { - keyboardFocusRows.add(KeyboardFocusRow.BOOKMARKS_BAR); + if (ChromeFeatureList.sAndroidBookmarkBar.isEnabled()) { + var bookmarkBarCoordinator = mBookmarkBarCoordinatorSupplier.get(); + if (bookmarkBarCoordinator != null && bookmarkBarCoordinator.isVisible()) { + keyboardFocusRows.add(KeyboardFocusRow.BOOKMARKS_BAR); + } } int currentFocusIndex = keyboardFocusRows.indexOf(oldKeyboardFocusRow);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java index 2beb693..5a90f67 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegate.java
@@ -282,7 +282,10 @@ buildModelForDivider(R.id.divider_line_id))); // Page Zoom - if (shouldShowPageZoomItem(currentTab)) modelList.add(buildPageZoomItem(currentTab)); + // Disable page zoom menu item on Reading Mode pages. + if (shouldShowPageZoomItem(currentTab) && !shouldShowReaderModePrefs(currentTab)) { + modelList.add(buildPageZoomItem(currentTab)); + } // Share if (ShareUtils.shouldEnableShare(currentTab)) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java index b1f280e..0d1b4b9 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
@@ -25,6 +25,7 @@ import org.chromium.base.ApplicationStatus; import org.chromium.base.Callback; import org.chromium.base.CommandLine; +import org.chromium.base.DeviceInfo; import org.chromium.base.Log; import org.chromium.base.ResettersForTesting; import org.chromium.base.Token; @@ -1896,7 +1897,11 @@ return true; } else if (id == R.id.toggle_bookmark_bar) { if (BookmarkBarUtils.isActivityStateBookmarkBarCompatible(mActivity)) { - BookmarkBarUtils.toggleUserPrefsShowBookmarksBar(mProfileSupplier.get()); + if (DeviceInfo.isDesktop()) { + BookmarkBarUtils.toggleUserPrefsShowBookmarksBar(mProfileSupplier.get()); + } else { + BookmarkBarUtils.toggleDevicePrefShowBookmarksBar(); + } return true; } }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/appearance/settings/AppearanceSettingsFragmentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/appearance/settings/AppearanceSettingsFragmentTest.java index 1d3ac37c..08af55c 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/appearance/settings/AppearanceSettingsFragmentTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/appearance/settings/AppearanceSettingsFragmentTest.java
@@ -33,11 +33,13 @@ import org.mockito.stubbing.Answer; import org.chromium.base.Callback; +import org.chromium.base.FeatureOverrides; import org.chromium.base.ThreadUtils; import org.chromium.base.supplier.ObservableSupplierImpl; import org.chromium.base.test.BaseJUnit4ClassRunner; import org.chromium.base.test.util.Batch; import org.chromium.chrome.browser.bookmarks.bar.BookmarkBarUtils; +import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.night_mode.NightModeMetrics.ThemeSettingsEntry; import org.chromium.chrome.browser.night_mode.NightModeUtils; import org.chromium.chrome.browser.night_mode.ThemeType; @@ -47,6 +49,7 @@ import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.toolbar.adaptive.AdaptiveToolbarStatePredictor; import org.chromium.chrome.browser.toolbar.adaptive.settings.AdaptiveToolbarSettingsFragment; +import org.chromium.chrome.test.OverrideContextWrapperTestRule; import org.chromium.components.browser_ui.settings.BlankUiTestActivitySettingsTestRule; import org.chromium.components.browser_ui.settings.ChromeSwitchPreference; import org.chromium.components.prefs.PrefChangeRegistrar; @@ -68,6 +71,10 @@ public final BlankUiTestActivitySettingsTestRule mSettingsTestRule = new BlankUiTestActivitySettingsTestRule(); + @Rule + public OverrideContextWrapperTestRule mOverrideContextRule = + new OverrideContextWrapperTestRule(); + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Mock private PrefChangeRegistrar.Natives mPrefChangeRegistrarJni; @@ -107,6 +114,12 @@ doAnswer(runCallbackWithValueAtIndex(mBookmarkBarSettingSupplier::set, 1)) .when(mPrefService) .setBoolean(eq(Pref.SHOW_BOOKMARK_BAR), anyBoolean()); + + // Explicitly override FeatureParam for consistency. + FeatureOverrides.Builder overrides = FeatureOverrides.newBuilder(); + overrides = + overrides.param(ChromeFeatureList.ANDROID_BOOKMARK_BAR, "show_bookmark_bar", true); + overrides.apply(); } @AfterClass @@ -133,7 +146,9 @@ @Test @SmallTest - public void testBookmarkBarPreferenceUpdatesSettingWhenChanged() { + public void testBookmarkBarPreferenceUpdatesSettingWhenChanged_Desktop() { + mOverrideContextRule.setIsDesktop(true); + ThreadUtils.runOnUiThreadBlocking(() -> mBookmarkBarSettingSupplier.set(true)); BookmarkBarUtils.setDeviceBookmarkBarCompatibleForTesting(true); launchSettings(); @@ -152,7 +167,9 @@ @Test @SmallTest - public void testBookmarkBarPreferenceIsUpdatedWhenSettingChanges() { + public void testBookmarkBarPreferenceIsUpdatedWhenSettingChanges_Desktop() { + mOverrideContextRule.setIsDesktop(true); + ThreadUtils.runOnUiThreadBlocking(() -> mBookmarkBarSettingSupplier.set(true)); BookmarkBarUtils.setDeviceBookmarkBarCompatibleForTesting(true); launchSettings(); @@ -169,6 +186,48 @@ @Test @SmallTest + public void testBookmarkBarPreferenceUpdatesSettingWhenChanged_Tablet() { + mOverrideContextRule.setIsDesktop(false); + + BookmarkBarUtils.setDeviceBookmarkBarCompatibleForTesting(true); + launchSettings(); + + final var bookmarkBarPref = assertSwitchExists(PREF_BOOKMARK_BAR); + Assert.assertTrue(bookmarkBarPref.isChecked()); + + ThreadUtils.runOnUiThreadBlocking(bookmarkBarPref::performClick); + Assert.assertFalse(bookmarkBarPref.isChecked()); + Assert.assertFalse(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled()); + Assert.assertTrue(BookmarkBarUtils.hasUserSetDevicePrefShowBookmarksBar()); + + ThreadUtils.runOnUiThreadBlocking(bookmarkBarPref::performClick); + Assert.assertTrue(bookmarkBarPref.isChecked()); + Assert.assertTrue(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled()); + Assert.assertTrue(BookmarkBarUtils.hasUserSetDevicePrefShowBookmarksBar()); + } + + @Test + @SmallTest + public void testBookmarkBarPreferenceIsUpdatedWhenSettingChanges_Tablet() { + mOverrideContextRule.setIsDesktop(false); + + BookmarkBarUtils.setDeviceBookmarkBarCompatibleForTesting(true); + launchSettings(); + + final var bookmarkBarPref = assertSwitchExists(PREF_BOOKMARK_BAR); + Assert.assertTrue(bookmarkBarPref.isChecked()); + + ThreadUtils.runOnUiThreadBlocking( + () -> BookmarkBarUtils.setDevicePrefShowBookmarksBar(false)); + Assert.assertFalse(bookmarkBarPref.isChecked()); + + ThreadUtils.runOnUiThreadBlocking( + () -> BookmarkBarUtils.setDevicePrefShowBookmarksBar(true)); + Assert.assertTrue(bookmarkBarPref.isChecked()); + } + + @Test + @SmallTest public void testToolbarShortcutPreferenceIsAbsentWhenDisabled() { AdaptiveToolbarStatePredictor.setToolbarStateForTesting(NONE); launchSettings();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/settings/PasswordsPreferenceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/settings/PasswordsPreferenceTest.java index 5202de11..84c7ef3 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/settings/PasswordsPreferenceTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/settings/PasswordsPreferenceTest.java
@@ -4,8 +4,7 @@ package org.chromium.chrome.browser.password_manager.settings; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.when; import androidx.test.filters.MediumTest; @@ -30,7 +29,6 @@ import org.chromium.chrome.browser.password_manager.PasswordManagerTestHelper; import org.chromium.chrome.browser.password_manager.PasswordManagerUtilBridge; import org.chromium.chrome.browser.password_manager.PasswordManagerUtilBridgeJni; -import org.chromium.chrome.browser.preferences.Pref; import org.chromium.chrome.browser.settings.MainSettings; import org.chromium.chrome.browser.settings.SettingsActivityTestRule; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; @@ -38,7 +36,6 @@ import org.chromium.chrome.test.transit.settings.SettingsActivityPublicTransitEntryPoints; import org.chromium.chrome.test.transit.settings.SettingsStation; import org.chromium.chrome.test.util.ChromeRenderTestRule; -import org.chromium.components.prefs.PrefService; import org.chromium.ui.test.util.DeviceRestriction; import org.chromium.ui.test.util.RenderTestRule.Component; @@ -66,8 +63,6 @@ @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); - @Mock PrefService mPrefService; - @Mock private PasswordManagerUtilBridge.Natives mPasswordManagerUtilBridgeJniMock; @Mock private LoginDbDeprecationUtilBridge.Natives mLoginDbDeprecationUtilBridgeJniMock; @@ -79,17 +74,13 @@ PasswordManagerUtilBridgeJni.setInstanceForTesting(mPasswordManagerUtilBridgeJniMock); LoginDbDeprecationUtilBridgeJni.setInstanceForTesting(mLoginDbDeprecationUtilBridgeJniMock); PasswordManagerTestHelper.setUpGmsCoreFakeBackends(); - - PasswordsPreference.setPrefServiceForTesting(mPrefService); } @Test @MediumTest @Feature({"RenderTest"}) public void testPwmStoppedWorkingSubtitle() throws IOException { - when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(any(), eq(true))) - .thenReturn(false); - when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true); + when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false); when(mLoginDbDeprecationUtilBridgeJniMock.getAutoExportCsvFilePath(any())) .thenReturn("random/file/path"); @@ -106,9 +97,7 @@ @Feature({"RenderTest"}) @Restriction({DeviceRestriction.RESTRICTION_TYPE_NON_AUTO}) public void testSomePasswordsNotAccessibleSubtitle() throws IOException { - when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(any(), eq(true))) - .thenReturn(true); - when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true); + when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(true); File fakeCsv = File.createTempFile("passwords", null, null); fakeCsv.deleteOnExit(); when(mLoginDbDeprecationUtilBridgeJniMock.getAutoExportCsvFilePath(any())) @@ -127,9 +116,7 @@ @Feature({"RenderTest"}) @Restriction({DeviceRestriction.RESTRICTION_TYPE_AUTO}) public void testSomePasswordsNotAccessibleSubtitleNotDisplayedOnAuto() throws IOException { - when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(any(), eq(true))) - .thenReturn(true); - when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true); + when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(true); File fakeCsv = File.createTempFile("passwords", null, null); fakeCsv.deleteOnExit(); when(mLoginDbDeprecationUtilBridgeJniMock.getAutoExportCsvFilePath(any()))
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabbed_mode/KeyboardFocusRowManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabbed_mode/KeyboardFocusRowManagerTest.java index 978e92344..065801b 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabbed_mode/KeyboardFocusRowManagerTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabbed_mode/KeyboardFocusRowManagerTest.java
@@ -17,6 +17,7 @@ import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; @@ -25,19 +26,23 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import org.chromium.base.FeatureOverrides; import org.chromium.base.ThreadUtils; import org.chromium.base.test.util.Batch; import org.chromium.base.test.util.CallbackHelper; import org.chromium.base.test.util.CommandLineFlags; 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.Restriction; import org.chromium.chrome.R; import org.chromium.chrome.browser.ChromeTabbedActivity; +import org.chromium.chrome.browser.bookmarks.bar.BookmarkBarUtils; import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.tab.TabObscuringHandler; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; +import org.chromium.chrome.test.OverrideContextWrapperTestRule; import org.chromium.chrome.test.transit.ChromeTransitTestRules; import org.chromium.chrome.test.transit.ReusedCtaTransitTestRule; import org.chromium.chrome.test.transit.page.WebPageStation; @@ -55,12 +60,17 @@ @RunWith(ChromeJUnit4ClassRunner.class) @Batch(Batch.PER_CLASS) @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) +@DisableFeatures(ChromeFeatureList.ANDROID_BOOKMARK_BAR) public class KeyboardFocusRowManagerTest { @Rule public ReusedCtaTransitTestRule<WebPageStation> mActivityTestRule = ChromeTransitTestRules.blankPageStartReusedActivityRule(); + @Rule + public OverrideContextWrapperTestRule mOverrideContextRule = + new OverrideContextWrapperTestRule(); + @Rule public MockitoRule mockito = MockitoJUnit.rule(); // todo delete if not needed private WebPageStation mPage; @@ -71,6 +81,12 @@ @BeforeClass public static void setUpClass() { TabbedRootUiCoordinator.setDisableTopControlsAnimationsForTesting(true); + + // Explicitly override FeatureParam for consistency. + FeatureOverrides.Builder overrides = FeatureOverrides.newBuilder(); + overrides = + overrides.param(ChromeFeatureList.ANDROID_BOOKMARK_BAR, "show_bookmark_bar", true); + overrides.apply(); } @Before @@ -80,6 +96,13 @@ mTabbedRootUiCoordinator = (TabbedRootUiCoordinator) mActivity.getRootUiCoordinatorForTesting(); mKeyboardFocusRowManager = mTabbedRootUiCoordinator.getKeyboardFocusRowManagerForTesting(); + mOverrideContextRule.setIsDesktop(true); + } + + @After + public void tearDown() { + setUserPrefsShowBookmarksBar(false); + setBookmarkBarFeatureParam(false); } @Test @@ -128,6 +151,9 @@ @Feature("KeyboardShortcuts") @EnableFeatures(ChromeFeatureList.ANDROID_BOOKMARK_BAR) public void testSwitchKeyboardFocusRow_withBookmarksBar() { + setBookmarkBarFeatureParam(true); + setUserPrefsShowBookmarksBar(true); + // Put something in the content view so we can focus on it. ChromeTabUtils.newTabFromMenu( InstrumentationRegistry.getInstrumentation(), mActivity, false, true); @@ -155,6 +181,9 @@ @Restriction(DeviceFormFactor.TABLET_OR_DESKTOP) @EnableFeatures(ChromeFeatureList.ANDROID_BOOKMARK_BAR) public void testSwitchKeyboardFocusRow_withBookmarkBarFocus() { + setBookmarkBarFeatureParam(true); + setUserPrefsShowBookmarksBar(true); + ThreadUtils.runOnUiThreadBlocking( mTabbedRootUiCoordinator::initializeBookmarkBarCoordinatorForTesting); @@ -306,4 +335,19 @@ KeyboardFocusRow.NONE, mKeyboardFocusRowManager.getKeyboardFocusRowForTesting()); } + + private void setUserPrefsShowBookmarksBar(boolean showBookmarksBar) { + ThreadUtils.runOnUiThreadBlocking( + () -> + BookmarkBarUtils.setUserPrefsShowBookmarksBar( + mActivity.getProfileProviderSupplier().get().getOriginalProfile(), + showBookmarksBar)); + } + + private void setBookmarkBarFeatureParam(boolean param) { + FeatureOverrides.Builder overrides = FeatureOverrides.newBuilder(); + overrides = + overrides.param(ChromeFeatureList.ANDROID_BOOKMARK_BAR, "show_bookmark_bar", param); + overrides.apply(); + } }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordCheckupLauncherTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordCheckupLauncherTest.java index f5369c8..6a39577 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordCheckupLauncherTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordCheckupLauncherTest.java
@@ -6,7 +6,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -39,15 +38,12 @@ import org.chromium.chrome.browser.safety_hub.SafetyHubFragment; import org.chromium.chrome.browser.settings.SettingsActivity; import org.chromium.chrome.browser.sync.SyncServiceFactory; -import org.chromium.components.prefs.PrefService; import org.chromium.components.signin.AccountManagerFacadeProvider; import org.chromium.components.signin.base.AccountInfo; import org.chromium.components.signin.test.util.FakeAccountManagerFacade; import org.chromium.components.signin.test.util.TestAccounts; import org.chromium.components.sync.SyncService; import org.chromium.components.sync.UserSelectableType; -import org.chromium.components.user_prefs.UserPrefs; -import org.chromium.components.user_prefs.UserPrefsJni; import org.chromium.ui.base.WindowAndroid; import org.chromium.ui.modaldialog.ModalDialogManager; @@ -71,10 +67,6 @@ @Mock private Profile mProfile; - @Mock private PrefService mPrefService; - - @Mock private UserPrefs.Natives mMockUserPrefsJni; - @Mock private PasswordManagerUtilBridge.Natives mMockPasswordManagerUtilBridgeJni; @Mock private SyncService mMockSyncService; @@ -101,11 +93,9 @@ @Before public void setUp() { - UserPrefsJni.setInstanceForTesting(mMockUserPrefsJni); PasswordManagerUtilBridgeJni.setInstanceForTesting(mMockPasswordManagerUtilBridgeJni); when(mProfile.getOriginalProfile()).thenReturn(mProfile); - when(mMockUserPrefsJni.get(mProfile)).thenReturn(mPrefService); SyncServiceFactory.setInstanceForTesting(mMockSyncService); when(mMockSyncService.isSyncFeatureEnabled()).thenReturn(true); @@ -141,9 +131,7 @@ public void testLaunchCheckupOnDeviceShowsAccountCheckup() throws PendingIntent.CanceledException { when(mMockSyncService.getSelectedTypes()).thenReturn(Set.of(UserSelectableType.PASSWORDS)); - when(mMockPasswordManagerUtilBridgeJni.isPasswordManagerAvailable( - eq(mPrefService), eq(true))) - .thenReturn(true); + when(mMockPasswordManagerUtilBridgeJni.isPasswordManagerAvailable(true)).thenReturn(true); PasswordCheckupLauncher.launchCheckupOnDevice( mProfile, mMockWindowAndroid, LEAK_DIALOG, TestAccounts.ACCOUNT1.getEmail()); @@ -155,9 +143,7 @@ public void testLaunchCheckupOnDeviceShowsLocalCheckup() throws PendingIntent.CanceledException { when(mMockSyncService.getSelectedTypes()).thenReturn(Collections.emptySet()); - when(mMockPasswordManagerUtilBridgeJni.isPasswordManagerAvailable( - eq(mPrefService), eq(true))) - .thenReturn(true); + when(mMockPasswordManagerUtilBridgeJni.isPasswordManagerAvailable(true)).thenReturn(true); PasswordCheckupLauncher.launchCheckupOnDevice( mProfile, mMockWindowAndroid, LEAK_DIALOG, TEST_NO_EMAIL_ADDRESS); @@ -171,9 +157,7 @@ // Local checkup will be launched from the leak detection dialog if the leaked credential is // stored only in the local store, even though the user is syncing passwords. when(mMockSyncService.getSelectedTypes()).thenReturn(Set.of(UserSelectableType.PASSWORDS)); - when(mMockPasswordManagerUtilBridgeJni.isPasswordManagerAvailable( - eq(mPrefService), eq(true))) - .thenReturn(true); + when(mMockPasswordManagerUtilBridgeJni.isPasswordManagerAvailable(true)).thenReturn(true); PasswordCheckupLauncher.launchCheckupOnDevice( mProfile, mMockWindowAndroid, LEAK_DIALOG, TEST_NO_EMAIL_ADDRESS);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java index 63f9762..c18d28f 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/tabbed_mode/TabbedAppMenuPropertiesDelegateUnitTest.java
@@ -1584,6 +1584,20 @@ context.getString(R.string.hide_reading_mode_text))); } + @Test + public void pageZoomMenuOption_NotVisibleInReadingMode() { + setUpMocksForPageMenu(); + PageZoomCoordinator.setShouldShowMenuItemForTesting(true); + doReturn(true) + .when(mTabbedAppMenuPropertiesDelegate) + .shouldShowReaderModePrefs(any(Tab.class)); + when(mTab.getUrl()).thenReturn(JUnitTestGURLs.CHROME_DISTILLER_EXAMPLE_URL); + + MVCListAdapter.ModelList modelList = mTabbedAppMenuPropertiesDelegate.getMenuItems(); + + assertFalse(isMenuVisible(modelList, R.id.page_zoom_id)); + } + private MVCListAdapter.ModelList setUpMenuWithIncognitoReauthPage(boolean isShowing) { setUpMocksForOverviewMenu(); when(mTabModelSelector.getCurrentModel()).thenReturn(mIncognitoTabModel);
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index ac05a7553..4a071e70 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn
@@ -1462,6 +1462,8 @@ "site_isolation/site_details.cc", "site_isolation/site_details.h", "site_protection/site_familiarity_heuristic_name.h", + "site_protection/site_familiarity_utils.cc", + "site_protection/site_familiarity_utils.h", "site_protection/site_protection_metrics.h", "site_protection/site_protection_metrics_observer.cc", "site_protection/site_protection_metrics_observer.h", @@ -3814,8 +3816,6 @@ "download/download_dir_policy_handler.h", "download/download_dir_util.cc", "download/download_dir_util.h", - "download/download_item_web_app_data.cc", - "download/download_item_web_app_data.h", "download/download_open_prompt.cc", "download/download_open_prompt.h", "download/download_ui_enterprise_util.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index eb424480..eea1c10 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc
@@ -13269,7 +13269,7 @@ flag_descriptions::kAndroidAnimatedProgressBarInBrowserName, flag_descriptions::kAndroidAnimatedProgressBarInBrowserDescription, kOsAndroid, - FEATURE_VALUE_TYPE(chrome::android::kAndroidAnimatedProgressBarInBrowser)}, + FEATURE_VALUE_TYPE(features::kAndroidAnimatedProgressBarInBrowser)}, #endif // BUILDFLAG(IS_ANDROID) {"enable-cross-device-pref-tracker",
diff --git a/chrome/browser/actor/actor_keyed_service_fake.cc b/chrome/browser/actor/actor_keyed_service_fake.cc index 590d358..ef11c6f 100644 --- a/chrome/browser/actor/actor_keyed_service_fake.cc +++ b/chrome/browser/actor/actor_keyed_service_fake.cc
@@ -13,7 +13,6 @@ namespace actor { using ::testing::_; -using ::testing::Invoke; ActorKeyedServiceFake::ActorKeyedServiceFake(Profile* profile) : ActorKeyedService(profile) {} @@ -33,15 +32,15 @@ for (auto& mock : {mock_ui_dispatcher, mock_task_ui_dispatcher}) { ON_CALL(*mock, OnPreTool(_, _)) - .WillByDefault(Invoke(UiEventDispatcherCallback<ToolRequest>( - base::BindRepeating(MakeOkResult)))); + .WillByDefault(UiEventDispatcherCallback<ToolRequest>( + base::BindRepeating(MakeOkResult))); ON_CALL(*mock, OnPostTool(_, _)) - .WillByDefault(Invoke(UiEventDispatcherCallback<ToolRequest>( - base::BindRepeating(MakeOkResult)))); + .WillByDefault(UiEventDispatcherCallback<ToolRequest>( + base::BindRepeating(MakeOkResult))); ON_CALL(*mock, OnActorTaskAsyncChange(_, _)) - .WillByDefault(Invoke(UiEventDispatcherCallback< - ui::UiEventDispatcher::ActorTaskAsyncChange>( - base::BindRepeating(MakeOkResult)))); + .WillByDefault(UiEventDispatcherCallback< + ui::UiEventDispatcher::ActorTaskAsyncChange>( + base::BindRepeating(MakeOkResult))); } auto execution_engine = ExecutionEngine::CreateForTesting( GetProfile(), std::move(ui_event_dispatcher));
diff --git a/chrome/browser/actor/execution_engine_unittest.cc b/chrome/browser/actor/execution_engine_unittest.cc index fa71409a..d74145b06 100644 --- a/chrome/browser/actor/execution_engine_unittest.cc +++ b/chrome/browser/actor/execution_engine_unittest.cc
@@ -43,7 +43,6 @@ using testing::_; using testing::Eq; using testing::Field; -using testing::Invoke; using testing::Property; using testing::VariantWith; using ChangeTaskState = ui::UiEventDispatcher::ChangeTaskState; @@ -152,15 +151,15 @@ for (auto& mock : {mock_ui_event_dispatcher_, task_mock_ui_event_dispatcher_}) { ON_CALL(*mock, OnPreTool(_, _)) - .WillByDefault(Invoke(UiEventDispatcherCallback<ToolRequest>( - base::BindRepeating(MakeOkResult)))); + .WillByDefault(UiEventDispatcherCallback<ToolRequest>( + base::BindRepeating(MakeOkResult))); ON_CALL(*mock, OnPostTool(_, _)) - .WillByDefault(Invoke(UiEventDispatcherCallback<ToolRequest>( - base::BindRepeating(MakeOkResult)))); + .WillByDefault(UiEventDispatcherCallback<ToolRequest>( + base::BindRepeating(MakeOkResult))); ON_CALL(*mock, OnActorTaskAsyncChange(_, _)) - .WillByDefault(Invoke(UiEventDispatcherCallback< - ui::UiEventDispatcher::ActorTaskAsyncChange>( - base::BindRepeating(MakeOkResult)))); + .WillByDefault(UiEventDispatcherCallback< + ui::UiEventDispatcher::ActorTaskAsyncChange>( + base::BindRepeating(MakeOkResult))); } } @@ -289,8 +288,8 @@ TEST_F(ExecutionEngineTest, UiOnPreToolFails) { EXPECT_CALL(*mock_ui_event_dispatcher_, OnPreTool(_, _)) - .WillOnce(Invoke(UiEventDispatcherCallback<ToolRequest>( - base::BindRepeating(MakeErrorResult)))); + .WillOnce(UiEventDispatcherCallback<ToolRequest>( + base::BindRepeating(MakeErrorResult))); EXPECT_CALL(*mock_ui_event_dispatcher_, OnPostTool(_, _)).Times(0); EXPECT_FALSE( Act(GURL("http://localhost/"), MakeClickCallback(kFakeContentNodeId))); @@ -301,8 +300,8 @@ TEST_F(ExecutionEngineTest, UiOnPostToolFails) { EXPECT_CALL(*mock_ui_event_dispatcher_, OnPreTool(_, _)).Times(1); EXPECT_CALL(*mock_ui_event_dispatcher_, OnPostTool(_, _)) - .WillOnce(Invoke(UiEventDispatcherCallback<ToolRequest>( - base::BindRepeating(MakeErrorResult)))); + .WillOnce(UiEventDispatcherCallback<ToolRequest>( + base::BindRepeating(MakeErrorResult))); EXPECT_FALSE( Act(GURL("http://localhost/"), MakeClickCallback(kFakeContentNodeId))); histograms_.ExpectUniqueSample(kActionResultHistogram, @@ -312,9 +311,9 @@ TEST_F(ExecutionEngineTest, ActFailsWhenAddTabFails) { EXPECT_CALL(*task_mock_ui_event_dispatcher_, OnActorTaskAsyncChange(VariantWith<AddTab>(_), _)) - .WillOnce(Invoke(UiEventDispatcherCallback< - ui::UiEventDispatcher::ActorTaskAsyncChange>( - base::BindRepeating(MakeErrorResult)))); + .WillOnce(UiEventDispatcherCallback< + ui::UiEventDispatcher::ActorTaskAsyncChange>( + base::BindRepeating(MakeErrorResult))); EXPECT_FALSE( Act(GURL("http://localhost/"), MakeClickCallback(kFakeContentNodeId))); histograms_.ExpectUniqueSample(kActionResultHistogram,
diff --git a/chrome/browser/actor/ui/actor_ui_state_manager_unittest.cc b/chrome/browser/actor/ui/actor_ui_state_manager_unittest.cc index 3f88dd3..d817a91 100644 --- a/chrome/browser/actor/ui/actor_ui_state_manager_unittest.cc +++ b/chrome/browser/actor/ui/actor_ui_state_manager_unittest.cc
@@ -34,7 +34,6 @@ using ::tabs::TabFeatures; using ::tabs::TabInterface; using ::testing::_; -using ::testing::Invoke; using ::testing::NiceMock; using ::testing::Return; using ::testing::ValuesIn; @@ -98,11 +97,11 @@ void ExpectUiTabStateChange(const UiTabState& expected_state) { ON_CALL(*mock_actor_ui_tab_controller(), OnUiTabStateChange(_, _)) - .WillByDefault(Invoke( + .WillByDefault( [&](UiTabState state, base::OnceCallback<void(bool)> callback) { EXPECT_EQ(state, expected_state); std::move(callback).Run(true); - })); + }); } ActorUiStateManager* actor_ui_state_manager() {
diff --git a/chrome/browser/actor/ui/actor_ui_tab_controller_unittest.cc b/chrome/browser/actor/ui/actor_ui_tab_controller_unittest.cc index b8b62c8..0fe0c06 100644 --- a/chrome/browser/actor/ui/actor_ui_tab_controller_unittest.cc +++ b/chrome/browser/actor/ui/actor_ui_tab_controller_unittest.cc
@@ -39,7 +39,6 @@ namespace { using ::tabs::MockTabInterface; using ::testing::_; -using ::testing::Invoke; using ::testing::MockFunction; using ::testing::Return; using ::testing::ReturnRef;
diff --git a/chrome/browser/android/compositor/layer/toolbar_layer.cc b/chrome/browser/android/compositor/layer/toolbar_layer.cc index 945f6ee..1e26ad9 100644 --- a/chrome/browser/android/compositor/layer/toolbar_layer.cc +++ b/chrome/browser/android/compositor/layer/toolbar_layer.cc
@@ -12,6 +12,7 @@ #include "cc/slim/ui_resource_layer.h" #include "chrome/browser/android/compositor/resources/toolbar_resource.h" #include "chrome/browser/flags/android/chrome_feature_list.h" +#include "chrome/browser/ui/ui_features.h" #include "components/viz/common/features.h" #include "components/viz/common/quads/offset_tag.h" #include "third_party/skia/include/core/SkColor.h" @@ -164,9 +165,13 @@ bool visible, const viz::OffsetTag& offset_tag) { bool is_progress_bar_visible = SkColorGetA(progress_bar_background_color); - if (features::IsAndroidAnimatedProgressBarInVizEnabled()) { + if (features::IsAndroidAnimatedProgressBarInVizEnabled() || + features::IsAndroidAnimatedProgressBarInBrowserEnabled()) { is_progress_bar_visible = visible; - progress_bar_layers_->SetOffsetTag(offset_tag); + + if (features::IsAndroidAnimatedProgressBarInVizEnabled()) { + progress_bar_layers_->SetOffsetTag(offset_tag); + } } progress_bar_background_layer_->SetHideLayerAndSubtree(!is_progress_bar_visible);
diff --git a/chrome/browser/bookmarks/android/BUILD.gn b/chrome/browser/bookmarks/android/BUILD.gn index cbce98f..8bbce27 100644 --- a/chrome/browser/bookmarks/android/BUILD.gn +++ b/chrome/browser/bookmarks/android/BUILD.gn
@@ -92,6 +92,7 @@ "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarButton.java", "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarButtonProperties.java", "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarButtonViewBinder.java", + "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarConstants.java", "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarItemsLayoutManager.java", "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarItemsProvider.java", "java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarProperties.java",
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarConstants.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarConstants.java new file mode 100644 index 0000000..93f4305 --- /dev/null +++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarConstants.java
@@ -0,0 +1,19 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.bookmarks.bar; + +import org.chromium.build.annotations.NullMarked; + +/** Constants for the Bookmark Bar feature. */ +@NullMarked +public class BookmarkBarConstants { + + /** + * Bookmark Bar preference, tracks whether or not the user wants to show the bookmark bar on the + * current device. + */ + public static final String BOOKMARK_BAR_SHOW_BOOKMARK_BAR = + "Chrome.BookmarkBar.ShowBookmarkBar"; +}
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarUtils.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarUtils.java index c5fdda4..a395849 100644 --- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarUtils.java +++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarUtils.java
@@ -13,6 +13,8 @@ import androidx.annotation.IntDef; import androidx.appcompat.content.res.AppCompatResources; +import org.chromium.base.ContextUtils; +import org.chromium.base.DeviceInfo; import org.chromium.base.ResettersForTesting; import org.chromium.base.supplier.LazyOneshotSupplier; import org.chromium.base.supplier.LazyOneshotSupplierImpl; @@ -131,6 +133,7 @@ /** * Returns true if the Bookmark Bar currently visible. The feature is visible when it is allowed * in the given context, and the show bookmark bar UserPref is enabled for the current user. + * When on tablets, we do not use the UserPref and instead use the device preference. * * @param context The context in which compatibility should be assessed. * @param profile The profile for which the user UserPref should be assessed. @@ -145,9 +148,14 @@ return false; } - return isUserPrefsShowBookmarksBarEnabled(profile); + // On Desktop, we sync with the UserPrefs, but on tablets we use a local device preference. + return DeviceInfo.isDesktop() + ? isUserPrefsShowBookmarksBarEnabled(profile) + : isDevicePrefShowBookmarksBarEnabled(); } + // UserPrefs methods - used on Desktop. + /** * Returns whether the bookmark bar should be shown based on the current user's UserPrefs. Note: * This is synced across devices for the user's profile. @@ -183,6 +191,64 @@ Pref.SHOW_BOOKMARK_BAR, !prefService.getBoolean(Pref.SHOW_BOOKMARK_BAR)); } + // Device preferences methods - used on tablets. + + /** + * Returns whether or not the bookmark bar should be shown based on the local device + * preferences. This is only used on tablets, where bookmarks bar does not sync with the user's + * desktop preference, but is instead stored locally on device. + * + * <p>Note: When a user has not previously set the device preference, the default return value + * is currently controlled by a FeatureParam for testing. + * + * @return Whether or not the bookmarks bar should be shown based on device preference. + */ + public static boolean isDevicePrefShowBookmarksBarEnabled() { + // If a user has set the show bookmarks bar setting explicitly, then we will use that value. + // If the user has never set the preference, then we will return a default, which is + // currently controlled with a FeatureParam. + return hasUserSetDevicePrefShowBookmarksBar() + ? ContextUtils.getAppSharedPreferences() + .getBoolean(BookmarkBarConstants.BOOKMARK_BAR_SHOW_BOOKMARK_BAR, false) + : ChromeFeatureList.sAndroidBookmarkBarShowBookmarkBar.getValue(); + } + + /** + * Set whether the bookmark bar should be shown at a device preferences level. This is only used + * on tablets, where bookmarks bar does not sync with the user's desktop preference, but is + * instead stored locally on the device. + * + * @param enabled The new device preference for enabling the bookmark bar. + */ + public static void setDevicePrefShowBookmarksBar(boolean enabled) { + ContextUtils.getAppSharedPreferences() + .edit() + .putBoolean(BookmarkBarConstants.BOOKMARK_BAR_SHOW_BOOKMARK_BAR, enabled) + .apply(); + } + + /** + * Returns true when the user has previously set the visibility of the bookmarks bar explicitly + * at the device preference level. This is only used on tablets, where bookmarks bar does not + * sync with the user's desktop preference, but is instead stored locally on the device. + * + * @return Whether the user has set show bookmarks bar device preference manually. + */ + public static boolean hasUserSetDevicePrefShowBookmarksBar() { + return ContextUtils.getAppSharedPreferences() + .contains(BookmarkBarConstants.BOOKMARK_BAR_SHOW_BOOKMARK_BAR); + } + + /** + * Toggles the value of the show bookmarks bar device preference, this is stored locally and + * only used on tablets. + */ + public static void toggleDevicePrefShowBookmarksBar() { + setDevicePrefShowBookmarksBar(!isDevicePrefShowBookmarksBarEnabled()); + } + + // Helper methods. + /** * Creates a list item to render in the bookmark bar for the specified bookmark item. * @@ -254,6 +320,8 @@ return UserPrefs.get(profile.getOriginalProfile()); } + // ForTesting methods. + /** * Sets whether the bookmark bar feature is forcibly allowed/disallowed for testing. *
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProvider.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProvider.java index 54e0305..99e7d53 100644 --- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProvider.java +++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProvider.java
@@ -5,11 +5,14 @@ package org.chromium.chrome.browser.bookmarks.bar; import android.app.Activity; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.res.Configuration; import androidx.annotation.NonNull; import org.chromium.base.Callback; +import org.chromium.base.ContextUtils; +import org.chromium.base.DeviceInfo; import org.chromium.base.ObserverList; import org.chromium.base.supplier.ObservableSupplier; import org.chromium.build.annotations.NullMarked; @@ -62,7 +65,7 @@ private final ObserverList<BookmarkBarVisibilityObserver> mObservers; private @Nullable PrefChangeRegistrar mPrefChangeRegistrar; - private final @Nullable PrefObserver mPrefObserver; + private @Nullable OnSharedPreferenceChangeListener mDevicePrefsListener; /** * Constructor. @@ -87,13 +90,19 @@ mProfileSupplierObserver = this::processProfileChange; mProfileSupplier.addObserver(mProfileSupplierObserver); - mPrefObserver = - new PrefObserver() { - @Override - public void onPreferenceChange() { - processPrefChange(); - } - }; + // On tablets we use local device prefs. + if (!DeviceInfo.isDesktop()) { + mDevicePrefsListener = + (sharedPreferences, key) -> { + if (key != null + && key.equals( + BookmarkBarConstants.BOOKMARK_BAR_SHOW_BOOKMARK_BAR)) { + processPrefChange(); + } + }; + ContextUtils.getAppSharedPreferences() + .registerOnSharedPreferenceChangeListener(mDevicePrefsListener); + } } /** @@ -120,6 +129,7 @@ mActivityLifecycleDispatcher.unregister(mConfigurationChangedListener); mProfileSupplier.removeObserver(mProfileSupplierObserver); destroyPrefChangeRegistrar(); + destroySharedPrefListener(); mObservers.clear(); } @@ -170,7 +180,20 @@ } } + private void destroySharedPrefListener() { + if (mDevicePrefsListener != null) { + ContextUtils.getAppSharedPreferences() + .unregisterOnSharedPreferenceChangeListener(mDevicePrefsListener); + mDevicePrefsListener = null; + } + } + @Nullable PrefObserver getPrefObserverForTesting() { - return mPrefObserver; + return new PrefObserver() { + @Override + public void onPreferenceChange() { + processPrefChange(); + } + }; } }
diff --git a/chrome/browser/bookmarks/android/javatests/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarTest.java b/chrome/browser/bookmarks/android/javatests/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarTest.java index f004a7b..d26cb814 100644 --- a/chrome/browser/bookmarks/android/javatests/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarTest.java +++ b/chrome/browser/bookmarks/android/javatests/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarTest.java
@@ -48,10 +48,12 @@ import org.hamcrest.Matcher; import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.chromium.base.FeatureOverrides; import org.chromium.base.ThreadUtils; import org.chromium.base.test.util.Batch; import org.chromium.base.test.util.CommandLineFlags; @@ -65,6 +67,7 @@ import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; +import org.chromium.chrome.test.OverrideContextWrapperTestRule; import org.chromium.chrome.test.transit.AutoResetCtaTransitTestRule; import org.chromium.chrome.test.transit.ChromeTransitTestRules; import org.chromium.chrome.test.util.BookmarkTestUtil; @@ -93,19 +96,32 @@ public AutoResetCtaTransitTestRule mCtaTestRule = ChromeTransitTestRules.autoResetCtaActivityRule(); + @Rule + public OverrideContextWrapperTestRule mOverrideContextRule = + new OverrideContextWrapperTestRule(); + private BookmarkModel mModel; private BookmarkId mDesktopFolderId; private @Nullable List<BookmarkId> mItemIds; + @BeforeClass + public static void classSetUp() { + // Explicitly override FeatureParam for consistency. + FeatureOverrides.Builder overrides = FeatureOverrides.newBuilder(); + overrides = + overrides.param(ChromeFeatureList.ANDROID_BOOKMARK_BAR, "show_bookmark_bar", true); + overrides.apply(); + } + @Before public void setUp() { - mCtaTestRule.startOnBlankPage(); + mOverrideContextRule.setIsDesktop(true); + mCtaTestRule.startOnBlankPage(); BookmarkBarUtils.setActivityStateBookmarkBarCompatibleForTesting(true); ThreadUtils.runOnUiThreadBlocking(() -> setBookmarkBarSetting(/* enabled= */ true)); waitForBookmarkBarVisibility(/* visible= */ true); BookmarkTestUtil.waitForBookmarkModelLoaded(); - ThreadUtils.runOnUiThreadBlocking( () -> { mModel = mCtaTestRule.getActivity().getBookmarkModelForTesting();
diff --git a/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarUtilsTest.java b/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarUtilsTest.java index b234e6e9..8d78d1a 100644 --- a/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarUtilsTest.java +++ b/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarUtilsTest.java
@@ -43,6 +43,7 @@ import org.robolectric.shadows.ShadowDrawable; import org.chromium.base.Callback; +import org.chromium.base.FeatureOverrides; import org.chromium.base.supplier.ObservableSupplierImpl; import org.chromium.base.test.BaseRobolectricTestRunner; import org.chromium.base.test.util.Features.DisableFeatures; @@ -53,6 +54,7 @@ import org.chromium.chrome.browser.preferences.Pref; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.ProfileProvider; +import org.chromium.chrome.test.OverrideContextWrapperTestRule; import org.chromium.components.bookmarks.BookmarkItem; import org.chromium.components.prefs.PrefService; import org.chromium.components.user_prefs.UserPrefsJni; @@ -67,6 +69,7 @@ /** Unit tests for {@link BookmarkBarUtils}. */ @RunWith(BaseRobolectricTestRunner.class) +@EnableFeatures(ChromeFeatureList.ANDROID_BOOKMARK_BAR) public class BookmarkBarUtilsTest { private static final String PHONE_QUALIFIER = @@ -78,6 +81,10 @@ public final ActivityScenarioRule<TestActivity> mActivityScenarioRule = new ActivityScenarioRule<>(TestActivity.class); + @Rule + public OverrideContextWrapperTestRule mOverrideContextRule = + new OverrideContextWrapperTestRule(); + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Mock private BiConsumer<BookmarkItem, Integer> mClickCallback; @@ -107,11 +114,18 @@ UserPrefsJni.setInstanceForTesting(mUserPrefsJni); mProfileProviderSupplier = new ObservableSupplierImpl<>(mProfileProvider); + + // Explicitly override FeatureParam for consistency. + FeatureOverrides.Builder overrides = FeatureOverrides.newBuilder(); + overrides = + overrides.param(ChromeFeatureList.ANDROID_BOOKMARK_BAR, "show_bookmark_bar", true); + overrides.apply(); } @After public void tearDown() { UserPrefsJni.setInstanceForTesting(null); + mOverrideContextRule.setIsDesktop(false); } @Test @@ -165,7 +179,6 @@ @Test @SmallTest @Config(qualifiers = PHONE_QUALIFIER) - @EnableFeatures(ChromeFeatureList.ANDROID_BOOKMARK_BAR) public void testIsFeatureEnabledWhenFlagIsEnabledOnPhone() { mActivityScenarioRule .getScenario() @@ -191,7 +204,6 @@ @Test @SmallTest @Config(qualifiers = TABLET_QUALIFIER) - @EnableFeatures(ChromeFeatureList.ANDROID_BOOKMARK_BAR) public void testIsFeatureEnabledWhenFlagIsEnabledOnTablet() { mActivityScenarioRule .getScenario() @@ -203,7 +215,8 @@ @Test @SmallTest - public void testIsBookmarkBarVisible() { + public void testIsBookmarkBarVisible_Desktop() { + mOverrideContextRule.setIsDesktop(true); mActivityScenarioRule .getScenario() .onActivity( @@ -230,7 +243,45 @@ @Test @SmallTest + public void testIsBookmarkBarVisible_Tablet() { + mOverrideContextRule.setIsDesktop(false); + mActivityScenarioRule + .getScenario() + .onActivity( + activity -> { + // Case: feature disallowed. + BookmarkBarUtils.setActivityStateBookmarkBarCompatibleForTesting(false); + assertFalse(BookmarkBarUtils.isBookmarkBarVisible(activity, mProfile)); + + // Case: feature allowed no device pref (FeatureParam = true). + BookmarkBarUtils.setActivityStateBookmarkBarCompatibleForTesting(true); + assertTrue(BookmarkBarUtils.isBookmarkBarVisible(activity, mProfile)); + + // Apply new FeatureParam override. + FeatureOverrides.Builder overrides = FeatureOverrides.newBuilder(); + overrides = + overrides.param( + ChromeFeatureList.ANDROID_BOOKMARK_BAR, + "show_bookmark_bar", + false); + overrides.apply(); + + // Case: feature allowed no device pref (FeatureParam = false). + assertFalse(BookmarkBarUtils.isBookmarkBarVisible(activity, mProfile)); + + // Case: feature allowed explicit device pref + BookmarkBarUtils.setDevicePrefShowBookmarksBar(true); + assertTrue(BookmarkBarUtils.isBookmarkBarVisible(activity, mProfile)); + }); + } + + // Test UserPrefs - only on Desktop + + @Test + @SmallTest public void testIsUserPrefsShowBookmarksBarEnabled() { + mOverrideContextRule.setIsDesktop(true); + mSetting.set(false); assertFalse(BookmarkBarUtils.isUserPrefsShowBookmarksBarEnabled(mProfile)); assertFalse(BookmarkBarUtils.isUserPrefsShowBookmarksBarEnabled(null)); @@ -243,6 +294,8 @@ @Test @SmallTest public void testSetUserPrefsShowBookmarksBar() { + mOverrideContextRule.setIsDesktop(true); + mSetting.set(false); assertFalse(BookmarkBarUtils.isUserPrefsShowBookmarksBarEnabled(mProfile)); @@ -256,6 +309,8 @@ @Test @SmallTest public void testToggleUserPrefsShowBookmarksBar() { + mOverrideContextRule.setIsDesktop(true); + mSetting.set(false); assertFalse(BookmarkBarUtils.isUserPrefsShowBookmarksBarEnabled(mProfile)); @@ -266,6 +321,68 @@ assertFalse(BookmarkBarUtils.isUserPrefsShowBookmarksBarEnabled(mProfile)); } + // Test device prefs - only on Tablet + + @Test + @SmallTest + public void testIsDevicePrefShowBookmarksBarEnabled() { + mOverrideContextRule.setIsDesktop(false); + + // User should not have set any preference yet. + assertFalse(BookmarkBarUtils.hasUserSetDevicePrefShowBookmarksBar()); + + // Even though user has not set a device preference, the FeatureParam will make it true. + assertTrue(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled()); + + // Apply new FeatureParam override. + FeatureOverrides.Builder overrides = FeatureOverrides.newBuilder(); + overrides = + overrides.param(ChromeFeatureList.ANDROID_BOOKMARK_BAR, "show_bookmark_bar", false); + overrides.apply(); + + assertFalse(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled()); + } + + @Test + @SmallTest + public void testSetDevicePrefShowBookmarksBar() { + mOverrideContextRule.setIsDesktop(false); + + // User should not have set any preference yet. + assertFalse(BookmarkBarUtils.hasUserSetDevicePrefShowBookmarksBar()); + + // Even though user has not set a device preference, the FeatureParam will make it true. + assertTrue(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled()); + + BookmarkBarUtils.setDevicePrefShowBookmarksBar(true); + assertTrue(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled()); + assertTrue(BookmarkBarUtils.hasUserSetDevicePrefShowBookmarksBar()); + + BookmarkBarUtils.setDevicePrefShowBookmarksBar(false); + assertFalse(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled()); + assertTrue(BookmarkBarUtils.hasUserSetDevicePrefShowBookmarksBar()); + } + + @Test + @SmallTest + public void testToggleDevicePrefShowBookmarksBar() { + mOverrideContextRule.setIsDesktop(false); + + // User should not have set any preference yet. + assertFalse(BookmarkBarUtils.hasUserSetDevicePrefShowBookmarksBar()); + + // Even though user has not set a device preference, the FeatureParam will make it true. + assertTrue(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled()); + + BookmarkBarUtils.toggleDevicePrefShowBookmarksBar(); + assertFalse(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled()); + assertTrue(BookmarkBarUtils.hasUserSetDevicePrefShowBookmarksBar()); + + BookmarkBarUtils.toggleDevicePrefShowBookmarksBar(); + assertTrue(BookmarkBarUtils.isDevicePrefShowBookmarksBarEnabled()); + assertTrue(BookmarkBarUtils.hasUserSetDevicePrefShowBookmarksBar()); + } + @Test @SmallTest public void testCreateListItem() {
diff --git a/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProviderTest.java b/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProviderTest.java index bd087afd..62eb2df 100644 --- a/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProviderTest.java +++ b/chrome/browser/bookmarks/android/junit/src/org/chromium/chrome/browser/bookmarks/bar/BookmarkBarVisibilityProviderTest.java
@@ -38,6 +38,7 @@ import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher; import org.chromium.chrome.browser.lifecycle.ConfigurationChangedObserver; import org.chromium.chrome.browser.profiles.Profile; +import org.chromium.chrome.test.OverrideContextWrapperTestRule; import org.chromium.components.prefs.PrefChangeRegistrar; import org.chromium.components.prefs.PrefChangeRegistrar.PrefObserver; import org.chromium.components.prefs.PrefChangeRegistrarJni; @@ -54,6 +55,10 @@ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Rule + public OverrideContextWrapperTestRule mOverrideContextRule = + new OverrideContextWrapperTestRule(); + @Mock private Activity mActivity; @Mock private Resources mResources; @Mock private ActivityLifecycleDispatcher mActivityLifecycleDispatcher; @@ -71,6 +76,7 @@ @Before public void setUp() { mProfileSupplier.set(mProfile); + mOverrideContextRule.setIsDesktop(true); // Set up mocks. when(mProfile.getOriginalProfile()).thenReturn(mProfile);
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_lifetime_manager_browsertest.cc b/chrome/browser/browsing_data/chrome_browsing_data_lifetime_manager_browsertest.cc index b3273d5..fc46dd5 100644 --- a/chrome/browser/browsing_data/chrome_browsing_data_lifetime_manager_browsertest.cc +++ b/chrome/browser/browsing_data/chrome_browsing_data_lifetime_manager_browsertest.cc
@@ -530,7 +530,7 @@ autofill::AutofillProfile profile( "01234567-89ab-cdef-fedc-ba9876543210", autofill::AutofillProfile::RecordType::kLocalOrSyncable, - AddressCountryCode("US")); + autofill::AddressCountryCode("US")); autofill::test::SetProfileInfo( &profile, "Marion", "Mitchell", "Morrison", "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", "91601", "US", "12345678910");
diff --git a/chrome/browser/download/BUILD.gn b/chrome/browser/download/BUILD.gn index d129f5b6e..f7b590a7 100644 --- a/chrome/browser/download/BUILD.gn +++ b/chrome/browser/download/BUILD.gn
@@ -38,7 +38,19 @@ ] if (!is_android) { - public += [ "download_commands.h" ] + public += [ + "download_commands.h", + "download_item_web_app_data.h", + ] + + sources += [ "download_item_web_app_data.cc" ] + + public_deps += [ "//components/webapps/common" ] + + deps += [ + "//chrome/browser/policy:path_parser", + "//chrome/common:chrome_features", + ] } if (safe_browsing_mode != 0) {
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc index 761487a..cae67ea9 100644 --- a/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc +++ b/chrome/browser/extensions/api/autofill_private/autofill_private_api.cc
@@ -182,9 +182,9 @@ adm.IsEligibleForAddressAccountStorage() ? autofill::AutofillProfile::RecordType::kAccount : autofill::AutofillProfile::RecordType::kLocalOrSyncable; - AddressCountryCode address_country_code = + autofill::AddressCountryCode address_country_code = country_code.has_value() - ? AddressCountryCode(std::string(*country_code)) + ? autofill::AddressCountryCode(std::string(*country_code)) : autofill::i18n_model_definition::kLegacyHierarchyCountryCode; return autofill::AutofillProfile(record_type, address_country_code); }
diff --git a/chrome/browser/extensions/api/autofill_private/autofill_util.cc b/chrome/browser/extensions/api/autofill_private/autofill_util.cc index 76265713..ee0e765 100644 --- a/chrome/browser/extensions/api/autofill_private/autofill_util.cc +++ b/chrome/browser/extensions/api/autofill_private/autofill_util.cc
@@ -290,9 +290,9 @@ const variations::VariationsService* variations_service = g_browser_process->variations_service(); model.SetCountries( - GeoIpCountryCode(variations_service - ? variations_service->GetLatestCountry() - : std::string()), + autofill::GeoIpCountryCode(variations_service + ? variations_service->GetLatestCountry() + : std::string()), extensions::ExtensionsBrowserClient::Get()->GetApplicationLocale()); const std::vector<std::unique_ptr<autofill::AutofillCountry>>& countries = model.countries();
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 23ab2f4..e31ddd68 100644 --- a/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.cc +++ b/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.cc
@@ -25,8 +25,6 @@ #include "chrome/browser/extensions/chrome_extension_function_details.h" #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/profiles/profile.h" -#include "chrome/browser/ui/browser.h" -#include "chrome/browser/ui/browser_finder.h" #include "chrome/common/actor/action_result.h" #include "chrome/common/extensions/api/experimental_actor.h" #include "chrome/common/extensions/api/tabs.h" @@ -132,94 +130,6 @@ return true; } -ExperimentalActorStartTaskFunction::ExperimentalActorStartTaskFunction() = - default; - -ExperimentalActorStartTaskFunction::~ExperimentalActorStartTaskFunction() = - default; - -ExtensionFunction::ResponseAction ExperimentalActorStartTaskFunction::Run() { - auto params = api::experimental_actor::StartTask::Params::Create(args()); - EXTENSION_FUNCTION_VALIDATE(params); - optimization_guide::proto::BrowserStartTask task; - if (!task.ParseFromArray(params->start_task_proto.data(), - params->start_task_proto.size())) { - return RespondNow( - Error("Parsing optimization_guide::proto::BrowserStartTask failed.")); - } - - // Convert from extension tab ids to TabHandles. - int32_t tab_handle = - ConvertSessionTabIdToTabHandle(task.tab_id(), browser_context()); - - auto* actor_service = actor::ActorKeyedService::Get(browser_context()); - - actor::TaskId task_id = actor_service->CreateTask(); - - // If a tab_id wasn't specified, create a new one. - // TODO(crbug.com/411462297): The client of this API should create a new tab - // themselves using the CreateTabAction and this code can be removed. - if (!tab_handle) { - // Get the most recently active browser for this profile. - Browser* browser = chrome::FindTabbedBrowser( - Profile::FromBrowserContext(browser_context()), - /*match_original_profiles=*/false); - // If no browser exists create one. - if (!browser) { - browser = Browser::Create( - Browser::CreateParams(Profile::FromBrowserContext(browser_context()), - /*user_gesture=*/false)); - } - - std::unique_ptr<actor::ToolRequest> create_tab = - std::make_unique<actor::CreateTabToolRequest>( - browser->session_id().id(), - WindowOpenDisposition::NEW_FOREGROUND_TAB); - std::vector<std::unique_ptr<actor::ToolRequest>> actions; - actions.push_back(std::move(create_tab)); - actor_service->PerformActions( - task_id, std::move(actions), - base::BindOnce(&ExperimentalActorStartTaskFunction::OnTabCreated, this, - browser->AsWeakPtr(), task_id)); - } else { - base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, - base::BindOnce(&ExperimentalActorStartTaskFunction::OnTaskStarted, this, - task_id, tab_handle)); - } - - return RespondLater(); -} - -void ExperimentalActorStartTaskFunction::OnTaskStarted(actor::TaskId task_id, - int32_t tab_id) { - optimization_guide::proto::BrowserStartTaskResult result; - result.set_task_id(task_id.value()); - result.set_tab_id(tab_id); - result.set_status(optimization_guide::proto::BrowserStartTaskResult::SUCCESS); - - std::vector<uint8_t> data_buffer(result.ByteSizeLong()); - result.SerializeToArray(&data_buffer[0], result.ByteSizeLong()); - Respond(ArgumentList(api::experimental_actor::StartTask::Results::Create( - std::move(data_buffer)))); -} - -void ExperimentalActorStartTaskFunction::OnTabCreated( - base::WeakPtr<Browser> browser, - actor::TaskId task_id, - actor::mojom::ActionResultCode result_code, - std::optional<size_t> index_of_failed_action, - std::vector<actor::ActionResultWithLatencyInfo> action_results) { - int32_t tab_id = 0; - // CreateTask assumes it always succeeds but we won't have a tab if the - // browser is closed during creation. - if (browser) { - tab_id = - browser->tab_strip_model()->GetActiveTab()->GetHandle().raw_value(); - } - OnTaskStarted(actor::TaskId(task_id), tab_id); -} - ExperimentalActorStopTaskFunction::ExperimentalActorStopTaskFunction() = default; @@ -237,80 +147,6 @@ ArgumentList(api::experimental_actor::StopTask::Results::Create())); } -ExperimentalActorExecuteActionFunction:: - ExperimentalActorExecuteActionFunction() = default; - -ExperimentalActorExecuteActionFunction:: - ~ExperimentalActorExecuteActionFunction() = default; - -ExtensionFunction::ResponseAction -ExperimentalActorExecuteActionFunction::Run() { -#if !BUILDFLAG(ENABLE_GLIC) - return RespondNow( - Error("Execute action not supported for this build configuration.")); -#else - auto params = api::experimental_actor::ExecuteAction::Params::Create(args()); - EXTENSION_FUNCTION_VALIDATE(params); - optimization_guide::proto::BrowserAction action; - if (!action.ParseFromArray(params->browser_action_proto.data(), - params->browser_action_proto.size())) { - return RespondNow( - Error("Parsing optimization_guide::proto::BrowserAction failed.")); - } - - int32_t tab_handle = - ConvertSessionTabIdToTabHandle(action.tab_id(), browser_context()); - action.set_tab_id(tab_handle); - - auto* actor_service = - actor::ActorKeyedServiceFactory::GetActorKeyedService(browser_context()); - - actor_service->GetJournal().Log( - GURL(), actor::TaskId(action.task_id()), - actor::mojom::JournalTrack::kActor, "ExperimentalActorExecutAction", - absl::StrFormat("Proto: %s", actor::ToBase64(action))); - - // BuildToolRequest looks for tab_ids on the individual action structs since - // that's where Glic puts them. However, the extension puts the tab_id on the - // BrowserAction itself. Use the BrowserAction's tab_id as the fallback tab so - // that, if Action doesn't provide a tab_id we'll use the - // BrowserAction.tab_id. This path should go away once extension clients are - // migrated to PerformActions. - tabs::TabInterface* browser_action_tab = - action.has_tab_id() ? tabs::TabHandle(action.tab_id()).Get() : nullptr; - - actor::BuildToolRequestResult requests = - actor::BuildToolRequest(action, browser_action_tab); - - if (!requests.has_value()) { - return RespondNow( - Error("Failed to convert BrowserAction to ToolRequests.")); - } - - actor_service->ExecuteAction( - actor::TaskId(action.task_id()), std::move(requests.value()), - base::BindOnce( - &ExperimentalActorExecuteActionFunction::OnResponseReceived, this)); - - return RespondLater(); -#endif -} - -void ExperimentalActorExecuteActionFunction::OnResponseReceived( - optimization_guide::proto::BrowserActionResult response) { - // Convert from tab handle to session tab id. - int32_t session_tab_id = - ConvertTabHandleToSessionTabId(response.tab_id(), browser_context()); - response.set_tab_id(session_tab_id); - - std::vector<uint8_t> data_buffer(response.ByteSizeLong()); - if (!data_buffer.empty()) { - response.SerializeToArray(&data_buffer[0], response.ByteSizeLong()); - } - Respond(ArgumentList(api::experimental_actor::ExecuteAction::Results::Create( - std::move(data_buffer)))); -} - ExperimentalActorCreateTaskFunction::ExperimentalActorCreateTaskFunction() = default; ExperimentalActorCreateTaskFunction::~ExperimentalActorCreateTaskFunction() =
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 16f3096..aac32cdd 100644 --- a/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.h +++ b/chrome/browser/extensions/api/experimental_actor/experimental_actor_api.h
@@ -13,8 +13,6 @@ #include "components/optimization_guide/proto/features/actions_data.pb.h" #include "extensions/browser/extension_function.h" -class Browser; - namespace extensions { // Base class for all experimental actor functions. See idl for more details. @@ -31,31 +29,6 @@ bool PreRunValidation(std::string* error) override; }; -// Starts an actor task. -class ExperimentalActorStartTaskFunction : public ExperimentalActorApiFunction { - public: - ExperimentalActorStartTaskFunction(); - - ExperimentalActorStartTaskFunction( - const ExperimentalActorStartTaskFunction&) = delete; - ExperimentalActorStartTaskFunction& operator=( - const ExperimentalActorStartTaskFunction&) = delete; - - protected: - ~ExperimentalActorStartTaskFunction() override; - ResponseAction Run() override; - void OnTaskStarted(actor::TaskId task_id, int32_t tab_id); - void OnTabCreated( - base::WeakPtr<Browser> browser, - actor::TaskId task_id, - actor::mojom::ActionResultCode result_code, - std::optional<size_t> index_of_failed_action, - std::vector<actor::ActionResultWithLatencyInfo> action_results); - - DECLARE_EXTENSION_FUNCTION("experimentalActor.startTask", - EXPERIMENTALACTOR_STARTTASK) -}; - // Stops an actor task. class ExperimentalActorStopTaskFunction : public ExperimentalActorApiFunction { public: @@ -74,27 +47,6 @@ EXPERIMENTALACTOR_STOPTASK) }; -// Executes an actor action. -class ExperimentalActorExecuteActionFunction - : public ExperimentalActorApiFunction { - public: - ExperimentalActorExecuteActionFunction(); - - ExperimentalActorExecuteActionFunction( - const ExperimentalActorExecuteActionFunction&) = delete; - ExperimentalActorExecuteActionFunction& operator=( - const ExperimentalActorExecuteActionFunction&) = delete; - - protected: - ~ExperimentalActorExecuteActionFunction() override; - ResponseAction Run() override; - void OnResponseReceived( - optimization_guide::proto::BrowserActionResult response); - - DECLARE_EXTENSION_FUNCTION("experimentalActor.executeAction", - EXPERIMENTALACTOR_EXECUTEACTION) -}; - class ExperimentalActorCreateTaskFunction : public ExperimentalActorApiFunction { public:
diff --git a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.cc b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.cc index bddac15..0fa4d91 100644 --- a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.cc +++ b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.cc
@@ -54,7 +54,7 @@ } if (autofill_client_->GetVariationConfigCountryCode() != - GeoIpCountryCode("US")) { + autofill::GeoIpCountryCode("US")) { return FastCheckoutTriggerOutcome::kUnsupportedCountry; }
diff --git a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl_unittest.cc b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl_unittest.cc index dbadd74..05225c38 100644 --- a/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl_unittest.cc +++ b/chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl_unittest.cc
@@ -37,7 +37,7 @@ using autofill::TestContentAutofillClient::TestContentAutofillClient; MOCK_METHOD(autofill::LogManager*, GetCurrentLogManager, (), (override)); MOCK_METHOD(bool, IsContextSecure, (), (const override)); - MOCK_METHOD(GeoIpCountryCode, + MOCK_METHOD(autofill::GeoIpCountryCode, GetVariationConfigCountryCode, (), (const override)); @@ -102,7 +102,7 @@ .WillByDefault(Return(&pdm())); ON_CALL(*autofill_client(), IsContextSecure).WillByDefault(Return(true)); ON_CALL(*autofill_client(), GetVariationConfigCountryCode) - .WillByDefault(Return(GeoIpCountryCode("US"))); + .WillByDefault(Return(autofill::GeoIpCountryCode("US"))); pdm().test_address_data_manager().SetAutofillProfileEnabled(true); pdm().test_payments_data_manager().SetAutofillPaymentMethodsEnabled(true);
diff --git a/chrome/browser/first_run/first_run.cc b/chrome/browser/first_run/first_run.cc index 71c9500..b4112ce 100644 --- a/chrome/browser/first_run/first_run.cc +++ b/chrome/browser/first_run/first_run.cc
@@ -431,9 +431,7 @@ return; } - // Safe to remove const as ScopedProfileKeepAlive just wraps a non-const - // Profile* as const in its constructor. - Profile* profile = const_cast<Profile*>(scoped_profile->profile()); + Profile* profile = scoped_profile->profile(); bookmark_model->BeginExtensiveChanges(); absl::Cleanup end_changes = [bookmark_model] {
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json index 52c6c57..a289e401 100644 --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json
@@ -3340,11 +3340,6 @@ "expiry_milestone": 150 }, { - "name": "enable-desktop-pwas-sync-changes", - "owners": [ "mek@google.com", "pwa-team@google.com" ], - "expiry_milestone": 135 - }, - { "name": "enable-desktop-pwas-tab-strip", "owners": [ "pwa-team@google.com" ], "expiry_milestone": 150 @@ -5972,7 +5967,7 @@ { "name": "instance-switcher-v2", "owners": [ "aishwaryarj@google.com", "rathomas@google.com", "clank-large-form-factors@google.com" ], - "expiry_milestone": 142 + "expiry_milestone": 144 }, { "name": "instant-hotspot-on-nearby", @@ -6059,7 +6054,7 @@ { "name": "ios-expanded-tips", "owners": ["scottyoder@google.com", "bwwilliams@google.com", "bling-pandamonium@google.com"], - "expiry_milestone": 142 + "expiry_milestone": 145 }, { "name": "ios-fill-recovery-password", @@ -6151,7 +6146,7 @@ { "name": "ios-provides-app-notification-settings", "owners": ["scottyoder@google.com", "bling-pandamonium@google.com"], - "expiry_milestone": 141 + "expiry_milestone": 148 }, { "name": "ios-provisional-notification-alert", @@ -6622,7 +6617,7 @@ "hchao@chromium.org", "chrome-secure-web-and-net@chromium.org" ], - "expiry_milestone": 142 + "expiry_milestone": 146 }, { "name": "local-network-access-check-webrtc", @@ -8248,7 +8243,7 @@ "kmg@google.com", "koilos@google.com" ], - "expiry_milestone": 141 + "expiry_milestone": 150 }, { "name": "process-rank-policy-android", @@ -9936,7 +9931,7 @@ { "name": "tracking-protection-3pcd", "owners": ["fmacintosh@google.com", "koilos@google.com"], - "expiry_milestone": 142 + "expiry_milestone": 146 }, { "name": "traffic-counters", @@ -10231,7 +10226,7 @@ { "name": "use-winrt-midi-api", "owners": [ "//third_party/blink/renderer/modules/webaudio/OWNERS", "toyoshim@chromium.org", "mjwilson@chromium.org" ], - "expiry_milestone": 142 + "expiry_milestone": 154 }, { "name": "use-xps-for-printing",
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc index e66a728..f2c0ed0 100644 --- a/chrome/browser/flags/android/chrome_feature_list.cc +++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -139,6 +139,7 @@ &download::features::kSmartSuggestionForLargeDownloads, &base::features::kCollectAndroidFrameTimelineMetrics, &download::features::kDownloadNotificationServiceUnifiedAPI, + &features::kAndroidAnimatedProgressBarInBrowser, &features::kAndroidAnimatedProgressBarInViz, &features::kAndroidBcivBottomControls, &features::kAndroidBrowserControlsInViz, @@ -196,7 +197,6 @@ &kAdaptiveButtonInTopToolbarCustomizationV2, &kAdaptiveButtonInTopToolbarPageSummary, &kAllowTabClosingUponMinimization, - &kAndroidAnimatedProgressBarInBrowser, &kAndroidAppIntegration, &kAndroidAppIntegrationV2, &kNewTabPageCustomization, @@ -541,10 +541,6 @@ "AllowTabClosingUponMinimization", base::FEATURE_ENABLED_BY_DEFAULT); -BASE_FEATURE(kAndroidAnimatedProgressBarInBrowser, - "AndroidAnimatedProgressBarInBrowser", - base::FEATURE_DISABLED_BY_DEFAULT); - BASE_FEATURE(kAndroidAppIntegration, "AndroidAppIntegration", base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h index 4c00f8ce..8e55912 100644 --- a/chrome/browser/flags/android/chrome_feature_list.h +++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -24,7 +24,6 @@ BASE_DECLARE_FEATURE(kNewTabPageCustomizationV2); BASE_DECLARE_FEATURE(kNewTabPageCustomizationToolbarButton); BASE_DECLARE_FEATURE(kNewTabPageCustomizationForMvt); -BASE_DECLARE_FEATURE(kAndroidAnimatedProgressBarInBrowser); BASE_DECLARE_FEATURE(kAndroidAnimatedProgressBarInViz); BASE_DECLARE_FEATURE(kAndroidAppIntegrationWithFavicon); BASE_DECLARE_FEATURE(kAndroidAppIntegrationMultiDataSource);
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 4d29c91..22a21d1 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
@@ -1455,6 +1455,9 @@ "multi_data_source_skip_device_check", false); + public static final BooleanCachedFeatureParam sAndroidBookmarkBarShowBookmarkBar = + newBooleanCachedFeatureParam(ANDROID_BOOKMARK_BAR, "show_bookmark_bar", false); + public static final BooleanCachedFeatureParam sAndroidComposeplateSkipLocaleCheck = newBooleanCachedFeatureParam(ANDROID_COMPOSEPLATE, "skip_locale_check", false); @@ -1795,6 +1798,7 @@ sAndroidAppIntegrationWithFaviconSkipSchemaCheck, sAndroidAppIntegrationWithFaviconUseLargeFavicon, sAndroidAppIntegrationWithFaviconZeroStateFaviconNumber, + sAndroidBookmarkBarShowBookmarkBar, sAndroidBottomToolbarDefaultToTop, sAndroidComposeplateHideIncognitoButton, sAndroidComposeplateSkipLocaleCheck,
diff --git a/chrome/browser/glic/BUILD.gn b/chrome/browser/glic/BUILD.gn index 6d07956..034e72e 100644 --- a/chrome/browser/glic/BUILD.gn +++ b/chrome/browser/glic/BUILD.gn
@@ -52,7 +52,6 @@ "public/glic_enabling.h", "public/glic_keyed_service.h", "public/glic_keyed_service_factory.h", - "widget/glic_side_panel_coordinator.h", "widget/glic_view.h", "widget/glic_window_controller.h", "widget/local_hotkey_manager.h", @@ -162,7 +161,6 @@ "widget/glic_floating_ui.h", "widget/glic_panel_hotkey_delegate.cc", "widget/glic_panel_hotkey_delegate.h", - "widget/glic_side_panel_coordinator.cc", "widget/glic_side_panel_ui.cc", "widget/glic_side_panel_ui.h", "widget/glic_view.cc",
diff --git a/chrome/browser/glic/widget/glic_side_panel_coordinator.h b/chrome/browser/glic/widget/glic_side_panel_coordinator.h deleted file mode 100644 index 5538b9da..0000000 --- a/chrome/browser/glic/widget/glic_side_panel_coordinator.h +++ /dev/null
@@ -1,36 +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 CHROME_BROWSER_GLIC_WIDGET_GLIC_SIDE_PANEL_COORDINATOR_H_ -#define CHROME_BROWSER_GLIC_WIDGET_GLIC_SIDE_PANEL_COORDINATOR_H_ - -#include <memory> - -class SidePanelEntryScope; -class SidePanelRegistry; - -namespace views { -class View; -} // namespace views - -namespace glic { - -// GlicSidePanelCoordinator handles the creation and registration of the -// glic SidePanelEntry. -class GlicSidePanelCoordinator { - public: - GlicSidePanelCoordinator(); - ~GlicSidePanelCoordinator() = default; - - // Create and register the Glic side panel entry. - void CreateAndRegisterEntry(SidePanelRegistry* global_registry); - - private: - // Gets the Glic WebView from the Glic service. - std::unique_ptr<views::View> CreateGlicWebView(SidePanelEntryScope& scope); -}; - -} // namespace glic - -#endif // CHROME_BROWSER_GLIC_WIDGET_GLIC_SIDE_PANEL_COORDINATOR_H_
diff --git a/chrome/browser/history/BUILD.gn b/chrome/browser/history/BUILD.gn index 2bf9957..0fd18cab 100644 --- a/chrome/browser/history/BUILD.gn +++ b/chrome/browser/history/BUILD.gn
@@ -2,77 +2,78 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import("//build/config/android/config.gni") -import("//build/config/android/rules.gni") -import("//chrome/browser/buildflags.gni") -import("//third_party/jni_zero/jni_zero.gni") +if (is_android) { + import("//build/config/android/config.gni") + import("//build/config/android/rules.gni") + import("//chrome/browser/buildflags.gni") + import("//third_party/jni_zero/jni_zero.gni") -android_library("java") { - srcjar_deps = [ ":jni_headers" ] - sources = [ - "java/src/org/chromium/chrome/browser/history/AppFilterCoordinator.java", - "java/src/org/chromium/chrome/browser/history/AppFilterMediator.java", - "java/src/org/chromium/chrome/browser/history/AppFilterProperties.java", - "java/src/org/chromium/chrome/browser/history/AppFilterSheetContent.java", - "java/src/org/chromium/chrome/browser/history/AppFilterViewBinder.java", - "java/src/org/chromium/chrome/browser/history/HistoryTabHelper.java", - ] + android_library("java") { + srcjar_deps = [ ":jni_headers" ] + sources = [ + "java/src/org/chromium/chrome/browser/history/AppFilterCoordinator.java", + "java/src/org/chromium/chrome/browser/history/AppFilterMediator.java", + "java/src/org/chromium/chrome/browser/history/AppFilterProperties.java", + "java/src/org/chromium/chrome/browser/history/AppFilterSheetContent.java", + "java/src/org/chromium/chrome/browser/history/AppFilterViewBinder.java", + "java/src/org/chromium/chrome/browser/history/HistoryTabHelper.java", + ] - deps = [ - ":java_resources", - "//base:base_java", - "//chrome/browser/tab:java", - "//components/browser_ui/bottomsheet/android:java", - "//content/public/android:content_java", - "//third_party/androidx:androidx_annotation_annotation_java", - "//third_party/androidx:androidx_annotation_annotation_java", - "//third_party/androidx:androidx_recyclerview_recyclerview_java", - "//third_party/jni_zero:jni_zero_java", - "//ui/android:ui_full_java", - ] - resources_package = "org.chromium.chrome.browser.history" -} + deps = [ + ":java_resources", + "//base:base_java", + "//chrome/browser/tab:java", + "//components/browser_ui/bottomsheet/android:java", + "//content/public/android:content_java", + "//third_party/androidx:androidx_annotation_annotation_java", + "//third_party/androidx:androidx_recyclerview_recyclerview_java", + "//third_party/jni_zero:jni_zero_java", + "//ui/android:ui_full_java", + ] + resources_package = "org.chromium.chrome.browser.history" + } -generate_jni("jni_headers") { - sources = - [ "java/src/org/chromium/chrome/browser/history/HistoryTabHelper.java" ] -} + generate_jni("jni_headers") { + sources = + [ "java/src/org/chromium/chrome/browser/history/HistoryTabHelper.java" ] + } -android_resources("java_resources") { - sources = [ - "java/res/layout/appfilter_content.xml", - "java/res/layout/appfilter_header.xml", - ] - deps = [ - "//chrome/browser/ui/android/strings:ui_strings_grd", - "//components/browser_ui/widget/android:java_resources", - ] -} + android_resources("java_resources") { + sources = [ + "java/res/layout/appfilter_content.xml", + "java/res/layout/appfilter_header.xml", + ] + deps = [ + "//chrome/browser/ui/android/strings:ui_strings_grd", + "//components/browser_ui/widget/android:java_resources", + ] + } -android_library("unit_device_javatests") { - testonly = true - resources_package = "org.chromium.chrome.browser.history" + android_library("unit_device_javatests") { + testonly = true + resources_package = "org.chromium.chrome.browser.history" - sources = [ "java/src/org/chromium/chrome/browser/history/AppFilterCoordinatorTest.java" ] + sources = [ "java/src/org/chromium/chrome/browser/history/AppFilterCoordinatorTest.java" ] - deps = [ - ":java", - ":java_resources", - "//base:base_java", - "//base:base_java_test_support", - "//chrome/browser/flags:java", - "//chrome/test/android:chrome_java_unit_test_support", - "//components/browser_ui/bottomsheet/android:factory_java", - "//components/browser_ui/bottomsheet/android:java", - "//components/browser_ui/bottomsheet/android:manager_java", - "//components/browser_ui/desktop_windowing/android:java", - "//components/browser_ui/widget/android:java", - "//content/public/test/android:content_java_test_support", - "//third_party/androidx:androidx_test_monitor_java", - "//third_party/androidx:androidx_test_rules_java", - "//third_party/androidx:androidx_test_runner_java", - "//third_party/junit", - "//ui/android:ui_java", - "//ui/android:ui_java_test_support", - ] + deps = [ + ":java", + ":java_resources", + "//base:base_java", + "//base:base_java_test_support", + "//chrome/browser/flags:java", + "//chrome/test/android:chrome_java_unit_test_support", + "//components/browser_ui/bottomsheet/android:factory_java", + "//components/browser_ui/bottomsheet/android:java", + "//components/browser_ui/bottomsheet/android:manager_java", + "//components/browser_ui/desktop_windowing/android:java", + "//components/browser_ui/widget/android:java", + "//content/public/test/android:content_java_test_support", + "//third_party/androidx:androidx_test_monitor_java", + "//third_party/androidx:androidx_test_rules_java", + "//third_party/androidx:androidx_test_runner_java", + "//third_party/junit", + "//ui/android:ui_java", + "//ui/android:ui_java_test_support", + ] + } }
diff --git a/chrome/browser/long_screenshots/long_screenshots_tab_service_unittest.cc b/chrome/browser/long_screenshots/long_screenshots_tab_service_unittest.cc index 78f911a..923049fb 100644 --- a/chrome/browser/long_screenshots/long_screenshots_tab_service_unittest.cc +++ b/chrome/browser/long_screenshots/long_screenshots_tab_service_unittest.cc
@@ -117,7 +117,7 @@ const int kTabId = 1U; LaxMockPaintPreviewRecorder recorder; - recorder.SetResponse(paint_preview::mojom::PaintPreviewStatus::kOk); + recorder.SetResponse(); OverrideInterface(&recorder); auto* service = GetService(); @@ -155,8 +155,7 @@ response->geometry_metadata = paint_preview::mojom::GeometryMetadataResponse::New(); response->skp.emplace(mojo_base::BigBuffer()); - recorder.SetResponse(paint_preview::mojom::PaintPreviewStatus::kOk, - std::move(response)); + recorder.SetResponse(std::move(response)); OverrideInterface(&recorder); auto* service = GetService(); @@ -182,7 +181,7 @@ const int kTabId = 1U; LaxMockPaintPreviewRecorder recorder; - recorder.SetResponse(paint_preview::mojom::PaintPreviewStatus::kOk); + recorder.SetResponse(); OverrideInterface(&recorder); auto* service = GetService(); @@ -214,7 +213,7 @@ auto files_1 = ListDir(path_1); ASSERT_EQ(1U, files_1.size()); - recorder.SetResponse(paint_preview::mojom::PaintPreviewStatus::kOk); + recorder.SetResponse(); service->CaptureTab(kTabId, GURL::EmptyGURL(), web_contents(), 1000, 1000, 2000, 2000, false, paint_preview::mojom::ClipCoordOverride::kNone, @@ -259,7 +258,8 @@ const int kTabId = 1U; LaxMockPaintPreviewRecorder recorder; - recorder.SetResponse(paint_preview::mojom::PaintPreviewStatus::kFailed); + recorder.SetResponse( + base::unexpected(paint_preview::mojom::PaintPreviewStatus::kFailed)); OverrideInterface(&recorder); auto* service = GetService();
diff --git a/chrome/browser/new_tab_page/modules/file_suggestion/microsoft_files_page_handler.cc b/chrome/browser/new_tab_page/modules/file_suggestion/microsoft_files_page_handler.cc index bb5dc7d..b9f889e 100644 --- a/chrome/browser/new_tab_page/modules/file_suggestion/microsoft_files_page_handler.cc +++ b/chrome/browser/new_tab_page/modules/file_suggestion/microsoft_files_page_handler.cc
@@ -11,6 +11,7 @@ #include "chrome/browser/new_tab_page/microsoft_auth/microsoft_auth_service_factory.h" #include "chrome/browser/new_tab_page/modules/file_suggestion/file_suggestion.mojom.h" #include "chrome/browser/new_tab_page/modules/microsoft_modules_helper.h" +#include "chrome/browser/new_tab_page/new_tab_page_util.h" #include "chrome/grit/generated_resources.h" #include "net/http/http_response_headers.h" #include "net/http/http_status_code.h" @@ -313,6 +314,10 @@ return TimeFormatAsIso8601(base::Time::Now()); } +std::string GetBoolAsString(bool x) { + return x ? "true" : "false"; +} + // The number of responses that should be found in the response body of JSON // batch requests. constexpr size_t kNonInsightsRequiredResponseSize = 2; @@ -540,6 +545,8 @@ MicrosoftFilesPageHandler::GetTrendingFiles(base::Value::Dict result) { auto* suggestions = result.FindList("value"); if (!suggestions) { + LogModuleError(ntp_features::kNtpSharepointModule, + "Content Error: Trending Files missing 'value' field"); request_result_ = MicrosoftFilesRequestResult::kContentError; return std::vector<file_suggestion::mojom::FilePtr>(); } @@ -565,6 +572,15 @@ "resourceVisualization.mediaType"); if (!id || !title || !url || !mime_type) { + LogModuleError( + ntp_features::kNtpSharepointModule, + base::StringPrintf( + "Content Error: Trending File missing field ('id': %s, " + "'resourceVisualization.title': %s, 'resourceReference.webUrl': " + "%s, " + "'resourceVisualization.mediaType': %s)", + GetBoolAsString(!id), GetBoolAsString(!title), + GetBoolAsString(!url), GetBoolAsString(!mime_type))); request_result_ = MicrosoftFilesRequestResult::kContentError; return std::vector<file_suggestion::mojom::FilePtr>(); } @@ -666,6 +682,21 @@ &sort_time); if (!id || !title || !item_url || !last_modified_time_str || !suggestion_has_formatted_time) { + LogModuleError( + ntp_features::kNtpSharepointModule, + base::StringPrintf( + "Content Error: Recent/Shared File ('response_id': %s) missing " + "field " + "('id': %s, 'name': %s, 'webUrl': %s, 'lastModifiedDateTime': " + "%s, " + "'fileSystemInfo.lastAccessedDateTime': %s, " + "'remoteItem.shared.sharedBy.user.displayName': %s, " + "'remoteItem.shared.sharedDateTime': %s)", + response_id, GetBoolAsString(!id), GetBoolAsString(!title), + GetBoolAsString(!item_url), + GetBoolAsString(!last_modified_time_str), + GetBoolAsString(!last_opened_time_str), + GetBoolAsString(!shared_by), GetBoolAsString(!shared_time_str))); request_result_ = MicrosoftFilesRequestResult::kContentError; return std::vector< std::pair<base::Time, file_suggestion::mojom::FilePtr>>(); @@ -729,6 +760,9 @@ base::Value::Dict result) { auto* responses = result.FindList("responses"); if (!responses) { + LogModuleError( + ntp_features::kNtpSharepointModule, + "Content Error: Recent/Shared response body missing 'responses' field"); request_result_ = MicrosoftFilesRequestResult::kContentError; return std::vector<file_suggestion::mojom::FilePtr>(); } @@ -736,6 +770,9 @@ // The response body should contain a list that has 2 dictionaries - one for // each request, with their own lists containing file data. if (responses->size() != kNonInsightsRequiredResponseSize) { + LogModuleError(ntp_features::kNtpSharepointModule, + "Content Error: Recent/Shared response body has incorrect " + "'responses' length"); request_result_ = MicrosoftFilesRequestResult::kContentError; return std::vector<file_suggestion::mojom::FilePtr>(); } @@ -748,6 +785,9 @@ const std::string* response_id = response_dict.FindString("id"); auto* suggestions = response_dict.FindListByDottedPath("body.value"); if (!response_id || !suggestions) { + LogModuleError(ntp_features::kNtpSharepointModule, + "Content Error: Recent/Shared response missing 'id' or " + "'body.value' field"); request_result_ = MicrosoftFilesRequestResult::kContentError; return std::vector<file_suggestion::mojom::FilePtr>(); } @@ -847,6 +887,9 @@ base::Value::Dict result) { auto* responses = result.FindList("responses"); if (!responses) { + LogModuleError( + ntp_features::kNtpSharepointModule, + "Content Error: Aggrefated response body missing 'responses' field"); request_result_ = MicrosoftFilesRequestResult::kContentError; return std::vector<file_suggestion::mojom::FilePtr>(); } @@ -854,6 +897,9 @@ // The response body should contain a list that has a dictionary for each // request, with their own lists containing file data. if (responses->size() != kCombinedSuggestionsRequiredResponseSize) { + LogModuleError(ntp_features::kNtpSharepointModule, + "Content Error: Aggregated response body 'responses' field " + "incorrect length"); request_result_ = MicrosoftFilesRequestResult::kContentError; return std::vector<file_suggestion::mojom::FilePtr>(); } @@ -867,6 +913,8 @@ auto response_body = response_dict.FindDict("body")->Clone(); auto* body_value = response_dict.FindListByDottedPath("body.value"); if (!response_id) { + LogModuleError(ntp_features::kNtpSharepointModule, + "Content Error: Aggregated response missing 'id' field"); request_result_ = MicrosoftFilesRequestResult::kContentError; return std::vector<file_suggestion::mojom::FilePtr>(); } else if (*response_id == "trending") {
diff --git a/chrome/browser/paint_preview/services/paint_preview_tab_service_unittest.cc b/chrome/browser/paint_preview/services/paint_preview_tab_service_unittest.cc index 5d6578c..dc13150 100644 --- a/chrome/browser/paint_preview/services/paint_preview_tab_service_unittest.cc +++ b/chrome/browser/paint_preview/services/paint_preview_tab_service_unittest.cc
@@ -126,7 +126,7 @@ const int kTabId = 1U; LaxMockPaintPreviewRecorder recorder; - recorder.SetResponse(mojom::PaintPreviewStatus::kOk); + recorder.SetResponse(); OverrideInterface(&recorder); auto* service = GetService(); @@ -160,7 +160,7 @@ const int kTabId = 1U; LaxMockPaintPreviewRecorder recorder; - recorder.SetResponse(mojom::PaintPreviewStatus::kFailed); + recorder.SetResponse(base::unexpected(mojom::PaintPreviewStatus::kFailed)); OverrideInterface(&recorder); auto* service = GetService(); @@ -194,7 +194,7 @@ const int kTabId = 1U; LaxMockPaintPreviewRecorder recorder; - recorder.SetResponse(mojom::PaintPreviewStatus::kOk); + recorder.SetResponse(); OverrideInterface(&recorder); auto* service = GetService(); @@ -225,6 +225,7 @@ auto files_1 = ListDir(path_1); ASSERT_EQ(1U, files_1.size()); + recorder.SetResponse(); service->CaptureTab(kTabId, web_contents(), false, 1.0, 10, 20, base::BindOnce([](PaintPreviewTabService::Status status) { EXPECT_EQ(status, PaintPreviewTabService::Status::kOk); @@ -380,7 +381,7 @@ const int kTabId = 1U; LaxMockPaintPreviewRecorder recorder; - recorder.SetResponse(mojom::PaintPreviewStatus::kOk); + recorder.SetResponse(); OverrideInterface(&recorder); auto service = BuildServiceWithCache({}); @@ -414,7 +415,7 @@ const int kTabId = 1U; LaxMockPaintPreviewRecorder recorder; - recorder.SetResponse(mojom::PaintPreviewStatus::kOk); + recorder.SetResponse(); OverrideInterface(&recorder); auto service = BuildServiceWithCache({kTabId + 1});
diff --git a/chrome/browser/password_manager/android/BUILD.gn b/chrome/browser/password_manager/android/BUILD.gn index 26a76938..67872a7 100644 --- a/chrome/browser/password_manager/android/BUILD.gn +++ b/chrome/browser/password_manager/android/BUILD.gn
@@ -20,8 +20,6 @@ source_set("backend_public") { sources = [ - "login_db_deprecation_runner_factory.cc", - "login_db_deprecation_runner_factory.h", "password_manager_settings_service_android_impl.cc", "password_manager_settings_service_android_impl.h", ] @@ -548,7 +546,6 @@ "fake_password_manager_lifecycle_helper.h", "first_cct_page_load_marker_unittest.cc", "generated_password_saved_message_delegate_unittest.cc", - "login_db_deprecation_runner_factory_unittest.cc", "mock_password_checkup_launcher_helper.cc", "mock_password_checkup_launcher_helper.h", "mock_password_manager_error_message_helper_bridge.cc",
diff --git a/chrome/browser/password_manager/android/credential_leak_controller_android.cc b/chrome/browser/password_manager/android/credential_leak_controller_android.cc index 3f2d65b..75ac004 100644 --- a/chrome/browser/password_manager/android/credential_leak_controller_android.cc +++ b/chrome/browser/password_manager/android/credential_leak_controller_android.cc
@@ -23,23 +23,6 @@ using password_manager::metrics_util::LeakDialogMetricsRecorder; using password_manager::metrics_util::LeakDialogType; -namespace { -CredentialLeakType AdjustLeakTypeForLoginDbDeprecation( - CredentialLeakType leak_type, - Profile* profile) { - if (!password_manager_android_util::LoginDbDeprecationReady( - profile->GetPrefs())) { - // While the login DB deprecation is being prepared (passwords are - // exported), saved passwords from the DB are still visible to the leak - // service (until the export finishes). Resetting the leak type to 0, - // ensures we don't show a dialog with options for saved passwords - // (i.e. Password Checkup), because those are not available at this time. - return 0; - } - return leak_type; -} - -} // namespace CredentialLeakControllerAndroid::CredentialLeakControllerAndroid( password_manager::CredentialLeakType leak_type, const GURL& origin, @@ -49,7 +32,7 @@ std::unique_ptr<PasswordCheckupLauncherHelper> checkup_launcher, std::unique_ptr<LeakDialogMetricsRecorder> metrics_recorder, std::string account_email) - : leak_type_(AdjustLeakTypeForLoginDbDeprecation(leak_type, profile)), + : leak_type_(leak_type), origin_(origin), username_(username), profile_(profile),
diff --git a/chrome/browser/password_manager/android/credential_leak_controller_android_unittest.cc b/chrome/browser/password_manager/android/credential_leak_controller_android_unittest.cc index 0d634d3..f3c48e68 100644 --- a/chrome/browser/password_manager/android/credential_leak_controller_android_unittest.cc +++ b/chrome/browser/password_manager/android/credential_leak_controller_android_unittest.cc
@@ -18,7 +18,6 @@ #include "components/password_manager/core/browser/features/password_features.h" #include "components/password_manager/core/browser/leak_detection_dialog_utils.h" #include "components/password_manager/core/browser/password_manager_metrics_util.h" -#include "components/password_manager/core/browser/split_stores_and_local_upm.h" #include "components/password_manager/core/common/password_manager_pref_names.h" #include "components/prefs/pref_service.h" #include "components/ukm/test_ukm_recorder.h" @@ -227,14 +226,12 @@ LeakDialogDismissalReason::kNoDirectInteraction); } -// The following tests are specific to the login DB deprecation. -TEST_F(CredentialLeakControllerAndroidTest, - LeakTypeResetToChangeIfLoginDbDeprecationNotReady) { - // The export state is only valid for users who are not enrolled in UPM. - password_manager::SetLegacySplitStoresPrefForTest(profile()->GetPrefs(), - false); - profile()->GetPrefs()->SetBoolean( - password_manager::prefs::kUpmUnmigratedPasswordsExported, false); +TEST_F(CredentialLeakControllerAndroidTest, LeakType) { + if (base::android::device_info::is_automotive()) { + // Automotive only uses the base leak type and doesn't display the + // "Check passwords" button. + GTEST_SKIP() << "This test should not run on automotive."; + } std::unique_ptr<MockPasswordCheckupLauncherHelper> mock_launcher = std::make_unique<MockPasswordCheckupLauncherHelper>(); @@ -254,75 +251,6 @@ LaunchCheckupOnDevice( _, profile(), _, password_manager::PasswordCheckReferrerAndroid::kLeakDialog, _)) - .Times(0); - controller->OnAcceptDialog(); -} - -TEST_F(CredentialLeakControllerAndroidTest, - LeakTypeNotResetIfPasswordsExported) { - if (base::android::device_info::is_automotive()) { - // Automotive only uses the base leak type and doesn't display the - // "Check passwords" button. - GTEST_SKIP() << "This test should not run on automotive."; - } - - // The export state is only valid for users who are not enrolled in UPM. - password_manager::SetLegacySplitStoresPrefForTest(profile()->GetPrefs(), - false); - profile()->GetPrefs()->SetBoolean( - password_manager::prefs::kUpmUnmigratedPasswordsExported, true); - - std::unique_ptr<MockPasswordCheckupLauncherHelper> mock_launcher = - std::make_unique<MockPasswordCheckupLauncherHelper>(); - MockPasswordCheckupLauncherHelper* weak_mock_launcher = mock_launcher.get(); - - // Setting `isReused` to true will normally cause the leak type to be - // `kCheckup`. - CredentialLeakControllerAndroid* controller = - MakeController(profile(), std::move(mock_launcher), IsSaved(false), - IsReused(true), IsSyncing(false), - /* account_email_ = */ ""); - - // Expect that despite the original leak being of type `kCheckup` the positive - // button is not the "Check" one. - EXPECT_CALL( - *weak_mock_launcher, - LaunchCheckupOnDevice( - _, profile(), _, - password_manager::PasswordCheckReferrerAndroid::kLeakDialog, _)) - .Times(1); - controller->OnAcceptDialog(); -} - -TEST_F(CredentialLeakControllerAndroidTest, - LeakTypeNotResetIfUPMAlreadyActive) { - if (base::android::device_info::is_automotive()) { - // Automotive only uses the base leak type and doesn't display the - // "Check passwords" button. - GTEST_SKIP() << "This test should not run on automotive."; - } - - password_manager::SetLegacySplitStoresPrefForTest(profile()->GetPrefs(), - true); - profile()->GetPrefs()->SetBoolean( - password_manager::prefs::kUpmUnmigratedPasswordsExported, false); - - std::unique_ptr<MockPasswordCheckupLauncherHelper> mock_launcher = - std::make_unique<MockPasswordCheckupLauncherHelper>(); - MockPasswordCheckupLauncherHelper* weak_mock_launcher = mock_launcher.get(); - - // Setting `isReused` to true will cause the leak type to be `kCheckup`. - CredentialLeakControllerAndroid* controller = - MakeController(profile(), std::move(mock_launcher), IsSaved(false), - IsReused(true), IsSyncing(false), - /* account_email_ = */ ""); - - // Check that the checkup option is presented to the user if UPM is active. - EXPECT_CALL( - *weak_mock_launcher, - LaunchCheckupOnDevice( - _, profile(), _, - password_manager::PasswordCheckReferrerAndroid::kLeakDialog, _)) .Times(1); controller->OnAcceptDialog(); }
diff --git a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelper.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelper.java index 4be6b10..15a9e9e 100644 --- a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelper.java +++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelper.java
@@ -34,16 +34,13 @@ import org.chromium.chrome.browser.password_manager.CredentialManagerLauncher.CredentialManagerBackendException; import org.chromium.chrome.browser.password_manager.CredentialManagerLauncher.CredentialManagerError; import org.chromium.chrome.browser.password_manager.PasswordCheckupClientHelper.PasswordManagerUnavailableException; -import org.chromium.chrome.browser.preferences.Pref; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.ProfileKeyedMap; import org.chromium.chrome.browser.pwm_disabled.PasswordCsvDownloadFlowControllerFactory; import org.chromium.chrome.browser.pwm_disabled.PasswordManagerUnavailableDialogCoordinator; import org.chromium.chrome.browser.sync.SyncServiceFactory; import org.chromium.components.browser_ui.settings.SettingsCustomTabLauncher; -import org.chromium.components.prefs.PrefService; import org.chromium.components.sync.SyncService; -import org.chromium.components.user_prefs.UserPrefs; import org.chromium.ui.modaldialog.ModalDialogManager; import java.lang.annotation.Retention; @@ -136,15 +133,6 @@ ManagePasswordsReferrer.MAX_VALUE); SyncService syncService = SyncServiceFactory.getForProfile(mProfile); - PrefService prefService = UserPrefs.get(mProfile); - - if (!PasswordManagerUtilBridge.isPasswordManagerAvailable(prefService) - && !prefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)) { - // The automatic export is ongoing. Usually a dialog offering the user to download - // the auto-exported CSV would be shown, but until the CSV is written, the dialog - // can't be shown. This is a rare corner-case. - return; - } if (!showPwmUnavailableOrDownloadCsvDialog( context, modalDialogManagerSupplier, settingsCustomTabLauncher)) { LoadingModalDialogCoordinator loadingDialogCoordinator = @@ -152,7 +140,6 @@ () -> assertNonNull(modalDialogManagerSupplier.get()), context); launchTheCredentialManager( referrer, - prefService, syncService, loadingDialogCoordinator, modalDialogManagerSupplier, @@ -178,8 +165,7 @@ activity, mProfile, PasswordManagerUtilBridge.isGooglePlayServicesUpdatable(), - PasswordManagerUtilBridge.isPasswordManagerAvailable( - UserPrefs.get(mProfile)), + PasswordManagerUtilBridge.isPasswordManagerAvailable(), settingsCustomTabLauncher); } @@ -194,7 +180,7 @@ return true; } - if (!PasswordManagerUtilBridge.isPasswordManagerAvailable(UserPrefs.get(mProfile))) { + if (!PasswordManagerUtilBridge.isPasswordManagerAvailable()) { new PasswordManagerUnavailableDialogCoordinator() .showDialog( context, @@ -426,14 +412,13 @@ @VisibleForTesting void launchTheCredentialManager( @ManagePasswordsReferrer int referrer, - PrefService prefService, @Nullable SyncService syncService, LoadingModalDialogCoordinator loadingDialogCoordinator, Supplier<@Nullable ModalDialogManager> modalDialogManagerSupplier, Context context, @Nullable String account) { assert syncService != null; - assert PasswordManagerUtilBridge.isPasswordManagerAvailable(prefService); + assert PasswordManagerUtilBridge.isPasswordManagerAvailable(); CredentialManagerLauncher credentialManagerLauncher = CredentialManagerLauncherFactory.getInstance().createLauncher(); assert credentialManagerLauncher != null; @@ -477,16 +462,6 @@ try { checkupClient = getPasswordCheckupClientHelper(); } catch (PasswordManagerUnavailableException exception) { - // This is slightly different than the access to the management UI where if there - // is an auto-exported CSV, a dialog shown even if the password manager is available - // If a checkup is possible, the results of the checkup are more important than - // the CSV. In addition, the CSV issue is being prominently presented on the - // main settings view. - if (!UserPrefs.get(mProfile).getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)) { - // The auto-exported file is not ready, so there it's not possible to show a - // dialog to download the CSV. - return; - } assert settingsCustomTabLauncher != null; showPwmUnavailableOrDownloadCsvDialog( context, modalDialogManagerSupplier, settingsCustomTabLauncher); @@ -673,7 +648,7 @@ // Callers shouldn't need to distinguish between the errors anymore, since there will be no // more partial support, so PASSWORD_MANGER_NOT_AVAILABLE will replace and include all the // errors. - if (!PasswordManagerUtilBridge.isPasswordManagerAvailable(UserPrefs.get(mProfile))) { + if (!PasswordManagerUtilBridge.isPasswordManagerAvailable()) { throw new PasswordManagerUnavailableException(); }
diff --git a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerUtilBridge.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerUtilBridge.java index c15c06e..7d902ff0 100644 --- a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerUtilBridge.java +++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordManagerUtilBridge.java
@@ -5,12 +5,10 @@ package org.chromium.chrome.browser.password_manager; import org.jni_zero.CalledByNative; -import org.jni_zero.JniType; import org.jni_zero.NativeMethods; import org.chromium.base.PackageUtils; import org.chromium.build.annotations.NullMarked; -import org.chromium.components.prefs.PrefService; /** Wrapper for utilities in password_manager_util. */ @NullMarked @@ -23,9 +21,9 @@ * * @return whether password manager functionality is available. */ - public static boolean isPasswordManagerAvailable(PrefService prefService) { + public static boolean isPasswordManagerAvailable() { return PasswordManagerUtilBridgeJni.get() - .isPasswordManagerAvailable(prefService, isInternalBackendPresent()); + .isPasswordManagerAvailable(isInternalBackendPresent()); } @CalledByNative @@ -47,7 +45,6 @@ @NativeMethods public interface Natives { - boolean isPasswordManagerAvailable( - @JniType("PrefService*") PrefService prefService, boolean isInternalBackendPresent); + boolean isPasswordManagerAvailable(boolean isInternalBackendPresent); } }
diff --git a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordsPreference.java b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordsPreference.java index 7875ef2..1836b36e 100644 --- a/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordsPreference.java +++ b/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordsPreference.java
@@ -68,7 +68,7 @@ } // This warning should override the managed text if any is present. - setUpPostDeprecationWarning(holder, prefService); + setUpPostDeprecationWarning(holder); } private void setUpManagedDisclaimerView(PreferenceViewHolder holder, PrefService prefService) { @@ -86,11 +86,10 @@ .password_saving_off_by_administrator); } - private void setUpPostDeprecationWarning(PreferenceViewHolder holder, PrefService prefService) { + private void setUpPostDeprecationWarning(PreferenceViewHolder holder) { assert mProfile != null : "Profile is not set!"; - boolean isPasswordManagerAvailable = - PasswordManagerUtilBridge.isPasswordManagerAvailable(prefService); + boolean isPasswordManagerAvailable = PasswordManagerUtilBridge.isPasswordManagerAvailable(); boolean hasPasswordsInCsv = LoginDbDeprecationUtilBridge.hasPasswordsInCsv(mProfile); // If there are no unmigrated passwords left in Chrome and the password manager is available @@ -99,14 +98,6 @@ return; } - // If the password manager is not available, but the auto-export hasn't finished yet - // don't show any subtitle either, because clicking the button will have no effect anyway - // and the version of the subtitle cannot be accurately determined. - if (!isPasswordManagerAvailable - && !prefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)) { - return; - } - // If either the password manager is not available or it is available but there are // unmigrated passwords left in Chrome, show a subtitle notifying the user of that. // Automotive doesn't support the export flow, so only the "stopped working"
diff --git a/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerCheckupHelperTest.java b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerCheckupHelperTest.java index 17ba893..e3ca992 100644 --- a/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerCheckupHelperTest.java +++ b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerCheckupHelperTest.java
@@ -55,19 +55,15 @@ import org.chromium.chrome.browser.password_manager.CredentialManagerLauncher.CredentialManagerError; import org.chromium.chrome.browser.password_manager.PasswordCheckupClientHelper.PasswordManagerUnavailableException; import org.chromium.chrome.browser.password_manager.PasswordManagerHelper.PasswordCheckOperation; -import org.chromium.chrome.browser.preferences.Pref; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.pwm_disabled.PasswordCsvDownloadFlowController; import org.chromium.chrome.browser.pwm_disabled.PasswordCsvDownloadFlowControllerFactory; import org.chromium.chrome.browser.sync.SyncServiceFactory; import org.chromium.components.browser_ui.settings.SettingsCustomTabLauncher; import org.chromium.components.browser_ui.test.BrowserUiTestFragmentActivity; -import org.chromium.components.prefs.PrefService; import org.chromium.components.signin.base.CoreAccountInfo; import org.chromium.components.sync.SyncService; import org.chromium.components.sync.UserSelectableType; -import org.chromium.components.user_prefs.UserPrefs; -import org.chromium.components.user_prefs.UserPrefsJni; import org.chromium.google_apis.gaia.GaiaId; import org.chromium.ui.modaldialog.ModalDialogManager; import org.chromium.ui.modaldialog.ModalDialogProperties; @@ -95,10 +91,6 @@ @Mock private Profile mProfile; - @Mock private PrefService mPrefService; - - @Mock private UserPrefs.Natives mUserPrefsJniMock; - @Mock private PasswordManagerUtilBridge.Natives mPasswordManagerUtilBridgeJniMock; @Mock private SyncService mSyncServiceMock; @@ -121,10 +113,8 @@ @Before public void setUp() { - UserPrefsJni.setInstanceForTesting(mUserPrefsJniMock); PasswordManagerUtilBridgeJni.setInstanceForTesting(mPasswordManagerUtilBridgeJniMock); mPasswordManagerHelper = new PasswordManagerHelper(mProfile); - when(mUserPrefsJniMock.get(mProfile)).thenReturn(mPrefService); SyncServiceFactory.setInstanceForTesting(mSyncServiceMock); when(mLoadingModalDialogCoordinator.getState()) .thenReturn(LoadingModalDialogCoordinator.State.PENDING); @@ -142,9 +132,7 @@ .addObserver(any(LoadingModalDialogCoordinator.Observer.class)); PasswordManagerBackendSupportHelper.setInstanceForTesting(mBackendSupportHelperMock); when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true); - when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable( - eq(mPrefService), eq(true))) - .thenReturn(true); + when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(true); when(mPasswordCheckupClientHelperFactoryMock.createHelper()) .thenReturn(mPasswordCheckupClientHelperMock); PasswordCheckupClientHelperFactory.setFactoryForTesting( @@ -154,9 +142,7 @@ @Test public void testThrowsPasswordManagerNotAvailableException() { - when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable( - eq(mPrefService), eq(true))) - .thenReturn(false); + when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false); chooseToSyncPasswords(); setUpSuccessfulRunPasswordCheckup(); @@ -1043,10 +1029,7 @@ @Test public void testShowDownloadCsvDialogIfCsvIsPresentAndPwmNotAvailable() { when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true); - when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable( - eq(mPrefService), eq(true))) - .thenReturn(false); - when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true); + when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false); LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(true); FragmentActivity testActivity = @@ -1075,10 +1058,7 @@ @Test public void testShowDownloadCsvDialogIfCsvIsPresentAndNoGms() { when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true); - when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable( - eq(mPrefService), eq(true))) - .thenReturn(false); - when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true); + when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false); LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(true); FragmentActivity testActivity = @@ -1106,10 +1086,7 @@ @Test public void testShowPwmUnavailableDialogNoCsvNoGms() { when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true); - when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable( - eq(mPrefService), eq(true))) - .thenReturn(false); - when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true); + when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false); LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(false); FragmentActivity testActivity = @@ -1131,10 +1108,7 @@ @Test public void testShowPwmUnavailableDialogNoCsvUpdatableGms() { when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true); - when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable( - eq(mPrefService), eq(true))) - .thenReturn(false); - when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true); + when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false); LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(false); FragmentActivity testActivity =
diff --git a/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelperTest.java b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelperTest.java index 607e53b..746e9a7 100644 --- a/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelperTest.java +++ b/chrome/browser/password_manager/android/junit/src/org/chromium/chrome/browser/password_manager/PasswordManagerHelperTest.java
@@ -6,10 +6,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; @@ -54,7 +52,6 @@ import org.chromium.chrome.browser.loading_modal.LoadingModalDialogCoordinator; import org.chromium.chrome.browser.password_manager.CredentialManagerLauncher.CredentialManagerBackendException; import org.chromium.chrome.browser.password_manager.CredentialManagerLauncher.CredentialManagerError; -import org.chromium.chrome.browser.preferences.Pref; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.pwm_disabled.PasswordCsvDownloadFlowController; import org.chromium.chrome.browser.pwm_disabled.PasswordCsvDownloadFlowControllerFactory; @@ -63,12 +60,9 @@ import org.chromium.components.browser_ui.settings.SettingsCustomTabLauncher; import org.chromium.components.browser_ui.settings.SettingsNavigation; import org.chromium.components.browser_ui.test.BrowserUiTestFragmentActivity; -import org.chromium.components.prefs.PrefService; import org.chromium.components.signin.base.CoreAccountInfo; import org.chromium.components.sync.SyncService; import org.chromium.components.sync.UserSelectableType; -import org.chromium.components.user_prefs.UserPrefs; -import org.chromium.components.user_prefs.UserPrefsJni; import org.chromium.google_apis.gaia.GaiaId; import org.chromium.google_apis.gaia.GoogleServiceAuthError; import org.chromium.google_apis.gaia.GoogleServiceAuthErrorState; @@ -100,10 +94,6 @@ @Mock private Profile mProfile; - @Mock private PrefService mPrefService; - - @Mock private UserPrefs.Natives mUserPrefsJniMock; - @Mock private PasswordManagerUtilBridge.Natives mPasswordManagerUtilBridgeJniMock; @Mock private SyncService mSyncServiceMock; @@ -129,13 +119,10 @@ @Before public void setUp() throws CredentialManagerBackendException { // TODO(crbug.com/40940922): Parametrise the tests for local and account. - UserPrefsJni.setInstanceForTesting(mUserPrefsJniMock); PasswordManagerUtilBridgeJni.setInstanceForTesting(mPasswordManagerUtilBridgeJniMock); - when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable( - eq(mPrefService), anyBoolean())) + when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(anyBoolean())) .thenReturn(true); mPasswordManagerHelper = new PasswordManagerHelper(mProfile); - when(mUserPrefsJniMock.get(mProfile)).thenReturn(mPrefService); SyncServiceFactory.setInstanceForTesting(mSyncServiceMock); when(mSyncServiceMock.isEngineInitialized()).thenReturn(true); when(mSyncServiceMock.getAuthError()) @@ -281,7 +268,6 @@ mPasswordManagerHelper.launchTheCredentialManager( ManagePasswordsReferrer.CHROME_SETTINGS, - mPrefService, mSyncServiceMock, mLoadingModalDialogCoordinator, mModalDialogManagerSupplier, @@ -299,7 +285,6 @@ mPasswordManagerHelper.launchTheCredentialManager( ManagePasswordsReferrer.CHROME_SETTINGS, - mPrefService, mSyncServiceMock, mLoadingModalDialogCoordinator, mModalDialogManagerSupplier, @@ -318,7 +303,6 @@ mPasswordManagerHelper.launchTheCredentialManager( ManagePasswordsReferrer.CHROME_SETTINGS, - mPrefService, mSyncServiceMock, mLoadingModalDialogCoordinator, mModalDialogManagerSupplier, @@ -336,7 +320,6 @@ mPasswordManagerHelper.launchTheCredentialManager( ManagePasswordsReferrer.CHROME_SETTINGS, - mPrefService, mSyncServiceMock, mLoadingModalDialogCoordinator, mModalDialogManagerSupplier, @@ -356,7 +339,6 @@ mPasswordManagerHelper.launchTheCredentialManager( ManagePasswordsReferrer.CHROME_SETTINGS, - mPrefService, mSyncServiceMock, mLoadingModalDialogCoordinator, mModalDialogManagerSupplier, @@ -376,7 +358,6 @@ mPasswordManagerHelper.launchTheCredentialManager( ManagePasswordsReferrer.CHROME_SETTINGS, - mPrefService, mSyncServiceMock, mLoadingModalDialogCoordinator, mModalDialogManagerSupplier, @@ -395,7 +376,6 @@ mPasswordManagerHelper.launchTheCredentialManager( ManagePasswordsReferrer.CHROME_SETTINGS, - mPrefService, mSyncServiceMock, mLoadingModalDialogCoordinator, mModalDialogManagerSupplier, @@ -417,7 +397,6 @@ mPasswordManagerHelper.launchTheCredentialManager( ManagePasswordsReferrer.CHROME_SETTINGS, - mPrefService, mSyncServiceMock, mLoadingModalDialogCoordinator, mModalDialogManagerSupplier, @@ -438,7 +417,6 @@ mPasswordManagerHelper.launchTheCredentialManager( ManagePasswordsReferrer.CHROME_SETTINGS, - mPrefService, mSyncServiceMock, mLoadingModalDialogCoordinator, mModalDialogManagerSupplier, @@ -460,7 +438,6 @@ mPasswordManagerHelper.launchTheCredentialManager( ManagePasswordsReferrer.CHROME_SETTINGS, - mPrefService, mSyncServiceMock, mLoadingModalDialogCoordinator, mModalDialogManagerSupplier, @@ -483,7 +460,6 @@ mPasswordManagerHelper.launchTheCredentialManager( ManagePasswordsReferrer.CHROME_SETTINGS, - mPrefService, mSyncServiceMock, mLoadingModalDialogCoordinator, mModalDialogManagerSupplier, @@ -501,7 +477,6 @@ mPasswordManagerHelper.launchTheCredentialManager( ManagePasswordsReferrer.CHROME_SETTINGS, - mPrefService, mSyncServiceMock, mLoadingModalDialogCoordinator, mModalDialogManagerSupplier, @@ -523,7 +498,6 @@ mPasswordManagerHelper.launchTheCredentialManager( ManagePasswordsReferrer.CHROME_SETTINGS, - mPrefService, mSyncServiceMock, mLoadingModalDialogCoordinator, mModalDialogManagerSupplier, @@ -541,7 +515,6 @@ mPasswordManagerHelper.launchTheCredentialManager( ManagePasswordsReferrer.CHROME_SETTINGS, - mPrefService, mSyncServiceMock, mLoadingModalDialogCoordinator, mModalDialogManagerSupplier, @@ -564,7 +537,6 @@ mPasswordManagerHelper.launchTheCredentialManager( ManagePasswordsReferrer.CHROME_SETTINGS, - mPrefService, mSyncServiceMock, mLoadingModalDialogCoordinator, mModalDialogManagerSupplier, @@ -585,7 +557,6 @@ mPasswordManagerHelper.launchTheCredentialManager( ManagePasswordsReferrer.CHROME_SETTINGS, - mPrefService, mSyncServiceMock, mLoadingModalDialogCoordinator, mModalDialogManagerSupplier, @@ -699,10 +670,7 @@ @Test public void testShowDownloadCsvDialogIfCsvIsPresentAndPwmNotAvailable() { when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true); - when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable( - eq(mPrefService), eq(true))) - .thenReturn(false); - when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true); + when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false); LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(true); FragmentActivity testActivity = @@ -732,9 +700,7 @@ @Test public void testShowDownloadCsvDialogIfCsvIsPresentAndPwmAvailable() { when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true); - when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable( - eq(mPrefService), eq(true))) - .thenReturn(true); + when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(true); LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(true); FragmentActivity testActivity = @@ -764,10 +730,7 @@ @Test public void testShowDownloadCsvDialogIfCsvIsPresentAndNoGms() { when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true); - when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable( - eq(mPrefService), eq(true))) - .thenReturn(false); - when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true); + when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false); LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(true); FragmentActivity testActivity = @@ -796,10 +759,7 @@ @Test public void testShowPwmUnavailableDialogNoCsvNoGms() { when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true); - when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable( - eq(mPrefService), eq(true))) - .thenReturn(false); - when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true); + when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false); LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(false); FragmentActivity testActivity = @@ -822,10 +782,7 @@ @Test public void testShowPwmUnavailableDialogNoCsvUpdatableGms() { when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true); - when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable( - eq(mPrefService), eq(true))) - .thenReturn(false); - when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(true); + when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(false); LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(false); FragmentActivity testActivity = @@ -845,48 +802,9 @@ dialogModel.get(ModalDialogProperties.TITLE)); } - @Test - public void testDoesntShowDialogIfExportNotFinished() { - when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true); - when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable( - eq(mPrefService), eq(true))) - .thenReturn(false); - when(mPrefService.getBoolean(Pref.UPM_UNMIGRATED_PASSWORDS_EXPORTED)).thenReturn(false); - LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(false); - - FragmentActivity testActivity = - Robolectric.buildActivity(BrowserUiTestFragmentActivity.class).setup().get(); - setUpUpdatableGmsCore(testActivity); - mPasswordManagerHelper.showPasswordSettings( - testActivity, - ManagePasswordsReferrer.CHROME_SETTINGS, - mModalDialogManagerSupplier, - /* managePasskeys= */ false, - TEST_NO_EMAIL_ADDRESS, - mSettingsCustomTabLauncher); - - // Check that the unavailability dialog is not shown. - PropertyModel dialogModel = mModalDialogManager.getCurrentDialogForTest(); - assertNull(dialogModel); - - // Check that the download CSV dialog is not shown. - PasswordCsvDownloadFlowController mockController = - mock(PasswordCsvDownloadFlowController.class); - PasswordCsvDownloadFlowControllerFactory.setControllerForTesting(mockController); - verify(mockController, never()) - .showDialogAndStartFlow( - any(), any(), anyBoolean(), anyBoolean(), eq(mSettingsCustomTabLauncher)); - - // Check that the management UI is not shown (the pwm is unavailable). - verify(mCredentialManagerLauncherMock, never()) - .getLocalCredentialManagerIntent(anyInt(), any(), any()); - } - private void setUpPwmAvailableWithoutUnmigratedPasswords() { when(mBackendSupportHelperMock.isBackendPresent()).thenReturn(true); - when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable( - eq(mPrefService), eq(true))) - .thenReturn(true); + when(mPasswordManagerUtilBridgeJniMock.isPasswordManagerAvailable(true)).thenReturn(true); LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(false); }
diff --git a/chrome/browser/password_manager/android/login_db_deprecation_runner_factory.cc b/chrome/browser/password_manager/android/login_db_deprecation_runner_factory.cc deleted file mode 100644 index e641e6c6..0000000 --- a/chrome/browser/password_manager/android/login_db_deprecation_runner_factory.cc +++ /dev/null
@@ -1,63 +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. - -#include "chrome/browser/password_manager/android/login_db_deprecation_runner_factory.h" - -#include "base/feature_list.h" -#include "base/no_destructor.h" -#include "build/build_config.h" -#include "chrome/browser/password_manager/android/password_manager_util_bridge.h" -#include "chrome/browser/profiles/profile.h" -#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h" -#include "components/password_manager/core/browser/export/login_db_deprecation_runner.h" -#include "components/password_manager/core/browser/features/password_features.h" -#include "components/password_manager/core/browser/password_manager_buildflags.h" -#include "components/password_manager/core/browser/split_stores_and_local_upm.h" -#include "components/password_manager/core/common/password_manager_pref_names.h" -#include "components/prefs/pref_service.h" -#include "content/public/browser/browser_context.h" - -LoginDbDeprecationRunnerFactory::LoginDbDeprecationRunnerFactory() - : ProfileKeyedServiceFactory("LoginDbDeprecationRunnerFactory", - ProfileSelections::BuildForRegularProfile()) {} -LoginDbDeprecationRunnerFactory::~LoginDbDeprecationRunnerFactory() = default; - -LoginDbDeprecationRunnerFactory* -LoginDbDeprecationRunnerFactory::GetInstance() { - static base::NoDestructor<LoginDbDeprecationRunnerFactory> instance; - return instance.get(); -} - -password_manager::LoginDbDeprecationRunner* -LoginDbDeprecationRunnerFactory::GetForProfile(Profile* profile) { - return static_cast<password_manager::LoginDbDeprecationRunner*>( - GetInstance()->GetServiceForBrowserContext(profile, true)); -} - -std::unique_ptr<KeyedService> -LoginDbDeprecationRunnerFactory::BuildServiceInstanceForBrowserContext( - content::BrowserContext* context) const { -#if BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND) - return nullptr; -#else - Profile* profile = Profile::FromBrowserContext(context); - PrefService* prefs = profile->GetPrefs(); - - // If the client is already migrated there is no need for export. - if (password_manager::GetLegacySplitStoresPref(prefs)) { - return nullptr; - } - - if (prefs->GetBoolean( - password_manager::prefs::kUpmUnmigratedPasswordsExported)) { - // Since saving new passwords is disabled, one export is enough to guarantee - // that all passwords have been preserved outside of the login database. - return nullptr; - } - - return std::make_unique<password_manager::LoginDbDeprecationRunner>( - std::make_unique<password_manager::LoginDbDeprecationPasswordExporter>( - profile->GetPrefs(), profile->GetPath())); -#endif // BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND) -}
diff --git a/chrome/browser/password_manager/android/login_db_deprecation_runner_factory.h b/chrome/browser/password_manager/android/login_db_deprecation_runner_factory.h deleted file mode 100644 index d68a9af..0000000 --- a/chrome/browser/password_manager/android/login_db_deprecation_runner_factory.h +++ /dev/null
@@ -1,34 +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. - -#ifndef CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_LOGIN_DB_DEPRECATION_RUNNER_FACTORY_H_ -#define CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_LOGIN_DB_DEPRECATION_RUNNER_FACTORY_H_ - -#include "base/no_destructor.h" -#include "base/types/pass_key.h" -#include "chrome/browser/profiles/profile_keyed_service_factory.h" -#include "components/password_manager/core/browser/export/login_db_deprecation_runner.h" -#include "content/public/browser/browser_context.h" - -// Creates the `LoginDbDeprecationRunner` which runs the pre-deprecation -// export for unmigrated passwords on Android. -class LoginDbDeprecationRunnerFactory : public ProfileKeyedServiceFactory { - public: - static LoginDbDeprecationRunnerFactory* GetInstance(); - static password_manager::LoginDbDeprecationRunner* GetForProfile( - Profile* profile); - - private: - friend class base::NoDestructor<LoginDbDeprecationRunnerFactory>; - - LoginDbDeprecationRunnerFactory(); - ~LoginDbDeprecationRunnerFactory() override; - - std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext( - content::BrowserContext* context) const override; - - base::RepeatingCallback<bool()> internal_backend_checker_; -}; - -#endif // CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_LOGIN_DB_DEPRECATION_RUNNER_FACTORY_H_
diff --git a/chrome/browser/password_manager/android/login_db_deprecation_runner_factory_unittest.cc b/chrome/browser/password_manager/android/login_db_deprecation_runner_factory_unittest.cc deleted file mode 100644 index 45cd857b..0000000 --- a/chrome/browser/password_manager/android/login_db_deprecation_runner_factory_unittest.cc +++ /dev/null
@@ -1,50 +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. - -#include "chrome/browser/password_manager/android/login_db_deprecation_runner_factory.h" - -#include "base/test/scoped_feature_list.h" -#include "chrome/test/base/testing_profile.h" -#include "components/password_manager/core/browser/features/password_features.h" -#include "components/password_manager/core/browser/split_stores_and_local_upm.h" -#include "components/password_manager/core/common/password_manager_pref_names.h" -#include "components/prefs/pref_service.h" -#include "components/prefs/testing_pref_service.h" -#include "content/public/test/browser_task_environment.h" -#include "testing/gtest/include/gtest/gtest.h" - -class LoginDbDeprecationRunnerFactoryTest : public testing::Test { - public: - LoginDbDeprecationRunnerFactoryTest() = default; - - protected: - content::BrowserTaskEnvironment task_env_; - TestingProfile testing_profile_; -}; - -TEST_F(LoginDbDeprecationRunnerFactoryTest, NullServiceIfMigrated) { - PrefService* prefs = testing_profile_.GetPrefs(); - password_manager::SetLegacySplitStoresPrefForTest(prefs, true); - EXPECT_FALSE( - LoginDbDeprecationRunnerFactory::GetForProfile(&testing_profile_)); -} - -TEST_F(LoginDbDeprecationRunnerFactoryTest, NullIfAlreadyExported) { - PrefService* prefs = testing_profile_.GetPrefs(); - password_manager::SetLegacySplitStoresPrefForTest(prefs, false); - prefs->SetBoolean(password_manager::prefs::kUpmUnmigratedPasswordsExported, - true); - EXPECT_FALSE( - LoginDbDeprecationRunnerFactory::GetForProfile(&testing_profile_)); -} - -TEST_F(LoginDbDeprecationRunnerFactoryTest, - NonNullServiceIfNotEligibleForMigration) { - PrefService* prefs = testing_profile_.GetPrefs(); - password_manager::SetLegacySplitStoresPrefForTest(prefs, false); - prefs->SetBoolean(password_manager::prefs::kUpmUnmigratedPasswordsExported, - false); - EXPECT_TRUE( - LoginDbDeprecationRunnerFactory::GetForProfile(&testing_profile_)); -}
diff --git a/chrome/browser/password_manager/android/login_db_deprecation_util_bridge.cc b/chrome/browser/password_manager/android/login_db_deprecation_util_bridge.cc index 4cb7710..5837c03 100644 --- a/chrome/browser/password_manager/android/login_db_deprecation_util_bridge.cc +++ b/chrome/browser/password_manager/android/login_db_deprecation_util_bridge.cc
@@ -6,8 +6,8 @@ #include "base/android/jni_android.h" #include "base/android/jni_string.h" #include "base/files/file_path.h" +#include "chrome/browser/password_manager/android/password_manager_android_util.h" #include "chrome/browser/profiles/profile.h" -#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h" // Must come after all headers that specialize FromJniType() / ToJniType(). #include "chrome/browser/password_manager/android/utils_jni_headers/LoginDbDeprecationUtilBridge_jni.h" @@ -18,6 +18,6 @@ return base::android::ConvertUTF8ToJavaString( env, profile->GetPath() .Append(FILE_PATH_LITERAL( - password_manager::kExportedPasswordsFileName)) + password_manager_android_util::kExportedPasswordsFileName)) .value()); }
diff --git a/chrome/browser/password_manager/android/password_manager_android_util.cc b/chrome/browser/password_manager/android/password_manager_android_util.cc index 6c96878..0fe7b2a 100644 --- a/chrome/browser/password_manager/android/password_manager_android_util.cc +++ b/chrome/browser/password_manager/android/password_manager_android_util.cc
@@ -15,16 +15,11 @@ #include "base/strings/string_number_conversions.h" #include "chrome/browser/password_manager/android/password_manager_util_bridge.h" #include "chrome/browser/password_manager/android/password_manager_util_bridge_interface.h" -#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h" #include "components/password_manager/core/browser/password_manager_buildflags.h" #include "components/password_manager/core/browser/password_manager_constants.h" -#include "components/password_manager/core/browser/password_manager_util.h" -#include "components/password_manager/core/browser/password_sync_util.h" #include "components/password_manager/core/browser/split_stores_and_local_upm.h" #include "components/password_manager/core/common/password_manager_pref_names.h" #include "components/prefs/pref_service.h" -#include "components/sync/base/pref_names.h" -#include "components/version_info/android/channel_getter.h" namespace password_manager_android_util { @@ -40,20 +35,8 @@ } #if !BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND) -// Called on startup to delete the login data files for users migrated to UPM -// or for users who had all the unmigrated passwords auto-exported. -// Must only be called if the value of the state pref -// `PasswordsUseUPMLocalAndSeparateStores` is `On` and there -// is no need for deactivation of local UPM or either UPM is already active or -// unmigrated passwords have already been auto-exported. -void MaybeDeleteLoginDataFiles(PrefService* prefs, - const base::FilePath& login_db_directory) { - bool already_active_in_upm = - password_manager::GetLegacySplitStoresPref(prefs); - bool login_db_ready_for_deprecation = - LoginDbDeprecationReady(prefs); - CHECK(already_active_in_upm || login_db_ready_for_deprecation); - +// Called on startup to delete the login data files. +void DeleteLoginDataFiles(const base::FilePath& login_db_directory) { base::FilePath profile_db_path = login_db_directory.Append(password_manager::kLoginDataForProfileFileName); base::FilePath account_db_path = @@ -67,10 +50,6 @@ bool success = base::DeleteFile(profile_db_path); base::UmaHistogramBoolean("PasswordManager.ProfileLoginData.RemovalStatus", success); - if (success) { - prefs->SetBoolean( - password_manager::prefs::kEmptyProfileStoreLoginDatabase, true); - } } base::DeleteFile(profile_db_journal_path); @@ -84,8 +63,8 @@ void DeleteAutoExportedCsv(PrefService* prefs, const base::FilePath& login_db_directory) { - base::FilePath csv_path = login_db_directory.Append( - FILE_PATH_LITERAL(password_manager::kExportedPasswordsFileName)); + base::FilePath csv_path = + login_db_directory.Append(FILE_PATH_LITERAL(kExportedPasswordsFileName)); if (base::PathExists(csv_path)) { bool success = base::DeleteFile(csv_path); if (success) { @@ -99,8 +78,9 @@ #endif // !BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND) +} // namespace + PasswordManagerNotAvailableReason GetPasswordManagerNotActiveReason( - PrefService* pref_service, PasswordManagerUtilBridgeInterface* util_bridge, bool is_internal_backend_present) { if (!is_internal_backend_present) { @@ -114,58 +94,30 @@ return PasswordManagerNotAvailableReason::kOutdatedGmsCore; } - CHECK(!pref_service->GetBoolean( - password_manager::prefs::kUpmUnmigratedPasswordsExported)); - return PasswordManagerNotAvailableReason::kAutoExportPending; + NOTREACHED(); } void RecordLocalUpmActivationMetrics( - PrefService* pref_service, PasswordManagerUtilBridgeInterface* util_bridge) { bool is_internal_backend_present = util_bridge->IsInternalBackendPresent(); bool is_pwm_available = - IsPasswordManagerAvailable(pref_service, is_internal_backend_present); + IsPasswordManagerAvailable(is_internal_backend_present); base::UmaHistogramBoolean("PasswordManager.LocalUpmActivated", is_pwm_available); if (!is_pwm_available) { base::UmaHistogramEnumeration( "PasswordManager.Android.NotAvailableReason", - GetPasswordManagerNotActiveReason(pref_service, util_bridge, + GetPasswordManagerNotActiveReason(util_bridge, is_internal_backend_present)); } } -void InitializeUpmUnmigratedPasswordsExportPref( - PrefService* prefs, - const base::FilePath& login_db_directory) { - // The umigrated passwords export pref should only be set for users who aren't - // already part of UPM. - if (password_manager::GetLegacySplitStoresPref(prefs)) { - return; - } - - // If there are no passwords saved, there is nothing to export prior to - // deprecation, so mark the export as done already. - if (prefs->GetBoolean( - password_manager::prefs::kEmptyProfileStoreLoginDatabase) || - !base::PathExists(login_db_directory.Append( - password_manager::kLoginDataForProfileFileName))) { - prefs->SetBoolean(password_manager::prefs::kUpmUnmigratedPasswordsExported, - true); - } -} - -} // namespace - bool IsPasswordManagerAvailable( - const PrefService* prefs, std::unique_ptr<PasswordManagerUtilBridgeInterface> util_bridge) { - return IsPasswordManagerAvailable(prefs, - util_bridge->IsInternalBackendPresent()); + return IsPasswordManagerAvailable(util_bridge->IsInternalBackendPresent()); } -bool IsPasswordManagerAvailable(const PrefService* prefs, - bool is_internal_backend_present) { +bool IsPasswordManagerAvailable(bool is_internal_backend_present) { if (!is_internal_backend_present) { return false; } @@ -173,35 +125,17 @@ if (!HasMinGmsVersionForFullUpmSupport()) { return false; } - bool upm_already_active = password_manager::GetLegacySplitStoresPref(prefs); - bool exported_umigrated_passwords = prefs->GetBoolean( - password_manager::prefs::kUpmUnmigratedPasswordsExported); - return upm_already_active || exported_umigrated_passwords; -} -bool LoginDbDeprecationReady(PrefService* prefs) { - bool upm_already_active = password_manager::GetLegacySplitStoresPref(prefs); - bool exported_umigrated_passwords = prefs->GetBoolean( - password_manager::prefs::kUpmUnmigratedPasswordsExported); - return upm_already_active || exported_umigrated_passwords; + return true; } void MaybeDeleteLoginDatabases( PrefService* pref_service, const base::FilePath& login_db_directory, std::unique_ptr<PasswordManagerUtilBridgeInterface> util_bridge) { - // For fresh installs in particular, it's important to do this before - // the backend creation, so that the Android backends are directly wired - // without requiring another restart. - password_manager_android_util::InitializeUpmUnmigratedPasswordsExportPref( - pref_service, login_db_directory); - // If the login DB is being deprecated, only record metrics and do not - // perform the activation algorithm. - RecordLocalUpmActivationMetrics(pref_service, util_bridge.get()); + RecordLocalUpmActivationMetrics(util_bridge.get()); #if !BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND) - if (LoginDbDeprecationReady(pref_service)) { - MaybeDeleteLoginDataFiles(pref_service, login_db_directory); - } + DeleteLoginDataFiles(login_db_directory); if (pref_service->GetBoolean( password_manager::prefs::kUpmAutoExportCsvNeedsDeletion)) { DeleteAutoExportedCsv(pref_service, login_db_directory);
diff --git a/chrome/browser/password_manager/android/password_manager_android_util.h b/chrome/browser/password_manager/android/password_manager_android_util.h index d0cd7ee7..43edd4c 100644 --- a/chrome/browser/password_manager/android/password_manager_android_util.h +++ b/chrome/browser/password_manager/android/password_manager_android_util.h
@@ -5,9 +5,10 @@ #ifndef CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_PASSWORD_MANAGER_ANDROID_UTIL_H_ #define CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_PASSWORD_MANAGER_ANDROID_UTIL_H_ +#include <memory> +#include <string_view> + #include "chrome/browser/password_manager/android/password_manager_util_bridge_interface.h" -#include "components/password_manager/core/browser/password_manager_metrics_util.h" -#include "components/password_manager/core/common/password_manager_pref_names.h" class PrefService; @@ -17,6 +18,9 @@ namespace password_manager_android_util { +inline constexpr std::string_view kExportedPasswordsFileName = + "ChromePasswords.csv"; + // These values are persisted to logs. Entries should not be renumbered and // numeric values should never be reused. // @@ -31,40 +35,25 @@ // GmsCore version doesn't support UPM at all, or not fully. kOutdatedGmsCore = 2, // GmsCore version supports UPM, but there are still unmigrated passwords. - kAutoExportPending = 3, + // Deprecated: kAutoExportPending = 3, - kMaxValue = kAutoExportPending + kMaxValue = kOutdatedGmsCore }; // LINT.ThenChange(/tools/metrics/histograms/metadata/password/enums.xml:PasswordManagerNotAvailableReason) // Checks whether the password manager can be used on Android. -// Once the login db is deprecated, for clients not fulfilling the criteria -// for talking to the Android backend, the password manager will no longer -// be available. // The criteria are: // - access to the internal backend // - GMS Core version with full UPM support -// - passwords were either migrated or exported bool IsPasswordManagerAvailable( - const PrefService* prefs, std::unique_ptr<PasswordManagerUtilBridgeInterface> util_bridge); // As above, except the caller already knows whether the internal backend // is present, probably because the call originates in Java. -bool IsPasswordManagerAvailable(const PrefService* prefs, - bool is_internal_backend_present); +bool IsPasswordManagerAvailable(bool is_internal_backend_present); -// The login DB is ready to be deprecated when all the passwords have either -// been already migrated to UPM or exported. -// -// Note: This should only be used if looking to identify whether deprecation -// is ongoing or not. For most other purposes `IsPasswordManagerAvailable` is -// the correct util to check. -bool LoginDbDeprecationReady(PrefService* pref_service); - -// The login database is deprecated on Android. This function deletes the data -// if the user already exported any leftover data. +// The login database is deprecated on Android. This function deletes the data. void MaybeDeleteLoginDatabases( PrefService* pref_service, const base::FilePath& login_db_directory,
diff --git a/chrome/browser/password_manager/android/password_manager_android_util_unittest.cc b/chrome/browser/password_manager/android/password_manager_android_util_unittest.cc index 7c6b722b..57d0c64 100644 --- a/chrome/browser/password_manager/android/password_manager_android_util_unittest.cc +++ b/chrome/browser/password_manager/android/password_manager_android_util_unittest.cc
@@ -18,19 +18,16 @@ #include "base/test/test_file_util.h" #include "build/build_config.h" #include "chrome/browser/password_manager/android/mock_password_manager_util_bridge.h" -#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h" #include "components/password_manager/core/browser/password_manager_constants.h" #include "components/password_manager/core/browser/split_stores_and_local_upm.h" #include "components/password_manager/core/common/password_manager_pref_names.h" #include "components/prefs/pref_registry_simple.h" #include "components/prefs/testing_pref_service.h" -#include "components/sync/test/test_sync_service.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using password_manager::GetSplitStoresUpmMinVersion; using password_manager::prefs::kUpmAutoExportCsvNeedsDeletion; -using password_manager::prefs::kUpmUnmigratedPasswordsExported; using testing::Return; namespace password_manager_android_util { @@ -39,11 +36,6 @@ class PasswordManagerAndroidUtilTest : public testing::Test { public: PasswordManagerAndroidUtilTest() { - password_manager::RegisterLegacySplitStoresPref(pref_service_.registry()); - pref_service_.registry()->RegisterBooleanPref( - password_manager::prefs::kEmptyProfileStoreLoginDatabase, false); - pref_service_.registry()->RegisterBooleanPref( - password_manager::prefs::kUpmUnmigratedPasswordsExported, false); pref_service_.registry()->RegisterBooleanPref( password_manager::prefs::kUpmAutoExportCsvNeedsDeletion, false); base::WriteFile(login_db_directory_.Append( @@ -77,15 +69,12 @@ // Make sure all the other criteria are fulfilled. base::android::device_info::set_gms_version_code_for_test( base::NumberToString(GetSplitStoresUpmMinVersion())); - password_manager::SetLegacySplitStoresPrefForTest(pref_service(), true); - pref_service()->SetBoolean(kUpmUnmigratedPasswordsExported, false); std::unique_ptr<MockPasswordManagerUtilBridge> mock_util_bridge = std::make_unique<MockPasswordManagerUtilBridge>(); EXPECT_CALL(*mock_util_bridge, IsInternalBackendPresent) .WillOnce(Return(false)); - EXPECT_FALSE( - IsPasswordManagerAvailable(pref_service(), std::move(mock_util_bridge))); + EXPECT_FALSE(IsPasswordManagerAvailable(std::move(mock_util_bridge))); } TEST_F(PasswordManagerAndroidUtilTest, @@ -94,19 +83,15 @@ std::make_unique<MockPasswordManagerUtilBridge>(); EXPECT_CALL(*mock_util_bridge, IsInternalBackendPresent) .WillOnce(Return(true)); - password_manager::SetLegacySplitStoresPrefForTest(pref_service(), true); - pref_service()->SetBoolean(kUpmUnmigratedPasswordsExported, false); // Set a GMS Core version that is lower than the min required version. base::android::device_info::set_gms_version_code_for_test( base::NumberToString(GetSplitStoresUpmMinVersion() - 1)); - EXPECT_FALSE( - IsPasswordManagerAvailable(pref_service(), std::move(mock_util_bridge))); + EXPECT_FALSE(IsPasswordManagerAvailable(std::move(mock_util_bridge))); } -TEST_F(PasswordManagerAndroidUtilTest, - PasswordManagerNotAvailablePasswordsUnmigratedPasswords) { +TEST_F(PasswordManagerAndroidUtilTest, PasswordManagerAvailable) { std::unique_ptr<MockPasswordManagerUtilBridge> mock_util_bridge = std::make_unique<MockPasswordManagerUtilBridge>(); EXPECT_CALL(*mock_util_bridge, IsInternalBackendPresent) @@ -115,43 +100,7 @@ base::android::device_info::set_gms_version_code_for_test( base::NumberToString(GetSplitStoresUpmMinVersion())); - password_manager::SetLegacySplitStoresPrefForTest(pref_service(), false); - pref_service()->SetBoolean(kUpmUnmigratedPasswordsExported, false); - - EXPECT_FALSE( - IsPasswordManagerAvailable(pref_service(), std::move(mock_util_bridge))); -} - -TEST_F(PasswordManagerAndroidUtilTest, PasswordManagerAvailableNoUpmMigration) { - std::unique_ptr<MockPasswordManagerUtilBridge> mock_util_bridge = - std::make_unique<MockPasswordManagerUtilBridge>(); - EXPECT_CALL(*mock_util_bridge, IsInternalBackendPresent) - .WillOnce(Return(true)); - - base::android::device_info::set_gms_version_code_for_test( - base::NumberToString(GetSplitStoresUpmMinVersion())); - - password_manager::SetLegacySplitStoresPrefForTest(pref_service(), false); - pref_service()->SetBoolean(kUpmUnmigratedPasswordsExported, true); - - EXPECT_TRUE( - IsPasswordManagerAvailable(pref_service(), std::move(mock_util_bridge))); -} - -TEST_F(PasswordManagerAndroidUtilTest, PasswordManagerAvailableUpmMigration) { - std::unique_ptr<MockPasswordManagerUtilBridge> mock_util_bridge = - std::make_unique<MockPasswordManagerUtilBridge>(); - EXPECT_CALL(*mock_util_bridge, IsInternalBackendPresent) - .WillOnce(Return(true)); - - base::android::device_info::set_gms_version_code_for_test( - base::NumberToString(GetSplitStoresUpmMinVersion())); - - password_manager::SetLegacySplitStoresPrefForTest(pref_service(), true); - pref_service()->SetBoolean(kUpmUnmigratedPasswordsExported, false); - - EXPECT_TRUE( - IsPasswordManagerAvailable(pref_service(), std::move(mock_util_bridge))); + EXPECT_TRUE(IsPasswordManagerAvailable(std::move(mock_util_bridge))); } TEST_F(PasswordManagerAndroidUtilTest, TestRecordsUpmNotActiveWhenNoGms) { @@ -190,31 +139,10 @@ PasswordManagerNotAvailableReason::kOutdatedGmsCore, 1); } -TEST_F(PasswordManagerAndroidUtilTest, - TestRecordsUpmNotActivateBeforeAutoExport) { +TEST_F(PasswordManagerAndroidUtilTest, TestRecordsUpmActive) { base::android::device_info::set_gms_version_code_for_test( base::NumberToString(GetSplitStoresUpmMinVersion())); - password_manager::SetLegacySplitStoresPrefForTest(pref_service(), false); - pref_service()->SetBoolean( - password_manager::prefs::kUpmUnmigratedPasswordsExported, false); - base::HistogramTester histogram_tester; - MaybeDeleteLoginDatabases(pref_service(), login_db_directory(), - GetMockBridgeWithBackendPresent()); - histogram_tester.ExpectUniqueSample("PasswordManager.LocalUpmActivated", - false, 1); - histogram_tester.ExpectUniqueSample( - "PasswordManager.Android.NotAvailableReason", - PasswordManagerNotAvailableReason::kAutoExportPending, 1); -} - -TEST_F(PasswordManagerAndroidUtilTest, TestRecordsUpmActiveIfExported) { - base::android::device_info::set_gms_version_code_for_test( - base::NumberToString(GetSplitStoresUpmMinVersion())); - - password_manager::SetLegacySplitStoresPrefForTest(pref_service(), false); - pref_service()->SetBoolean( - password_manager::prefs::kUpmUnmigratedPasswordsExported, true); base::HistogramTester histogram_tester; MaybeDeleteLoginDatabases(pref_service(), login_db_directory(), GetMockBridgeWithBackendPresent()); @@ -224,63 +152,13 @@ "PasswordManager.Android.NotAvailableReason", 0); } -TEST_F(PasswordManagerAndroidUtilTest, TestRecordsUpmActiveIfAlreadyActive) { - base::android::device_info::set_gms_version_code_for_test( - base::NumberToString(GetSplitStoresUpmMinVersion())); - - password_manager::SetLegacySplitStoresPrefForTest(pref_service(), true); - pref_service()->SetBoolean( - password_manager::prefs::kUpmUnmigratedPasswordsExported, false); - base::HistogramTester histogram_tester; - MaybeDeleteLoginDatabases(pref_service(), login_db_directory(), - GetMockBridgeWithBackendPresent()); - histogram_tester.ExpectUniqueSample("PasswordManager.LocalUpmActivated", true, - 1); - histogram_tester.ExpectTotalCount( - "PasswordManager.Android.NotAvailableReason", 0); -} - -TEST_F(PasswordManagerAndroidUtilTest, - InitUnmigratedExportUnchangedIfMigrated) { - password_manager::SetLegacySplitStoresPrefForTest(pref_service(), true); - MaybeDeleteLoginDatabases(pref_service(), login_db_directory(), - GetMockBridgeWithBackendPresent()); - EXPECT_TRUE(pref_service() - ->FindPreference( - password_manager::prefs::kUpmUnmigratedPasswordsExported) - ->IsDefaultValue()); -} - -TEST_F(PasswordManagerAndroidUtilTest, InitUnmigratedExportPrefTrueEmptyDb) { - password_manager::SetLegacySplitStoresPrefForTest(pref_service(), false); - pref_service()->SetBoolean( - password_manager::prefs::kEmptyProfileStoreLoginDatabase, true); - pref_service()->SetBoolean( - password_manager::prefs::kUpmUnmigratedPasswordsExported, false); - MaybeDeleteLoginDatabases(pref_service(), login_db_directory(), - GetMockBridgeWithBackendPresent()); - EXPECT_TRUE(pref_service()->GetBoolean( - password_manager::prefs::kUpmUnmigratedPasswordsExported)); -} - -TEST_F(PasswordManagerAndroidUtilTest, - DeletesLoginDataFilesAfterUnmigratedPasswordsExported) { +TEST_F(PasswordManagerAndroidUtilTest, DeletesLoginDataFiles) { base::HistogramTester histogram_tester; const char kRemovalStatusProfileMetric[] = "PasswordManager.ProfileLoginData.RemovalStatus"; const char kRemovalStatusAccountMetric[] = "PasswordManager.AccountLoginData.RemovalStatus"; - // Assume an unmigrated user. - password_manager::SetLegacySplitStoresPrefForTest(pref_service(), false); - // With unmigrated passwords exported. - pref_service()->SetBoolean(kUpmUnmigratedPasswordsExported, true); - - // And for whom the initial passwords deletion failed, so they still have - // passwords in the db. - pref_service()->SetBoolean( - password_manager::prefs::kEmptyProfileStoreLoginDatabase, false); - // Creating the login data files for testing. base::FilePath profile_db_path = login_db_directory().Append( password_manager::kLoginDataForProfileFileName); @@ -308,60 +186,6 @@ EXPECT_FALSE(PathExists(account_db_path)); EXPECT_FALSE(PathExists(profile_db_journal_path)); EXPECT_FALSE(PathExists(account_db_journal_path)); - EXPECT_TRUE(pref_service()->GetBoolean( - password_manager::prefs::kEmptyProfileStoreLoginDatabase)); - - histogram_tester.ExpectUniqueSample(kRemovalStatusProfileMetric, true, 1); - histogram_tester.ExpectUniqueSample(kRemovalStatusAccountMetric, true, 1); -} - -// This test is relevant for users for whom prior db deletion attempts failed. -TEST_F(PasswordManagerAndroidUtilTest, - DeletesLoginDataFilesForAlreadyMigratedUser) { - base::HistogramTester histogram_tester; - const char kRemovalStatusProfileMetric[] = - "PasswordManager.ProfileLoginData.RemovalStatus"; - const char kRemovalStatusAccountMetric[] = - "PasswordManager.AccountLoginData.RemovalStatus"; - - // Assume a migrated user. - password_manager::SetLegacySplitStoresPrefForTest(pref_service(), true); - // No unmigrated passwords, so nothing was exported. - pref_service()->SetBoolean(kUpmUnmigratedPasswordsExported, false); - // And for whom the initial passwords deletion failed, so they still have - // passwords in the db. - pref_service()->SetBoolean( - password_manager::prefs::kEmptyProfileStoreLoginDatabase, false); - - // Creating the login data files for testing. - base::FilePath profile_db_path = login_db_directory().Append( - password_manager::kLoginDataForProfileFileName); - base::FilePath account_db_path = login_db_directory().Append( - password_manager::kLoginDataForAccountFileName); - base::FilePath profile_db_journal_path = login_db_directory().Append( - password_manager::kLoginDataJournalForProfileFileName); - base::FilePath account_db_journal_path = login_db_directory().Append( - password_manager::kLoginDataJournalForAccountFileName); - - base::WriteFile(profile_db_path, "Test content"); - base::WriteFile(account_db_path, "Test content"); - base::WriteFile(profile_db_journal_path, "Test content"); - base::WriteFile(account_db_journal_path, "Test content"); - - EXPECT_TRUE(PathExists(profile_db_path)); - EXPECT_TRUE(PathExists(account_db_path)); - EXPECT_TRUE(PathExists(profile_db_journal_path)); - EXPECT_TRUE(PathExists(account_db_journal_path)); - - MaybeDeleteLoginDatabases(pref_service(), login_db_directory(), - GetMockBridgeWithBackendPresent()); - - EXPECT_FALSE(PathExists(profile_db_path)); - EXPECT_FALSE(PathExists(account_db_path)); - EXPECT_FALSE(PathExists(profile_db_journal_path)); - EXPECT_FALSE(PathExists(account_db_journal_path)); - EXPECT_TRUE(pref_service()->GetBoolean( - password_manager::prefs::kEmptyProfileStoreLoginDatabase)); histogram_tester.ExpectUniqueSample(kRemovalStatusProfileMetric, true, 1); histogram_tester.ExpectUniqueSample(kRemovalStatusAccountMetric, true, 1); @@ -375,7 +199,7 @@ // Creating the csv for testing. base::FilePath csv_path = - login_db_directory().Append(password_manager::kExportedPasswordsFileName); + login_db_directory().Append(kExportedPasswordsFileName); base::WriteFile(csv_path, "Test content"); @@ -398,7 +222,7 @@ // Creating the csv for testing. base::FilePath csv_path = - login_db_directory().Append(password_manager::kExportedPasswordsFileName); + login_db_directory().Append(kExportedPasswordsFileName); base::WriteFile(csv_path, "Test content");
diff --git a/chrome/browser/password_manager/android/password_manager_util_bridge.cc b/chrome/browser/password_manager/android/password_manager_util_bridge.cc index 1dfdf400..af8ceb1 100644 --- a/chrome/browser/password_manager/android/password_manager_util_bridge.cc +++ b/chrome/browser/password_manager/android/password_manager_util_bridge.cc
@@ -21,10 +21,9 @@ jboolean JNI_PasswordManagerUtilBridge_IsPasswordManagerAvailable( JNIEnv* env, - PrefService* pref_service, jboolean is_internal_backend_present) { return password_manager_android_util::IsPasswordManagerAvailable( - pref_service, is_internal_backend_present); + is_internal_backend_present); } namespace password_manager_android_util {
diff --git a/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/FakePasswordCheckControllerFactory.java b/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/FakePasswordCheckControllerFactory.java index b079e77..fb6da2f 100644 --- a/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/FakePasswordCheckControllerFactory.java +++ b/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/FakePasswordCheckControllerFactory.java
@@ -6,7 +6,6 @@ import org.chromium.chrome.browser.password_manager.PasswordManagerHelper; import org.chromium.chrome.browser.password_manager.PasswordStoreBridge; -import org.chromium.components.prefs.PrefService; import org.chromium.components.sync.SyncService; public class FakePasswordCheckControllerFactory extends PasswordCheckControllerFactory { @@ -15,7 +14,6 @@ @Override public PasswordCheckController create( SyncService syncService, - PrefService prefService, PasswordStoreBridge passwordStoreBridge, PasswordManagerHelper passwordManagerHelper) { mPasswordCheckController = new FakePasswordCheckController();
diff --git a/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/GmsCorePasswordCheckControllerTest.java b/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/GmsCorePasswordCheckControllerTest.java index 6889887..47c018e 100644 --- a/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/GmsCorePasswordCheckControllerTest.java +++ b/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/GmsCorePasswordCheckControllerTest.java
@@ -32,12 +32,9 @@ import org.chromium.chrome.browser.pwd_check_wrapper.PasswordCheckController.PasswordCheckResult; import org.chromium.chrome.browser.pwd_check_wrapper.PasswordCheckController.PasswordStorageType; import org.chromium.chrome.browser.sync.SyncServiceFactory; -import org.chromium.components.prefs.PrefService; import org.chromium.components.signin.base.CoreAccountInfo; import org.chromium.components.sync.SyncService; import org.chromium.components.sync.UserSelectableType; -import org.chromium.components.user_prefs.UserPrefs; -import org.chromium.components.user_prefs.UserPrefsJni; import org.chromium.google_apis.gaia.GaiaId; import java.util.OptionalInt; @@ -57,8 +54,6 @@ @Mock private SyncService mSyncService; @Mock private PasswordStoreBridge mPasswordStoreBridge; @Mock private Profile mProfile; - @Mock private UserPrefs.Natives mUserPrefsJniMock; - @Mock private PrefService mPrefService; @Mock private PasswordManagerUtilBridge.Natives mPasswordManagerUtilBridgeNativeMock; @Mock private PasswordManagerHelper.Natives mPasswordManagerHelperNativeMock; FakePasswordCheckupClientHelper mPasswordCheckupClientHelper; @@ -67,7 +62,7 @@ @Before public void setUp() { - setupUserProfileWithMockPrefService(); + when(mProfile.getOriginalProfile()).thenReturn(mProfile); configureMockSyncServiceToSyncPasswords(); configurePasswordManagerBackendSupport(); setFakePasswordCheckupClientHelper(); @@ -81,7 +76,7 @@ private void configurePasswordManagerBackendSupport() { PasswordManagerUtilBridgeJni.setInstanceForTesting(mPasswordManagerUtilBridgeNativeMock); PasswordManagerHelperJni.setInstanceForTesting(mPasswordManagerHelperNativeMock); - when(mPasswordManagerUtilBridgeNativeMock.isPasswordManagerAvailable(mPrefService, true)) + when(mPasswordManagerUtilBridgeNativeMock.isPasswordManagerAvailable(true)) .thenReturn(true); FakePasswordManagerBackendSupportHelper helper = @@ -110,12 +105,6 @@ PasswordCheckupClientHelperFactory.setFactoryForTesting(passwordCheckupClientHelperFactory); } - private void setupUserProfileWithMockPrefService() { - when(mProfile.getOriginalProfile()).thenReturn(mProfile); - UserPrefsJni.setInstanceForTesting(mUserPrefsJniMock); - when(mUserPrefsJniMock.get(mProfile)).thenReturn(mPrefService); - } - /** * The flow: checkPasswords is called -> as a result of password check 0 breached credentials * are obtained -> 10 passwords overall have been loaded.
diff --git a/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/PasswordCheckControllerFactory.java b/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/PasswordCheckControllerFactory.java index c8549d8..22ed97b 100644 --- a/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/PasswordCheckControllerFactory.java +++ b/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/PasswordCheckControllerFactory.java
@@ -9,20 +9,18 @@ import org.chromium.chrome.browser.password_manager.PasswordManagerHelper; import org.chromium.chrome.browser.password_manager.PasswordManagerUtilBridge; import org.chromium.chrome.browser.password_manager.PasswordStoreBridge; -import org.chromium.components.prefs.PrefService; import org.chromium.components.sync.SyncService; @NullMarked public class PasswordCheckControllerFactory { public PasswordCheckController create( @Nullable SyncService syncService, - PrefService prefService, PasswordStoreBridge passwordStoreBridge, PasswordManagerHelper passwordManagerHelper) { // This is only used by the old Safety Check, which is only opened from the PhishGuard // dialog and only if the phished credential is saved in both local and account stores. // This means that UPM is completely available. - assert PasswordManagerUtilBridge.isPasswordManagerAvailable(prefService); + assert PasswordManagerUtilBridge.isPasswordManagerAvailable(); return new GmsCorePasswordCheckController( syncService, passwordStoreBridge, passwordManagerHelper); }
diff --git a/chrome/browser/password_manager/factories/password_store_backend_factory.cc b/chrome/browser/password_manager/factories/password_store_backend_factory.cc index 768d11c..6f864da 100644 --- a/chrome/browser/password_manager/factories/password_store_backend_factory.cc +++ b/chrome/browser/password_manager/factories/password_store_backend_factory.cc
@@ -47,6 +47,7 @@ using password_manager_android_util::PasswordManagerUtilBridge; #endif +#if BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND) std::unique_ptr<PasswordStoreBackend> CreateProfilePasswordStoreBuiltInBackend( const base::FilePath& login_db_directory, PrefService* prefs, @@ -54,57 +55,35 @@ std::unique_ptr<password_manager::LoginDatabase> login_db( password_manager::CreateLoginDatabaseForProfileStorage(login_db_directory, prefs)); -#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) +#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) password_manager::LoginDatabase* login_db_ptr = login_db.get(); -#endif // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) +#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) std::unique_ptr<PasswordStoreBackend> backend = std::make_unique<PasswordStoreBuiltInBackend>( std::move(login_db), syncer::WipeModelUponSyncDisabledBehavior::kNever, prefs, os_crypt_async); -#if BUILDFLAG(IS_ANDROID) - // base::Unretained() is safe because `prefs` lives as long as the profile - // and IntermediateCallbackForSettingPrefs() ensures the callback isn't - // invoked if the profile is being destroyed. - auto is_profile_db_empty_cb = - base::BindPostTaskToCurrentDefault(base::BindRepeating( - &password_manager::IntermediateCallbackForSettingPrefs, - backend->AsWeakPtr(), - base::BindRepeating( - &PrefService::SetBoolean, base::Unretained(prefs), - password_manager::prefs::kEmptyProfileStoreLoginDatabase))); - login_db_ptr->SetIsEmptyCb(std::move(is_profile_db_empty_cb)); -#endif // BUILDFLAG(IS_ANDROID) - #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) base::FilePath user_data_dir; policy::path_parser::CheckUserDataDirPolicy(&user_data_dir); // If `user_data_dir` is empty it means that policy did not set it. login_db_ptr->SetIsUserDataDirPolicySet(!user_data_dir.empty()); -#endif +#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) return backend; } - -#if !BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND) - +#else // BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND) // Creates the backend for the profile `PasswordStore` on Android, after // login db deprecation. std::unique_ptr<PasswordStoreBackend> CreateProfilePasswordStoreBackendAndroid( PrefService* prefs, const base::FilePath& login_db_directory, os_crypt_async::OSCryptAsync* os_crypt_async) { - if (!password_manager_android_util::LoginDbDeprecationReady(prefs)) { - // There are still passwords that need exporting, so instantiate the - // backend that connects to the login DB. - return CreateProfilePasswordStoreBuiltInBackend(login_db_directory, prefs, - os_crypt_async); - } // Once the login DB is deprecated, there are only 2 options for // the backend: an empty one if the Android backend isn't supported, // or the Android backend. if (password_manager_android_util::IsPasswordManagerAvailable( - prefs, std::make_unique<PasswordManagerUtilBridge>())) { + std::make_unique<PasswordManagerUtilBridge>())) { return std::make_unique< password_manager::PasswordStoreAndroidLocalBackend>(); } @@ -119,15 +98,14 @@ // The account store shouldn't have an associated login DB with existing // passwords, so no pre-export step is required. if (password_manager_android_util::IsPasswordManagerAvailable( - prefs, std::make_unique<PasswordManagerUtilBridge>())) { + std::make_unique<PasswordManagerUtilBridge>())) { return std::make_unique< password_manager::PasswordStoreAndroidAccountBackend>( password_manager::kAccountStore); } return std::make_unique<password_manager::PasswordStoreEmptyBackend>(); } - -#endif // !BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND) +#endif // BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND) } // namespace std::unique_ptr<PasswordStoreBackend> CreateProfilePasswordStoreBackend(
diff --git a/chrome/browser/password_manager/password_manager_settings_service_factory.cc b/chrome/browser/password_manager/password_manager_settings_service_factory.cc index aa47f80..27b4691 100644 --- a/chrome/browser/password_manager/password_manager_settings_service_factory.cc +++ b/chrome/browser/password_manager/password_manager_settings_service_factory.cc
@@ -84,11 +84,7 @@ std::unique_ptr<password_manager::PasswordManagerSettingsService> PasswordManagerSettingsServiceFactory::CreateService(Profile* profile) const { #if BUILDFLAG(IS_ANDROID) - // For the first run after the feature is enabled, before the unmigrated - // passwords are exported, `IsPasswordManagerAvailable` can return false. - // However, password saving isn't possible in that run anyway. if (password_manager_android_util::IsPasswordManagerAvailable( - profile->GetPrefs(), std::make_unique< password_manager_android_util::PasswordManagerUtilBridge>())) { return std::make_unique<PasswordManagerSettingsServiceAndroidImpl>(
diff --git a/chrome/browser/password_manager/profile_password_store_factory.cc b/chrome/browser/password_manager/profile_password_store_factory.cc index 18aed654..354c6b6 100644 --- a/chrome/browser/password_manager/profile_password_store_factory.cc +++ b/chrome/browser/password_manager/profile_password_store_factory.cc
@@ -30,7 +30,6 @@ #include "services/network/public/cpp/shared_url_loader_factory.h" #if BUILDFLAG(IS_ANDROID) -#include "chrome/browser/password_manager/android/login_db_deprecation_runner_factory.h" #include "chrome/browser/password_manager/android/password_manager_util_bridge.h" #endif // BUILDFLAG(IS_ANDROID) @@ -91,14 +90,6 @@ affiliation_service->RegisterSource(std::move(password_affiliation_adapter)); #endif -#if BUILDFLAG(IS_ANDROID) && !BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND) - CHECK(util_bridge.IsInternalBackendPresent()); - password_manager::LoginDbDeprecationRunner* login_db_deprecation_runner = - LoginDbDeprecationRunnerFactory::GetForProfile(profile); - if (login_db_deprecation_runner) { - login_db_deprecation_runner->StartExportWithDelay(ps); - } -#endif DelayReportingPasswordStoreMetrics(profile); return ps; @@ -142,9 +133,6 @@ .Build()) { DependsOn(AffiliationServiceFactory::GetInstance()); DependsOn(CredentialsCleanerRunnerFactory::GetInstance()); -#if BUILDFLAG(IS_ANDROID) - DependsOn(LoginDbDeprecationRunnerFactory::GetInstance()); -#endif } ProfilePasswordStoreFactory::~ProfilePasswordStoreFactory() = default;
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc index 581384fc..8305005 100644 --- a/chrome/browser/prefs/browser_prefs.cc +++ b/chrome/browser/prefs/browser_prefs.cc
@@ -1143,6 +1143,16 @@ "ash.desks.desks_lacros_profile_id_list"; #endif // BUILDFLAG(IS_CHROMEOS) +#if BUILDFLAG(IS_ANDROID) +// Deprecated 09/2025. +constexpr char kObsoleteUpmUnmigratedPasswordsExported[] = + "profile.upm_unmigrated_passwords_exported"; +constexpr char kObsoletePasswordsUseUPMLocalAndSeparateStores[] = + "passwords_use_upm_local_and_separate_stores"; +constexpr char kObsoleteEmptyProfileStoreLoginDatabase[] = + "password_manager.empty_profile_store_login_database"; +#endif // BUILDFLAG(IS_ANDROID) + // Register local state used only for migration (clearing or moving to a new // key). void RegisterLocalStatePrefsForMigration(PrefRegistrySimple* registry) { @@ -1648,6 +1658,14 @@ // Deprecated 08/2025. registry->RegisterListPref(kDesksLacrosProfileIdList); #endif // BUILDFLAG(IS_CHROMEOS) + +#if BUILDFLAG(IS_ANDROID) + // Deprecated 09/2025. + registry->RegisterBooleanPref(kObsoleteUpmUnmigratedPasswordsExported, false); + registry->RegisterIntegerPref(kObsoletePasswordsUseUPMLocalAndSeparateStores, + 0); + registry->RegisterBooleanPref(kObsoleteEmptyProfileStoreLoginDatabase, false); +#endif // BUILDFLAG(IS_ANDROID) } } // namespace @@ -2993,6 +3011,13 @@ profile_prefs->ClearPref(kDesksLacrosProfileIdList); #endif // BUILDFLAG(IS_CHROMEOS) +#if BUILDFLAG(IS_ANDROID) + // Added 09/2025. + profile_prefs->ClearPref(kObsoleteUpmUnmigratedPasswordsExported); + profile_prefs->ClearPref(kObsoletePasswordsUseUPMLocalAndSeparateStores); + profile_prefs->ClearPref(kObsoleteEmptyProfileStoreLoginDatabase); +#endif // BUILDFLAG(IS_ANDROID) + // Please don't delete the following line. It is used by PRESUBMIT.py. // END_MIGRATE_OBSOLETE_PROFILE_PREFS
diff --git a/chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.cc b/chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.cc index a37cef1a..8c5e41a 100644 --- a/chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.cc +++ b/chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.cc
@@ -27,7 +27,7 @@ } // namespace -ScopedProfileKeepAlive::ScopedProfileKeepAlive(const Profile* profile, +ScopedProfileKeepAlive::ScopedProfileKeepAlive(Profile* profile, ProfileKeepAliveOrigin origin) : profile_(profile->GetWeakPtr()), origin_(origin) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); @@ -65,7 +65,7 @@ // static void ScopedProfileKeepAlive::RemoveKeepAliveOnUIThread( - base::WeakPtr<const Profile> profile, + base::WeakPtr<Profile> profile, ProfileKeepAliveOrigin origin) { SCOPED_CRASH_KEY_STRING32("ProfileKeepAlive", "origin", GetOriginName(origin));
diff --git a/chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h b/chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h index 9ec47174..2e75741 100644 --- a/chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h +++ b/chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h
@@ -25,22 +25,22 @@ // off-the-record Profile triggers a DCHECK. class ScopedProfileKeepAlive { public: - ScopedProfileKeepAlive(const Profile* profile, ProfileKeepAliveOrigin origin); + ScopedProfileKeepAlive(Profile* profile, ProfileKeepAliveOrigin origin); ~ScopedProfileKeepAlive(); ScopedProfileKeepAlive(const ScopedProfileKeepAlive&) = delete; ScopedProfileKeepAlive& operator=(const ScopedProfileKeepAlive&) = delete; - const Profile* profile() { return profile_.get(); } + Profile* profile() { return profile_.get(); } ProfileKeepAliveOrigin origin() { return origin_; } private: // Called after the ScopedProfileKeepAlive has been deleted, so this is a // static method where we pass parameters manually. - static void RemoveKeepAliveOnUIThread(base::WeakPtr<const Profile> profile, + static void RemoveKeepAliveOnUIThread(base::WeakPtr<Profile> profile, ProfileKeepAliveOrigin origin); - const base::WeakPtr<const Profile> profile_; + const base::WeakPtr<Profile> profile_; const ProfileKeepAliveOrigin origin_; };
diff --git a/chrome/browser/profiles/profile.cc b/chrome/browser/profiles/profile.cc index f5e973b..546c9c27 100644 --- a/chrome/browser/profiles/profile.cc +++ b/chrome/browser/profiles/profile.cc
@@ -593,10 +593,6 @@ return chrome_variations_client_.get(); } -base::WeakPtr<const Profile> Profile::GetWeakPtr() const { - return weak_factory_.GetWeakPtr(); -} - base::WeakPtr<Profile> Profile::GetWeakPtr() { return weak_factory_.GetWeakPtr(); }
diff --git a/chrome/browser/profiles/profile.h b/chrome/browser/profiles/profile.h index 25c4d29..806ecfcc 100644 --- a/chrome/browser/profiles/profile.h +++ b/chrome/browser/profiles/profile.h
@@ -477,7 +477,6 @@ virtual void RecordPrimaryMainFrameNavigation() = 0; - base::WeakPtr<const Profile> GetWeakPtr() const; base::WeakPtr<Profile> GetWeakPtr(); // Experimental getters/setters to gauge the performance of caching
diff --git a/chrome/browser/profiles/profile_manager.cc b/chrome/browser/profiles/profile_manager.cc index ebefa6a0..60faa5d 100644 --- a/chrome/browser/profiles/profile_manager.cc +++ b/chrome/browser/profiles/profile_manager.cc
@@ -1214,7 +1214,7 @@ return Profile::CreateProfile(path, this, Profile::CreateMode::kAsynchronous); } -bool ProfileManager::HasKeepAliveForTesting(const Profile* profile, +bool ProfileManager::HasKeepAliveForTesting(Profile* profile, ProfileKeepAliveOrigin origin) { DCHECK(profile); ProfileInfo* info = GetProfileInfoByPath(profile->GetPath()); @@ -1243,7 +1243,7 @@ base::UmaHistogramCounts100("Profile.ZombieProfileCount", zombie_count); } -void ProfileManager::AddKeepAlive(const Profile* profile, +void ProfileManager::AddKeepAlive(Profile* profile, ProfileKeepAliveOrigin origin) { DCHECK_NE(ProfileKeepAliveOrigin::kWaitingForFirstBrowserWindow, origin); @@ -1286,7 +1286,7 @@ } } -void ProfileManager::RemoveKeepAlive(const Profile* profile, +void ProfileManager::RemoveKeepAlive(Profile* profile, ProfileKeepAliveOrigin origin) { DCHECK_NE(ProfileKeepAliveOrigin::kWaitingForFirstBrowserWindow, origin); @@ -1340,7 +1340,7 @@ UnloadProfileIfNoKeepAlive(info); } -void ProfileManager::ClearFirstBrowserWindowKeepAlive(const Profile* profile) { +void ProfileManager::ClearFirstBrowserWindowKeepAlive(Profile* profile) { CHECK(profile); CHECK(!profile->IsOffTheRecord());
diff --git a/chrome/browser/profiles/profile_manager.h b/chrome/browser/profiles/profile_manager.h index 364e68d..7f7a93d 100644 --- a/chrome/browser/profiles/profile_manager.h +++ b/chrome/browser/profiles/profile_manager.h
@@ -310,8 +310,7 @@ // Used for testing. Returns true if |profile| has at least one ref of type // |origin|. - bool HasKeepAliveForTesting(const Profile* profile, - ProfileKeepAliveOrigin origin); + bool HasKeepAliveForTesting(Profile* profile, ProfileKeepAliveOrigin origin); // Disables the periodic reporting of profile metrics, as this is causing // tests to time out. @@ -338,7 +337,7 @@ // Removes the kWaitingForFirstBrowserWindow keepalive. This allows a // Profile* to be deleted from now on, even if it never had a visible // browser window. - void ClearFirstBrowserWindowKeepAlive(const Profile* profile); + void ClearFirstBrowserWindowKeepAlive(Profile* profile); // Returns whether |path| is allowed for profile creation. bool IsAllowedProfilePath(const base::FilePath& path) const; @@ -442,8 +441,8 @@ // Increments/decrements the refcount on a |profile|. (it must not be an // off-the-record profile) - void AddKeepAlive(const Profile* profile, ProfileKeepAliveOrigin origin); - void RemoveKeepAlive(const Profile* profile, ProfileKeepAliveOrigin origin); + void AddKeepAlive(Profile* profile, ProfileKeepAliveOrigin origin); + void RemoveKeepAlive(Profile* profile, ProfileKeepAliveOrigin origin); void RecordZombieMetrics();
diff --git a/chrome/browser/resources/autofill_ml_internals/log_details.css b/chrome/browser/resources/autofill_ml_internals/log_details.css index e3c6a79b..1b44e337 100644 --- a/chrome/browser/resources/autofill_ml_internals/log_details.css +++ b/chrome/browser/resources/autofill_ml_internals/log_details.css
@@ -26,7 +26,6 @@ border-radius: 4px; padding: 8px; margin-bottom: 16px; - display: flex; gap: 8px; } @@ -34,6 +33,12 @@ width: 300px; } +.field-details-row { + display: flex; + gap: 8px; + margin-bottom: 8px; +} + .histogram { width: 500px; }
diff --git a/chrome/browser/resources/autofill_ml_internals/log_details.html.ts b/chrome/browser/resources/autofill_ml_internals/log_details.html.ts index 982e3c4..54644da 100644 --- a/chrome/browser/resources/autofill_ml_internals/log_details.html.ts +++ b/chrome/browser/resources/autofill_ml_internals/log_details.html.ts
@@ -32,46 +32,54 @@ <h3>Fields</h3> ${this.log.fieldPredictions.map((field: MlFieldPredictionLog) => html` <div class="field"> - <div class="field-properties"> - <div class="row"> - <span class="label">Label</span> - <span>${field.label}</span> + <div class="field-details-row"> + <div class="field-properties"> + <div class="row"> + <span class="label">Label</span> + <span>${field.label}</span> + </div> + <div class="row"> + <span class="label">Placeholder</span> + <span>${field.placeholder}</span> + </div> + <div class="row"> + <span class="label">Name</span> + <span>${field.name}</span> + </div> + <div class="row"> + <span class="label">ID</span> + <span>${field.id}</span> + </div> + <div class="row"> + <span class="label">Autocomplete</span> + <span>${field.autocomplete}</span> + </div> + <div class="row"> + <span class="label">Form Control Type</span> + <span>${field.formControlType}</span> + </div> </div> - <div class="row"> - <span class="label">Placeholder</span> - <span>${field.placeholder}</span> - </div> - <div class="row"> - <span class="label">Name</span> - <span>${field.name}</span> - </div> - <div class="row"> - <span class="label">ID</span> - <span>${field.id}</span> - </div> - <div class="row"> - <span class="label">Autocomplete</span> - <span>${field.autocomplete}</span> - </div> - <div class="row"> - <span class="label">Form Control Type</span> - <span>${field.formControlType}</span> + <div class="histogram"> + ${this.getTopPredictions_(field.probabilities).map(p => html` + <div class="histogram-row"> + <div class="histogram-label" title="${p.type}"> + ${p.type} + </div> + <div class="histogram-bar-container"> + <div class="histogram-bar" + style="width: ${p.percentage}"> + </div> + </div> + <div class="histogram-value">${p.percentage}</div> + </div> + `)} </div> </div> - <div class="histogram"> - ${this.getTopPredictions_(field.probabilities).map(prediction => html` - <div class="histogram-row"> - <div class="histogram-label" title="${prediction.type}"> - ${prediction.type} - </div> - <div class="histogram-bar-container"> - <div class="histogram-bar" - style="width: ${prediction.percentage}"> - </div> - </div> - <div class="histogram-value">${prediction.percentage}</div> - </div> - `)} + <div class="row"> + <span class="label">Tokens</span> + <span> + ${this.formatFieldTokens_(field.tokenizedFieldRepresentation)} + </span> </div> `)} </div>
diff --git a/chrome/browser/resources/autofill_ml_internals/log_details.ts b/chrome/browser/resources/autofill_ml_internals/log_details.ts index a0eb362..cd18379 100644 --- a/chrome/browser/resources/autofill_ml_internals/log_details.ts +++ b/chrome/browser/resources/autofill_ml_internals/log_details.ts
@@ -68,6 +68,10 @@ .sort((a, b) => b.probability - a.probability) .slice(0, LogDetailsElement.TOP_PREDICTIONS_COUNT); } + + protected formatFieldTokens_(tokens: string[]): string { + return tokens.join(' / '); + } } declare global {
diff --git a/chrome/browser/resources/history/BUILD.gn b/chrome/browser/resources/history/BUILD.gn index 7404dd3..8f86b23 100644 --- a/chrome/browser/resources/history/BUILD.gn +++ b/chrome/browser/resources/history/BUILD.gn
@@ -23,27 +23,31 @@ "images/avatar_surrounding_illustration_dark.svg", ] - # Files holding a Polymer element definition and have an equivalent .html file. - web_component_files = [ - "app.ts", - "history_embeddings_promo.ts", - "history_item.ts", - "history_list.ts", - "history_toolbar.ts", - "side_bar.ts", - "synced_device_card.ts", - "synced_device_manager.ts", - ] - ts_files = [ + "app.html.ts", + "app.ts", "browser_service.ts", "constants.ts", "externs.ts", "history.ts", + "history_embeddings_promo.html.ts", + "history_embeddings_promo.ts", + "history_item.html.ts", + "history_item.ts", + "history_list.html.ts", + "history_list.ts", + "history_toolbar.html.ts", + "history_toolbar.ts", "lazy_load.ts", "query_manager.ts", "router.ts", "searched_label.ts", + "side_bar.html.ts", + "side_bar.ts", + "synced_device_card.html.ts", + "synced_device_card.ts", + "synced_device_manager.html.ts", + "synced_device_manager.ts", ] # Files that are passed as input to css_to_wrapper().
diff --git a/chrome/browser/resources/history/app.html b/chrome/browser/resources/history/app.html.ts similarity index 93% rename from chrome/browser/resources/history/app.html rename to chrome/browser/resources/history/app.html.ts index 1552fc9..ab3721d 100644 --- a/chrome/browser/resources/history/app.html +++ b/chrome/browser/resources/history/app.html.ts
@@ -1,4 +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. +import {html} from '//resources/lit/v3_0/lit.rollup.js'; + +import type {HistoryAppElement} from './app.js'; + +export function getHtml(this: HistoryAppElement) { + // clang-format off + return html`<!--_html_template_start_--> <history-query-manager .queryResult="${this.queryResult_}" @query-finished="${this.onQueryFinished_}" @@ -101,7 +111,7 @@ .scrollTarget="${this.scrollTarget_}" .scrollOffset="${this.tabContentScrollOffset_}"> </history-list> - ${this.historyClustersSelected_() ? html` + ${this.historyClustersSelected_() ? html` <history-clusters id="history-clusters" ?is-active="${this.getShowResultsByGroup_()}" .query="${this.queryState_.searchTerm}" @@ -139,3 +149,6 @@ </history-side-bar> </cr-drawer>`}'> </cr-lazy-render-lit> +<!--_html_template_end_-->`; + // clang-format on +}
diff --git a/chrome/browser/resources/history/app.ts b/chrome/browser/resources/history/app.ts index 1e4c6602..e1f67f4 100644 --- a/chrome/browser/resources/history/app.ts +++ b/chrome/browser/resources/history/app.ts
@@ -872,9 +872,6 @@ } } -// Exported to be used in the autogenerated Lit template file -export type AppElement = HistoryAppElement; - declare global { interface HTMLElementTagNameMap { 'history-app': HistoryAppElement;
diff --git a/chrome/browser/resources/history/history_embeddings_promo.html b/chrome/browser/resources/history/history_embeddings_promo.html.ts similarity index 70% rename from chrome/browser/resources/history/history_embeddings_promo.html rename to chrome/browser/resources/history/history_embeddings_promo.html.ts index 21d38c7..1948b83 100644 --- a/chrome/browser/resources/history/history_embeddings_promo.html +++ b/chrome/browser/resources/history/history_embeddings_promo.html.ts
@@ -1,5 +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. +import {html} from '//resources/lit/v3_0/lit.rollup.js'; +import type {HistoryEmbeddingsPromoElement} from './history_embeddings_promo.js'; + +export function getHtml(this: HistoryEmbeddingsPromoElement) { + // clang-format off + return html`<!--_html_template_start_--> <div id="promo" role="dialog" aria-label="$i18n{historyEmbeddingsPromoLabel}" ?hidden="${!this.shown_}"> <cr-icon-button id="close" iron-icon="cr:close" @@ -31,3 +40,6 @@ </div> </div> </div> +<!--_html_template_end_-->`; + // clang-format on +}
diff --git a/chrome/browser/resources/history/history_item.html b/chrome/browser/resources/history/history_item.html.ts similarity index 90% rename from chrome/browser/resources/history/history_item.html rename to chrome/browser/resources/history/history_item.html.ts index bb84668..ce6a732e 100644 --- a/chrome/browser/resources/history/history_item.html +++ b/chrome/browser/resources/history/history_item.html.ts
@@ -1,3 +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. + +import {html} from '//resources/lit/v3_0/lit.rollup.js'; + +import type {HistoryItemElement} from './history_item.js'; + +export function getHtml(this: HistoryItemElement) { + // clang-format off + return html`<!--_html_template_start_--> <div id="main-container"> <div id="background-clip" aria-hidden="true"> <div id="background"></div> @@ -88,3 +99,6 @@ <div id="time-gap-separator" ?hidden="${!this.hasTimeGap}"></div> </div> </div> +<!--_html_template_end_-->`; + // clang-format on +}
diff --git a/chrome/browser/resources/history/history_list.html b/chrome/browser/resources/history/history_list.html.ts similarity index 86% rename from chrome/browser/resources/history/history_list.html rename to chrome/browser/resources/history/history_list.html.ts index 3cedc81..742f790d 100644 --- a/chrome/browser/resources/history/history_list.html +++ b/chrome/browser/resources/history/history_list.html.ts
@@ -1,3 +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. + +import {html} from '//resources/lit/v3_0/lit.rollup.js'; + +import type {HistoryListElement} from './history_list.js'; + +export function getHtml(this: HistoryListElement) { + // clang-format off + return html`<!--_html_template_start_--> <div id="noResults" class="centered-message" ?hidden="${this.hasResults_()}"> ${this.noResultsMessage_()} @@ -63,4 +74,7 @@ $i18n{removeBookmark} </button> </cr-action-menu>`}'> - </cr-lazy-render-lit> + </cr-lazy-render-lit> +<!--_html_template_end_-->`; + // clang-format on +}
diff --git a/chrome/browser/resources/history/history_toolbar.html b/chrome/browser/resources/history/history_toolbar.html.ts similarity index 71% rename from chrome/browser/resources/history/history_toolbar.html rename to chrome/browser/resources/history/history_toolbar.html.ts index fadb3ee..43e543a 100644 --- a/chrome/browser/resources/history/history_toolbar.html +++ b/chrome/browser/resources/history/history_toolbar.html.ts
@@ -1,3 +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. + +import {html} from '//resources/lit/v3_0/lit.rollup.js'; + +import type {HistoryToolbarElement} from './history_toolbar.js'; + +export function getHtml(this: HistoryToolbarElement) { + // clang-format off + return html`<!--_html_template_start_--> <cr-toolbar id="mainToolbar" disable-right-content-grow ?has-overlay="${this.itemsSelected_}" @@ -29,3 +40,6 @@ </cr-button> </cr-toolbar-selection-overlay> +<!--_html_template_end_-->`; + // clang-format on +}
diff --git a/chrome/browser/resources/history/side_bar.html b/chrome/browser/resources/history/side_bar.html.ts similarity index 80% rename from chrome/browser/resources/history/side_bar.html rename to chrome/browser/resources/history/side_bar.html.ts index f877875..40aebfd9 100644 --- a/chrome/browser/resources/history/side_bar.html +++ b/chrome/browser/resources/history/side_bar.html.ts
@@ -1,3 +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. + +import {html} from '//resources/lit/v3_0/lit.rollup.js'; + +import type {HistorySideBarElement} from './side_bar.js'; + +export function getHtml(this: HistorySideBarElement) { + // clang-format off + return html`<!--_html_template_start_--> <cr-menu-selector id="menu" selected="${this.selectedPage}" @selected-changed="${this.onSelectorSelectedChanged_}" selectable=".page-item" attr-for-selected="path" @@ -43,3 +54,6 @@ <div ?hidden="${!this.showGMAAndGAA_}">$i18nRaw{sidebarFooterGMAAndGAA}</div> </div> </div> +<!--_html_template_end_-->`; + // clang-format on +}
diff --git a/chrome/browser/resources/history/side_bar.ts b/chrome/browser/resources/history/side_bar.ts index 7dd5ac8..1aebcba 100644 --- a/chrome/browser/resources/history/side_bar.ts +++ b/chrome/browser/resources/history/side_bar.ts
@@ -241,9 +241,6 @@ } } -// Exported to be used in the autogenerated Lit template file -export type SideBarElement = HistorySideBarElement; - declare global { interface HTMLElementTagNameMap { 'history-side-bar': HistorySideBarElement;
diff --git a/chrome/browser/resources/history/synced_device_card.html b/chrome/browser/resources/history/synced_device_card.html.ts similarity index 77% rename from chrome/browser/resources/history/synced_device_card.html rename to chrome/browser/resources/history/synced_device_card.html.ts index 15a7d8b..6756ae09 100644 --- a/chrome/browser/resources/history/synced_device_card.html +++ b/chrome/browser/resources/history/synced_device_card.html.ts
@@ -1,3 +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. + +import {html} from '//resources/lit/v3_0/lit.rollup.js'; + +import type {HistorySyncedDeviceCardElement} from './synced_device_card.js'; + +export function getHtml(this: HistorySyncedDeviceCardElement) { + // clang-format off + return html`<!--_html_template_start_--> <div id="history-item-container"> <div class="card-title" id="card-heading" aria-expanded="${this.opened}" aria-controls="collapse" @click="${this.toggleTabCard}"> @@ -40,3 +51,6 @@ </div> </cr-collapse> </div> +<!--_html_template_end_-->`; + // clang-format on +}
diff --git a/chrome/browser/resources/history/synced_device_manager.html b/chrome/browser/resources/history/synced_device_manager.html.ts similarity index 84% rename from chrome/browser/resources/history/synced_device_manager.html rename to chrome/browser/resources/history/synced_device_manager.html.ts index 78c196e..ce340126 100644 --- a/chrome/browser/resources/history/synced_device_manager.html +++ b/chrome/browser/resources/history/synced_device_manager.html.ts
@@ -1,3 +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. + +import {html} from '//resources/lit/v3_0/lit.rollup.js'; + +import type {HistorySyncedDeviceManagerElement} from './synced_device_manager.js'; + +export function getHtml(this: HistorySyncedDeviceManagerElement) { + // clang-format off + return html`<!--_html_template_start_--> <div id="synced-device-list" class="history-cards" ?hidden="${!this.syncedDevices_.length}"> ${this.syncedDevices_.map((syncedDevice, index) => html` @@ -70,3 +81,6 @@ </cr-action-menu> `}'> </cr-lazy-render-lit> +<!--_html_template_end_-->`; + // clang-format on +}
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.html b/chrome/browser/resources/settings/privacy_page/privacy_page.html index ad1e708..e8e57eac 100644 --- a/chrome/browser/resources/settings/privacy_page/privacy_page.html +++ b/chrome/browser/resources/settings/privacy_page/privacy_page.html
@@ -701,94 +701,7 @@ </settings-subpage> </template> </template> - <template is="dom-if" if="[[enableExperimentalWebPlatformFeatures_]]"> - <template is="dom-if" route-path="/content/bluetoothScanning" no-search> - <settings-subpage page-title="$i18n{siteSettingsBluetoothScanning}" - search-label="$i18n{siteSettingsAllSitesSearch}" - search-term="{{searchFilter_}}"> - <settings-bluetooth-scanning-page search-term="[[searchFilter_]]"> - </settings-bluetooth-scanning-page> - </settings-subpage> - </template> - </template> - <template is="dom-if" route-path="/content/vr" no-search> - <settings-subpage page-title="$i18n{siteSettingsVr}" - search-label="$i18n{siteSettingsAllSitesSearch}" - search-term="{{searchFilter_}}"> - <settings-vr-page search-term="[[searchFilter_]]"></settings-vr-page> - </settings-subpage> - </template> - <template is="dom-if" route-path="/content/ar" no-search> - <settings-subpage page-title="$i18n{siteSettingsAr}" - search-label="$i18n{siteSettingsAllSitesSearch}" - search-term="{{searchFilter_}}"> - <settings-ar-page search-term="[[searchFilter_]]"></settings-ar-page> - </settings-subpage> - </template> - <template is="dom-if" if="[[enableHandTrackingContentSetting_]]"> - <template is="dom-if" route-path="/content/handTracking" no-search> - <settings-subpage page-title="$i18n{siteSettingsHandTracking}" - search-label="$i18n{siteSettingsAllSitesSearch}" - search-term="{{searchFilter_}}"> - <settings-hand-tracking-page search-term="[[searchFilter_]]"> - </settings-hand-tracking-page> - </settings-subpage> - </template> - </template> - <template is="dom-if" route-path="/content/idleDetection" no-search> - <settings-subpage page-title="$i18n{siteSettingsIdleDetection}" - search-label="$i18n{siteSettingsAllSitesSearch}" - search-term="{{searchFilter_}}"> - <settings-idle-detection-page search-term="[[searchFilter_]]"> - </settings-idle-detection-page> - </settings-subpage> - </template> - <template is="dom-if" route-path="/content/windowManagement" no-search> - <settings-subpage page-title="$i18n{siteSettingsWindowManagement}" - search-label="$i18n{siteSettingsAllSitesSearch}" - search-term="{{searchFilter_}}"> - <settings-window-management-page search-term="[[searchFilter_]]"> - </settings-window-management-page> - </settings-subpage> - </template> - <template is="dom-if" route-path="/content/localFonts" no-search> - <settings-subpage page-title="$i18n{fonts}" - search-label="$i18n{siteSettingsAllSitesSearch}" - search-term="{{searchFilter_}}"> - <settings-local-fonts-page search-term="[[searchFilter_]]"> - </settings-local-fonts-page> - </settings-subpage> - </template> - <template is="dom-if" route-path="/content/storageAccess" no-search> - <settings-subpage page-title="$i18n{siteSettingsStorageAccess}" - search-label="$i18n{siteSettingsAllSitesSearch}" - search-term="{{searchFilter_}}"> - <settings-storage-access-page search-term="[[searchFilter_]]"> - </settings-storage-access-page> - </settings-subpage> - </template> - <template is="dom-if" if="[[autoPictureInPictureEnabled_]]"> - <template is="dom-if" route-path="/content/autoPictureInPicture" no-search> - <settings-subpage page-title="$i18n{siteSettingsAutoPictureInPicture}" - search-label="$i18n{siteSettingsAllSitesSearch}" - search-term="{{searchFilter_}}"> - <settings-auto-picture-in-picture-page - search-term="[[searchFilter_]]"> - </settings-auto-picture-in-picture-page> - </settings-subpage> - </template> - </template> - <template is="dom-if" if="[[capturedSurfaceControlEnabled_]]"> - <template is="dom-if" route-path="/content/capturedSurfaceControl" no-search> - <settings-subpage page-title="$i18n{siteSettingsCapturedSurfaceControl}" - search-label="$i18n{siteSettingsAllSitesSearch}" - search-term="{{searchFilter_}}"> - <settings-captured-surface-control-page - search-term="[[searchFilter_]]"> - </settings-captured-surface-control-page> - </settings-subpage> - </template> - </template> + <if expr="is_chromeos"> <template is="dom-if" if="[[enableSmartCardReadersContentSetting_]]"> <template is="dom-if"
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.ts b/chrome/browser/resources/settings/privacy_page/privacy_page.ts index b45a26e..702382ac 100644 --- a/chrome/browser/resources/settings/privacy_page/privacy_page.ts +++ b/chrome/browser/resources/settings/privacy_page/privacy_page.ts
@@ -126,13 +126,6 @@ }, }, - enableHandTrackingContentSetting_: { - type: Boolean, - value() { - return loadTimeData.getBoolean('enableHandTrackingContentSetting'); - }, - }, - enableFederatedIdentityApiContentSetting_: { type: Boolean, value() { @@ -181,16 +174,6 @@ loadTimeData.getBoolean('isPrivacySandboxRestrictedNoticeEnabled'), }, - autoPictureInPictureEnabled_: { - type: Boolean, - value: () => loadTimeData.getBoolean('autoPictureInPictureEnabled'), - }, - - capturedSurfaceControlEnabled_: { - type: Boolean, - value: () => loadTimeData.getBoolean('capturedSurfaceControlEnabled'), - }, - /** * Whether the File System Access Persistent Permissions UI should be * displayed. @@ -296,7 +279,6 @@ declare private enableDeleteBrowsingDataRevamp_: boolean; declare private enableFederatedIdentityApiContentSetting_: boolean; declare private enablePaymentHandlerContentSetting_: boolean; - declare private enableHandTrackingContentSetting_: boolean; declare private enableExperimentalWebPlatformFeatures_: boolean; // <if expr="is_chromeos"> declare private enableSmartCardReadersContentSetting_: boolean; @@ -306,8 +288,6 @@ declare private isPrivacySandboxRestricted_: boolean; declare private isPrivacySandboxRestrictedNoticeEnabled_: boolean; private privateStateTokensEnabled_: boolean; - declare private autoPictureInPictureEnabled_: boolean; - declare private capturedSurfaceControlEnabled_: boolean; declare private enableWebAppInstallation_: boolean; declare private enableLocalNetworkAccessSetting_: boolean; declare private focusConfig_: FocusConfig; @@ -506,15 +486,25 @@ triggerId = 'securityLinkRow'; break; case 'siteSettings': + case 'siteSettingsAr': case 'siteSettingsAutomaticFullscreen': + case 'siteSettingsAutoPictureInPicture': + case 'siteSettingsBluetoothScanning': + case 'siteSettingsCapturedSurfaceControl': case 'siteSettingsHandlers': + case 'siteSettingsHandTracking': + case 'siteSettingsIdleDetection': case 'siteSettingsKeyboardLock': + case 'siteSettingsLocalFonts': case 'siteSettingsLocalNetworkAccess': case 'siteSettingsLocation': case 'siteSettingsNotifications': case 'siteSettingsPdfDocuments': case 'siteSettingsSiteData': + case 'siteSettingsStorageAccess': + case 'siteSettingsVr': case 'siteSettingsWebAppInstallation': + case 'siteSettingsWindowManagement': case 'siteSettingsZoomLevels': triggerId = 'permissionsLinkRow'; break;
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page_index.html b/chrome/browser/resources/settings/privacy_page/privacy_page_index.html index fb981e1..85dcbb7 100644 --- a/chrome/browser/resources/settings/privacy_page/privacy_page_index.html +++ b/chrome/browser/resources/settings/privacy_page/privacy_page_index.html
@@ -124,6 +124,21 @@ </template> <template is="dom-if" if="[[renderView_( + routes_.SITE_SETTINGS_AR, currentRoute, inSearchMode)]]"> + <settings-ar-page slot="view" id="siteSettingsAr" data-parent-view-id="old"> + </settings-ar-page> + </template> + + <template is="dom-if" if="[[autoPictureInPictureEnabled_]]"> + <template is="dom-if" if="[[renderView_( + routes_.SITE_SETTINGS_AUTO_PICTURE_IN_PICTURE, currentRoute, inSearchMode)]]"> + <settings-auto-picture-in-picture-page slot="view" + id="siteSettingsAutoPictureInPicture" data-parent-view-id="old"> + </settings-auto-picture-in-picture-page> + </template> + </template> + + <template is="dom-if" if="[[renderView_( routes_.SITE_SETTINGS_AUTOMATIC_FULLSCREEN, currentRoute, inSearchMode)]]"> <settings-automatic-full-screen-page slot="view" @@ -132,6 +147,33 @@ </settings-automatic-full-screen-page> </template> + <template is="dom-if" if="[[enableExperimentalWebPlatformFeatures_]]"> + <template is="dom-if" if="[[renderView_( + routes_.SITE_SETTINGS_BLUETOOTH_SCANNING, currentRoute, inSearchMode)]]"> + <settings-bluetooth-scanning-page slot="view" + id="siteSettingsBluetoothScanning" data-parent-view-id="old"> + </settings-bluetooth-scanning-page> + </template> + </template> + + <template is="dom-if" if="[[capturedSurfaceControlEnabled_]]"> + <template is="dom-if" if="[[renderView_( + routes_.SITE_SETTINGS_CAPTURED_SURFACE_CONTROL, currentRoute, inSearchMode)]]"> + <settings-captured-surface-control-page slot="view" + id="siteSettingsCapturedSurfaceControl" data-parent-view-id="old"> + </settings-captured-surface-control-page> + </template> + </template> + + <template is="dom-if" if="[[enableHandTrackingContentSetting_]]"> + <template is="dom-if" if="[[renderView_( + routes_.SITE_SETTINGS_HAND_TRACKING, currentRoute, inSearchMode)]]"> + <settings-hand-tracking-page slot="view" id="siteSettingsHandTracking" + data-parent-view-id="old"> + </settings-hand-tracking-page> + </template> + </template> + <template is="dom-if" if="[[renderView_( routes_.SITE_SETTINGS_HANDLERS, currentRoute, inSearchMode)]]"> <protocol-handlers slot="view" id="siteSettingsHandlers" @@ -141,6 +183,13 @@ </protocol-handlers> </template> + <template is="dom-if" if="[[renderView_( + routes_.SITE_SETTINGS_IDLE_DETECTION, currentRoute, inSearchMode)]]"> + <settings-idle-detection-page slot="view" id="siteSettingsIdleDetection" + data-parent-view-id="old"> + </settings-idle-detection-page> + </template> + <template is="dom-if" if="[[enableKeyboardLockPrompt_]]"> <template is="dom-if" if="[[renderView_( routes_.SITE_SETTINGS_KEYBOARD_LOCK, currentRoute, inSearchMode)]]"> @@ -162,6 +211,13 @@ </template> <template is="dom-if" if="[[renderView_( + routes_.SITE_SETTINGS_LOCAL_FONTS, currentRoute, inSearchMode)]]"> + <settings-local-fonts-page slot="view" id="siteSettingsLocalFonts" + data-parent-view-id="old"> + </settings-local-fonts-page> + </template> + + <template is="dom-if" if="[[renderView_( routes_.SITE_SETTINGS_LOCATION, currentRoute, inSearchMode)]]"> <settings-geolocation-page slot="view" id="siteSettingsLocation" data-parent-view-id="old" prefs="{{prefs}}"> @@ -189,6 +245,19 @@ </settings-site-data> </template> + <template is="dom-if" if="[[renderView_( + routes_.SITE_SETTINGS_STORAGE_ACCESS, currentRoute, inSearchMode)]]"> + <settings-storage-access-page slot="view" id="siteSettingsStorageAccess" + data-parent-view-id="old"> + </settings-storage-access-page> + </template> + + <template is="dom-if" if="[[renderView_( + routes_.SITE_SETTINGS_VR, currentRoute, inSearchMode)]]"> + <settings-vr-page slot="view" id="siteSettingsVr" data-parent-view-id="old"> + </settings-vr-page> + </template> + <template is="dom-if" if="[[enableWebAppInstallation_]]"> <template is="dom-if" if="[[renderView_( routes_.SITE_SETTINGS_WEB_APP_INSTALLATION, currentRoute, @@ -201,6 +270,13 @@ </template> <template is="dom-if" if="[[renderView_( + routes_.SITE_SETTINGS_WINDOW_MANAGEMENT, currentRoute, inSearchMode)]]"> + <settings-window-management-page slot="view" + id="siteSettingsWindowManagement" data-parent-view-id="old"> + </settings-window-management-page> + </template> + + <template is="dom-if" if="[[renderView_( routes_.SITE_SETTINGS_ZOOM_LEVELS, currentRoute, inSearchMode)]]"> <zoom-levels slot="view" id="siteSettingsZoomLevels" data-parent-view-id="old">
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page_index.ts b/chrome/browser/resources/settings/privacy_page/privacy_page_index.ts index 58c4684..627672b 100644 --- a/chrome/browser/resources/settings/privacy_page/privacy_page_index.ts +++ b/chrome/browser/resources/settings/privacy_page/privacy_page_index.ts
@@ -79,6 +79,31 @@ value: false, }, + autoPictureInPictureEnabled_: { + type: Boolean, + value: () => loadTimeData.getBoolean('autoPictureInPictureEnabled'), + }, + + capturedSurfaceControlEnabled_: { + type: Boolean, + value: () => loadTimeData.getBoolean('capturedSurfaceControlEnabled'), + }, + + enableExperimentalWebPlatformFeatures_: { + type: Boolean, + value: () => { + return loadTimeData.getBoolean( + 'enableExperimentalWebPlatformFeatures'); + }, + }, + + enableHandTrackingContentSetting_: { + type: Boolean, + value: () => { + return loadTimeData.getBoolean('enableHandTrackingContentSetting'); + }, + }, + enableIncognitoTrackingProtections_: { type: Boolean, value: () => @@ -88,9 +113,7 @@ enableSecurityKeysSubpage_: { type: Boolean, readOnly: true, - value: () => { - return loadTimeData.getBoolean('enableSecurityKeysSubpage'); - }, + value: () => loadTimeData.getBoolean('enableSecurityKeysSubpage'), }, enableKeyboardLockPrompt_: { @@ -136,6 +159,10 @@ declare private pageVisibility_: PageVisibility; declare private routes_: SettingsRoutes; declare private showPrivacyGuidePromo_: boolean; + declare private autoPictureInPictureEnabled_: boolean; + declare private capturedSurfaceControlEnabled_: boolean; + declare private enableExperimentalWebPlatformFeatures_: boolean; + declare private enableHandTrackingContentSetting_: boolean; declare private enableIncognitoTrackingProtections_: boolean; declare private enableKeyboardLockPrompt_: boolean; declare private enableLocalNetworkAccessSetting_: boolean; @@ -199,13 +226,31 @@ return ['securityKeys']; case routes.SITE_SETTINGS: return ['siteSettings']; + case routes.SITE_SETTINGS_AR: + return ['siteSettingsAr']; case routes.SITE_SETTINGS_AUTOMATIC_FULLSCREEN: return ['siteSettingsAutomaticFullscreen']; + case routes.SITE_SETTINGS_AUTO_PICTURE_IN_PICTURE: + assert(this.autoPictureInPictureEnabled_); + return ['siteSettingsAutoPictureInPicture']; + case routes.SITE_SETTINGS_BLUETOOTH_SCANNING: + assert(this.enableExperimentalWebPlatformFeatures_); + return ['siteSettingsBluetoothScanning']; + case routes.SITE_SETTINGS_CAPTURED_SURFACE_CONTROL: + assert(this.capturedSurfaceControlEnabled_); + return ['siteSettingsCapturedSurfaceControl']; case routes.SITE_SETTINGS_HANDLERS: return ['siteSettingsHandlers']; + case routes.SITE_SETTINGS_HAND_TRACKING: + assert(this.enableHandTrackingContentSetting_); + return ['siteSettingsHandTracking']; + case routes.SITE_SETTINGS_IDLE_DETECTION: + return ['siteSettingsIdleDetection']; case routes.SITE_SETTINGS_KEYBOARD_LOCK: assert(this.enableKeyboardLockPrompt_); return ['siteSettingsKeyboardLock']; + case routes.SITE_SETTINGS_LOCAL_FONTS: + return ['siteSettingsLocalFonts']; case routes.SITE_SETTINGS_LOCAL_NETWORK_ACCESS: assert(this.enableLocalNetworkAccessSetting_); return ['siteSettingsLocalNetworkAccess']; @@ -217,9 +262,15 @@ return ['siteSettingsPdfDocuments']; case routes.SITE_SETTINGS_SITE_DATA: return ['siteSettingsSiteData']; + case routes.SITE_SETTINGS_STORAGE_ACCESS: + return ['siteSettingsStorageAccess']; + case routes.SITE_SETTINGS_VR: + return ['siteSettingsVr']; case routes.SITE_SETTINGS_WEB_APP_INSTALLATION: assert(this.enableWebAppInstallation_); return ['siteSettingsWebAppInstallation']; + case routes.SITE_SETTINGS_WINDOW_MANAGEMENT: + return ['siteSettingsWindowManagement']; case routes.SITE_SETTINGS_ZOOM_LEVELS: return ['siteSettingsZoomLevels']; case routes.SAFETY_HUB:
diff --git a/chrome/browser/resources/settings/route.ts b/chrome/browser/resources/settings/route.ts index 41b2a7bf..d503b24 100644 --- a/chrome/browser/resources/settings/route.ts +++ b/chrome/browser/resources/settings/route.ts
@@ -81,15 +81,18 @@ // routes. r.SITE_SETTINGS_ADS = r.SITE_SETTINGS.createChild('ads'); r.SITE_SETTINGS_AR = r.SITE_SETTINGS.createChild('ar'); + r.SITE_SETTINGS_AR.hasMigratedToPlugin = true; r.SITE_SETTINGS_AUTOMATIC_DOWNLOADS = r.SITE_SETTINGS.createChild('automaticDownloads'); if (loadTimeData.getBoolean('autoPictureInPictureEnabled')) { r.SITE_SETTINGS_AUTO_PICTURE_IN_PICTURE = r.SITE_SETTINGS.createChild('autoPictureInPicture'); + r.SITE_SETTINGS_AUTO_PICTURE_IN_PICTURE.hasMigratedToPlugin = true; } if (loadTimeData.getBoolean('capturedSurfaceControlEnabled')) { r.SITE_SETTINGS_CAPTURED_SURFACE_CONTROL = r.SITE_SETTINGS.createChild('capturedSurfaceControl'); + r.SITE_SETTINGS_CAPTURED_SURFACE_CONTROL.hasMigratedToPlugin = true; } // <if expr="is_chromeos"> if (loadTimeData.getBoolean('enableSmartCardReadersContentSetting')) { @@ -104,8 +107,10 @@ r.SITE_SETTINGS_CLIPBOARD = r.SITE_SETTINGS.createChild('clipboard'); if (loadTimeData.getBoolean('enableHandTrackingContentSetting')) { r.SITE_SETTINGS_HAND_TRACKING = r.SITE_SETTINGS.createChild('handTracking'); + r.SITE_SETTINGS_HAND_TRACKING.hasMigratedToPlugin = true; } r.SITE_SETTINGS_IDLE_DETECTION = r.SITE_SETTINGS.createChild('idleDetection'); + r.SITE_SETTINGS_IDLE_DETECTION.hasMigratedToPlugin = true; r.SITE_SETTINGS_IMAGES = r.SITE_SETTINGS.createChild('images'); r.SITE_SETTINGS_MIXEDSCRIPT = r.SITE_SETTINGS.createChild('insecureContent'); r.SITE_SETTINGS_JAVASCRIPT = r.SITE_SETTINGS.createChild('javascript'); @@ -150,18 +155,22 @@ r.SITE_SETTINGS_SITE_DATA = r.SITE_SETTINGS.createChild('siteData'); r.SITE_SETTINGS_SITE_DATA.hasMigratedToPlugin = true; r.SITE_SETTINGS_VR = r.SITE_SETTINGS.createChild('vr'); + r.SITE_SETTINGS_VR.hasMigratedToPlugin = true; if (loadTimeData.getBoolean('enableExperimentalWebPlatformFeatures')) { r.SITE_SETTINGS_BLUETOOTH_SCANNING = r.SITE_SETTINGS.createChild('bluetoothScanning'); + r.SITE_SETTINGS_BLUETOOTH_SCANNING.hasMigratedToPlugin = true; } r.SITE_SETTINGS_WINDOW_MANAGEMENT = r.SITE_SETTINGS.createChild('windowManagement'); + r.SITE_SETTINGS_WINDOW_MANAGEMENT.hasMigratedToPlugin = true; r.SITE_SETTINGS_FILE_SYSTEM_WRITE = r.SITE_SETTINGS.createChild('filesystem'); r.SITE_SETTINGS_FILE_SYSTEM_WRITE_DETAILS = r.SITE_SETTINGS_FILE_SYSTEM_WRITE.createChild('siteDetails'); r.SITE_SETTINGS_LOCAL_FONTS = r.SITE_SETTINGS.createChild('localFonts'); r.SITE_SETTINGS_STORAGE_ACCESS = r.SITE_SETTINGS.createChild('storageAccess'); + r.SITE_SETTINGS_STORAGE_ACCESS.hasMigratedToPlugin = true; r.SITE_SETTINGS_AUTOMATIC_FULLSCREEN = r.SITE_SETTINGS.createChild('automaticFullScreen'); r.SITE_SETTINGS_AUTOMATIC_FULLSCREEN.hasMigratedToPlugin = true;
diff --git a/chrome/browser/resources/settings/site_settings/ar_page.html b/chrome/browser/resources/settings/site_settings/ar_page.html index cdc5845d..8dba1501 100644 --- a/chrome/browser/resources/settings/site_settings/ar_page.html +++ b/chrome/browser/resources/settings/site_settings/ar_page.html
@@ -1,19 +1,23 @@ - <style include="settings-shared site-settings-shared"></style> - <div class="content-settings-header secondary"> - $i18n{siteSettingsArDescription} - </div> - <!-- TODO(crbug.com/40176677): Fix redesign string when available.--> - <settings-category-default-radio-group - category="[[contentSettingsTypesEnum_.AR]]" - allow-option-label="$i18n{siteSettingsArAsk}" - allow-option-icon="privacy:cardboard" - block-option-label="$i18n{siteSettingsArBlock}" - block-option-icon="privacy:cardboard-off"> - </settings-category-default-radio-group> - <category-setting-exceptions - category="[[contentSettingsTypesEnum_.AR]]" - read-only-list - allow-header="$i18n{siteSettingsArAllowedExceptions}" - block-header="$i18n{siteSettingsArBlockedExceptions}" - search-filter="[[searchTerm]]"> - </category-setting-exceptions> +<style include="settings-shared site-settings-shared"></style> +<settings-subpage page-title="$i18n{siteSettingsAr}" + search-label="$i18n{siteSettingsAllSitesSearch}" + search-term="{{searchTerm}}"> + <div class="content-settings-header secondary"> + $i18n{siteSettingsArDescription} + </div> + <!-- TODO(crbug.com/40176677): Fix redesign string when available.--> + <settings-category-default-radio-group + category="[[contentSettingsTypesEnum_.AR]]" + allow-option-label="$i18n{siteSettingsArAsk}" + allow-option-icon="privacy:cardboard" + block-option-label="$i18n{siteSettingsArBlock}" + block-option-icon="privacy:cardboard-off"> + </settings-category-default-radio-group> + <category-setting-exceptions + category="[[contentSettingsTypesEnum_.AR]]" + read-only-list + allow-header="$i18n{siteSettingsArAllowedExceptions}" + block-header="$i18n{siteSettingsArBlockedExceptions}" + search-filter="[[searchTerm]]"> + </category-setting-exceptions> +</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/ar_page.ts b/chrome/browser/resources/settings/site_settings/ar_page.ts index 5950b8a..fb751c2 100644 --- a/chrome/browser/resources/settings/site_settings/ar_page.ts +++ b/chrome/browser/resources/settings/site_settings/ar_page.ts
@@ -4,16 +4,20 @@ import './settings_category_default_radio_group.js'; import './site_settings_shared.css.js'; +import '../settings_page/settings_subpage.js'; import '../settings_shared.css.js'; import './category_setting_exceptions.js'; import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js'; import {ContentSettingsTypes} from '../site_settings/constants.js'; import {getTemplate} from './ar_page.html.js'; -export class ArPageElement extends PolymerElement { +const ArPageElementBase = SettingsViewMixin(PolymerElement); + +export class ArPageElement extends ArPageElementBase { static get is() { return 'settings-ar-page'; } @@ -35,6 +39,11 @@ } declare searchTerm: string; + + // SettingsViewMixin implementation. + override focusBackButton() { + this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton(); + } } declare global {
diff --git a/chrome/browser/resources/settings/site_settings/auto_picture_in_picture_page.html b/chrome/browser/resources/settings/site_settings/auto_picture_in_picture_page.html index bff39e3..f7569fc 100644 --- a/chrome/browser/resources/settings/site_settings/auto_picture_in_picture_page.html +++ b/chrome/browser/resources/settings/site_settings/auto_picture_in_picture_page.html
@@ -1,4 +1,7 @@ - <style include="settings-shared site-settings-shared"></style> +<style include="settings-shared site-settings-shared"></style> +<settings-subpage page-title="$i18n{siteSettingsAutoPictureInPicture}" + search-label="$i18n{siteSettingsAllSitesSearch}" + search-term="{{searchTerm}}"> <div class="content-settings-header secondary"> $i18n{siteSettingsAutoPictureInPictureDescription} </div> @@ -18,3 +21,4 @@ "$i18n{siteSettingsAutoPictureInPictureBlockedExceptions}" search-filter="[[searchTerm]]"> </category-setting-exceptions> +</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/auto_picture_in_picture_page.ts b/chrome/browser/resources/settings/site_settings/auto_picture_in_picture_page.ts index 000f2e7..ecad7b9 100644 --- a/chrome/browser/resources/settings/site_settings/auto_picture_in_picture_page.ts +++ b/chrome/browser/resources/settings/site_settings/auto_picture_in_picture_page.ts
@@ -4,16 +4,21 @@ import './settings_category_default_radio_group.js'; import './site_settings_shared.css.js'; +import '../settings_page/settings_subpage.js'; import '../settings_shared.css.js'; import './category_setting_exceptions.js'; import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js'; import {ContentSettingsTypes} from '../site_settings/constants.js'; import {getTemplate} from './auto_picture_in_picture_page.html.js'; -export class AutoPictureInPicturePageElement extends PolymerElement { +const AutoPictureInPicturePageElementBase = SettingsViewMixin(PolymerElement); + +export class AutoPictureInPicturePageElement extends + AutoPictureInPicturePageElementBase { static get is() { return 'settings-auto-picture-in-picture-page'; } @@ -35,6 +40,11 @@ } declare searchTerm: string; + + // SettingsViewMixin implementation. + override focusBackButton() { + this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton(); + } } declare global {
diff --git a/chrome/browser/resources/settings/site_settings/bluetooth_scanning_page.html b/chrome/browser/resources/settings/site_settings/bluetooth_scanning_page.html index ac29f9b..5dc4f61 100644 --- a/chrome/browser/resources/settings/site_settings/bluetooth_scanning_page.html +++ b/chrome/browser/resources/settings/site_settings/bluetooth_scanning_page.html
@@ -1,21 +1,25 @@ - <style include="settings-shared site-settings-shared"></style> - <div class="content-settings-header secondary"> - $i18n{siteSettingsBluetoothScanningDescription} - </div> - <settings-category-default-radio-group - category="[[contentSettingsTypesEnum_.BLUETOOTH_SCANNING]]" - allow-option-label= - "$i18n{siteSettingsBluetoothScanningAsk}" - allow-option-icon="settings:bluetooth-scanning" - block-option-label="$i18n{siteSettingsBluetoothScanningBlock}" - block-option-icon="settings:bluetooth-off"> - </settings-category-default-radio-group> - <category-setting-exceptions - category="[[contentSettingsTypesEnum_.BLUETOOTH_SCANNING]]" - read-only-list - block-header= - "$i18n{siteSettingsBluetoothScanningBlockedExceptions}" - allow-header= - "$i18n{siteSettingsBluetoothScanningAllowedExceptions}" - search-filter="[[searchTerm]]"> - </category-setting-exceptions> +<style include="settings-shared site-settings-shared"></style> +<settings-subpage page-title="$i18n{siteSettingsBluetoothScanning}" + search-label="$i18n{siteSettingsAllSitesSearch}" + search-term="{{searchTerm}}"> + <div class="content-settings-header secondary"> + $i18n{siteSettingsBluetoothScanningDescription} + </div> + <settings-category-default-radio-group + category="[[contentSettingsTypesEnum_.BLUETOOTH_SCANNING]]" + allow-option-label= + "$i18n{siteSettingsBluetoothScanningAsk}" + allow-option-icon="settings:bluetooth-scanning" + block-option-label="$i18n{siteSettingsBluetoothScanningBlock}" + block-option-icon="settings:bluetooth-off"> + </settings-category-default-radio-group> + <category-setting-exceptions + category="[[contentSettingsTypesEnum_.BLUETOOTH_SCANNING]]" + read-only-list + block-header= + "$i18n{siteSettingsBluetoothScanningBlockedExceptions}" + allow-header= + "$i18n{siteSettingsBluetoothScanningAllowedExceptions}" + search-filter="[[searchTerm]]"> + </category-setting-exceptions> +</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/bluetooth_scanning_page.ts b/chrome/browser/resources/settings/site_settings/bluetooth_scanning_page.ts index aebdd1fb..3f911bc 100644 --- a/chrome/browser/resources/settings/site_settings/bluetooth_scanning_page.ts +++ b/chrome/browser/resources/settings/site_settings/bluetooth_scanning_page.ts
@@ -4,16 +4,21 @@ import './settings_category_default_radio_group.js'; import './site_settings_shared.css.js'; +import '../settings_page/settings_subpage.js'; import '../settings_shared.css.js'; import './category_setting_exceptions.js'; import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js'; import {ContentSettingsTypes} from '../site_settings/constants.js'; import {getTemplate} from './bluetooth_scanning_page.html.js'; -export class BluetoothScanningPageElement extends PolymerElement { +const BluetoothScanningPageElementBase = SettingsViewMixin(PolymerElement); + +export class BluetoothScanningPageElement extends + BluetoothScanningPageElementBase { static get is() { return 'settings-bluetooth-scanning-page'; } @@ -35,6 +40,11 @@ } declare searchTerm: string; + + // SettingsViewMixin implementation. + override focusBackButton() { + this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton(); + } } declare global {
diff --git a/chrome/browser/resources/settings/site_settings/captured_surface_control_page.html b/chrome/browser/resources/settings/site_settings/captured_surface_control_page.html index 86d15662..caf2d3e 100644 --- a/chrome/browser/resources/settings/site_settings/captured_surface_control_page.html +++ b/chrome/browser/resources/settings/site_settings/captured_surface_control_page.html
@@ -1,4 +1,7 @@ - <style include="settings-shared site-settings-shared"></style> +<style include="settings-shared site-settings-shared"></style> +<settings-subpage page-title="$i18n{siteSettingsCapturedSurfaceControl}" + search-label="$i18n{siteSettingsAllSitesSearch}" + search-term="{{searchTerm}}"> <div class="content-settings-header secondary"> $i18n{siteSettingsCapturedSurfaceControlDescription} </div> @@ -18,3 +21,4 @@ "$i18n{siteSettingsCapturedSurfaceControlBlockedExceptions}" search-filter="[[searchTerm]]"> </category-setting-exceptions> +</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/captured_surface_control_page.ts b/chrome/browser/resources/settings/site_settings/captured_surface_control_page.ts index 1db186f..95910ed 100644 --- a/chrome/browser/resources/settings/site_settings/captured_surface_control_page.ts +++ b/chrome/browser/resources/settings/site_settings/captured_surface_control_page.ts
@@ -4,16 +4,21 @@ import './settings_category_default_radio_group.js'; import './site_settings_shared.css.js'; +import '../settings_page/settings_subpage.js'; import '../settings_shared.css.js'; import './category_setting_exceptions.js'; import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js'; import {ContentSettingsTypes} from '../site_settings/constants.js'; import {getTemplate} from './captured_surface_control_page.html.js'; -export class CapturedSurfaceControlPageElement extends PolymerElement { +const CapturedSurfaceControlPageElementBase = SettingsViewMixin(PolymerElement); + +export class CapturedSurfaceControlPageElement extends + CapturedSurfaceControlPageElementBase { static get is() { return 'settings-captured-surface-control-page'; } @@ -35,6 +40,11 @@ } declare searchTerm: string; + + // SettingsViewMixin implementation. + override focusBackButton() { + this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton(); + } } declare global {
diff --git a/chrome/browser/resources/settings/site_settings/hand_tracking_page.html b/chrome/browser/resources/settings/site_settings/hand_tracking_page.html index 1677efc0..69e1c40 100644 --- a/chrome/browser/resources/settings/site_settings/hand_tracking_page.html +++ b/chrome/browser/resources/settings/site_settings/hand_tracking_page.html
@@ -1,4 +1,7 @@ - <style include="settings-shared site-settings-shared"></style> +<style include="settings-shared site-settings-shared"></style> +<settings-subpage page-title="$i18n{siteSettingsHandTracking}" + search-label="$i18n{siteSettingsAllSitesSearch}" + search-term="{{searchTerm}}"> <div class="content-settings-header secondary"> $i18n{siteSettingsHandTrackingDescription} </div> @@ -16,3 +19,4 @@ block-header="$i18n{siteSettingsHandTrackingBlockedExceptions}" search-filter="[[searchTerm]]"> </category-setting-exceptions> +</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/hand_tracking_page.ts b/chrome/browser/resources/settings/site_settings/hand_tracking_page.ts index 9d7c333..e307ea3c 100644 --- a/chrome/browser/resources/settings/site_settings/hand_tracking_page.ts +++ b/chrome/browser/resources/settings/site_settings/hand_tracking_page.ts
@@ -4,16 +4,20 @@ import './settings_category_default_radio_group.js'; import './site_settings_shared.css.js'; +import '../settings_page/settings_subpage.js'; import '../settings_shared.css.js'; import './category_setting_exceptions.js'; import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js'; import {ContentSettingsTypes} from '../site_settings/constants.js'; import {getTemplate} from './hand_tracking_page.html.js'; -export class HandTrackingPageElement extends PolymerElement { +const HandTrackingPageElementBase = SettingsViewMixin(PolymerElement); + +export class HandTrackingPageElement extends HandTrackingPageElementBase { static get is() { return 'settings-hand-tracking-page'; } @@ -35,6 +39,11 @@ } declare searchTerm: string; + + // SettingsViewMixin implementation. + override focusBackButton() { + this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton(); + } } declare global {
diff --git a/chrome/browser/resources/settings/site_settings/idle_detection_page.html b/chrome/browser/resources/settings/site_settings/idle_detection_page.html index 2855024..dbaba16 100644 --- a/chrome/browser/resources/settings/site_settings/idle_detection_page.html +++ b/chrome/browser/resources/settings/site_settings/idle_detection_page.html
@@ -1,4 +1,7 @@ - <style include="settings-shared site-settings-shared"></style> +<style include="settings-shared site-settings-shared"></style> +<settings-subpage page-title="$i18n{siteSettingsIdleDetection}" + search-label="$i18n{siteSettingsAllSitesSearch}" + search-term="{{searchTerm}}"> <div class="content-settings-header secondary"> $i18n{siteSettingsDeviceUseDescription} </div> @@ -15,3 +18,4 @@ block-header="$i18n{siteSettingsDeviceUseBlockedExceptions}" search-filter="[[searchTerm]]"> </category-setting-exceptions> +</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/idle_detection_page.ts b/chrome/browser/resources/settings/site_settings/idle_detection_page.ts index e2262f5a..b204b53 100644 --- a/chrome/browser/resources/settings/site_settings/idle_detection_page.ts +++ b/chrome/browser/resources/settings/site_settings/idle_detection_page.ts
@@ -4,16 +4,20 @@ import './settings_category_default_radio_group.js'; import './site_settings_shared.css.js'; +import '../settings_page/settings_subpage.js'; import '../settings_shared.css.js'; import './category_setting_exceptions.js'; import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js'; import {ContentSettingsTypes} from '../site_settings/constants.js'; import {getTemplate} from './idle_detection_page.html.js'; -export class IdleDetectionPageElement extends PolymerElement { +const IdleDetectionPageElementBase = SettingsViewMixin(PolymerElement); + +export class IdleDetectionPageElement extends IdleDetectionPageElementBase { static get is() { return 'settings-idle-detection-page'; } @@ -35,6 +39,11 @@ } declare searchTerm: string; + + // SettingsViewMixin implementation. + override focusBackButton() { + this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton(); + } } declare global {
diff --git a/chrome/browser/resources/settings/site_settings/local_fonts_page.html b/chrome/browser/resources/settings/site_settings/local_fonts_page.html index 859a2b84..af4b95e 100644 --- a/chrome/browser/resources/settings/site_settings/local_fonts_page.html +++ b/chrome/browser/resources/settings/site_settings/local_fonts_page.html
@@ -1,4 +1,7 @@ - <style include="settings-shared site-settings-shared"></style> +<style include="settings-shared site-settings-shared"></style> +<settings-subpage page-title="$i18n{fonts}" + search-label="$i18n{siteSettingsAllSitesSearch}" + search-term="{{searchTerm}}"> <div class="content-settings-header secondary"> $i18n{siteSettingsFontsDescription} </div> @@ -15,3 +18,4 @@ allow-header="$i18n{siteSettingsFontsAllowedExceptions}" search-filter="[[searchTerm]]"> </category-setting-exceptions> +</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/local_fonts_page.ts b/chrome/browser/resources/settings/site_settings/local_fonts_page.ts index 3c3928a..b7b8bed7 100644 --- a/chrome/browser/resources/settings/site_settings/local_fonts_page.ts +++ b/chrome/browser/resources/settings/site_settings/local_fonts_page.ts
@@ -4,16 +4,20 @@ import './settings_category_default_radio_group.js'; import './site_settings_shared.css.js'; +import '../settings_page/settings_subpage.js'; import '../settings_shared.css.js'; import './category_setting_exceptions.js'; import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js'; import {ContentSettingsTypes} from '../site_settings/constants.js'; import {getTemplate} from './local_fonts_page.html.js'; -export class LocalFontsPageElement extends PolymerElement { +const LocalFontsPageElementBase = SettingsViewMixin(PolymerElement); + +export class LocalFontsPageElement extends LocalFontsPageElementBase { static get is() { return 'settings-local-fonts-page'; } @@ -35,6 +39,11 @@ } declare searchTerm: string; + + // SettingsViewMixin implementation. + override focusBackButton() { + this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton(); + } } declare global {
diff --git a/chrome/browser/resources/settings/site_settings/storage_access_page.html b/chrome/browser/resources/settings/site_settings/storage_access_page.html index bb0de7b..0c5e18f 100644 --- a/chrome/browser/resources/settings/site_settings/storage_access_page.html +++ b/chrome/browser/resources/settings/site_settings/storage_access_page.html
@@ -1,4 +1,7 @@ - <style include="settings-shared site-settings-shared"></style> +<style include="settings-shared site-settings-shared"></style> +<settings-subpage page-title="$i18n{siteSettingsStorageAccess}" + search-label="$i18n{siteSettingsAllSitesSearch}" + search-term="{{searchTerm}}"> <div class="content-settings-header secondary"> $i18n{storageAccessDescription} </div> @@ -26,3 +29,4 @@ category-header="$i18n{storageAccessAllowedExceptions}" search-filter="[[searchTerm]]"> </storage-access-site-list> +</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/storage_access_page.ts b/chrome/browser/resources/settings/site_settings/storage_access_page.ts index 633bce2..207b911 100644 --- a/chrome/browser/resources/settings/site_settings/storage_access_page.ts +++ b/chrome/browser/resources/settings/site_settings/storage_access_page.ts
@@ -9,11 +9,14 @@ import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js'; import {ContentSetting, ContentSettingsTypes} from '../site_settings/constants.js'; import {getTemplate} from './storage_access_page.html.js'; -export class StorageAccessPageElement extends PolymerElement { +const StorageAccessPageElementBase = SettingsViewMixin(PolymerElement); + +export class StorageAccessPageElement extends StorageAccessPageElementBase { static get is() { return 'settings-storage-access-page'; } @@ -41,6 +44,11 @@ } declare searchTerm: string; + + // SettingsViewMixin implementation. + override focusBackButton() { + this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton(); + } } declare global {
diff --git a/chrome/browser/resources/settings/site_settings/vr_page.html b/chrome/browser/resources/settings/site_settings/vr_page.html index 71a7d791..c6aab02 100644 --- a/chrome/browser/resources/settings/site_settings/vr_page.html +++ b/chrome/browser/resources/settings/site_settings/vr_page.html
@@ -1,4 +1,7 @@ - <style include="settings-shared site-settings-shared"></style> +<style include="settings-shared site-settings-shared"></style> +<settings-subpage page-title="$i18n{siteSettingsVr}" + search-label="$i18n{siteSettingsAllSitesSearch}" + search-term="{{searchTerm}}"> <div class="content-settings-header secondary"> $i18n{siteSettingsVrDescription} </div> @@ -16,3 +19,4 @@ block-header="$i18n{siteSettingsVrBlockedExceptions}" search-filter="[[searchTerm]]"> </category-setting-exceptions> +</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/vr_page.ts b/chrome/browser/resources/settings/site_settings/vr_page.ts index 7e467b2..af449cf 100644 --- a/chrome/browser/resources/settings/site_settings/vr_page.ts +++ b/chrome/browser/resources/settings/site_settings/vr_page.ts
@@ -4,16 +4,20 @@ import './settings_category_default_radio_group.js'; import './site_settings_shared.css.js'; +import '../settings_page/settings_subpage.js'; import '../settings_shared.css.js'; import './category_setting_exceptions.js'; import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js'; import {ContentSettingsTypes} from '../site_settings/constants.js'; import {getTemplate} from './vr_page.html.js'; -export class VrPageElement extends PolymerElement { +const VrPageElementBase = SettingsViewMixin(PolymerElement); + +export class VrPageElement extends VrPageElementBase { static get is() { return 'settings-vr-page'; } @@ -35,6 +39,11 @@ } declare searchTerm: string; + + // SettingsViewMixin implementation. + override focusBackButton() { + this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton(); + } } declare global {
diff --git a/chrome/browser/resources/settings/site_settings/window_management_page.html b/chrome/browser/resources/settings/site_settings/window_management_page.html index 3eda5d8..d1b9f44b3 100644 --- a/chrome/browser/resources/settings/site_settings/window_management_page.html +++ b/chrome/browser/resources/settings/site_settings/window_management_page.html
@@ -1,4 +1,7 @@ - <style include="settings-shared site-settings-shared"></style> +<style include="settings-shared site-settings-shared"></style> +<settings-subpage page-title="$i18n{siteSettingsWindowManagement}" + search-label="$i18n{siteSettingsAllSitesSearch}" + search-term="{{searchTerm}}"> <div class="content-settings-header secondary"> $i18n{siteSettingsWindowManagementDescription} </div> @@ -16,3 +19,4 @@ "$i18n{siteSettingsWindowManagementBlockedExceptions}" search-filter="[[searchTerm]]"> </category-setting-exceptions> +</settings-subpage>
diff --git a/chrome/browser/resources/settings/site_settings/window_management_page.ts b/chrome/browser/resources/settings/site_settings/window_management_page.ts index 54f3a8a6..2b82e29a3 100644 --- a/chrome/browser/resources/settings/site_settings/window_management_page.ts +++ b/chrome/browser/resources/settings/site_settings/window_management_page.ts
@@ -4,16 +4,21 @@ import './settings_category_default_radio_group.js'; import './site_settings_shared.css.js'; +import '../settings_page/settings_subpage.js'; import '../settings_shared.css.js'; import './category_setting_exceptions.js'; import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; +import {SettingsViewMixin} from '../settings_page/settings_view_mixin.js'; import {ContentSettingsTypes} from '../site_settings/constants.js'; import {getTemplate} from './window_management_page.html.js'; -export class WindowManagementPageElement extends PolymerElement { +const WindowManagementPageElementBase = SettingsViewMixin(PolymerElement); + +export class WindowManagementPageElement extends + WindowManagementPageElementBase { static get is() { return 'settings-window-management-page'; } @@ -35,6 +40,11 @@ } declare searchTerm: string; + + // SettingsViewMixin implementation. + override focusBackButton() { + this.shadowRoot!.querySelector('settings-subpage')!.focusBackButton(); + } } declare global {
diff --git a/chrome/browser/resources/side_panel/read_anything/content_controller.ts b/chrome/browser/resources/side_panel/read_anything/content_controller.ts index c74c1314..08a5084c 100644 --- a/chrome/browser/resources/side_panel/read_anything/content_controller.ts +++ b/chrome/browser/resources/side_panel/read_anything/content_controller.ts
@@ -117,20 +117,22 @@ } private createTextNode_(nodeId: number): Node { - // When creating text nodes, save the first text node id. We need this - // node id to call InitAXPosition in playSpeech. If it's not saved here, - // we have to retrieve it through a DOM search such as createTreeWalker, - // which can be computationally expensive. - if (chrome.readingMode.isReadAloudEnabled) { - this.speechController_.initializeSpeechTree(nodeId); - } - const textContent = chrome.readingMode.getTextContent(nodeId); const textNode = document.createTextNode(textContent); this.nodeStore_.setDomNode(textNode, nodeId); const isOverline = chrome.readingMode.isOverline(nodeId); const shouldBold = chrome.readingMode.shouldBold(nodeId); + // When creating text nodes, save the first text node id. We need this + // node id to call InitAXPosition in playSpeech. If it's not saved here, + // we have to retrieve it through a DOM search such as createTreeWalker, + // which can be computationally expensive. + // This needs to be done after the text node is created and added to the + // node store. + if (chrome.readingMode.isReadAloudEnabled) { + this.speechController_.initializeSpeechTree(nodeId); + } + if (!shouldBold && !isOverline) { return textNode; }
diff --git a/chrome/browser/resources/side_panel/read_anything/node_store.ts b/chrome/browser/resources/side_panel/read_anything/node_store.ts index 4e88596..1fc2431 100644 --- a/chrome/browser/resources/side_panel/read_anything/node_store.ts +++ b/chrome/browser/resources/side_panel/read_anything/node_store.ts
@@ -6,7 +6,6 @@ import {getWordCount, isRectMostlyVisible} from './common.js'; import type {ReadAloudNode} from './read_aloud/read_aloud_types.js'; -import {AxReadAloudNode} from './read_aloud/read_aloud_types.js'; // A two-way map where each key is unique and each value is unique. The keys are // DOM nodes and the values are numbers, representing AXNodeIDs. @@ -181,9 +180,7 @@ } hasAnyNode(nodes: ReadAloudNode[]): boolean { - return nodes.some( - node => node instanceof AxReadAloudNode && - this.getDomNode(node.axNodeId) !== undefined); + return nodes.some(node => node && node.domNode() !== undefined); } getDomNode(axNodeId: number): Node|undefined { @@ -221,9 +218,11 @@ // TODO: crbug.com/440400392- Handle hidden image node ids for read aloud // when non-AXNode-based read aloud nodes are used. areNodesAllHidden(nodes: ReadAloudNode[]): boolean { - return nodes.every( - node => node instanceof AxReadAloudNode && - this.hiddenImageNodesIds_.has(node.axNodeId)); + return nodes.every(node => { + const domNode = node && node.domNode(); + const id = domNode && this.getAxId(domNode); + return !!id && this.hiddenImageNodesIds_.has(id); + }); } addImageToFetch(nodeId: number): void {
diff --git a/chrome/browser/resources/side_panel/read_anything/read_aloud/highlighter.ts b/chrome/browser/resources/side_panel/read_anything/read_aloud/highlighter.ts index b1be79e..192043e6 100644 --- a/chrome/browser/resources/side_panel/read_anything/read_aloud/highlighter.ts +++ b/chrome/browser/resources/side_panel/read_anything/read_aloud/highlighter.ts
@@ -9,7 +9,6 @@ import {getReadAloudModel} from './read_aloud_model_browser_proxy.js'; import type {ReadAloudModelBrowserProxy} from './read_aloud_model_browser_proxy.js'; -import {AxReadAloudNode} from './read_aloud_types.js'; import type {Segment} from './read_aloud_types.js'; import {getCurrentSpeechRate, isInvalidHighlightForWordHighlighting} from './speech_presentation_rules.js'; import {VoiceLanguageController} from './voice_language_controller.js'; @@ -242,12 +241,7 @@ let didApplyHighlight = false; for (const segment of highlightSegments) { const {node, start, length: segmentLength} = segment; - if (!(node instanceof AxReadAloudNode)) { - continue; - } - const nodeId = node.axNodeId; - - const domNode = this.nodeStore_.getDomNode(nodeId); + const domNode = node.domNode(); if (!domNode) { continue; } @@ -298,10 +292,7 @@ this.resetPreviousHighlight(); for (const {node, start, length} of segments) { - if (!(node instanceof AxReadAloudNode)) { - continue; - } - const element = this.nodeStore_.getDomNode(node.axNodeId) as HTMLElement; + const element = node.domNode() as HTMLElement; if (!element) { continue; }
diff --git a/chrome/browser/resources/side_panel/read_anything/read_aloud/read_aloud_model_browser_proxy.ts b/chrome/browser/resources/side_panel/read_anything/read_aloud/read_aloud_model_browser_proxy.ts index 7ee738c..ab3fc54 100644 --- a/chrome/browser/resources/side_panel/read_anything/read_aloud/read_aloud_model_browser_proxy.ts +++ b/chrome/browser/resources/side_panel/read_anything/read_aloud/read_aloud_model_browser_proxy.ts
@@ -34,7 +34,7 @@ // Handle initialization. isInitialized(): boolean; - init(context: ReadAloudNode|string): void; + init(context: ReadAloudNode): void; } export function getReadAloudModel(): ReadAloudModelBrowserProxy {
diff --git a/chrome/browser/resources/side_panel/read_anything/read_aloud/read_aloud_types.ts b/chrome/browser/resources/side_panel/read_anything/read_aloud/read_aloud_types.ts index 765d4dcc..8df1c46 100644 --- a/chrome/browser/resources/side_panel/read_anything/read_aloud/read_aloud_types.ts +++ b/chrome/browser/resources/side_panel/read_anything/read_aloud/read_aloud_types.ts
@@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import {NodeStore} from '../node_store.js'; + // This file contains type definitions for the data structures // used in the read aloud feature. It provides an abstraction layer for // representing text nodes and segments, allowing the core logic to work @@ -12,10 +14,28 @@ // segmentation method is used. export abstract class ReadAloudNode { abstract equals(other: ReadAloudNode|undefined|null): boolean; + + abstract domNode(): Node|undefined; + + static create(node: Node, nodeStore = NodeStore.getInstance()): ReadAloudNode + |undefined { + if (chrome.readingMode.isTsTextSegmentationEnabled) { + return new DomReadAloudNode(node); + } + + const axNodeId = nodeStore.getAxId(node); + if (axNodeId) { + return new AxReadAloudNode(axNodeId, nodeStore); + } + + return undefined; + } } export class AxReadAloudNode extends ReadAloudNode { - constructor(public readonly axNodeId: number) { + constructor( + public readonly axNodeId: number, + private readonly nodeStore_ = NodeStore.getInstance()) { super(); } @@ -26,8 +46,35 @@ return this.axNodeId === other.axNodeId; } + + domNode(): Node|undefined { + return this.nodeStore_.getDomNode(this.axNodeId); + } } +// Represents a node used by read aloud that's based entirely on the DOM. +export class DomReadAloudNode extends ReadAloudNode { + constructor(public readonly node: Node) { + super(); + } + + equals(other: ReadAloudNode|undefined|null): boolean { + if (!(other instanceof DomReadAloudNode)) { + return false; + } + return this.node.isEqualNode(other.node); + } + + getText(): string { + return this.node.textContent || ''; + } + + domNode(): Node|undefined { + return this.node; + } +} + + // A segment of text within a single node. export interface Segment { node: ReadAloudNode;
diff --git a/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_controller.ts b/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_controller.ts index 679f2ee2..79d5adc 100644 --- a/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_controller.ts +++ b/chrome/browser/resources/side_panel/read_anything/read_aloud/speech_controller.ts
@@ -13,8 +13,8 @@ import {ReadAloudHighlighter} from './highlighter.js'; import {getReadAloudModel} from './read_aloud_model_browser_proxy.js'; import type {ReadAloudModelBrowserProxy} from './read_aloud_model_browser_proxy.js'; -import {AxReadAloudNode} from './read_aloud_types.js'; -import type {ReadAloudNode, Segment} from './read_aloud_types.js'; +import {ReadAloudNode} from './read_aloud_types.js'; +import type {Segment} from './read_aloud_types.js'; import {PauseActionSource, SpeechEngineState, SpeechModel} from './speech_model.js'; import type {SpeechPlayingState} from './speech_model.js'; import {getCurrentSpeechRate, isInvalidHighlightForWordHighlighting} from './speech_presentation_rules.js'; @@ -177,7 +177,15 @@ // TODO: crbug.com/40927698 - This step should be skipped on migrating to // a non-AXPosition-based text segmentation strategy. - this.readAloudModel_.init(new AxReadAloudNode(firstTextNode)); + const domNode = this.nodeStore_.getDomNode(firstTextNode); + if (!domNode) { + return; + } + const readAloudNode = ReadAloudNode.create(domNode); + if (!readAloudNode) { + return; + } + this.readAloudModel_.init(readAloudNode); } onSelectionChange() { @@ -404,8 +412,15 @@ // Iterate through the nodes asynchronously so that we can show the spinner // in the toolbar while we move up to the selection. setTimeout(() => { - this.movePlaybackToNode_( - new AxReadAloudNode(startingNodeId), startingOffset); + const domNode = this.nodeStore_.getDomNode(startingNodeId); + if (!domNode) { + return; + } + const readAloudNode = ReadAloudNode.create(domNode); + if (!readAloudNode) { + return; + } + this.movePlaybackToNode_(readAloudNode, startingOffset); // Set everything to previous and then play the next granularity, which // includes the selection. this.highlighter_.resetPreviousHighlight(); @@ -830,8 +845,7 @@ } const lastNode = lastPosition.node; - if (lastNode instanceof AxReadAloudNode && - this.nodeStore_.getDomNode(lastNode.axNodeId)) { + if (lastNode.domNode()) { this.movePlaybackToNode_(lastNode, lastPosition.offset); this.setState_(savedSpeechPlayingState); this.wordBoundaries_.state = savedWordBoundaryState;
diff --git a/chrome/browser/resources/webui_browser/BUILD.gn b/chrome/browser/resources/webui_browser/BUILD.gn index 197f9eb0..99b66be 100644 --- a/chrome/browser/resources/webui_browser/BUILD.gn +++ b/chrome/browser/resources/webui_browser/BUILD.gn
@@ -21,6 +21,7 @@ "browser_proxy.ts", "content_region.ts", "icons.html.ts", + "side_panel.html.ts", "side_panel.ts", "tab_element.ts", "tab_element.html.ts", @@ -35,6 +36,7 @@ "app.css", "bookmark_element.css", "content_region.css", + "side_panel.css", "tab_element.css", "tab_strip.css", "webview.css",
diff --git a/chrome/browser/resources/webui_browser/app.css b/chrome/browser/resources/webui_browser/app.css index 4b89b69a..4d7164e 100644 --- a/chrome/browser/resources/webui_browser/app.css +++ b/chrome/browser/resources/webui_browser/app.css
@@ -137,3 +137,11 @@ content-region { border-top: 1px var(--color-toolbar-separator) solid; } + +content-region[showing-side-panel] { + border-top: 1px var(--color-toolbar-separator) solid; + border-right: 1px var(--color-toolbar-separator) solid; + border-left: none; + border-bottom: none; + border-radius: 0 16px 0 0; +} \ No newline at end of file
diff --git a/chrome/browser/resources/webui_browser/app.html.ts b/chrome/browser/resources/webui_browser/app.html.ts index 29c5298..2e12f252 100644 --- a/chrome/browser/resources/webui_browser/app.html.ts +++ b/chrome/browser/resources/webui_browser/app.html.ts
@@ -58,9 +58,11 @@ </webui-browser-bookmark-bar> </div> <div id="main"> - <content-region id="contentRegion""> + <content-region id="contentRegion" + ?showing-side-panel="${this.showingSidePanel_}"> </content-region> - <side-panel id="sidePanel"></side-panel> + <side-panel id="sidePanel" @side-panel-closed="${this.onSidePanelClosed_}"> + </side-panel> </div> </div>
diff --git a/chrome/browser/resources/webui_browser/app.ts b/chrome/browser/resources/webui_browser/app.ts index cc59d665..9a97092e 100644 --- a/chrome/browser/resources/webui_browser/app.ts +++ b/chrome/browser/resources/webui_browser/app.ts
@@ -57,6 +57,7 @@ return { backButtonDisabled_: {state: true, type: Boolean}, forwardButtonDisabled_: {state: true, type: Boolean}, + showingSidePanel_: {state: true, type: Boolean}, reloadOrStopIcon_: {state: true, type: String}, }; } @@ -67,6 +68,7 @@ protected accessor backButtonDisabled_: boolean = true; protected accessor forwardButtonDisabled_: boolean = true; protected accessor reloadOrStopIcon_: string = 'icon-refresh'; + protected accessor showingSidePanel_: boolean = false; constructor() { super(); @@ -78,6 +80,7 @@ const callbackRouter = BrowserProxy.getCallbackRouter(); callbackRouter.showSidePanel.addListener(this.showSidePanel_.bind(this)); + callbackRouter.closeSidePanel.addListener(this.closeSidePanel_.bind(this)); } override connectedCallback() { @@ -247,8 +250,21 @@ this.reloadOrStopIcon_ = isLoading ? 'icon-clear' : 'icon-refresh'; } - protected showSidePanel_(guestContentsId: number) { - this.$.sidePanel.show(guestContentsId); + + protected showSidePanel_(guestContentsId: number, title: string) { + this.showingSidePanel_ = true; + this.$.sidePanel.show(guestContentsId, title); + } + + protected closeSidePanel_() { + this.$.sidePanel.close(); + this.showingSidePanel_ = false; + } + + // This function is called when the side panel closes itself. For example, + // when user clicks the close "x" button. + protected onSidePanelClosed_() { + this.showingSidePanel_ = false; } }
diff --git a/chrome/browser/resources/webui_browser/side_panel.css b/chrome/browser/resources/webui_browser/side_panel.css new file mode 100644 index 0000000..fa7b559 --- /dev/null +++ b/chrome/browser/resources/webui_browser/side_panel.css
@@ -0,0 +1,42 @@ +/* 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. */ + +/* #css_wrapper_metadata_start + * #type=style-lit + * #scheme=relative + * #css_wrapper_metadata_end */ + +#frame { + display: flex; + flex-direction: column; + height: 100%; + background-color: var(--color-toolbar); + padding: 8px; + box-sizing: border-box; +} + +#header { + display: flex; + align-items: center; + padding: 0 10px; +} + +#header h2 { + font-size: 14px; + font-weight: 500; + line-height: 20px; +} + +#closeButton { + --cr-icon-button-icon-size: 16px; + /* Put button at the header tail */ + margin-left: auto; +} + +#content { + flex: 1; + border-radius: 16px; + border: 1px var(--color-side-panel-content-area-separator) solid; + overflow: hidden; +}
diff --git a/chrome/browser/resources/webui_browser/side_panel.html.ts b/chrome/browser/resources/webui_browser/side_panel.html.ts new file mode 100644 index 0000000..407eb04b --- /dev/null +++ b/chrome/browser/resources/webui_browser/side_panel.html.ts
@@ -0,0 +1,31 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import '//resources/cr_elements/cr_icon_button/cr_icon_button.js'; + +import {html, nothing} from 'chrome://resources/lit/v3_0/lit.rollup.js'; + +import type {SidePanel} from './side_panel.js'; + +export function getHtml(this: SidePanel) { + if (!this.showing_) { + return nothing; + } + + // clang-format off + return html`<!--_html_template_start_--> +<div id="frame"> + <div id="header"> + <h2>${this.title_}</h2> + <cr-icon-button + id="closeButton" + iron-icon="cr:clear" + @click="${this.close}"> + </cr-icon-button> + </div> + <div id="content">${this.webView}</div> +</div> +<!--_html_template_end_-->`; + // clang-format on +}
diff --git a/chrome/browser/resources/webui_browser/side_panel.ts b/chrome/browser/resources/webui_browser/side_panel.ts index 40f799a..05be51c 100644 --- a/chrome/browser/resources/webui_browser/side_panel.ts +++ b/chrome/browser/resources/webui_browser/side_panel.ts
@@ -2,8 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import {CrLitElement, nothing} from '//resources/lit/v3_0/lit.rollup.js'; +import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js'; +import {BrowserProxy} from './browser_proxy.js'; +import {getCss} from './side_panel.css.js'; +import {getHtml} from './side_panel.html.js'; import {WebviewElement} from './webview.js'; export class SidePanel extends CrLitElement { @@ -11,24 +14,42 @@ return 'side-panel'; } - private webView_?: WebviewElement; - private showing_: boolean = false; - - override render() { - if (!this.showing_) { - return nothing; - } - - return this.webView_; + static override get styles() { + return getCss(); } - show(guestContentsId: number) { - this.webView_ = new WebviewElement(); - this.webView_.guestId = guestContentsId; + override render() { + return getHtml.bind(this)(); + } + + static override get properties() { + return { + showing_: {state: true, type: Boolean}, + title_: {state: true, type: String}, + webView: {state: true, type: Object}, + }; + } + + accessor webView: WebviewElement|null = null; + protected accessor title_: string = ''; + protected accessor showing_: boolean = false; + + show(guestContentsId: number, title: string) { + this.webView = new WebviewElement(); + this.webView.guestId = guestContentsId; + this.title_ = title; this.showing_ = true; this.requestUpdate(); } + async close() { + this.showing_ = false; + this.webView = null; + this.dispatchEvent(new Event('side-panel-closed', {bubbles: true})); + await this.updateComplete; + BrowserProxy.getPageHandler().onSidePanelClosed(); + } + hide() { this.showing_ = false; }
diff --git a/chrome/browser/safe_browsing/android/password_reuse_controller_android.cc b/chrome/browser/safe_browsing/android/password_reuse_controller_android.cc index a29027d..5e9559b 100644 --- a/chrome/browser/safe_browsing/android/password_reuse_controller_android.cc +++ b/chrome/browser/safe_browsing/android/password_reuse_controller_android.cc
@@ -87,16 +87,6 @@ return l10n_util::GetStringUTF16(IDS_CLOSE); } - // This is a rare corner-case. It can only occur for users with no GMS Core, - // outdated GMS Core, or users who have failed automatic password migration - // to GMS Core. In addition, for this case to occur the user has to have - // entered a password on a phishing website in the exact time interval in - // which Chrome was exporting passwods to an internally-stored CSV or - // in-between export tries if the first attempt failed. - if (!password_manager_android_util::LoginDbDeprecationReady(pref_service_)) { - return l10n_util::GetStringUTF16(IDS_CLOSE); - } - if (password_type_.account_type() == ReusedPasswordAccountType::SAVED_PASSWORD) { return l10n_util::GetStringUTF16(IDS_PAGE_INFO_CHECK_PASSWORDS_BUTTON); @@ -117,10 +107,6 @@ return std::u16string(); } - if (!password_manager_android_util::LoginDbDeprecationReady(pref_service_)) { - return std::u16string(); - } - if (password_type_.account_type() == ReusedPasswordAccountType::SAVED_PASSWORD) { return l10n_util::GetStringUTF16(
diff --git a/chrome/browser/safe_browsing/android/password_reuse_controller_android_unittest.cc b/chrome/browser/safe_browsing/android/password_reuse_controller_android_unittest.cc index 4c7b0753..db5aecdc 100644 --- a/chrome/browser/safe_browsing/android/password_reuse_controller_android_unittest.cc +++ b/chrome/browser/safe_browsing/android/password_reuse_controller_android_unittest.cc
@@ -13,7 +13,6 @@ #include "chrome/browser/safe_browsing/chrome_password_protection_service.h" #include "chrome/test/base/chrome_render_view_host_test_harness.h" #include "components/password_manager/core/browser/features/password_features.h" -#include "components/password_manager/core/browser/split_stores_and_local_upm.h" #include "components/prefs/pref_service.h" #include "components/prefs/testing_pref_service.h" #include "components/strings/grit/components_strings.h" @@ -132,80 +131,12 @@ delete controller; } -TEST_F(PasswordReuseControllerAndroidTest, - VerifyButtonTextLoginDbExportNotDone) { - // Password export is only relevant if UPM is not already active. - password_manager::SetLegacySplitStoresPrefForTest(profile()->GetPrefs(), - false); - profile()->GetPrefs()->SetBoolean( - password_manager::prefs::kUpmUnmigratedPasswordsExported, false); - - MockOnWarningDone empty_callback; - ReusedPasswordAccountType password_type; - - PasswordReuseControllerAndroid* controller = - MakeController(nullptr, password_type, empty_callback.Get()); - - { - ASSERT_EQ(l10n_util::GetStringUTF16(IDS_CLOSE), - controller->GetPrimaryButtonText()); - ASSERT_EQ(std::u16string(), controller->GetSecondaryButtonText()); - } - { - password_type.set_account_type(ReusedPasswordAccountType::SAVED_PASSWORD); - password_type.set_is_account_syncing(false); - - controller->SetReusedPasswordAccountTypeForTesting(password_type); - - ASSERT_EQ(l10n_util::GetStringUTF16(IDS_CLOSE), - controller->GetPrimaryButtonText()); - ASSERT_EQ(std::u16string(), controller->GetSecondaryButtonText()); - } - { - password_type.set_account_type(ReusedPasswordAccountType::GMAIL); - password_type.set_is_account_syncing(true); - - controller->SetReusedPasswordAccountTypeForTesting(password_type); - - ASSERT_EQ(l10n_util::GetStringUTF16(IDS_PAGE_INFO_PROTECT_ACCOUNT_BUTTON), - controller->GetPrimaryButtonText()); - ASSERT_EQ( - l10n_util::GetStringUTF16(IDS_PAGE_INFO_IGNORE_PASSWORD_WARNING_BUTTON), - controller->GetSecondaryButtonText()); - } - { - ReusedPasswordAccountType empty_reused_password; - controller->SetReusedPasswordAccountTypeForTesting(empty_reused_password); - - ASSERT_EQ(l10n_util::GetStringUTF16(IDS_CLOSE), - controller->GetPrimaryButtonText()); - ASSERT_EQ(std::u16string(), controller->GetSecondaryButtonText()); - } - { - password_type.set_account_type(ReusedPasswordAccountType::GMAIL); - password_type.set_is_account_syncing(false); - - controller->SetReusedPasswordAccountTypeForTesting(password_type); - - ASSERT_EQ(l10n_util::GetStringUTF16(IDS_CLOSE), - controller->GetPrimaryButtonText()); - ASSERT_EQ(std::u16string(), controller->GetSecondaryButtonText()); - } - - delete controller; -} - -TEST_F(PasswordReuseControllerAndroidTest, - VerifyButtonTextLoginDbDeprecationUPMActive) { +TEST_F(PasswordReuseControllerAndroidTest, VerifyButtonText) { // Skipping on automotive, since the regular button text for // SAVED_PASSWORD does not apply there. if (base::android::device_info::is_automotive()) { GTEST_SKIP() << "This test should not run on automotive."; } - password_manager::SetLegacySplitStoresPrefForTest(profile()->GetPrefs(), - true); - profile()->GetPrefs()->SetBoolean( - password_manager::prefs::kUpmUnmigratedPasswordsExported, false); MockOnWarningDone empty_callback; ReusedPasswordAccountType password_type;
diff --git a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckCoordinator.java b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckCoordinator.java index 40fe866d..7d5a7d16 100644 --- a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckCoordinator.java +++ b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckCoordinator.java
@@ -22,7 +22,6 @@ import org.chromium.chrome.browser.password_manager.PasswordStoreBridge; import org.chromium.chrome.browser.pwd_check_wrapper.PasswordCheckControllerFactory; import org.chromium.components.browser_ui.settings.SettingsCustomTabLauncher; -import org.chromium.components.prefs.PrefService; import org.chromium.components.signin.base.CoreAccountInfo; import org.chromium.components.sync.SyncService; import org.chromium.ui.modaldialog.ModalDialogManager; @@ -62,7 +61,6 @@ SafetyCheckBridge bridge, ObservableSupplier<@Nullable ModalDialogManager> modalDialogManagerSupplier, @Nullable SyncService syncService, - PrefService prefService, PasswordStoreBridge passwordStoreBridge, PasswordManagerHelper passwordManagerHelper, SettingsCustomTabLauncher settingsCustomTabLauncher) { @@ -72,7 +70,6 @@ bridge, modalDialogManagerSupplier, syncService, - prefService, passwordStoreBridge, passwordManagerHelper, settingsCustomTabLauncher); @@ -84,7 +81,6 @@ SafetyCheckBridge bridge, ObservableSupplier<@Nullable ModalDialogManager> modalDialogManagerSupplier, @Nullable SyncService syncService, - PrefService prefService, PasswordStoreBridge passwordStoreBridge, PasswordManagerHelper passwordManagerHelper, SettingsCustomTabLauncher settingsCustomTabLauncher) { @@ -128,7 +124,6 @@ mUpdatesClient, bridge, syncService, - prefService, new Handler(), passwordStoreBridge, new PasswordCheckControllerFactory(),
diff --git a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediator.java b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediator.java index 63a8a60..a9d799d 100644 --- a/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediator.java +++ b/chrome/browser/safety_check/android/java/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediator.java
@@ -44,7 +44,6 @@ import org.chromium.chrome.browser.safety_check.SafetyCheckProperties.UpdatesState; import org.chromium.chrome.browser.settings.SettingsNavigationFactory; import org.chromium.components.browser_ui.settings.SettingsCustomTabLauncher; -import org.chromium.components.prefs.PrefService; import org.chromium.components.sync.SyncService; import org.chromium.content_public.common.ContentUrlConstants; import org.chromium.ui.modaldialog.ModalDialogManager; @@ -190,7 +189,6 @@ SafetyCheckUpdatesDelegate client, SafetyCheckBridge bridge, @Nullable SyncService syncService, - PrefService prefService, Handler handler, PasswordStoreBridge passwordStoreBridge, PasswordCheckControllerFactory passwordCheckControllerFactory, @@ -207,7 +205,7 @@ mPreferenceManager = ChromeSharedPreferences.getInstance(); mPasswordCheckController = passwordCheckControllerFactory.create( - syncService, prefService, passwordStoreBridge, passwordManagerHelper); + syncService, passwordStoreBridge, passwordManagerHelper); mPasswordManagerHelper = passwordManagerHelper; mModalDialogManagerSupplier = modalDialogManagerSupplier; mSettingsCustomTabLauncher = settingsCustomTabLauncher;
diff --git a/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediatorTest.java b/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediatorTest.java index ae7d86a..6493d6f 100644 --- a/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediatorTest.java +++ b/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckMediatorTest.java
@@ -72,12 +72,9 @@ import org.chromium.chrome.browser.sync.SyncServiceFactory; import org.chromium.components.browser_ui.settings.SettingsCustomTabLauncher; import org.chromium.components.browser_ui.settings.SettingsNavigation; -import org.chromium.components.prefs.PrefService; import org.chromium.components.signin.base.CoreAccountInfo; import org.chromium.components.sync.SyncService; import org.chromium.components.sync.UserSelectableType; -import org.chromium.components.user_prefs.UserPrefs; -import org.chromium.components.user_prefs.UserPrefsJni; import org.chromium.content_public.browser.BrowserContextHandle; import org.chromium.google_apis.gaia.GaiaId; import org.chromium.ui.modaldialog.ModalDialogManager; @@ -122,8 +119,6 @@ @Mock private PasswordCheckupClientHelper mPasswordCheckupHelper; @Mock private CredentialManagerLauncher mCredentialManagerLauncher; @Mock private PasswordStoreBridge mPasswordStoreBridge; - @Mock private PrefService mPrefService; - @Mock private UserPrefs.Natives mUserPrefsJniMock; // TODO(crbug.com/40854050): Use fake instead of mocking @Mock private PasswordManagerBackendSupportHelper mBackendSupportHelperMock; @@ -210,7 +205,6 @@ mUpdatesDelegate, new SafetyCheckBridge(mProfile), mSyncService, - mPrefService, mHandler, mPasswordStoreBridge, mPasswordCheckControllerFactory, @@ -243,14 +237,11 @@ SafetyCheckBridgeJni.setInstanceForTesting(mSafetyCheckBridge); - UserPrefsJni.setInstanceForTesting(mUserPrefsJniMock); - when(mUserPrefsJniMock.get(mProfile)).thenReturn(mPrefService); - mSafetyCheckModel = SafetyCheckProperties.createSafetyCheckModel(); mPasswordCheckModel = PasswordsCheckPreferenceProperties.createPasswordSafetyCheckModel("Passwords"); mPasswordCheckControllerFactory = new FakePasswordCheckControllerFactory(); - when(mPasswordManagerUtilBridgeNativeMock.isPasswordManagerAvailable(mPrefService, true)) + when(mPasswordManagerUtilBridgeNativeMock.isPasswordManagerAvailable(true)) .thenReturn(mUseGmsApi); // TODO(crbug.com/40854050): Use existing fake instead of mocking PasswordCheckupClientHelperFactory mockPasswordCheckFactory = @@ -692,6 +683,7 @@ @Test public void testClickListenerLeadsToUPMAccountPasswordCheckup() { // Order: initial state -> safety check triggered -> check done -> load completed. + LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(false); mMediator.setInitialState(); assertEquals(PasswordsState.CHECKING, mPasswordCheckModel.get(PASSWORDS_STATE)); @@ -748,6 +740,7 @@ @Test public void testClickListenerLeadsToUPMLocalPasswordCheckup() { + LoginDbDeprecationUtilBridge.setHasCsvFileForTesting(false); PropertyModel passwordCheckLocalModel = PasswordsCheckPreferenceProperties.createPasswordSafetyCheckModel("Passwords"); mMediator =
diff --git a/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckSettingsFragmentTest.java b/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckSettingsFragmentTest.java index aba2711..df799b1 100644 --- a/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckSettingsFragmentTest.java +++ b/chrome/browser/safety_check/android/javatests/src/org/chromium/chrome/browser/safety_check/SafetyCheckSettingsFragmentTest.java
@@ -6,8 +6,6 @@ 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.mockito.Mockito.when; import android.content.Context; @@ -44,7 +42,6 @@ import org.chromium.chrome.browser.settings.SettingsActivityTestRule; import org.chromium.chrome.browser.sync.SyncServiceFactory; import org.chromium.chrome.test.ChromeJUnit4ClassRunner; -import org.chromium.components.prefs.PrefService; import org.chromium.components.signin.base.CoreAccountInfo; import org.chromium.components.sync.SyncService; import org.chromium.components.sync.UserSelectableType; @@ -186,8 +183,7 @@ } private void configurePasswordManagerUtilBridge() { - when(mPasswordManagerUtilBridgeNativeMock.isPasswordManagerAvailable( - any(PrefService.class), eq(true))) + when(mPasswordManagerUtilBridgeNativeMock.isPasswordManagerAvailable(true)) .thenReturn(true); }
diff --git a/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubModuleDelegateImpl.java b/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubModuleDelegateImpl.java index b1b685c3..01d11cc 100644 --- a/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubModuleDelegateImpl.java +++ b/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubModuleDelegateImpl.java
@@ -25,7 +25,6 @@ import org.chromium.components.browser_ui.settings.SettingsCustomTabLauncher; import org.chromium.components.signin.metrics.SigninAccessPoint; import org.chromium.components.sync.SyncService; -import org.chromium.components.user_prefs.UserPrefs; import org.chromium.content_public.common.ContentUrlConstants; import org.chromium.ui.modaldialog.ModalDialogManager; @@ -94,7 +93,7 @@ return INVALID_PASSWORD_COUNT; } - if (PasswordManagerUtilBridge.isPasswordManagerAvailable(UserPrefs.get(mProfile))) { + if (PasswordManagerUtilBridge.isPasswordManagerAvailable()) { return passwordStoreBridge.getPasswordStoreCredentialsCountForAccountStore(); } return INVALID_PASSWORD_COUNT; @@ -106,7 +105,7 @@ return INVALID_PASSWORD_COUNT; } - if (PasswordManagerUtilBridge.isPasswordManagerAvailable(UserPrefs.get(mProfile))) { + if (PasswordManagerUtilBridge.isPasswordManagerAvailable()) { return passwordStoreBridge.getPasswordStoreCredentialsCountForProfileStore(); } return INVALID_PASSWORD_COUNT;
diff --git a/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubPasswordsFetchService.java b/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubPasswordsFetchService.java index 1c91caf4..12a58ef 100644 --- a/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubPasswordsFetchService.java +++ b/chrome/browser/safety_hub/android/java/src/org/chromium/chrome/browser/safety_hub/SafetyHubPasswordsFetchService.java
@@ -143,7 +143,7 @@ /** Returns true if a password fetch can be performed, namely if GMSCore can be called. */ public boolean canPerformFetch() { - return PasswordManagerUtilBridge.isPasswordManagerAvailable(mPrefService); + return PasswordManagerUtilBridge.isPasswordManagerAvailable(); } public void clearPrefs() {
diff --git a/chrome/browser/safety_hub/android/junit/src/org/chromium/chrome/browser/safety_hub/SafetyHubTestRule.java b/chrome/browser/safety_hub/android/junit/src/org/chromium/chrome/browser/safety_hub/SafetyHubTestRule.java index 5265bb1..b856631b 100644 --- a/chrome/browser/safety_hub/android/junit/src/org/chromium/chrome/browser/safety_hub/SafetyHubTestRule.java +++ b/chrome/browser/safety_hub/android/junit/src/org/chromium/chrome/browser/safety_hub/SafetyHubTestRule.java
@@ -109,7 +109,7 @@ } public void setPasswordManagerAvailable(boolean isPasswordManagerAvailable) { - when(mPasswordManagerUtilBridgeNatives.isPasswordManagerAvailable(mPrefService, true)) + when(mPasswordManagerUtilBridgeNatives.isPasswordManagerAvailable(true)) .thenReturn(isPasswordManagerAvailable); }
diff --git a/chrome/browser/sessions/session_data_deleter.cc b/chrome/browser/sessions/session_data_deleter.cc index 81a37a4f4..d9ca5675 100644 --- a/chrome/browser/sessions/session_data_deleter.cc +++ b/chrome/browser/sessions/session_data_deleter.cc
@@ -114,11 +114,10 @@ /*perform_storage_cleanup=*/false, base::Time(), base::Time::Max(), base::BindOnce(&SessionDataDeleterInternal::OnStorageDeletionDone, this)); - // The const_cast here is safe, as the profile received in the constructor - // is not const. It is just that ScopedProfileKeepAlive wraps it as const. + if (auto* media_device_salt_service = MediaDeviceSaltServiceFactory::GetInstance()->GetForBrowserContext( - const_cast<Profile*>(profile_keep_alive_->profile()))) { + profile_keep_alive_->profile())) { media_device_salt_service->DeleteSalts( base::Time(), base::Time::Max(), base::BindRepeating(&StorageKeyMatcher, storage_policy_),
diff --git a/chrome/browser/signin/signin_promo_unittest.cc b/chrome/browser/signin/signin_promo_unittest.cc index 68936f1..bf9ef97 100644 --- a/chrome/browser/signin/signin_promo_unittest.cc +++ b/chrome/browser/signin/signin_promo_unittest.cc
@@ -348,7 +348,8 @@ autofill::AutofillProfile CreateAddress( const std::string& country_code = "US") { - return autofill::test::StandardProfile(AddressCountryCode(country_code)); + return autofill::test::StandardProfile( + autofill::AddressCountryCode(country_code)); } private:
diff --git a/chrome/browser/site_protection/site_familiarity_utils.cc b/chrome/browser/site_protection/site_familiarity_utils.cc new file mode 100644 index 0000000..1bce109 --- /dev/null +++ b/chrome/browser/site_protection/site_familiarity_utils.cc
@@ -0,0 +1,23 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/site_protection/site_familiarity_utils.h" + +#include "components/content_settings/core/common/features.h" +#include "components/prefs/pref_service.h" +#include "components/safe_browsing/core/common/safe_browsing_prefs.h" +#include "content/public/common/content_features.h" + +namespace site_protection { + +bool AreV8OptimizationsDisabledOnUnfamiliarSites(const PrefService& prefs) { + return base::FeatureList::IsEnabled( + features::kProcessSelectionDeferringConditions) && + base::FeatureList::IsEnabled( + content_settings::features:: + kBlockV8OptimizerOnUnfamiliarSitesSetting) && + prefs.GetBoolean(prefs::kJavascriptOptimizerBlockedForUnfamiliarSites); +} + +} // namespace site_protection
diff --git a/chrome/browser/site_protection/site_familiarity_utils.h b/chrome/browser/site_protection/site_familiarity_utils.h new file mode 100644 index 0000000..0e65183 --- /dev/null +++ b/chrome/browser/site_protection/site_familiarity_utils.h
@@ -0,0 +1,19 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SITE_PROTECTION_SITE_FAMILIARITY_UTILS_H_ +#define CHROME_BROWSER_SITE_PROTECTION_SITE_FAMILIARITY_UTILS_H_ + +class PrefService; + +namespace site_protection { + +// Returns whether v8-optimizations are disabled on sites which are unfamiliar +// to the user. Site familiarity is computed using a heuristic based on the +// user's navigation history. +bool AreV8OptimizationsDisabledOnUnfamiliarSites(const PrefService& prefs); + +} // namespace site_protection + +#endif // CHROME_BROWSER_SITE_PROTECTION_SITE_FAMILIARITY_UTILS_H_
diff --git a/chrome/browser/site_protection/site_protection_metrics_observer.cc b/chrome/browser/site_protection/site_protection_metrics_observer.cc index 7c38cb3..2b89973 100644 --- a/chrome/browser/site_protection/site_protection_metrics_observer.cc +++ b/chrome/browser/site_protection/site_protection_metrics_observer.cc
@@ -13,6 +13,7 @@ #include "chrome/browser/history/history_service_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/site_protection/site_familiarity_heuristic_name.h" +#include "chrome/browser/site_protection/site_familiarity_utils.h" #include "chrome/browser/site_protection/site_protection_metrics.h" #include "components/history/core/browser/history_service.h" #include "components/history/core/browser/history_types.h" @@ -139,8 +140,7 @@ void SiteProtectionMetricsObserver::DidFinishNavigation( content::NavigationHandle* navigation_handle) { - if (!base::FeatureList::IsEnabled( - features::kProcessSelectionDeferringConditions)) { + if (!AreV8OptimizationsDisabledOnUnfamiliarSites(*profile_->GetPrefs())) { return; }
diff --git a/chrome/browser/site_protection/site_protection_metrics_observer_unittest.cc b/chrome/browser/site_protection/site_protection_metrics_observer_unittest.cc index fcefb6c..28581ff 100644 --- a/chrome/browser/site_protection/site_protection_metrics_observer_unittest.cc +++ b/chrome/browser/site_protection/site_protection_metrics_observer_unittest.cc
@@ -20,10 +20,14 @@ #include "chrome/common/chrome_features.h" #include "chrome/test/base/chrome_render_view_host_test_harness.h" #include "chrome/test/base/testing_browser_process.h" +#include "components/content_settings/core/common/features.h" #include "components/history/core/browser/history_service.h" #include "components/history/core/browser/history_types.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/safe_browsing/core/common/safe_browsing_prefs.h" #include "components/site_engagement/content/site_engagement_helper.h" #include "components/site_engagement/content/site_engagement_service.h" +#include "components/sync_preferences/testing_pref_service_syncable.h" #include "components/ukm/test_ukm_recorder.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/spare_render_process_host_manager.h" @@ -657,6 +661,8 @@ SiteProtectionMetricsObserverV8OptTest() { feature_list_.InitWithFeatures( /*enabled_features=*/{features::kProcessSelectionDeferringConditions, + content_settings::features:: + kBlockV8OptimizerOnUnfamiliarSitesSetting, features::kOriginKeyedProcessesByDefault}, /*disabled_features=*/{}); } @@ -665,6 +671,9 @@ old_browser_client_ = SetBrowserClientForTesting(&browser_client_); SiteProtectionMetricsObserverTest::SetUp(); + profile()->GetTestingPrefService()->SetBoolean( + prefs::kJavascriptOptimizerBlockedForUnfamiliarSites, true); + content::SpareRenderProcessHostManager::Get().CleanupSparesForTesting(); test_render_process_host_factory_ =
diff --git a/chrome/browser/sync/test/integration/autofill_helper.cc b/chrome/browser/sync/test/integration/autofill_helper.cc index ad1081f..a1e9393 100644 --- a/chrome/browser/sync/test/integration/autofill_helper.cc +++ b/chrome/browser/sync/test/integration/autofill_helper.cc
@@ -217,7 +217,7 @@ } AutofillProfile CreateUniqueAutofillProfile() { - AutofillProfile profile(AddressCountryCode("US")); + AutofillProfile profile(autofill::AddressCountryCode("US")); autofill::test::SetProfileInfoWithGuid( &profile, base::Uuid::GenerateRandomV4().AsLowercaseString().c_str(), "First", "Middle", "Last", "email@domain.tld", "Company", "123 Main St",
diff --git a/chrome/browser/ui/android/fast_checkout/ui_view_android_utils.cc b/chrome/browser/ui/android/fast_checkout/ui_view_android_utils.cc index 2a29976..a475f0a 100644 --- a/chrome/browser/ui/android/fast_checkout/ui_view_android_utils.cc +++ b/chrome/browser/ui/android/fast_checkout/ui_view_android_utils.cc
@@ -114,8 +114,9 @@ JNIEnv* env, const base::android::JavaParamRef<jobject>& jprofile, const std::string& locale) { - AddressCountryCode country_code = AddressCountryCode(ConvertJavaStringToUTF8( - Java_FastCheckoutAutofillProfile_getCountryCode(env, jprofile))); + autofill::AddressCountryCode country_code = + autofill::AddressCountryCode(ConvertJavaStringToUTF8( + Java_FastCheckoutAutofillProfile_getCountryCode(env, jprofile))); auto profile = std::make_unique<autofill::AutofillProfile>(country_code); // Only set the guid if it is an existing profile (Java guid not empty). // Otherwise, keep the generated one.
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 27dc461..48a8120 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
@@ -87,8 +87,6 @@ android:layout="@layout/incognito_indicator" android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_marginVertical="@dimen/location_bar_vertical_margin" - android:layout_marginHorizontal="@dimen/incognito_indicator_lateral_margin" android:visibility="gone" /> <ViewStub
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBar.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBar.java index eea3372..63c87516 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBar.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarProgressBar.java
@@ -163,7 +163,8 @@ setAlpha(0.0f); mAnimationLogic = new ProgressAnimationSmooth(); - if (!ChromeFeatureList.sAndroidAnimatedProgressBarInViz.isEnabled()) { + if (!(ChromeFeatureList.sAndroidAnimatedProgressBarInViz.isEnabled() + || ChromeFeatureList.sAndroidAnimatedProgressBarInBrowser.isEnabled())) { setVisibility(View.VISIBLE); }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java index 3c94cf1..e5ed9ae1 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TopToolbarOverlayMediator.java
@@ -4,12 +4,16 @@ package org.chromium.chrome.browser.toolbar.top; +import android.animation.TimeAnimator; +import android.animation.TimeAnimator.TimeListener; import android.content.Context; +import android.graphics.Rect; import android.view.View; import androidx.annotation.ColorInt; import org.chromium.base.Callback; +import org.chromium.base.MathUtils; import org.chromium.base.ResettersForTesting; import org.chromium.base.supplier.ObservableSupplier; import org.chromium.build.annotations.NullMarked; @@ -114,6 +118,25 @@ private @Nullable OffsetTag mBottomProgressBarOffsetTag; private @ControlsPosition int mControlsPosition; + private float mAnimatedProgress; + private float mTargetProgress; + private static final long ANIMATION_DURATION_MS = 3000; + + private final TimeAnimator mProgressBarAnimation = new TimeAnimator(); + + { + mProgressBarAnimation.setTimeListener( + new TimeListener() { + @Override + public void onTimeUpdate( + TimeAnimator animation, long totalTimeMs, long deltaTimeMs) { + mAnimatedProgress += (deltaTimeMs / ((float) ANIMATION_DURATION_MS)); + mAnimatedProgress = Math.min(mAnimatedProgress, mTargetProgress); + updateProgress(); + } + }); + } + TopToolbarOverlayMediator( PropertyModel model, Context context, @@ -176,7 +199,18 @@ @Override public void onLoadProgressChanged(Tab tab, float progress) { - updateProgress(); + mTargetProgress = progress; + if (ChromeFeatureList.sAndroidAnimatedProgressBarInBrowser + .isEnabled()) { + if (MathUtils.areFloatsEqual(1.0f, progress)) { + mProgressBarAnimation.cancel(); + updateProgress(); + } else if (!mProgressBarAnimation.isStarted()) { + mProgressBarAnimation.start(); + } + } else { + updateProgress(); + } } @Override @@ -397,6 +431,23 @@ onProgressBarOffsetTagsChanged(); } + if (ChromeFeatureList.sAndroidAnimatedProgressBarInBrowser.isEnabled()) { + Rect foregroundRect = drawingInfo.progressBarRect; + Rect backgroundRect = drawingInfo.progressBarBackgroundRect; + Rect staticBackgroundRect = drawingInfo.progressBarStaticBackgroundRect; + int progressX = + foregroundRect.left + + Math.round(mAnimatedProgress * staticBackgroundRect.width()); + int gap = backgroundRect.left - foregroundRect.right; + drawingInfo.progressBarRect.set( + foregroundRect.left, foregroundRect.top, progressX, foregroundRect.bottom); + drawingInfo.progressBarBackgroundRect.set( + progressX + gap, + backgroundRect.top, + backgroundRect.right, + backgroundRect.bottom); + } + // TODO(https://crbug.com/439461293) Try not updating the model if nothing changed. mModel.set( TopToolbarOverlayProperties.PROGRESS_BAR_INFO,
diff --git a/chrome/browser/ui/browser_element_identifiers.cc b/chrome/browser/ui/browser_element_identifiers.cc index 79a9a0e..963efdd 100644 --- a/chrome/browser/ui/browser_element_identifiers.cc +++ b/chrome/browser/ui/browser_element_identifiers.cc
@@ -20,7 +20,11 @@ DEFINE_ELEMENT_IDENTIFIER_VALUE(kConstrainedDialogWebViewElementId); DEFINE_ELEMENT_IDENTIFIER_VALUE(kContentsCaptureBorder); DEFINE_ELEMENT_IDENTIFIER_VALUE(kContentsContainerViewElementId); -DEFINE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorViewElementId); +DEFINE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorLeadingEdgeElementId); +DEFINE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorLeadingTopCornerElementId); +DEFINE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorTopEdgeElementId); +DEFINE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorTrailingEdgeElementId); +DEFINE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorTrailingTopCornerElementId); DEFINE_ELEMENT_IDENTIFIER_VALUE(kCookieControlsIconElementId); DEFINE_ELEMENT_IDENTIFIER_VALUE(kCustomizeChromeSidePanelWebViewElementId); DEFINE_ELEMENT_IDENTIFIER_VALUE(kDataSharingBubbleElementId);
diff --git a/chrome/browser/ui/browser_element_identifiers.h b/chrome/browser/ui/browser_element_identifiers.h index 48e939a..aabebbd 100644 --- a/chrome/browser/ui/browser_element_identifiers.h +++ b/chrome/browser/ui/browser_element_identifiers.h
@@ -29,7 +29,11 @@ DECLARE_ELEMENT_IDENTIFIER_VALUE(kConstrainedDialogWebViewElementId); DECLARE_ELEMENT_IDENTIFIER_VALUE(kContentsCaptureBorder); DECLARE_ELEMENT_IDENTIFIER_VALUE(kContentsContainerViewElementId); -DECLARE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorViewElementId); +DECLARE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorLeadingEdgeElementId); +DECLARE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorLeadingTopCornerElementId); +DECLARE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorTopEdgeElementId); +DECLARE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorTrailingEdgeElementId); +DECLARE_ELEMENT_IDENTIFIER_VALUE(kContentsSeparatorTrailingTopCornerElementId); DECLARE_ELEMENT_IDENTIFIER_VALUE(kCookieControlsIconElementId); DECLARE_ELEMENT_IDENTIFIER_VALUE(kCustomizeChromeSidePanelWebViewElementId); DECLARE_ELEMENT_IDENTIFIER_VALUE(kDataSharingBubbleElementId);
diff --git a/chrome/browser/ui/browser_window/internal/BUILD.gn b/chrome/browser/ui/browser_window/internal/BUILD.gn index 07e44ea..26cbf4e 100644 --- a/chrome/browser/ui/browser_window/internal/BUILD.gn +++ b/chrome/browser/ui/browser_window/internal/BUILD.gn
@@ -61,6 +61,7 @@ "//chrome/browser/ui/tabs/organization", "//chrome/browser/ui/tabs/saved_tab_groups", "//chrome/browser/ui/tabs/tab_strip_api", + "//chrome/browser/ui/tabs/tab_strip_api:impl_header", "//chrome/browser/ui/toasts", "//chrome/browser/ui/toolbar/chrome_labs", "//chrome/browser/ui/views/download",
diff --git a/chrome/browser/ui/browser_window/internal/browser_window_features.cc b/chrome/browser/ui/browser_window/internal/browser_window_features.cc index 25ab77d..3b295f50 100644 --- a/chrome/browser/ui/browser_window/internal/browser_window_features.cc +++ b/chrome/browser/ui/browser_window/internal/browser_window_features.cc
@@ -59,7 +59,7 @@ #include "chrome/browser/ui/tabs/split_tab_highlight_controller.h" #include "chrome/browser/ui/tabs/tab_group_deletion_dialog_controller.h" #include "chrome/browser/ui/tabs/tab_list_bridge.h" -#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.h" +#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.h" #include "chrome/browser/ui/tabs/vertical_tab_strip_state_controller.h" #include "chrome/browser/ui/toasts/toast_controller.h" #include "chrome/browser/ui/toasts/toast_features.h" @@ -144,8 +144,8 @@ #include "chrome/browser/glic/browser_ui/glic_iph_controller.h" #include "chrome/browser/glic/public/glic_enabling.h" #include "chrome/browser/glic/public/glic_keyed_service.h" -#include "chrome/browser/glic/widget/glic_side_panel_coordinator.h" #include "chrome/browser/ui/tabs/glic_actor_task_icon_controller.h" +#include "chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator.h" #endif #if defined(USE_AURA) @@ -256,7 +256,7 @@ tab_strip_model_ = browser->GetTabStripModel(); tab_strip_service_ = - std::make_unique<TabStripServiceImpl>(browser, tab_strip_model_); + std::make_unique<TabStripServiceMojoHandler>(browser, tab_strip_model_); memory_saver_bubble_controller_ = std::make_unique<memory_saver::MemorySaverBubbleController>(browser); @@ -561,7 +561,8 @@ #if BUILDFLAG(ENABLE_GLIC) glic_side_panel_coordinator_ = - std::make_unique<glic::GlicSidePanelCoordinator>(); + std::make_unique<glic::GlicSidePanelCoordinator>( + browser_view->GetProfile()); #endif // BUILDFLAG(ENABLE_GLIC) side_panel_coordinator_->Init(browser_view->browser());
diff --git a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc index 33f45153..ae9eff5 100644 --- a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc +++ b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
@@ -148,6 +148,7 @@ #include "ui/events/event_constants.h" #include "ui/views/accessibility/view_accessibility.h" #include "ui/views/controls/webview/webview.h" +#include "ui/views/interaction/element_tracker_views.h" #include "ui/views/test/widget_test.h" #include "ui/views/view_utils.h" #include "ui/views/widget/any_widget_observer.h" @@ -8852,6 +8853,23 @@ {features::kSideBySide, {}}}, {}); } + + bool AreAnyRoundedCornersShowing() { + const ui::ElementContext context = + views::ElementTrackerViews::GetContextForView( + BrowserView::GetBrowserViewForBrowser(browser())); + views::View* start_corner = + views::ElementTrackerViews::GetInstance()->GetFirstMatchingView( + kContentsSeparatorLeadingTopCornerElementId, context); + views::View* end_corner = + views::ElementTrackerViews::GetInstance()->GetFirstMatchingView( + kContentsSeparatorTrailingTopCornerElementId, context); + return (start_corner && start_corner->GetVisible()) || + (end_corner && end_corner->GetVisible()); + } + + private: + base::test::ScopedFeatureList feature_list_; }; IN_PROC_BROWSER_TEST_F(LensOverlayControllerSideBySideBrowserTest, @@ -8985,9 +9003,7 @@ ->layer() ->GetTargetRoundedCornerRadius() .upper_right() > 0); - EXPECT_TRUE(BrowserView::GetBrowserViewForBrowser(browser()) - ->GetSidePanelRoundedCornerForTesting() - ->GetVisible()); + EXPECT_TRUE(AreAnyRoundedCornersShowing()); } IN_PROC_BROWSER_TEST_F(LensOverlayControllerSideBySideBrowserTest, @@ -9022,9 +9038,7 @@ ->layer() ->GetTargetRoundedCornerRadius() .upper_right() > 0); - EXPECT_FALSE(BrowserView::GetBrowserViewForBrowser(browser()) - ->GetSidePanelRoundedCornerForTesting() - ->GetVisible()); + EXPECT_FALSE(AreAnyRoundedCornersShowing()); // Switch to the first tab. browser()->tab_strip_model()->ActivateTabAt(0); @@ -9048,9 +9062,7 @@ ->layer() ->GetTargetRoundedCornerRadius() .upper_right() > 0); - EXPECT_FALSE(BrowserView::GetBrowserViewForBrowser(browser()) - ->GetSidePanelRoundedCornerForTesting() - ->GetVisible()); + EXPECT_FALSE(AreAnyRoundedCornersShowing()); // Switch back to the second tab. browser()->tab_strip_model()->ActivateTabAt(1); @@ -9065,9 +9077,7 @@ ->layer() ->GetTargetRoundedCornerRadius() .upper_right() > 0); - EXPECT_FALSE(BrowserView::GetBrowserViewForBrowser(browser()) - ->GetSidePanelRoundedCornerForTesting() - ->GetVisible()); + EXPECT_FALSE(AreAnyRoundedCornersShowing()); } IN_PROC_BROWSER_TEST_F(LensOverlayControllerSideBySideBrowserTest,
diff --git a/chrome/browser/ui/lens/lens_overlay_query_controller.cc b/chrome/browser/ui/lens/lens_overlay_query_controller.cc index e438a90..5fe6cd4 100644 --- a/chrome/browser/ui/lens/lens_overlay_query_controller.cc +++ b/chrome/browser/ui/lens/lens_overlay_query_controller.cc
@@ -703,17 +703,18 @@ /*url_loader_factory=*/profile_ ? profile_->GetURLLoaderFactory().get() : g_browser_process->shared_url_loader_factory(), - /*url=*/fetch_url, - /*content_type=*/kContentType, - /*timeout=*/timeout, - /*post_data=*/std::move(request_string), - /*headers=*/request_headers, - /*cors_exempt_headers=*/cors_exempt_headers, chrome::GetChannel(), - /*request_params=*/ + /*identity_manager=*/nullptr, EndpointFetcher::RequestParams::Builder(http_method, kTrafficAnnotationTag) + .SetChannel(chrome::GetChannel()) + .SetContentType(kContentType) + .SetCorsExemptHeaders(cors_exempt_headers) .SetCredentialsMode(CredentialsMode::kInclude) + .SetHeaders(request_headers) + .SetPostData(std::move(request_string)) .SetSetSiteForCookies(true) + .SetTimeout(timeout) + .SetUrl(fetch_url) .SetUploadProgressCallback(std::move(upload_progress_callback)) .Build()); }
diff --git a/chrome/browser/ui/tabs/tab_strip_api/BUILD.gn b/chrome/browser/ui/tabs/tab_strip_api/BUILD.gn index 71e69250..d1c66c3 100644 --- a/chrome/browser/ui/tabs/tab_strip_api/BUILD.gn +++ b/chrome/browser/ui/tabs/tab_strip_api/BUILD.gn
@@ -19,6 +19,7 @@ visibility = [ ":browser_tests", ":impl", + ":impl_header", ":tab_strip_api", ":unit_tests", ] @@ -113,7 +114,6 @@ source_set("tab_strip_api") { sources = [ "tab_strip_service.h", - "tab_strip_service_impl.h", "tab_strip_service_register.h", ] @@ -123,8 +123,18 @@ "//chrome/browser/ui/tabs:tab_strip_model_observer", ] + deps = [ ":mojom_experiment" ] +} + +# This target is needed to prevent circular dependency. +source_set("impl_header") { + sources = [ "tab_strip_service_mojo_handler.h" ] + deps = [ + ":mojom", ":mojom_experiment", + ":tab_strip_api", + "adapters", "//chrome/browser/ui/tabs/tab_strip_api/events", ] } @@ -134,15 +144,14 @@ "event_broadcaster.cc", "event_broadcaster.h", "tab_strip_service_impl.cc", - - # intermediary transition classes. Will be renamed - "tab_strip_service_sync_impl.cc", - "tab_strip_service_sync_impl.h", + "tab_strip_service_mojo_handler.cc", + "tab_strip_service_mojo_handler.h", ] public_deps = [ "//chrome/browser:browser_public_dependencies" ] deps = [ + ":impl_header", ":mojom_experiment", ":tab_strip_api", "adapters:impl",
diff --git a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_experiment_service_unittest.cc b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_experiment_service_unittest.cc index 8754f999..83f6d7d 100644 --- a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_experiment_service_unittest.cc +++ b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_experiment_service_unittest.cc
@@ -8,7 +8,7 @@ #include "base/test/task_environment.h" #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_experiment_api.mojom.h" #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.h" -#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.h" +#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.h" #include "chrome/browser/ui/tabs/tab_strip_api/testing/toy_tab_strip.h" #include "chrome/browser/ui/tabs/tab_strip_api/testing/toy_tab_strip_browser_adapter.h" #include "chrome/browser/ui/tabs/tab_strip_api/testing/toy_tab_strip_model_adapter.h" @@ -31,10 +31,10 @@ void SetUp() override { tab_strip_ = std::make_unique<testing::ToyTabStrip>(); - auto tab_strip_service = std::make_unique<TabStripServiceSyncImpl>( + auto tab_strip_service = std::make_unique<TabStripServiceImpl>( std::make_unique<testing::ToyTabStripBrowserAdapter>(tab_strip_.get()), std::make_unique<testing::ToyTabStripModelAdapter>(tab_strip_.get())); - impl_ = std::make_unique<TabStripServiceImpl>( + impl_ = std::make_unique<TabStripServiceMojoHandler>( std::move(tab_strip_service), std::make_unique<testing::ToyTabStripModelAdapter>(tab_strip_.get())); impl_->AcceptExperimental(client_.BindNewPipeAndPassReceiver()); @@ -45,7 +45,7 @@ private: content::BrowserTaskEnvironment task_environment_; - std::unique_ptr<TabStripServiceImpl> impl_; + std::unique_ptr<TabStripServiceMojoHandler> impl_; }; TEST_F(TabStripExperimentServiceImplTest, UpdateTabGroupVisual) {
diff --git a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.cc b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.cc index 1f4a8fc1..1606a12 100644 --- a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.cc +++ b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.cc
@@ -1,155 +1,308 @@ // Copyright 2025 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. + #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.h" +#include <algorithm> +#include <optional> +#include <utility> + +#include "base/strings/string_number_conversions.h" #include "base/types/expected.h" +#include "chrome/browser/ui/browser_tabstrip.h" +#include "chrome/browser/ui/browser_window/public/browser_window_interface.h" +#include "chrome/browser/ui/tabs/tab_strip_api/adapters/browser_adapter_impl.h" #include "chrome/browser/ui/tabs/tab_strip_api/adapters/tab_strip_model_adapter_impl.h" +#include "chrome/browser/ui/tabs/tab_strip_api/converters/tab_converters.h" #include "chrome/browser/ui/tabs/tab_strip_api/event_broadcaster.h" -#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service.h" -#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.h" +#include "chrome/browser/ui/tabs/tab_strip_api/events/tab_strip_event_recorder.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h" +#include "mojo/public/mojom/base/error.mojom.h" +#include "url/gurl.h" -// Starts a mutation session that suppresses incoming messages to prevent -// re-entrancy and replays all recorded mutations on session destruction. -class MutationSession { - public: - explicit MutationSession(tabs_api::events::TabStripEventRecorder* recorder) - : recorder_(recorder) { - recorder_->StopNotificationAndStartRecording(); - } - - ~MutationSession() { recorder_->PlayRecordingsAndStartNotification(); } - - // Disallow copy and assign. - MutationSession(const MutationSession&) = delete; - MutationSession& operator=(const MutationSession&) = delete; - - private: - raw_ptr<tabs_api::events::TabStripEventRecorder> recorder_; -}; +namespace tabs_api { TabStripServiceImpl::TabStripServiceImpl(BrowserWindowInterface* browser, TabStripModel* tab_strip_model) : TabStripServiceImpl( - std::make_unique<tabs_api::TabStripServiceSyncImpl>(browser, - tab_strip_model), + std::make_unique<tabs_api::BrowserAdapterImpl>(browser), std::make_unique<tabs_api::TabStripModelAdapterImpl>( tab_strip_model)) {} TabStripServiceImpl::TabStripServiceImpl( - std::unique_ptr<tabs_api::TabStripService> service, - std::unique_ptr<tabs_api::TabStripModelAdapter> tab_strip_model_adapter) - : tab_strip_service_(std::move(service)), - tab_strip_model_adapter_(std::move(tab_strip_model_adapter)) { - recorder_ = std::make_unique<tabs_api::events::TabStripEventRecorder>( - tab_strip_model_adapter_.get(), - base::BindRepeating(&TabStripServiceImpl::BroadcastEvents, - base::Unretained(this))); - tab_strip_service_->AddObserver(recorder_.get()); + std::unique_ptr<BrowserAdapter> browser_adapter, + std::unique_ptr<TabStripModelAdapter> tab_strip_model_adapter) + : browser_adapter_(std::move(browser_adapter)), + tab_strip_model_adapter_(std::move(tab_strip_model_adapter)) {} + +TabStripServiceImpl::~TabStripServiceImpl() = default; + +TabStripService::GetTabsResult TabStripServiceImpl::GetTabs() { + return tab_strip_model_adapter_->GetTabStripTopology(); } -void TabStripServiceImpl::BroadcastEvents( - const std::vector<tabs_api::events::Event>& events) const { - tabs_api::EventBroadcaster broadcaster; - broadcaster.Broadcast(observers_, events); -} - -TabStripServiceImpl::~TabStripServiceImpl() { - tab_strip_service_->RemoveObserver(recorder_.get()); - - // Clear all observers - // TODO (crbug.com/412955607): Implement a removal mechanism similar to - // TabStripModelObserver where on shutdown of the TabStripService, it notifies - // to all clients that service is shutting down. - observers_.Clear(); -} - -void TabStripServiceImpl::GetTabs(GetTabsCallback callback) { - MutationSession recorder_session(recorder_.get()); - auto snapshot = tabs_api::mojom::TabsSnapshot::New(); - auto result = tab_strip_service_->GetTabs(); - if (!result.has_value()) { - std::move(callback).Run(base::unexpected(std::move(result.error()))); +mojom::TabStripService::GetTabResult TabStripServiceImpl::GetTab( + const tabs_api::NodeId& tab_mojom_id) { + if (tab_mojom_id.Type() != tabs_api::NodeId::Type::kContent) { + return base::unexpected( + mojo_base::mojom::Error::New(mojo_base::mojom::Code::kInvalidArgument, + "only tab content ids accepted")); } - snapshot->tab_strip = std::move(result.value()); - mojo::AssociatedRemote<tabs_api::mojom::TabsObserver> stream; - auto pending_receiver = stream.BindNewEndpointAndPassReceiver(); - observers_.Add(std::move(stream)); - snapshot->stream = std::move(pending_receiver); + int32_t tab_id; + if (!base::StringToInt(tab_mojom_id.Id(), &tab_id)) { + return base::unexpected(mojo_base::mojom::Error::New( + mojo_base::mojom::Code::kInvalidArgument, "invalid tab id provided")); + } - std::move(callback).Run(std::move(snapshot)); + tabs_api::mojom::TabPtr tab_result; + // TODO (crbug.com/412709270) TabStripModel or TabCollections should have an + // api that can fetch id without of relying on indexes. + auto tabs = tab_strip_model_adapter_->GetTabs(); + for (unsigned int i = 0; i < tabs.size(); ++i) { + auto& handle = tabs.at(i); + if (tab_id == handle.raw_value()) { + auto renderer_data = tab_strip_model_adapter_->GetTabRendererData(i); + const ui::ColorProvider& color_provider = + tab_strip_model_adapter_->GetColorProvider(); + tab_result = tabs_api::converters::BuildMojoTab( + handle, renderer_data, color_provider, + tab_strip_model_adapter_->GetTabStates(handle)); + } + } + + if (tab_result) { + return std::move(tab_result); + } else { + return base::unexpected(mojo_base::mojom::Error::New( + mojo_base::mojom::Code::kNotFound, "Tab not found")); + } } -void TabStripServiceImpl::GetTab(const tabs_api::NodeId& tab_mojom_id, - GetTabCallback callback) { - MutationSession recorder_session(recorder_.get()); - - std::move(callback).Run(tab_strip_service_->GetTab(tab_mojom_id)); -} - -void TabStripServiceImpl::CreateTabAt( +mojom::TabStripService::CreateTabAtResult TabStripServiceImpl::CreateTabAt( const std::optional<tabs_api::Position>& pos, - const std::optional<GURL>& url, - CreateTabAtCallback callback) { - MutationSession recorder_session(recorder_.get()); + const std::optional<GURL>& url) { + GURL target_url; + if (url.has_value()) { + target_url = url.value(); + } + std::optional<int> index; + if (pos.has_value()) { + // TODO(crbug.com/409086859): Does not use the parent_id yet. Currently only + // inserts in the unpinned collection. + index = pos->index(); + } - std::move(callback).Run(tab_strip_service_->CreateTabAt(pos, url)); + auto tab_handle = browser_adapter_->AddTabAt(target_url, index); + if (tab_handle == tabs::TabHandle::Null()) { + // Missing content can happen for a number of reasons. i.e. If the profile + // is shutting down or if navigation requests are blocked due to some + // internal state. This is usually because the browser is not in the + // required state to perform the action. + return base::unexpected(mojo_base::mojom::Error::New( + mojo_base::mojom::Code::kInternal, "Failed to create WebContents")); + } + + auto tab_index = tab_strip_model_adapter_->GetIndexForHandle(tab_handle); + if (!tab_index.has_value()) { + return base::unexpected(mojo_base::mojom::Error::New( + mojo_base::mojom::Code::kInternal, + "Could not find the index of the newly created tab")); + } + + auto renderer_data = + tab_strip_model_adapter_->GetTabRendererData(tab_index.value()); + const ui::ColorProvider& color_provider = + tab_strip_model_adapter_->GetColorProvider(); + auto mojo_tab = tabs_api::converters::BuildMojoTab( + tab_handle, renderer_data, color_provider, + tab_strip_model_adapter_->GetTabStates(tab_handle)); + return mojo_tab->Clone(); } -void TabStripServiceImpl::CloseTabs(const std::vector<tabs_api::NodeId>& ids, - CloseTabsCallback callback) { - MutationSession recorder_session(recorder_.get()); +mojom::TabStripService::CloseTabsResult TabStripServiceImpl::CloseTabs( + const std::vector<tabs_api::NodeId>& ids) { + std::vector<int32_t> tab_content_targets; + for (const auto& id : ids) { + if (id.Type() != tabs_api::NodeId::Type::kContent) { + return base::unexpected(mojo_base::mojom::Error::New( + mojo_base::mojom::Code::kUnimplemented, + "only content tab closing has been implemented right now")); + } + int32_t numeric_id; + if (!base::StringToInt(id.Id(), &numeric_id)) { + return base::unexpected(mojo_base::mojom::Error::New( + mojo_base::mojom::Code::kInvalidArgument, "invalid tab content id")); + } + tab_content_targets.push_back(numeric_id); + } - std::move(callback).Run(tab_strip_service_->CloseTabs(ids)); + std::vector<size_t> tab_strip_indices; + // Transform targets from ids to indices in the tabstrip. + for (auto target : tab_content_targets) { + auto target_idx = + tab_strip_model_adapter_->GetIndexForHandle(tabs::TabHandle(target)); + if (!target_idx.has_value()) { + return base::unexpected(mojo_base::mojom::Error::New( + mojo_base::mojom::Code::kNotFound, "could not find the a tab")); + } + tab_strip_indices.push_back(target_idx.value()); + } + + // Close from last to first, that way the removals won't change the index of + // the next target. + std::ranges::sort(tab_strip_indices, std::ranges::greater()); + for (auto idx : tab_strip_indices) { + tab_strip_model_adapter_->CloseTab(idx); + } + + return std::monostate(); } -void TabStripServiceImpl::ActivateTab(const tabs_api::NodeId& id, - ActivateTabCallback callback) { - MutationSession recorder_session(recorder_.get()); +mojom::TabStripService::ActivateTabResult TabStripServiceImpl::ActivateTab( + const tabs_api::NodeId& id) { + if (id.Type() != tabs_api::NodeId::Type::kContent) { + return base::unexpected( + mojo_base::mojom::Error::New(mojo_base::mojom::Code::kInvalidArgument, + "only a content tab id can be provided")); + } - std::move(callback).Run(tab_strip_service_->ActivateTab(id)); + int32_t handle_id; + if (!base::StringToInt(id.Id(), &handle_id)) { + return base::unexpected(mojo_base::mojom::Error::New( + mojo_base::mojom::Code::kInvalidArgument, "id is malformed")); + } + + auto maybe_idx = + tab_strip_model_adapter_->GetIndexForHandle(tabs::TabHandle(handle_id)); + if (!maybe_idx.has_value()) { + return base::unexpected(mojo_base::mojom::Error::New( + mojo_base::mojom::Code::kNotFound, "tab not found")); + } + + tab_strip_model_adapter_->ActivateTab(maybe_idx.value()); + return std::monostate(); } -void TabStripServiceImpl::SetSelectedTabs( +mojom::TabStripService::SetSelectedTabsResult +TabStripServiceImpl::SetSelectedTabs( const std::vector<tabs_api::NodeId>& selection, - const tabs_api::NodeId& tab_to_activate, - SetSelectedTabsCallback callback) { - MutationSession recorder_session(recorder_.get()); + const tabs_api::NodeId& tab_to_activate) { + if (std::find(selection.begin(), selection.end(), tab_to_activate) == + selection.end()) { + return base::unexpected(mojo_base::mojom::Error::New( + mojo_base::mojom::Code::kInvalidArgument, + "the selection must include the tab_to_activate")); + } - std::move(callback).Run( - tab_strip_service_->SetSelectedTabs(selection, tab_to_activate)); + auto is_not_content_id = [](tabs_api::NodeId id) { + return id.Type() != tabs_api::NodeId::Type::kContent; + }; + if (std::find_if(selection.begin(), selection.end(), is_not_content_id) != + selection.end()) { + return base::unexpected(mojo_base::mojom::Error::New( + mojo_base::mojom::Code::kInvalidArgument, + "the selection can only include content IDs")); + } + + std::vector<tabs::TabHandle> selection_handles; + for (auto& id : selection) { + int32_t handle_id; + if (!base::StringToInt(id.Id(), &handle_id)) { + return base::unexpected(mojo_base::mojom::Error::New( + mojo_base::mojom::Code::kInvalidArgument, "id is malformed")); + } + selection_handles.push_back(tabs::TabHandle(handle_id)); + } + + int32_t activate_handle_id; + if (!base::StringToInt(tab_to_activate.Id(), &activate_handle_id)) { + return base::unexpected(mojo_base::mojom::Error::New( + mojo_base::mojom::Code::kInvalidArgument, "activate id is malformed")); + } + + tab_strip_model_adapter_->SetTabSelection( + selection_handles, tabs::TabHandle(activate_handle_id)); + + return std::monostate(); } -void TabStripServiceImpl::MoveTab(const tabs_api::NodeId& id, - const tabs_api::Position& position, - MoveTabCallback callback) { - MutationSession recorder_session(recorder_.get()); - - std::move(callback).Run(tab_strip_service_->MoveTab(id, position)); -} - -void TabStripServiceImpl::UpdateTabGroupVisual( +mojom::TabStripService::MoveTabResult TabStripServiceImpl::MoveTab( const tabs_api::NodeId& id, - const tab_groups::TabGroupVisualData& visual_data, - UpdateTabGroupVisualCallback callback) { - MutationSession recorder_session(recorder_.get()); + const tabs_api::Position& position) { + if (position.index() >= tab_strip_model_adapter_->GetTabs().size()) { + return base::unexpected( + mojo_base::mojom::Error::New(mojo_base::mojom::Code::kInvalidArgument, + "position cannot exceed tab strip")); + } - std::move(callback).Run( - tab_strip_service_->UpdateTabGroupVisual(id, visual_data)); + switch (id.Type()) { + case tabs_api::NodeId::Type::kContent: { + std::optional<tabs::TabHandle> tab_handle = id.ToTabHandle(); + if (!tab_handle.has_value()) { + return base::unexpected(mojo_base::mojom::Error::New( + mojo_base::mojom::Code::kInvalidArgument, "id is malformed")); + } + // TODO(crbug.com/409086859): Add error handling for cases where a + // position's parent id is impossible to be moved to. + tab_strip_model_adapter_->MoveTab(tab_handle.value(), position); + break; + } + case tabs_api::NodeId::Type::kCollection: { + tab_strip_model_adapter_->MoveCollection(id, position); + break; + } + default: + return base::unexpected(mojo_base::mojom::Error::New( + mojo_base::mojom::Code::kInvalidArgument, "invalid node type")); + } + + return std::monostate(); } -tabs_api::TabStripService* TabStripServiceImpl::GetSyncVersion() const { - return tab_strip_service_.get(); +// tabs_api::mojom::TabStripExperimentalService overrides +// +// TabStripExperimentalService is intended for quick prototyping for +// experimental apis that may not necessarily fit in the standard +// TabStripService. +mojom::TabStripExperimentService::UpdateTabGroupVisualResult +TabStripServiceImpl::UpdateTabGroupVisual( + const tabs_api::NodeId& id, + const tab_groups::TabGroupVisualData& visual_data) { + if (id.Type() != tabs_api::NodeId::Type::kCollection) { + return base::unexpected(mojo_base::mojom::Error::New( + mojo_base::mojom::Code::kInvalidArgument, "id must be a collection")); + } + + const std::optional<tabs::TabCollectionHandle> collection_handle = + id.ToTabCollectionHandle(); + if (!collection_handle.has_value()) { + return base::unexpected(mojo_base::mojom::Error::New( + mojo_base::mojom::Code::kInvalidArgument, "id is malformed")); + } + + const std::optional<const tab_groups::TabGroupId> group_id = + tab_strip_model_adapter_->FindGroupIdFor(collection_handle.value()); + if (!group_id.has_value()) { + return base::unexpected( + mojo_base::mojom::Error::New(mojo_base::mojom::Code::kNotFound, + "group with the specified ID not found.")); + } + + tab_strip_model_adapter_->UpdateTabGroupVisuals(group_id.value(), + visual_data); + + return std::monostate(); } -void TabStripServiceImpl::Accept( - mojo::PendingReceiver<tabs_api::mojom::TabStripService> client) { - clients_.Add(this, std::move(client)); +void TabStripServiceImpl::AddObserver(TabStripModelObserver* observer) { + tab_strip_model_adapter_->AddObserver(observer); } -void TabStripServiceImpl::AcceptExperimental( - mojo::PendingReceiver<tabs_api::mojom::TabStripExperimentService> client) { - experiment_clients_.Add(this, std::move(client)); +void TabStripServiceImpl::RemoveObserver(TabStripModelObserver* observer) { + tab_strip_model_adapter_->RemoveObserver(observer); } + +} // namespace tabs_api
diff --git a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.h b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.h index 1e77bf5..62bb6248c 100644 --- a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.h +++ b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.h
@@ -5,89 +5,60 @@ #ifndef CHROME_BROWSER_UI_TABS_TAB_STRIP_API_TAB_STRIP_SERVICE_IMPL_H_ #define CHROME_BROWSER_UI_TABS_TAB_STRIP_API_TAB_STRIP_SERVICE_IMPL_H_ -#include "base/functional/callback.h" -#include "base/memory/raw_ptr.h" -#include "base/observer_list.h" #include "chrome/browser/ui/tabs/tab_strip_api/adapters/browser_adapter.h" #include "chrome/browser/ui/tabs/tab_strip_api/adapters/tab_strip_model_adapter.h" -#include "chrome/browser/ui/tabs/tab_strip_api/events/tab_strip_event_recorder.h" #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_api.mojom.h" #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_experiment_api.mojom.h" #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service.h" -#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_register.h" -#include "chrome/browser/ui/tabs/tab_strip_api/types/node_id.h" -#include "components/tab_groups/tab_group_visual_data.h" -#include "mojo/public/cpp/bindings/receiver_set.h" -#include "mojo/public/cpp/bindings/remote_set.h" -class BrowserWindowInterface; -class TabStripModel; +namespace tabs_api { -// TODO (crbug.com/409086859). See bug for dd. -// tabs_api::mojom::TabStripController is an experimental TabStrip Api between -// any view and the TabStripModel. -class TabStripServiceImpl : public tabs_api::mojom::TabStripService, - public tabs_api::mojom::TabStripExperimentService, - public TabStripModelObserver, - public TabStripServiceRegister { +class TabStripServiceImpl : public TabStripService { public: TabStripServiceImpl(BrowserWindowInterface* browser, TabStripModel* tab_strip_model); TabStripServiceImpl( - std::unique_ptr<tabs_api::TabStripService> service, - std::unique_ptr<tabs_api::TabStripModelAdapter> tab_strip_model_adapter); - TabStripServiceImpl(const TabStripServiceImpl&&) = delete; - TabStripServiceImpl& operator=(const TabStripServiceImpl&) = delete; + std::unique_ptr<BrowserAdapter> browser_adapter, + std::unique_ptr<TabStripModelAdapter> tab_strip_model_adapter); + TabStripServiceImpl(const TabStripServiceImpl&) = delete; + TabStripServiceImpl operator=(const TabStripServiceImpl&&) = delete; ~TabStripServiceImpl() override; - // TabStripServiceregister overrides - void Accept( - mojo::PendingReceiver<tabs_api::mojom::TabStripService> client) override; - void AcceptExperimental( - mojo::PendingReceiver<tabs_api::mojom::TabStripExperimentService> client) - override; - - // tabs_api::mojom::TabStripService overrides - void GetTabs(GetTabsCallback callback) override; - void GetTab(const tabs_api::NodeId& id, GetTabCallback callback) override; - void CreateTabAt(const std::optional<tabs_api::Position>& pos, - const std::optional<GURL>& url, - CreateTabAtCallback callback) override; - void CloseTabs(const std::vector<tabs_api::NodeId>& ids, - CloseTabsCallback callback) override; - void ActivateTab(const tabs_api::NodeId& id, - ActivateTabCallback callback) override; - void SetSelectedTabs(const std::vector<tabs_api::NodeId>& selection, - const tabs_api::NodeId& tab_to_activate, - SetSelectedTabsCallback callback) override; - void MoveTab(const tabs_api::NodeId& id, - const tabs_api::Position& position, - MoveTabCallback callback) override; + TabStripService::GetTabsResult GetTabs() override; + mojom::TabStripService::GetTabResult GetTab( + const tabs_api::NodeId& id) override; + mojom::TabStripService::CreateTabAtResult CreateTabAt( + const std::optional<tabs_api::Position>& pos, + const std::optional<GURL>& url) override; + mojom::TabStripService::CloseTabsResult CloseTabs( + const std::vector<tabs_api::NodeId>& ids) override; + mojom::TabStripService::ActivateTabResult ActivateTab( + const tabs_api::NodeId& id) override; + mojom::TabStripService::SetSelectedTabsResult SetSelectedTabs( + const std::vector<tabs_api::NodeId>& selection, + const tabs_api::NodeId& tab_to_activate) override; + mojom::TabStripService::MoveTabResult MoveTab( + const tabs_api::NodeId& id, + const tabs_api::Position& position) override; // tabs_api::mojom::TabStripExperimentalService overrides // // TabStripExperimentalService is intended for quick prototyping for // experimental apis that may not necessarily fit in the standard // TabStripService. - void UpdateTabGroupVisual(const tabs_api::NodeId& id, - const tab_groups::TabGroupVisualData& visual_data, - UpdateTabGroupVisualCallback) override; + mojom::TabStripExperimentService::UpdateTabGroupVisualResult + UpdateTabGroupVisual( + const tabs_api::NodeId& id, + const tab_groups::TabGroupVisualData& visual_data) override; - tabs_api::TabStripService* GetSyncVersion() const; + void AddObserver(TabStripModelObserver* observer) override; + void RemoveObserver(TabStripModelObserver* observer) override; private: - void BroadcastEvents( - const std::vector<tabs_api::events::Event>& events) const; - - std::unique_ptr<tabs_api::TabStripService> tab_strip_service_; + std::unique_ptr<tabs_api::BrowserAdapter> browser_adapter_; std::unique_ptr<tabs_api::TabStripModelAdapter> tab_strip_model_adapter_; - std::unique_ptr<tabs_api::events::TabStripEventRecorder> recorder_; - - // Mojo plumbing. - mojo::ReceiverSet<tabs_api::mojom::TabStripService> clients_; - mojo::ReceiverSet<tabs_api::mojom::TabStripExperimentService> - experiment_clients_; - mojo::AssociatedRemoteSet<tabs_api::mojom::TabsObserver> observers_; }; +} // namespace tabs_api + #endif // CHROME_BROWSER_UI_TABS_TAB_STRIP_API_TAB_STRIP_SERVICE_IMPL_H_
diff --git a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl_browsertest.cc b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl_browsertest.cc index ea5a55c..c943393 100644 --- a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl_browsertest.cc +++ b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl_browsertest.cc
@@ -17,6 +17,7 @@ #include "chrome/browser/ui/tabs/tab_group_model.h" #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_api.mojom.h" #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_experiment_api.mojom.h" +#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/ui_features.h" #include "chrome/test/base/in_process_browser_test.h" @@ -121,12 +122,13 @@ void SetUpOnMainThread() override { InProcessBrowserTest::SetUpOnMainThread(); - tab_strip_service_impl_ = std::make_unique<TabStripServiceImpl>( - browser(), browser()->tab_strip_model()); + tab_strip_service_mojo_handler_ = + std::make_unique<TabStripServiceMojoHandler>( + browser(), browser()->tab_strip_model()); } void TearDownOnMainThread() override { - tab_strip_service_impl_.reset(); + tab_strip_service_mojo_handler_.reset(); InProcessBrowserTest::TearDownOnMainThread(); } @@ -141,7 +143,7 @@ std::unique_ptr<Observation> SetUpObservation() { auto observation = std::make_unique<Observation>(); - tab_strip_service_impl_->Accept( + tab_strip_service_mojo_handler_->Accept( observation->remote.BindNewPipeAndPassReceiver()); base::RunLoop run_loop; @@ -158,12 +160,12 @@ } base::test::ScopedFeatureList feature_list_; - std::unique_ptr<TabStripServiceImpl> tab_strip_service_impl_; + std::unique_ptr<TabStripServiceMojoHandler> tab_strip_service_mojo_handler_; }; IN_PROC_BROWSER_TEST_F(TabStripServiceImplBrowserTest, CreateTabAt) { mojo::Remote<TabStripService> remote; - tab_strip_service_impl_->Accept(remote.BindNewPipeAndPassReceiver()); + tab_strip_service_mojo_handler_->Accept(remote.BindNewPipeAndPassReceiver()); TabStripModel* model = GetTabStripModel(); const int expected_tab_count = model->count() + 1; @@ -192,7 +194,7 @@ IN_PROC_BROWSER_TEST_F(TabStripServiceImplBrowserTest, Observation) { mojo::Remote<TabStripService> remote; - tab_strip_service_impl_->Accept(remote.BindNewPipeAndPassReceiver()); + tab_strip_service_mojo_handler_->Accept(remote.BindNewPipeAndPassReceiver()); TestTabStripClient client; mojo::AssociatedReceiver<tabs_api::mojom::TabsObserver> receiver(&client); const GURL url("http://example.com/"); @@ -258,7 +260,7 @@ IN_PROC_BROWSER_TEST_F(TabStripServiceImplBrowserTest, CloseTabs) { mojo::Remote<TabStripService> remote; - tab_strip_service_impl_->Accept(remote.BindNewPipeAndPassReceiver()); + tab_strip_service_mojo_handler_->Accept(remote.BindNewPipeAndPassReceiver()); const int starting_num_tabs = GetTabStripModel()->GetTabCount(); @@ -293,7 +295,7 @@ IN_PROC_BROWSER_TEST_F(TabStripServiceImplBrowserTest, ActivateTab) { mojo::Remote<TabStripService> remote; - tab_strip_service_impl_->Accept(remote.BindNewPipeAndPassReceiver()); + tab_strip_service_mojo_handler_->Accept(remote.BindNewPipeAndPassReceiver()); tabs_api::NodeId created_id; base::RunLoop create_loop; @@ -330,7 +332,7 @@ // Create 5 tabs and select 3 random ones. IN_PROC_BROWSER_TEST_F(TabStripServiceImplBrowserTest, SetSelectedTabs) { mojo::Remote<TabStripService> remote; - tab_strip_service_impl_->Accept(remote.BindNewPipeAndPassReceiver()); + tab_strip_service_mojo_handler_->Accept(remote.BindNewPipeAndPassReceiver()); auto observation = SetUpObservation(); @@ -399,7 +401,7 @@ IN_PROC_BROWSER_TEST_F(TabStripServiceImplBrowserTest, MoveTab) { mojo::Remote<TabStripService> remote; - tab_strip_service_impl_->Accept(remote.BindNewPipeAndPassReceiver()); + tab_strip_service_mojo_handler_->Accept(remote.BindNewPipeAndPassReceiver()); auto observation = SetUpObservation(); @@ -444,7 +446,7 @@ IN_PROC_BROWSER_TEST_F(TabStripServiceImplBrowserTest, MoveTabIntoGroup) { mojo::Remote<TabStripService> remote; - tab_strip_service_impl_->Accept(remote.BindNewPipeAndPassReceiver()); + tab_strip_service_mojo_handler_->Accept(remote.BindNewPipeAndPassReceiver()); TabStripModel* model = GetTabStripModel(); for (int i = 0; i < 3; i++) { @@ -490,7 +492,7 @@ IN_PROC_BROWSER_TEST_F(TabStripServiceImplBrowserTest, MoveCollection) { mojo::Remote<TabStripService> remote; - tab_strip_service_impl_->Accept(remote.BindNewPipeAndPassReceiver()); + tab_strip_service_mojo_handler_->Accept(remote.BindNewPipeAndPassReceiver()); TabStripModel* model = GetTabStripModel(); for (int i = 0; i < 3; i++) { @@ -537,8 +539,8 @@ UpdateTabGroupVisualData) { mojo::Remote<TabStripService> remote; mojo::Remote<TabStripExperimentService> experiment_remote; - tab_strip_service_impl_->Accept(remote.BindNewPipeAndPassReceiver()); - tab_strip_service_impl_->AcceptExperimental( + tab_strip_service_mojo_handler_->Accept(remote.BindNewPipeAndPassReceiver()); + tab_strip_service_mojo_handler_->AcceptExperimental( experiment_remote.BindNewPipeAndPassReceiver()); TabStripModel* model = GetTabStripModel();
diff --git a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl_unittest.cc b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl_unittest.cc index 1e666b3c..97952b2 100644 --- a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl_unittest.cc +++ b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl_unittest.cc
@@ -13,7 +13,8 @@ #include "chrome/browser/ui/tabs/tab_strip_api/adapters/browser_adapter.h" #include "chrome/browser/ui/tabs/tab_strip_api/adapters/tab_strip_model_adapter.h" #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_api.mojom.h" -#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.h" +#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.h" +#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.h" #include "chrome/browser/ui/tabs/tab_strip_api/testing/toy_tab_strip.h" #include "chrome/browser/ui/tabs/tab_strip_api/testing/toy_tab_strip_browser_adapter.h" #include "chrome/browser/ui/tabs/tab_strip_api/testing/toy_tab_strip_model_adapter.h" @@ -41,10 +42,10 @@ void SetUp() override { tab_strip_ = std::make_unique<testing::ToyTabStrip>(); - auto tab_strip_service = std::make_unique<TabStripServiceSyncImpl>( + auto tab_strip_service = std::make_unique<TabStripServiceImpl>( std::make_unique<testing::ToyTabStripBrowserAdapter>(tab_strip_.get()), std::make_unique<testing::ToyTabStripModelAdapter>(tab_strip_.get())); - impl_ = std::make_unique<TabStripServiceImpl>( + impl_ = std::make_unique<TabStripServiceMojoHandler>( std::move(tab_strip_service), std::make_unique<testing::ToyTabStripModelAdapter>(tab_strip_.get())); impl_->Accept(client_.BindNewPipeAndPassReceiver()); @@ -57,7 +58,7 @@ private: content::BrowserTaskEnvironment task_environment_; - std::unique_ptr<TabStripServiceImpl> impl_; + std::unique_ptr<TabStripServiceMojoHandler> impl_; }; TEST_F(TabStripServiceImplTest, CreateNewTab) {
diff --git a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.cc b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.cc new file mode 100644 index 0000000..89b1c44 --- /dev/null +++ b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.cc
@@ -0,0 +1,159 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.h" + +#include "base/types/expected.h" +#include "chrome/browser/ui/tabs/tab_strip_api/adapters/tab_strip_model_adapter_impl.h" +#include "chrome/browser/ui/tabs/tab_strip_api/event_broadcaster.h" +#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service.h" +#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_impl.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" + +// Starts a mutation session that suppresses incoming messages to prevent +// re-entrancy and replays all recorded mutations on session destruction. +class MutationSession { + public: + explicit MutationSession(tabs_api::events::TabStripEventRecorder* recorder) + : recorder_(recorder) { + recorder_->StopNotificationAndStartRecording(); + } + + ~MutationSession() { recorder_->PlayRecordingsAndStartNotification(); } + + // Disallow copy and assign. + MutationSession(const MutationSession&) = delete; + MutationSession& operator=(const MutationSession&) = delete; + + private: + raw_ptr<tabs_api::events::TabStripEventRecorder> recorder_; +}; + +TabStripServiceMojoHandler::TabStripServiceMojoHandler( + BrowserWindowInterface* browser, + TabStripModel* tab_strip_model) + : TabStripServiceMojoHandler( + std::make_unique<tabs_api::TabStripServiceImpl>(browser, + tab_strip_model), + std::make_unique<tabs_api::TabStripModelAdapterImpl>( + tab_strip_model)) {} + +TabStripServiceMojoHandler::TabStripServiceMojoHandler( + std::unique_ptr<tabs_api::TabStripService> service, + std::unique_ptr<tabs_api::TabStripModelAdapter> tab_strip_model_adapter) + : tab_strip_service_(std::move(service)), + tab_strip_model_adapter_(std::move(tab_strip_model_adapter)) { + recorder_ = std::make_unique<tabs_api::events::TabStripEventRecorder>( + tab_strip_model_adapter_.get(), + base::BindRepeating(&TabStripServiceMojoHandler::BroadcastEvents, + base::Unretained(this))); + tab_strip_service_->AddObserver(recorder_.get()); +} + +void TabStripServiceMojoHandler::BroadcastEvents( + const std::vector<tabs_api::events::Event>& events) const { + tabs_api::EventBroadcaster broadcaster; + broadcaster.Broadcast(observers_, events); +} + +TabStripServiceMojoHandler::~TabStripServiceMojoHandler() { + tab_strip_service_->RemoveObserver(recorder_.get()); + + // Clear all observers + // TODO (crbug.com/412955607): Implement a removal mechanism similar to + // TabStripModelObserver where on shutdown of the TabStripService, it notifies + // to all clients that service is shutting down. + observers_.Clear(); +} + +void TabStripServiceMojoHandler::GetTabs(GetTabsCallback callback) { + MutationSession recorder_session(recorder_.get()); + + auto snapshot = tabs_api::mojom::TabsSnapshot::New(); + auto result = tab_strip_service_->GetTabs(); + if (!result.has_value()) { + std::move(callback).Run(base::unexpected(std::move(result.error()))); + } + snapshot->tab_strip = std::move(result.value()); + + mojo::AssociatedRemote<tabs_api::mojom::TabsObserver> stream; + auto pending_receiver = stream.BindNewEndpointAndPassReceiver(); + observers_.Add(std::move(stream)); + snapshot->stream = std::move(pending_receiver); + + std::move(callback).Run(std::move(snapshot)); +} + +void TabStripServiceMojoHandler::GetTab(const tabs_api::NodeId& tab_mojom_id, + GetTabCallback callback) { + MutationSession recorder_session(recorder_.get()); + + std::move(callback).Run(tab_strip_service_->GetTab(tab_mojom_id)); +} + +void TabStripServiceMojoHandler::CreateTabAt( + const std::optional<tabs_api::Position>& pos, + const std::optional<GURL>& url, + CreateTabAtCallback callback) { + MutationSession recorder_session(recorder_.get()); + + std::move(callback).Run(tab_strip_service_->CreateTabAt(pos, url)); +} + +void TabStripServiceMojoHandler::CloseTabs( + const std::vector<tabs_api::NodeId>& ids, + CloseTabsCallback callback) { + MutationSession recorder_session(recorder_.get()); + + std::move(callback).Run(tab_strip_service_->CloseTabs(ids)); +} + +void TabStripServiceMojoHandler::ActivateTab(const tabs_api::NodeId& id, + ActivateTabCallback callback) { + MutationSession recorder_session(recorder_.get()); + + std::move(callback).Run(tab_strip_service_->ActivateTab(id)); +} + +void TabStripServiceMojoHandler::SetSelectedTabs( + const std::vector<tabs_api::NodeId>& selection, + const tabs_api::NodeId& tab_to_activate, + SetSelectedTabsCallback callback) { + MutationSession recorder_session(recorder_.get()); + + std::move(callback).Run( + tab_strip_service_->SetSelectedTabs(selection, tab_to_activate)); +} + +void TabStripServiceMojoHandler::MoveTab(const tabs_api::NodeId& id, + const tabs_api::Position& position, + MoveTabCallback callback) { + MutationSession recorder_session(recorder_.get()); + + std::move(callback).Run(tab_strip_service_->MoveTab(id, position)); +} + +void TabStripServiceMojoHandler::UpdateTabGroupVisual( + const tabs_api::NodeId& id, + const tab_groups::TabGroupVisualData& visual_data, + UpdateTabGroupVisualCallback callback) { + MutationSession recorder_session(recorder_.get()); + + std::move(callback).Run( + tab_strip_service_->UpdateTabGroupVisual(id, visual_data)); +} + +tabs_api::TabStripService* TabStripServiceMojoHandler::GetTabStripService() + const { + return tab_strip_service_.get(); +} + +void TabStripServiceMojoHandler::Accept( + mojo::PendingReceiver<tabs_api::mojom::TabStripService> client) { + clients_.Add(this, std::move(client)); +} + +void TabStripServiceMojoHandler::AcceptExperimental( + mojo::PendingReceiver<tabs_api::mojom::TabStripExperimentService> client) { + experiment_clients_.Add(this, std::move(client)); +}
diff --git a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.h b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.h new file mode 100644 index 0000000..2fb9fb1 --- /dev/null +++ b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_mojo_handler.h
@@ -0,0 +1,95 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_TABS_TAB_STRIP_API_TAB_STRIP_SERVICE_MOJO_HANDLER_H_ +#define CHROME_BROWSER_UI_TABS_TAB_STRIP_API_TAB_STRIP_SERVICE_MOJO_HANDLER_H_ + +#include "base/functional/callback.h" +#include "base/memory/raw_ptr.h" +#include "base/observer_list.h" +#include "chrome/browser/ui/tabs/tab_strip_api/adapters/browser_adapter.h" +#include "chrome/browser/ui/tabs/tab_strip_api/adapters/tab_strip_model_adapter.h" +#include "chrome/browser/ui/tabs/tab_strip_api/events/tab_strip_event_recorder.h" +#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_api.mojom.h" +#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_experiment_api.mojom.h" +#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service.h" +#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_register.h" +#include "chrome/browser/ui/tabs/tab_strip_api/types/node_id.h" +#include "components/tab_groups/tab_group_visual_data.h" +#include "mojo/public/cpp/bindings/receiver_set.h" +#include "mojo/public/cpp/bindings/remote_set.h" + +class BrowserWindowInterface; +class TabStripModel; + +// TODO (crbug.com/409086859). See bug for dd. +// tabs_api::mojom::TabStripController is an experimental TabStrip Api between +// any view and the TabStripModel. +class TabStripServiceMojoHandler + : public tabs_api::mojom::TabStripService, + public tabs_api::mojom::TabStripExperimentService, + public TabStripModelObserver, + public TabStripServiceRegister { + public: + TabStripServiceMojoHandler(BrowserWindowInterface* browser, + TabStripModel* tab_strip_model); + TabStripServiceMojoHandler( + std::unique_ptr<tabs_api::TabStripService> service, + std::unique_ptr<tabs_api::TabStripModelAdapter> tab_strip_model_adapter); + TabStripServiceMojoHandler(const TabStripServiceMojoHandler&&) = delete; + TabStripServiceMojoHandler& operator=(const TabStripServiceMojoHandler&) = + delete; + ~TabStripServiceMojoHandler() override; + + // TabStripServiceregister overrides + void Accept( + mojo::PendingReceiver<tabs_api::mojom::TabStripService> client) override; + void AcceptExperimental( + mojo::PendingReceiver<tabs_api::mojom::TabStripExperimentService> client) + override; + + // tabs_api::mojom::TabStripService overrides + void GetTabs(GetTabsCallback callback) override; + void GetTab(const tabs_api::NodeId& id, GetTabCallback callback) override; + void CreateTabAt(const std::optional<tabs_api::Position>& pos, + const std::optional<GURL>& url, + CreateTabAtCallback callback) override; + void CloseTabs(const std::vector<tabs_api::NodeId>& ids, + CloseTabsCallback callback) override; + void ActivateTab(const tabs_api::NodeId& id, + ActivateTabCallback callback) override; + void SetSelectedTabs(const std::vector<tabs_api::NodeId>& selection, + const tabs_api::NodeId& tab_to_activate, + SetSelectedTabsCallback callback) override; + void MoveTab(const tabs_api::NodeId& id, + const tabs_api::Position& position, + MoveTabCallback callback) override; + + // tabs_api::mojom::TabStripExperimentalService overrides + // + // TabStripExperimentalService is intended for quick prototyping for + // experimental apis that may not necessarily fit in the standard + // TabStripService. + void UpdateTabGroupVisual(const tabs_api::NodeId& id, + const tab_groups::TabGroupVisualData& visual_data, + UpdateTabGroupVisualCallback) override; + + tabs_api::TabStripService* GetTabStripService() const; + + private: + void BroadcastEvents( + const std::vector<tabs_api::events::Event>& events) const; + + std::unique_ptr<tabs_api::TabStripService> tab_strip_service_; + std::unique_ptr<tabs_api::TabStripModelAdapter> tab_strip_model_adapter_; + std::unique_ptr<tabs_api::events::TabStripEventRecorder> recorder_; + + // Mojo plumbing. + mojo::ReceiverSet<tabs_api::mojom::TabStripService> clients_; + mojo::ReceiverSet<tabs_api::mojom::TabStripExperimentService> + experiment_clients_; + mojo::AssociatedRemoteSet<tabs_api::mojom::TabsObserver> observers_; +}; + +#endif // CHROME_BROWSER_UI_TABS_TAB_STRIP_API_TAB_STRIP_SERVICE_MOJO_HANDLER_H_
diff --git a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.cc b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.cc deleted file mode 100644 index 71fca9c1..0000000 --- a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.cc +++ /dev/null
@@ -1,309 +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. - -#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.h" - -#include <algorithm> -#include <optional> -#include <utility> - -#include "base/strings/string_number_conversions.h" -#include "base/types/expected.h" -#include "chrome/browser/ui/browser_tabstrip.h" -#include "chrome/browser/ui/browser_window/public/browser_window_interface.h" -#include "chrome/browser/ui/tabs/tab_strip_api/adapters/browser_adapter_impl.h" -#include "chrome/browser/ui/tabs/tab_strip_api/adapters/tab_strip_model_adapter_impl.h" -#include "chrome/browser/ui/tabs/tab_strip_api/converters/tab_converters.h" -#include "chrome/browser/ui/tabs/tab_strip_api/event_broadcaster.h" -#include "chrome/browser/ui/tabs/tab_strip_api/events/tab_strip_event_recorder.h" -#include "chrome/browser/ui/tabs/tab_strip_model.h" -#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h" -#include "mojo/public/mojom/base/error.mojom.h" -#include "url/gurl.h" - -namespace tabs_api { - -TabStripServiceSyncImpl::TabStripServiceSyncImpl( - BrowserWindowInterface* browser, - TabStripModel* tab_strip_model) - : TabStripServiceSyncImpl( - std::make_unique<tabs_api::BrowserAdapterImpl>(browser), - std::make_unique<tabs_api::TabStripModelAdapterImpl>( - tab_strip_model)) {} - -TabStripServiceSyncImpl::TabStripServiceSyncImpl( - std::unique_ptr<BrowserAdapter> browser_adapter, - std::unique_ptr<TabStripModelAdapter> tab_strip_model_adapter) - : browser_adapter_(std::move(browser_adapter)), - tab_strip_model_adapter_(std::move(tab_strip_model_adapter)) {} - -TabStripServiceSyncImpl::~TabStripServiceSyncImpl() = default; - -TabStripService::GetTabsResult TabStripServiceSyncImpl::GetTabs() { - return tab_strip_model_adapter_->GetTabStripTopology(); -} - -mojom::TabStripService::GetTabResult TabStripServiceSyncImpl::GetTab( - const tabs_api::NodeId& tab_mojom_id) { - if (tab_mojom_id.Type() != tabs_api::NodeId::Type::kContent) { - return base::unexpected( - mojo_base::mojom::Error::New(mojo_base::mojom::Code::kInvalidArgument, - "only tab content ids accepted")); - } - - int32_t tab_id; - if (!base::StringToInt(tab_mojom_id.Id(), &tab_id)) { - return base::unexpected(mojo_base::mojom::Error::New( - mojo_base::mojom::Code::kInvalidArgument, "invalid tab id provided")); - } - - tabs_api::mojom::TabPtr tab_result; - // TODO (crbug.com/412709270) TabStripModel or TabCollections should have an - // api that can fetch id without of relying on indexes. - auto tabs = tab_strip_model_adapter_->GetTabs(); - for (unsigned int i = 0; i < tabs.size(); ++i) { - auto& handle = tabs.at(i); - if (tab_id == handle.raw_value()) { - auto renderer_data = tab_strip_model_adapter_->GetTabRendererData(i); - const ui::ColorProvider& color_provider = - tab_strip_model_adapter_->GetColorProvider(); - tab_result = tabs_api::converters::BuildMojoTab( - handle, renderer_data, color_provider, - tab_strip_model_adapter_->GetTabStates(handle)); - } - } - - if (tab_result) { - return std::move(tab_result); - } else { - return base::unexpected(mojo_base::mojom::Error::New( - mojo_base::mojom::Code::kNotFound, "Tab not found")); - } -} - -mojom::TabStripService::CreateTabAtResult TabStripServiceSyncImpl::CreateTabAt( - const std::optional<tabs_api::Position>& pos, - const std::optional<GURL>& url) { - GURL target_url; - if (url.has_value()) { - target_url = url.value(); - } - std::optional<int> index; - if (pos.has_value()) { - // TODO(crbug.com/409086859): Does not use the parent_id yet. Currently only - // inserts in the unpinned collection. - index = pos->index(); - } - - auto tab_handle = browser_adapter_->AddTabAt(target_url, index); - if (tab_handle == tabs::TabHandle::Null()) { - // Missing content can happen for a number of reasons. i.e. If the profile - // is shutting down or if navigation requests are blocked due to some - // internal state. This is usually because the browser is not in the - // required state to perform the action. - return base::unexpected(mojo_base::mojom::Error::New( - mojo_base::mojom::Code::kInternal, "Failed to create WebContents")); - } - - auto tab_index = tab_strip_model_adapter_->GetIndexForHandle(tab_handle); - if (!tab_index.has_value()) { - return base::unexpected(mojo_base::mojom::Error::New( - mojo_base::mojom::Code::kInternal, - "Could not find the index of the newly created tab")); - } - - auto renderer_data = - tab_strip_model_adapter_->GetTabRendererData(tab_index.value()); - const ui::ColorProvider& color_provider = - tab_strip_model_adapter_->GetColorProvider(); - auto mojo_tab = tabs_api::converters::BuildMojoTab( - tab_handle, renderer_data, color_provider, - tab_strip_model_adapter_->GetTabStates(tab_handle)); - return mojo_tab->Clone(); -} - -mojom::TabStripService::CloseTabsResult TabStripServiceSyncImpl::CloseTabs( - const std::vector<tabs_api::NodeId>& ids) { - std::vector<int32_t> tab_content_targets; - for (const auto& id : ids) { - if (id.Type() != tabs_api::NodeId::Type::kContent) { - return base::unexpected(mojo_base::mojom::Error::New( - mojo_base::mojom::Code::kUnimplemented, - "only content tab closing has been implemented right now")); - } - int32_t numeric_id; - if (!base::StringToInt(id.Id(), &numeric_id)) { - return base::unexpected(mojo_base::mojom::Error::New( - mojo_base::mojom::Code::kInvalidArgument, "invalid tab content id")); - } - tab_content_targets.push_back(numeric_id); - } - - std::vector<size_t> tab_strip_indices; - // Transform targets from ids to indices in the tabstrip. - for (auto target : tab_content_targets) { - auto target_idx = - tab_strip_model_adapter_->GetIndexForHandle(tabs::TabHandle(target)); - if (!target_idx.has_value()) { - return base::unexpected(mojo_base::mojom::Error::New( - mojo_base::mojom::Code::kNotFound, "could not find the a tab")); - } - tab_strip_indices.push_back(target_idx.value()); - } - - // Close from last to first, that way the removals won't change the index of - // the next target. - std::ranges::sort(tab_strip_indices, std::ranges::greater()); - for (auto idx : tab_strip_indices) { - tab_strip_model_adapter_->CloseTab(idx); - } - - return std::monostate(); -} - -mojom::TabStripService::ActivateTabResult TabStripServiceSyncImpl::ActivateTab( - const tabs_api::NodeId& id) { - if (id.Type() != tabs_api::NodeId::Type::kContent) { - return base::unexpected( - mojo_base::mojom::Error::New(mojo_base::mojom::Code::kInvalidArgument, - "only a content tab id can be provided")); - } - - int32_t handle_id; - if (!base::StringToInt(id.Id(), &handle_id)) { - return base::unexpected(mojo_base::mojom::Error::New( - mojo_base::mojom::Code::kInvalidArgument, "id is malformed")); - } - - auto maybe_idx = - tab_strip_model_adapter_->GetIndexForHandle(tabs::TabHandle(handle_id)); - if (!maybe_idx.has_value()) { - return base::unexpected(mojo_base::mojom::Error::New( - mojo_base::mojom::Code::kNotFound, "tab not found")); - } - - tab_strip_model_adapter_->ActivateTab(maybe_idx.value()); - return std::monostate(); -} - -mojom::TabStripService::SetSelectedTabsResult -TabStripServiceSyncImpl::SetSelectedTabs( - const std::vector<tabs_api::NodeId>& selection, - const tabs_api::NodeId& tab_to_activate) { - if (std::find(selection.begin(), selection.end(), tab_to_activate) == - selection.end()) { - return base::unexpected(mojo_base::mojom::Error::New( - mojo_base::mojom::Code::kInvalidArgument, - "the selection must include the tab_to_activate")); - } - - auto is_not_content_id = [](tabs_api::NodeId id) { - return id.Type() != tabs_api::NodeId::Type::kContent; - }; - if (std::find_if(selection.begin(), selection.end(), is_not_content_id) != - selection.end()) { - return base::unexpected(mojo_base::mojom::Error::New( - mojo_base::mojom::Code::kInvalidArgument, - "the selection can only include content IDs")); - } - - std::vector<tabs::TabHandle> selection_handles; - for (auto& id : selection) { - int32_t handle_id; - if (!base::StringToInt(id.Id(), &handle_id)) { - return base::unexpected(mojo_base::mojom::Error::New( - mojo_base::mojom::Code::kInvalidArgument, "id is malformed")); - } - selection_handles.push_back(tabs::TabHandle(handle_id)); - } - - int32_t activate_handle_id; - if (!base::StringToInt(tab_to_activate.Id(), &activate_handle_id)) { - return base::unexpected(mojo_base::mojom::Error::New( - mojo_base::mojom::Code::kInvalidArgument, "activate id is malformed")); - } - - tab_strip_model_adapter_->SetTabSelection( - selection_handles, tabs::TabHandle(activate_handle_id)); - - return std::monostate(); -} - -mojom::TabStripService::MoveTabResult TabStripServiceSyncImpl::MoveTab( - const tabs_api::NodeId& id, - const tabs_api::Position& position) { - if (position.index() >= tab_strip_model_adapter_->GetTabs().size()) { - return base::unexpected( - mojo_base::mojom::Error::New(mojo_base::mojom::Code::kInvalidArgument, - "position cannot exceed tab strip")); - } - - switch (id.Type()) { - case tabs_api::NodeId::Type::kContent: { - std::optional<tabs::TabHandle> tab_handle = id.ToTabHandle(); - if (!tab_handle.has_value()) { - return base::unexpected(mojo_base::mojom::Error::New( - mojo_base::mojom::Code::kInvalidArgument, "id is malformed")); - } - // TODO(crbug.com/409086859): Add error handling for cases where a - // position's parent id is impossible to be moved to. - tab_strip_model_adapter_->MoveTab(tab_handle.value(), position); - break; - } - case tabs_api::NodeId::Type::kCollection: { - tab_strip_model_adapter_->MoveCollection(id, position); - break; - } - default: - return base::unexpected(mojo_base::mojom::Error::New( - mojo_base::mojom::Code::kInvalidArgument, "invalid node type")); - } - - return std::monostate(); -} - -// tabs_api::mojom::TabStripExperimentalService overrides -// -// TabStripExperimentalService is intended for quick prototyping for -// experimental apis that may not necessarily fit in the standard -// TabStripService. -mojom::TabStripExperimentService::UpdateTabGroupVisualResult -TabStripServiceSyncImpl::UpdateTabGroupVisual( - const tabs_api::NodeId& id, - const tab_groups::TabGroupVisualData& visual_data) { - if (id.Type() != tabs_api::NodeId::Type::kCollection) { - return base::unexpected(mojo_base::mojom::Error::New( - mojo_base::mojom::Code::kInvalidArgument, "id must be a collection")); - } - - const std::optional<tabs::TabCollectionHandle> collection_handle = - id.ToTabCollectionHandle(); - if (!collection_handle.has_value()) { - return base::unexpected(mojo_base::mojom::Error::New( - mojo_base::mojom::Code::kInvalidArgument, "id is malformed")); - } - - const std::optional<const tab_groups::TabGroupId> group_id = - tab_strip_model_adapter_->FindGroupIdFor(collection_handle.value()); - if (!group_id.has_value()) { - return base::unexpected( - mojo_base::mojom::Error::New(mojo_base::mojom::Code::kNotFound, - "group with the specified ID not found.")); - } - - tab_strip_model_adapter_->UpdateTabGroupVisuals(group_id.value(), - visual_data); - - return std::monostate(); -} - -void TabStripServiceSyncImpl::AddObserver(TabStripModelObserver* observer) { - tab_strip_model_adapter_->AddObserver(observer); -} - -void TabStripServiceSyncImpl::RemoveObserver(TabStripModelObserver* observer) { - tab_strip_model_adapter_->RemoveObserver(observer); -} - -} // namespace tabs_api
diff --git a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.h b/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.h deleted file mode 100644 index 0bb973b..0000000 --- a/chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_sync_impl.h +++ /dev/null
@@ -1,64 +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 CHROME_BROWSER_UI_TABS_TAB_STRIP_API_TAB_STRIP_SERVICE_SYNC_IMPL_H_ -#define CHROME_BROWSER_UI_TABS_TAB_STRIP_API_TAB_STRIP_SERVICE_SYNC_IMPL_H_ - -#include "chrome/browser/ui/tabs/tab_strip_api/adapters/browser_adapter.h" -#include "chrome/browser/ui/tabs/tab_strip_api/adapters/tab_strip_model_adapter.h" -#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_api.mojom.h" -#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_experiment_api.mojom.h" -#include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service.h" - -namespace tabs_api { - -class TabStripServiceSyncImpl : public TabStripService { - public: - TabStripServiceSyncImpl(BrowserWindowInterface* browser, - TabStripModel* tab_strip_model); - - TabStripServiceSyncImpl( - std::unique_ptr<BrowserAdapter> browser_adapter, - std::unique_ptr<TabStripModelAdapter> tab_strip_model_adapter); - - ~TabStripServiceSyncImpl() override; - - TabStripService::GetTabsResult GetTabs() override; - mojom::TabStripService::GetTabResult GetTab( - const tabs_api::NodeId& id) override; - mojom::TabStripService::CreateTabAtResult CreateTabAt( - const std::optional<tabs_api::Position>& pos, - const std::optional<GURL>& url) override; - mojom::TabStripService::CloseTabsResult CloseTabs( - const std::vector<tabs_api::NodeId>& ids) override; - mojom::TabStripService::ActivateTabResult ActivateTab( - const tabs_api::NodeId& id) override; - mojom::TabStripService::SetSelectedTabsResult SetSelectedTabs( - const std::vector<tabs_api::NodeId>& selection, - const tabs_api::NodeId& tab_to_activate) override; - mojom::TabStripService::MoveTabResult MoveTab( - const tabs_api::NodeId& id, - const tabs_api::Position& position) override; - - // tabs_api::mojom::TabStripExperimentalService overrides - // - // TabStripExperimentalService is intended for quick prototyping for - // experimental apis that may not necessarily fit in the standard - // TabStripService. - mojom::TabStripExperimentService::UpdateTabGroupVisualResult - UpdateTabGroupVisual( - const tabs_api::NodeId& id, - const tab_groups::TabGroupVisualData& visual_data) override; - - void AddObserver(TabStripModelObserver* observer) override; - void RemoveObserver(TabStripModelObserver* observer) override; - - private: - std::unique_ptr<tabs_api::BrowserAdapter> browser_adapter_; - std::unique_ptr<tabs_api::TabStripModelAdapter> tab_strip_model_adapter_; -}; - -} // namespace tabs_api - -#endif // CHROME_BROWSER_UI_TABS_TAB_STRIP_API_TAB_STRIP_SERVICE_SYNC_IMPL_H_
diff --git a/chrome/browser/ui/ui_features.cc b/chrome/browser/ui/ui_features.cc index a79370a..14ce803 100644 --- a/chrome/browser/ui/ui_features.cc +++ b/chrome/browser/ui/ui_features.cc
@@ -726,4 +726,15 @@ } #endif // !BUILDFLAG(IS_ANDROID) +#if BUILDFLAG(IS_ANDROID) +BASE_FEATURE(kAndroidAnimatedProgressBarInBrowser, + "AndroidAnimatedProgressBarInBrowser", + base::FEATURE_DISABLED_BY_DEFAULT); + +bool IsAndroidAnimatedProgressBarInBrowserEnabled() { + return base::FeatureList::IsEnabled( + features::kAndroidAnimatedProgressBarInBrowser); +} +#endif // BUILDFLAG(IS_ANDROID) + } // namespace features
diff --git a/chrome/browser/ui/ui_features.h b/chrome/browser/ui/ui_features.h index 3559187..c4cbd0b 100644 --- a/chrome/browser/ui/ui_features.h +++ b/chrome/browser/ui/ui_features.h
@@ -420,6 +420,12 @@ bool IsBookmarkTabGroupConversionEnabled(); +#if BUILDFLAG(IS_ANDROID) +BASE_DECLARE_FEATURE(kAndroidAnimatedProgressBarInBrowser); + +bool IsAndroidAnimatedProgressBarInBrowserEnabled(); +#endif // BUILDFLAG(IS_ANDROID) + } // namespace features #endif // CHROME_BROWSER_UI_UI_FEATURES_H_
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc index a6031f9..c8b2a11 100644 --- a/chrome/browser/ui/views/frame/browser_view.cc +++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -999,10 +999,10 @@ toolbar_ = top_container_->AddChildView( std::make_unique<ToolbarView>(browser_.get(), this)); - contents_separator_ = + top_container_separator_ = top_container_->AddChildView(std::make_unique<ContentsSeparator>()); - contents_separator_->SetProperty(views::kElementIdentifierKey, - kContentsSeparatorViewElementId); + top_container_separator_->SetProperty(views::kElementIdentifierKey, + kContentsSeparatorTopEdgeElementId); contents_container_ = AddChildView(std::move(contents_container)); set_contents_view(contents_container_); @@ -1015,29 +1015,33 @@ AddChildView(std::move(vertical_tab_strip_container)); } - right_aligned_side_panel_separator_ = - AddChildView(std::make_unique<ContentsSeparator>()); - right_aligned_side_panel_separator_->SetProperty( - views::kElementIdentifierKey, - kRightAlignedSidePanelSeparatorViewElementId); - const bool is_right_aligned = GetProfile()->GetPrefs()->GetBoolean( prefs::kSidePanelHorizontalAlignment); unified_side_panel_ = AddChildView(std::make_unique<SidePanel>( this, is_right_aligned ? SidePanel::HorizontalAlignment::kRight : SidePanel::HorizontalAlignment::kLeft)); - left_aligned_side_panel_separator_ = - AddChildView(std::make_unique<ContentsSeparator>()); - left_aligned_side_panel_separator_->SetProperty( - views::kElementIdentifierKey, - kLeftAlignedSidePanelSeparatorViewElementId); - side_panel_rounded_corner_ = - AddChildView(std::make_unique<ContentsRoundedCorner>( - this, views::ShapeContextTokens::kContentSeparatorRadius, - base::BindRepeating(&SidePanel::IsRightAligned, - base::Unretained(unified_side_panel_)))); - side_panel_rounded_corner_->SetProperty(views::kElementIdentifierKey, - kSidePanelRoundedCornerViewElementId); + + // `MultiContentsView` owns separators when `SideBySide` is enabled. + if (!multi_contents_view_) { + right_aligned_side_panel_separator_ = + AddChildView(std::make_unique<ContentsSeparator>()); + right_aligned_side_panel_separator_->SetProperty( + views::kElementIdentifierKey, + kRightAlignedSidePanelSeparatorViewElementId); + + left_aligned_side_panel_separator_ = + AddChildView(std::make_unique<ContentsSeparator>()); + left_aligned_side_panel_separator_->SetProperty( + views::kElementIdentifierKey, + kLeftAlignedSidePanelSeparatorViewElementId); + side_panel_rounded_corner_ = + AddChildView(std::make_unique<ContentsRoundedCorner>( + this, views::ShapeContextTokens::kContentSeparatorRadius, + base::BindRepeating(&SidePanel::IsRightAligned, + base::Unretained(unified_side_panel_)))); + side_panel_rounded_corner_->SetProperty( + views::kElementIdentifierKey, kSidePanelRoundedCornerViewElementId); + } // InfoBarContainer needs to be added as a child here for drop-shadow, but // needs to come after toolbar in focus order (see EnsureFocusOrder()). @@ -1127,7 +1131,7 @@ webui_tab_strip_ = nullptr; toolbar_ = nullptr; - contents_separator_ = nullptr; + top_container_separator_ = nullptr; loading_bar_ = nullptr; find_bar_host_view_ = nullptr; infobar_container_ = nullptr; @@ -2788,8 +2792,6 @@ is_right_aligned ? SidePanel::HorizontalAlignment::kRight : SidePanel::HorizontalAlignment::kLeft); GetBrowserViewLayout()->Layout(this); - side_panel_rounded_corner_->DeprecatedLayoutImmediately(); - side_panel_rounded_corner_->SchedulePaint(); } void BrowserView::FocusBookmarksToolbar() { @@ -5209,7 +5211,7 @@ contents_container_, multi_contents_view_, left_aligned_side_panel_separator_, unified_side_panel_, right_aligned_side_panel_separator_, side_panel_rounded_corner_, - contents_separator_)); + top_container_separator_)); browser_view_layout->SetUseBrowserContentMinimumSize( ShouldUseBrowserContentMinimumSize());
diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h index 75cf121..8393cf9 100644 --- a/chrome/browser/ui/views/frame/browser_view.h +++ b/chrome/browser/ui/views/frame/browser_view.h
@@ -1186,7 +1186,11 @@ std::unique_ptr<TabSearchBubbleHost> tab_search_bubble_host_; // Separator between top container and contents. - raw_ptr<views::View> contents_separator_ = nullptr; + // Note: when `SideBySide` feature is disabled, this separator is also + // used when not in `TopContainerOverlayView. Once the feature is fully + // rolled out, we can rely on `MultiContentsView` to manage the contents + // separator when not overlaid (i.e. no immersive fullscreen). + raw_ptr<views::View> top_container_separator_ = nullptr; // Loading bar (part of top container for / WebUI tab strip). raw_ptr<TopContainerLoadingBar> loading_bar_ = nullptr; @@ -1225,6 +1229,9 @@ // Conceptually this member should exist if and only if the // side_panel_coordinator is created. raw_ptr<SidePanel> unified_side_panel_ = nullptr; + + // These are only non-null when the `SideBySide` feature is disabled. + // Otherwise, `multi_contents_view_` will create its own separators. raw_ptr<views::View> right_aligned_side_panel_separator_ = nullptr; raw_ptr<views::View> left_aligned_side_panel_separator_ = nullptr; raw_ptr<views::View> side_panel_rounded_corner_ = nullptr;
diff --git a/chrome/browser/ui/views/frame/browser_view_layout.cc b/chrome/browser/ui/views/frame/browser_view_layout.cc index 84ae670..ec24160 100644 --- a/chrome/browser/ui/views/frame/browser_view_layout.cc +++ b/chrome/browser/ui/views/frame/browser_view_layout.cc
@@ -48,6 +48,7 @@ #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/size.h" +#include "ui/views/controls/separator.h" #include "ui/views/controls/webview/webview.h" #include "ui/views/view.h" #include "ui/views/view_utils.h" @@ -245,7 +246,7 @@ views::View* unified_side_panel, views::View* right_aligned_side_panel_separator, views::View* side_panel_rounded_corner, - views::View* contents_separator) + views::View* top_container_separator) : delegate_(std::move(delegate)), browser_view_(browser_view), window_scrim_(window_scrim), @@ -258,11 +259,11 @@ infobar_container_(infobar_container), contents_container_(contents_container), multi_contents_view_(multi_contents_view), - left_aligned_side_panel_separator_(left_aligned_side_panel_separator), unified_side_panel_(unified_side_panel), + left_aligned_side_panel_separator_(left_aligned_side_panel_separator), right_aligned_side_panel_separator_(right_aligned_side_panel_separator), side_panel_rounded_corner_(side_panel_rounded_corner), - contents_separator_(contents_separator), + top_container_separator_(top_container_separator), tab_strip_(tab_strip_region_view_->tab_strip()), dialog_host_(std::make_unique<BrowserModalDialogHostViews>(this)) {} @@ -605,11 +606,30 @@ if (delegate_->IsContentsSeparatorEnabled() && (toolbar_->GetVisible() || bookmark_bar_) && available_bounds.y() > 0) { - SetViewVisibility(contents_separator_, true); - const int separator_height = - contents_separator_->GetPreferredSize().height(); - contents_separator_->SetBounds(available_bounds.x(), available_bounds.y(), - available_bounds.width(), separator_height); + int separator_height = 0; + if (multi_contents_view_) { + const bool show_overlay_contents_separator = + delegate_->GetImmersiveModeController()->IsEnabled(); + if (show_overlay_contents_separator) { + separator_height = + top_container_separator_->GetPreferredSize().height(); + SetViewVisibility(top_container_separator_, true); + top_container_separator_->SetBounds( + available_bounds.x(), available_bounds.y(), + available_bounds.width(), separator_height); + } + // If the loading bar will be shown, it's supposed to replace the + // separator. + multi_contents_view_->SetShouldShowTopSeparator( + !show_overlay_contents_separator && !loading_bar_); + } else { + separator_height = top_container_separator_->GetPreferredSize().height(); + SetViewVisibility(top_container_separator_, true); + top_container_separator_->SetBounds( + available_bounds.x(), available_bounds.y(), available_bounds.width(), + separator_height); + } + if (loading_bar_) { SetViewVisibility(loading_bar_, true); loading_bar_->SetBounds(available_bounds.x(), available_bounds.y() - 2, @@ -619,7 +639,10 @@ } available_bounds.set_y(available_bounds.y() + separator_height); } else { - SetViewVisibility(contents_separator_, false); + SetViewVisibility(top_container_separator_, false); + if (multi_contents_view_) { + multi_contents_view_->SetShouldShowTopSeparator(false); + } if (loading_bar_) { SetViewVisibility(loading_bar_, false); } @@ -649,7 +672,7 @@ // TODO(crbug.com/41344902): Remove once the pixel canvas is enabled on // all aura platforms. if (top_container_ == bookmark_bar_->parent()) { - top_container_->ReorderChildView(contents_separator_, + top_container_->ReorderChildView(top_container_separator_, top_container_->children().size()); } } @@ -729,9 +752,10 @@ views::View* side_panel_separator = side_panel_right_aligned ? right_aligned_side_panel_separator_.get() : left_aligned_side_panel_separator_.get(); - CHECK(side_panel_separator); const int separator_width = - is_in_split ? 0 : side_panel_separator->GetPreferredSize().width(); + is_in_split || !side_panel_separator + ? 0 + : side_panel_separator->GetPreferredSize().width(); // Side panel occupies some of the container's space. The side panel should // never occupy more space than is available in the content window, and @@ -827,39 +851,46 @@ if (unified_side_panel_) { unified_side_panel_->SetBoundsRect(layout_result.side_panel_bounds); } - if (right_aligned_side_panel_separator_) { + + if (multi_contents_view_) { + multi_contents_view_->SetShouldShowLeadingSeparator( + layout_result.side_panel_visible && !is_in_split && + (layout_result.side_panel_right_aligned == base::i18n::IsRTL())); + + multi_contents_view_->SetShouldShowTrailingSeparator( + layout_result.side_panel_visible && !is_in_split && + (layout_result.side_panel_right_aligned != base::i18n::IsRTL())); + } else { SetViewVisibility(right_aligned_side_panel_separator_, layout_result.side_panel_visible && layout_result.side_panel_right_aligned && !is_in_split); right_aligned_side_panel_separator_->SetBoundsRect( layout_result.separator_bounds); - } - if (left_aligned_side_panel_separator_) { SetViewVisibility(left_aligned_side_panel_separator_, layout_result.side_panel_visible && !layout_result.side_panel_right_aligned && !is_in_split); left_aligned_side_panel_separator_->SetBoundsRect( layout_result.separator_bounds); - } - if (side_panel_rounded_corner_) { SetViewVisibility(side_panel_rounded_corner_, layout_result.side_panel_visible && !is_in_split); if (layout_result.side_panel_visible) { // Adjust the rounded corner bounds based on the side panel bounds. const int corner_size = side_panel_rounded_corner_->GetPreferredSize().width(); + + const int top_separator_height = views::Separator::kThickness; if (layout_result.contents_container_after_side_panel) { side_panel_rounded_corner_->SetBounds( layout_result.side_panel_bounds.right(), - layout_result.side_panel_bounds.y() - views::Separator::kThickness, + layout_result.side_panel_bounds.y() - top_separator_height, corner_size, corner_size); } else { side_panel_rounded_corner_->SetBounds( layout_result.side_panel_bounds.x() - corner_size, - layout_result.side_panel_bounds.y() - views::Separator::kThickness, + layout_result.side_panel_bounds.y() - top_separator_height, corner_size, corner_size); } } @@ -909,7 +940,9 @@ int min_width = kMainBrowserContentsMinimumWidth - unified_side_panel_->GetMinimumSize().width() - - right_aligned_side_panel_separator_->GetPreferredSize().width(); + (right_aligned_side_panel_separator_ + ? right_aligned_side_panel_separator_->GetPreferredSize().width() + : 0); // When in split view, the minimum width of the contents is higher. if (base::FeatureList::IsEnabled(features::kSideBySide)) {
diff --git a/chrome/browser/ui/views/frame/browser_view_layout.h b/chrome/browser/ui/views/frame/browser_view_layout.h index 8132c2d9..6fffdc1 100644 --- a/chrome/browser/ui/views/frame/browser_view_layout.h +++ b/chrome/browser/ui/views/frame/browser_view_layout.h
@@ -66,7 +66,7 @@ views::View* unified_side_panel, views::View* right_aligned_side_panel_separator, views::View* side_panel_rounded_corner, - views::View* contents_separator); + views::View* top_container_separator); BrowserViewLayout(const BrowserViewLayout&) = delete; BrowserViewLayout& operator=(const BrowserViewLayout&) = delete; @@ -164,11 +164,20 @@ const raw_ptr<InfoBarContainerView> infobar_container_; const raw_ptr<views::View> contents_container_; const raw_ptr<MultiContentsView> multi_contents_view_; - const raw_ptr<views::View> left_aligned_side_panel_separator_; const raw_ptr<views::View> unified_side_panel_; + + // TODO(crbug.com/424236535): These can be removed once `SideBySide` is + // launched. + const raw_ptr<views::View> left_aligned_side_panel_separator_; const raw_ptr<views::View> right_aligned_side_panel_separator_; const raw_ptr<views::View> side_panel_rounded_corner_; - const raw_ptr<views::View> contents_separator_; + + // The contents separator used for when the top container is overlaid. + // Note: when `SideBySide` feature is disabled, this separator is also + // used when not overlaid. Once the feature is fully rolled out, we can + // rely on `MultiContentsView` to manage the contents separator when not + // overlaid (i.e. no immersive fullscreen). + const raw_ptr<views::View> top_container_separator_ = nullptr; // These views are dynamically set. raw_ptr<views::View> webui_tab_strip_ = nullptr;
diff --git a/chrome/browser/ui/views/frame/browser_view_layout_unittest.cc b/chrome/browser/ui/views/frame/browser_view_layout_unittest.cc index 65b4dc5..dae1cd1 100644 --- a/chrome/browser/ui/views/frame/browser_view_layout_unittest.cc +++ b/chrome/browser/ui/views/frame/browser_view_layout_unittest.cc
@@ -182,6 +182,10 @@ infobar_container_ = browser_view_->AddChildView( std::make_unique<InfoBarContainerView>(nullptr)); + left_aligned_side_panel_separator_ = + browser_view_->AddChildView(std::make_unique<views::Separator>()); + right_aligned_side_panel_separator_ = + browser_view_->AddChildView(std::make_unique<views::Separator>()); side_panel_rounded_corner_ = browser_view_->AddChildView(CreateFixedSizeView(gfx::Size(16, 16))); @@ -213,10 +217,8 @@ /*web_app_window_title=*/nullptr, tab_strip_region_view_, /*vertical_tab_strip_container=*/nullptr, toolbar_, infobar_container_, contents_container_, - /*multi_contents_view=*/nullptr, - /*left_aligned_side_panel_separator=*/nullptr, - /*unified_side_panel=*/nullptr, - /*right_aligned_side_panel_separator=*/nullptr, + /*multi_contents_view=*/nullptr, left_aligned_side_panel_separator_, + /*unified_side_panel=*/nullptr, right_aligned_side_panel_separator_, side_panel_rounded_corner_, separator_); layout->set_webui_tab_strip(webui_tab_strip()); layout_ = layout.get(); @@ -238,6 +240,8 @@ toolbar_ = nullptr; separator_ = nullptr; infobar_container_ = nullptr; + left_aligned_side_panel_separator_ = nullptr; + right_aligned_side_panel_separator_ = nullptr; side_panel_rounded_corner_ = nullptr; contents_container_ = nullptr; contents_web_view_ = nullptr; @@ -269,6 +273,8 @@ raw_ptr<views::View> toolbar_; raw_ptr<views::Separator> separator_; raw_ptr<InfoBarContainerView> infobar_container_; + raw_ptr<views::View> left_aligned_side_panel_separator_; + raw_ptr<views::View> right_aligned_side_panel_separator_; raw_ptr<views::View> side_panel_rounded_corner_; raw_ptr<views::View> contents_container_; raw_ptr<views::View> contents_web_view_;
diff --git a/chrome/browser/ui/views/frame/multi_contents_view.cc b/chrome/browser/ui/views/frame/multi_contents_view.cc index b2ebcdc..e71df1a 100644 --- a/chrome/browser/ui/views/frame/multi_contents_view.cc +++ b/chrome/browser/ui/views/frame/multi_contents_view.cc
@@ -9,12 +9,15 @@ #include "base/check_deref.h" #include "base/feature_list.h" +#include "base/i18n/rtl.h" #include "base/notreached.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser_element_identifiers.h" #include "chrome/browser/ui/ui_features.h" #include "chrome/browser/ui/views/frame/browser_view.h" #include "chrome/browser/ui/views/frame/contents_container_view.h" +#include "chrome/browser/ui/views/frame/contents_rounded_corner.h" +#include "chrome/browser/ui/views/frame/contents_separator.h" #include "chrome/browser/ui/views/frame/contents_web_view.h" #include "chrome/browser/ui/views/frame/multi_contents_drop_target_view.h" #include "chrome/browser/ui/views/frame/multi_contents_resize_area.h" @@ -24,6 +27,7 @@ #include "chrome/browser/ui/views/frame/scrim_view.h" #include "chrome/browser/ui/views/frame/top_container_background.h" #include "chrome/browser/ui/views/new_tab_footer/footer_web_view.h" +#include "chrome/browser/ui/views/toolbar/toolbar_view.h" #include "chrome/common/pref_names.h" #include "components/prefs/pref_service.h" #include "content/public/browser/web_contents.h" @@ -34,6 +38,14 @@ #include "ui/ozone/public/ozone_platform.h" #include "ui/views/view_class_properties.h" +void MultiContentsView::ContentsSeparators::Reset() { + top_separator = nullptr; + leading_separator = nullptr; + trailing_separator = nullptr; + top_leading_rounded_corner = nullptr; + top_trailing_rounded_corner = nullptr; +} + MultiContentsView::MultiContentsView( BrowserView* browser_view, std::unique_ptr<MultiContentsViewDelegate> delegate) @@ -44,6 +56,8 @@ end_contents_view_inset_( gfx::Insets(kSplitViewContentInset).set_top(0).set_left(0)) { SetLayoutManager(std::make_unique<views::DelegatingLayoutManager>(this)); + SetProperty(views::kElementIdentifierKey, kMultiContentsViewElementId); + contents_container_views_.push_back( AddChildView(std::make_unique<ContentsContainerView>(browser_view_))); contents_container_views_[0] @@ -57,6 +71,43 @@ AddChildView(std::make_unique<ContentsContainerView>(browser_view_))); contents_container_views_[1]->SetVisible(false); + drop_target_view_ = + AddChildView(std::make_unique<MultiContentsDropTargetView>()); + drop_target_controller_ = + std::make_unique<MultiContentsViewDropTargetController>( + *drop_target_view_, *delegate_); + + contents_separators_.top_separator = + AddChildView(std::make_unique<ContentsSeparator>()); + contents_separators_.top_separator->SetProperty( + views::kElementIdentifierKey, kContentsSeparatorTopEdgeElementId); + + contents_separators_.leading_separator = + AddChildView(std::make_unique<ContentsSeparator>()); + contents_separators_.leading_separator->SetProperty( + views::kElementIdentifierKey, kContentsSeparatorLeadingEdgeElementId); + + contents_separators_.trailing_separator = + AddChildView(std::make_unique<ContentsSeparator>()); + contents_separators_.trailing_separator->SetProperty( + views::kElementIdentifierKey, kContentsSeparatorTrailingEdgeElementId); + + contents_separators_.top_leading_rounded_corner = + AddChildView(std::make_unique<ContentsRoundedCorner>( + browser_view_, views::ShapeContextTokens::kContentSeparatorRadius, + base::BindRepeating([]() { return base::i18n::IsRTL(); }))); + contents_separators_.top_leading_rounded_corner->SetProperty( + views::kElementIdentifierKey, + kContentsSeparatorLeadingTopCornerElementId); + + contents_separators_.top_trailing_rounded_corner = + AddChildView(std::make_unique<ContentsRoundedCorner>( + browser_view_, views::ShapeContextTokens::kContentSeparatorRadius, + base::BindRepeating([]() { return !base::i18n::IsRTL(); }))); + contents_separators_.top_trailing_rounded_corner->SetProperty( + views::kElementIdentifierKey, + kContentsSeparatorTrailingTopCornerElementId); + for (auto* contents_container_view : contents_container_views_) { web_contents_focused_subscriptions_.push_back( contents_container_view->contents_view()->AddWebContentsFocusedCallback( @@ -72,13 +123,6 @@ } } - SetProperty(views::kElementIdentifierKey, kMultiContentsViewElementId); - - drop_target_view_ = - AddChildView(std::make_unique<MultiContentsDropTargetView>()); - drop_target_controller_ = - std::make_unique<MultiContentsViewDropTargetController>( - *drop_target_view_, *delegate_); is_drag_drop_pref_enabled_ = browser_view_->GetProfile()->GetPrefs()->GetBoolean( prefs::kSplitViewDragAndDropEnabled); @@ -96,6 +140,7 @@ } drop_target_view_ = nullptr; resize_area_ = nullptr; + contents_separators_.Reset(); RemoveAllChildViews(); } @@ -131,7 +176,24 @@ const int drop_target_width = IsDragAndDropEnabled() ? drop_target_view_->GetPreferredWidth(width()) : 0; - return gfx::Size(width() - drop_target_width, height()); + const int separator_height = + contents_separators_.should_show_top + ? contents_separators_.top_separator->GetPreferredSize().height() + : 0; + + const int leading_separator_width = + contents_separators_.should_show_leading + ? contents_separators_.leading_separator->GetPreferredSize().height() + : 0; + + const int trailing_separator_width = + contents_separators_.should_show_trailing + ? contents_separators_.trailing_separator->GetPreferredSize().height() + : 0; + + return gfx::Size(width() - drop_target_width - leading_separator_width - + trailing_separator_width, + height() - separator_height); } bool MultiContentsView::IsInSplitView() const { @@ -347,11 +409,12 @@ if (!size_bounds.is_fully_bounded()) { return layouts; } + const int width = size_bounds.width().value(); + const int height = size_bounds.height().value(); - int height = size_bounds.height().value(); - int width = size_bounds.width().value(); + gfx::Rect available_space = CalculateSeparatorLayouts( + gfx::Rect(width, height), layouts.child_layouts); - const gfx::Rect available_space(width, height); ViewWidths widths = GetViewWidths(available_space); gfx::Rect drop_target_rect(widths.drop_target_width, @@ -406,6 +469,66 @@ return layouts; } +gfx::Rect MultiContentsView::CalculateSeparatorLayouts( + const gfx::Rect& available_space, + std::vector<views::ChildLayout>& child_layouts) const { + const int width = available_space.width(); + const int height = available_space.height(); + + const int separator_height = + contents_separators_.should_show_top + ? contents_separators_.top_separator->GetPreferredSize().height() + : 0; + child_layouts.emplace_back( + contents_separators_.top_separator.get(), + contents_separators_.should_show_top, + gfx::Rect(available_space.origin(), {width, separator_height})); + + const int leading_separator_width = + contents_separators_.should_show_leading + ? contents_separators_.leading_separator->GetPreferredSize().width() + : 0; + child_layouts.emplace_back( + contents_separators_.leading_separator.get(), + contents_separators_.should_show_leading, + gfx::Rect(available_space.origin(), {leading_separator_width, height})); + + const int trailing_separator_width = + contents_separators_.should_show_trailing + ? contents_separators_.trailing_separator->GetPreferredSize().width() + : 0; + child_layouts.emplace_back( + contents_separators_.trailing_separator.get(), + contents_separators_.should_show_trailing, + gfx::Rect(available_space.right() - trailing_separator_width, + available_space.y(), trailing_separator_width, height)); + + child_layouts.emplace_back( + contents_separators_.top_leading_rounded_corner.get(), + contents_separators_.should_show_leading && + contents_separators_.should_show_top, + gfx::Rect( + available_space.origin(), + contents_separators_.top_leading_rounded_corner->GetPreferredSize())); + + child_layouts.emplace_back( + contents_separators_.top_trailing_rounded_corner.get(), + contents_separators_.should_show_trailing && + contents_separators_.should_show_top, + gfx::Rect({available_space.right() - + contents_separators_.top_trailing_rounded_corner + ->GetPreferredSize() + .width(), + available_space.y()}, + contents_separators_.top_trailing_rounded_corner + ->GetPreferredSize())); + + return gfx::Rect(available_space.x() + leading_separator_width, + available_space.y() + separator_height, + width - trailing_separator_width - leading_separator_width, + height - separator_height); +} + MultiContentsView::ViewWidths MultiContentsView::GetViewWidths( gfx::Rect available_space) const { ViewWidths widths; @@ -488,5 +611,32 @@ InvalidateLayout(); } +void MultiContentsView::SetShouldShowTopSeparator(bool should_show) { + if (contents_separators_.should_show_top == should_show) { + return; + } + contents_separators_.should_show_top = should_show; + + InvalidateLayout(); +} + +void MultiContentsView::SetShouldShowLeadingSeparator(bool should_show) { + if (contents_separators_.should_show_leading == should_show) { + return; + } + contents_separators_.should_show_leading = should_show; + + InvalidateLayout(); +} + +void MultiContentsView::SetShouldShowTrailingSeparator(bool should_show) { + if (contents_separators_.should_show_trailing == should_show) { + return; + } + contents_separators_.should_show_trailing = should_show; + + InvalidateLayout(); +} + BEGIN_METADATA(MultiContentsView) END_METADATA
diff --git a/chrome/browser/ui/views/frame/multi_contents_view.h b/chrome/browser/ui/views/frame/multi_contents_view.h index 4613d278..37f59ce2 100644 --- a/chrome/browser/ui/views/frame/multi_contents_view.h +++ b/chrome/browser/ui/views/frame/multi_contents_view.h
@@ -10,6 +10,7 @@ #include "base/callback_list.h" #include "base/functional/callback_forward.h" +#include "base/gtest_prod_util.h" #include "base/memory/raw_ptr.h" #include "chrome/browser/ui/ui_features.h" #include "chrome/browser/ui/views/frame/contents_container_view.h" @@ -151,6 +152,10 @@ bool IsDragAndDropEnabled() const; void OnDragAndDropPrefStateChange(); + void SetShouldShowTopSeparator(bool should_show); + void SetShouldShowLeadingSeparator(bool should_show); + void SetShouldShowTrailingSeparator(bool should_show); + void set_min_contents_width_for_testing(int width) { min_contents_width_for_testing_ = std::make_optional(width); } @@ -172,6 +177,23 @@ } private: + FRIEND_TEST_ALL_PREFIXES(MultiContentsViewBrowserTest, SeparatorLayout); + + // Encapsulates the views required to draw a separator around contents. + struct ContentsSeparators { + void Reset(); + + raw_ptr<views::View> top_separator = nullptr; + raw_ptr<views::View> leading_separator = nullptr; + raw_ptr<views::View> trailing_separator = nullptr; + raw_ptr<views::View> top_leading_rounded_corner = nullptr; + raw_ptr<views::View> top_trailing_rounded_corner = nullptr; + + bool should_show_top = false; + bool should_show_leading = false; + bool should_show_trailing = false; + }; + static constexpr int kMinWebContentsWidth = 200; static constexpr double kMinWebContentsWidthPercentage = 0.1; @@ -179,6 +201,12 @@ views::ProposedLayout CalculateProposedLayout( const views::SizeBounds& size_bounds) const override; + // Adds separator layouts to the given list and returns the remaining + // space after the layout. + gfx::Rect CalculateSeparatorLayouts( + const gfx::Rect& available_space, + std::vector<views::ChildLayout>& child_layouts) const; + int GetInactiveIndex() const; void OnWebContentsFocused(views::WebView*); @@ -199,6 +227,8 @@ raw_ptr<BrowserView> browser_view_; std::unique_ptr<MultiContentsViewDelegate> delegate_; + ContentsSeparators contents_separators_; + // Holds ContentsContainerViews, when not in a split view the second // ContentsContainerView is not visible. std::vector<ContentsContainerView*> contents_container_views_;
diff --git a/chrome/browser/ui/views/frame/multi_contents_view_browsertest.cc b/chrome/browser/ui/views/frame/multi_contents_view_browsertest.cc index 7e18b56..f6bbac04 100644 --- a/chrome/browser/ui/views/frame/multi_contents_view_browsertest.cc +++ b/chrome/browser/ui/views/frame/multi_contents_view_browsertest.cc
@@ -38,6 +38,7 @@ #include "ui/base/ozone_buildflags.h" #include "ui/compositor/layer_tree_owner.h" #include "ui/ozone/public/ozone_platform.h" +#include "ui/views/controls/separator.h" #include "ui/views/interaction/element_tracker_views.h" #include "ui/views/view_utils.h" #include "url/gurl.h" @@ -517,3 +518,54 @@ // Should resized twice. EXPECT_EQ(GetResizeCount(split_tab), 2); } + +IN_PROC_BROWSER_TEST_F(MultiContentsViewBrowserTest, SeparatorLayout) { + MultiContentsView* view = multi_contents_view(); + view->SetShouldShowTrailingSeparator(true); + view->SetShouldShowLeadingSeparator(true); + view->SetShouldShowTopSeparator(true); + + gfx::Rect initial_bounds(10, 20, 100, 80); + std::vector<views::ChildLayout> actual_child_layouts; + + gfx::Rect remaining_space = + view->CalculateSeparatorLayouts(initial_bounds, actual_child_layouts); + + constexpr int kSeparatorThickness = views::Separator::kThickness; + + gfx::Rect expected_remaining_space( + initial_bounds.x() + kSeparatorThickness, + initial_bounds.y() + kSeparatorThickness, + initial_bounds.width() - 2 * kSeparatorThickness, + initial_bounds.height() - kSeparatorThickness); + EXPECT_EQ(expected_remaining_space, remaining_space); + + std::vector<views::ChildLayout> expected_separator_layouts; + expected_separator_layouts.emplace_back( + view->contents_separators_.top_separator.get(), true, + gfx::Rect(10, 20, 100, kSeparatorThickness)); + expected_separator_layouts.emplace_back( + view->contents_separators_.leading_separator.get(), true, + gfx::Rect(10, 20, kSeparatorThickness, 80)); + expected_separator_layouts.emplace_back( + view->contents_separators_.trailing_separator.get(), true, + gfx::Rect(10 + 100 - kSeparatorThickness, 20, kSeparatorThickness, 80)); + expected_separator_layouts.emplace_back( + view->contents_separators_.top_leading_rounded_corner.get(), true, + gfx::Rect(initial_bounds.origin(), + view->contents_separators_.top_leading_rounded_corner + ->GetPreferredSize())); + expected_separator_layouts.emplace_back( + view->contents_separators_.top_trailing_rounded_corner.get(), true, + gfx::Rect( + gfx::Point(initial_bounds.right() - + view->contents_separators_.top_trailing_rounded_corner + ->GetPreferredSize() + .width(), + initial_bounds.y()), + view->contents_separators_.top_trailing_rounded_corner + ->GetPreferredSize())); + + EXPECT_THAT(actual_child_layouts, + testing::UnorderedElementsAreArray(expected_separator_layouts)); +}
diff --git a/chrome/browser/ui/views/frame/multi_contents_view_interactive_uitest.cc b/chrome/browser/ui/views/frame/multi_contents_view_interactive_uitest.cc index 2f5f405..0012bd4 100644 --- a/chrome/browser/ui/views/frame/multi_contents_view_interactive_uitest.cc +++ b/chrome/browser/ui/views/frame/multi_contents_view_interactive_uitest.cc
@@ -230,7 +230,7 @@ ObserveState(kMultiContentsViewBoundsChangedObserver, browser()), SelectTab(kTabStripElementId, 2, InputType::kMouse), WaitForActiveTabChange(2), - CheckState(kMultiContentsViewBoundsChangedObserver, 1), + CheckState(kMultiContentsViewBoundsChangedObserver, 0), StopObservingState(kMultiContentsViewBoundsChangedObserver)); } @@ -636,17 +636,19 @@ // Verify expected contents separators are visible. Note, only one side // panel separator should be visible and the side panel is right aligned // by default. - WaitForShow(kContentsSeparatorViewElementId), - WaitForShow(kRightAlignedSidePanelSeparatorViewElementId), - WaitForHide(kLeftAlignedSidePanelSeparatorViewElementId), - WaitForShow(kSidePanelRoundedCornerViewElementId), + WaitForShow(kContentsSeparatorTopEdgeElementId), + WaitForShow(kContentsSeparatorTrailingEdgeElementId), + WaitForHide(kContentsSeparatorLeadingEdgeElementId), + WaitForShow(kContentsSeparatorTrailingTopCornerElementId), + WaitForHide(kContentsSeparatorLeadingTopCornerElementId), // Open split view. CreateTabsAndEnterSplitView(), // Verify no contents separators are visible. - WaitForHide(kContentsSeparatorViewElementId), - WaitForHide(kRightAlignedSidePanelSeparatorViewElementId), - WaitForHide(kLeftAlignedSidePanelSeparatorViewElementId), - WaitForHide(kSidePanelRoundedCornerViewElementId)); + WaitForHide(kContentsSeparatorTopEdgeElementId), + WaitForHide(kContentsSeparatorTrailingEdgeElementId), + WaitForHide(kContentsSeparatorLeadingEdgeElementId), + WaitForHide(kContentsSeparatorTrailingTopCornerElementId), + WaitForHide(kContentsSeparatorLeadingTopCornerElementId)); } IN_PROC_BROWSER_TEST_F(MultiContentsViewUiTest,
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc index ce82ddaf7..94456d6 100644 --- a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc +++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
@@ -2247,6 +2247,7 @@ } void OmniboxViewViews::OnDidPresentCompositorFrame( + ui::Compositor* compositor, uint32_t frame_token, const gfx::PresentationFeedback& feedback) { if (latency_histogram_state_ == COMPOSITING_STARTED) {
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.h b/chrome/browser/ui/views/omnibox/omnibox_view_views.h index 34c01b7..6c6c3329 100644 --- a/chrome/browser/ui/views/omnibox/omnibox_view_views.h +++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.h
@@ -300,6 +300,7 @@ void OnCompositingStarted(ui::Compositor* compositor, base::TimeTicks start_time) override; void OnDidPresentCompositorFrame( + ui::Compositor* compositor, uint32_t frame_token, const gfx::PresentationFeedback& feedback) override; void OnCompositingShuttingDown(ui::Compositor* compositor) override;
diff --git a/chrome/browser/ui/views/payments/shipping_address_editor_view_controller.cc b/chrome/browser/ui/views/payments/shipping_address_editor_view_controller.cc index 188bf94..217bd5e 100644 --- a/chrome/browser/ui/views/payments/shipping_address_editor_view_controller.cc +++ b/chrome/browser/ui/views/payments/shipping_address_editor_view_controller.cc
@@ -146,9 +146,9 @@ const variations::VariationsService* variations_service = g_browser_process->variations_service(); model->SetCountries( - GeoIpCountryCode(variations_service - ? variations_service->GetLatestCountry() - : std::string()), + autofill::GeoIpCountryCode( + variations_service ? variations_service->GetLatestCountry() + : std::string()), state()->GetApplicationLocale()); if (model->countries().size() != countries_.size()) { UpdateCountries(model.get()); @@ -402,9 +402,9 @@ const variations::VariationsService* variations_service = g_browser_process->variations_service(); local_model.SetCountries( - GeoIpCountryCode(variations_service - ? variations_service->GetLatestCountry() - : std::string()), + autofill::GeoIpCountryCode(variations_service + ? variations_service->GetLatestCountry() + : std::string()), state()->GetApplicationLocale()); model = &local_model; }
diff --git a/chrome/browser/ui/views/payments/shipping_address_editor_view_controller_browsertest.cc b/chrome/browser/ui/views/payments/shipping_address_editor_view_controller_browsertest.cc index f453748..3916a5c 100644 --- a/chrome/browser/ui/views/payments/shipping_address_editor_view_controller_browsertest.cc +++ b/chrome/browser/ui/views/payments/shipping_address_editor_view_controller_browsertest.cc
@@ -515,8 +515,8 @@ SelectingIncompleteAddress) { NavigateTo("/payment_request_dynamic_shipping_test.html"); // Add incomplete address. - autofill::AutofillProfile profile( - AddressCountryCode(base::UTF16ToUTF8(kCountryWithoutStatesCode))); + autofill::AutofillProfile profile(autofill::AddressCountryCode( + base::UTF16ToUTF8(kCountryWithoutStatesCode))); profile.SetInfo(autofill::NAME_FULL, kNameFull, kLocale); AddAutofillProfile(profile);
diff --git a/chrome/browser/ui/views/side_panel/BUILD.gn b/chrome/browser/ui/views/side_panel/BUILD.gn index 5d68e63..edb82ca 100644 --- a/chrome/browser/ui/views/side_panel/BUILD.gn +++ b/chrome/browser/ui/views/side_panel/BUILD.gn
@@ -94,6 +94,12 @@ "side_panel_web_ui_view.cc", "side_panel_web_ui_view.h", ] + if (enable_glic) { + sources += [ + "glic/glic_side_panel_coordinator.cc", + "glic/glic_side_panel_coordinator.h", + ] + } public_deps = [ "//base", "//chrome/browser:browser_public_dependencies",
diff --git a/chrome/browser/ui/views/side_panel/glic/OWNERS b/chrome/browser/ui/views/side_panel/glic/OWNERS new file mode 100644 index 0000000..f7b6e02 --- /dev/null +++ b/chrome/browser/ui/views/side_panel/glic/OWNERS
@@ -0,0 +1,2 @@ +perrier@chromium.org +file://chrome/browser/glic/OWNERS \ No newline at end of file
diff --git a/chrome/browser/glic/widget/glic_side_panel_coordinator.cc b/chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator.cc similarity index 65% rename from chrome/browser/glic/widget/glic_side_panel_coordinator.cc rename to chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator.cc index f295c43..57c2183 100644 --- a/chrome/browser/glic/widget/glic_side_panel_coordinator.cc +++ b/chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator.cc
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/glic/widget/glic_side_panel_coordinator.h" +#include "chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator.h" #include "base/functional/callback.h" #include "chrome/browser/glic/host/glic_ui.h" @@ -19,22 +19,32 @@ namespace glic { -GlicSidePanelCoordinator::GlicSidePanelCoordinator() = default; +GlicSidePanelCoordinator::GlicSidePanelCoordinator(Profile* profile) + : glic_service_(GlicKeyedServiceFactory::GetGlicKeyedService(profile)) {} void GlicSidePanelCoordinator::CreateAndRegisterEntry( SidePanelRegistry* global_registry) { - global_registry->Register(std::make_unique<SidePanelEntry>( + auto entry = std::make_unique<SidePanelEntry>( SidePanelEntry::Key(SidePanelEntry::Id::kGlic), base::BindRepeating(&GlicSidePanelCoordinator::CreateGlicWebView, base::Unretained(this)), - /*default_content_width_callback=*/base::NullCallback())); + /*default_content_width_callback=*/base::NullCallback()); + entry->AddObserver(this); + global_registry->Register(std::move(entry)); +} + +void GlicSidePanelCoordinator::OnEntryHidden(SidePanelEntry* entry) { + if (glic_service_) { + glic_service_->ClosePanel(); + } } std::unique_ptr<views::View> GlicSidePanelCoordinator::CreateGlicWebView( SidePanelEntryScope& scope) { - auto* const glic_service = GlicKeyedServiceFactory::GetGlicKeyedService( - scope.GetBrowserWindowInterface().GetProfile()); - return glic_service->window_controller().CreateGlicViewForSidePanel(); + if (!glic_service_) { + return nullptr; + } + return glic_service_->window_controller().CreateGlicViewForSidePanel(); } } // namespace glic
diff --git a/chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator.h b/chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator.h new file mode 100644 index 0000000..9aeab29b --- /dev/null +++ b/chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator.h
@@ -0,0 +1,47 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_GLIC_GLIC_SIDE_PANEL_COORDINATOR_H_ +#define CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_GLIC_GLIC_SIDE_PANEL_COORDINATOR_H_ + +#include <memory> + +#include "chrome/browser/ui/views/side_panel/side_panel_entry_observer.h" + +class Profile; + +class SidePanelEntryScope; +class SidePanelRegistry; + +namespace views { +class View; +} // namespace views + +namespace glic { + +class GlicKeyedService; + +// GlicSidePanelCoordinator handles the creation and registration of the +// glic SidePanelEntry. +class GlicSidePanelCoordinator : public SidePanelEntryObserver { + public: + explicit GlicSidePanelCoordinator(Profile* profile); + ~GlicSidePanelCoordinator() override = default; + + // Create and register the Glic side panel entry. + void CreateAndRegisterEntry(SidePanelRegistry* global_registry); + + // `SidePanelEntryObserver`: + void OnEntryHidden(SidePanelEntry* entry) override; + + private: + // Gets the Glic WebView from the Glic service. + std::unique_ptr<views::View> CreateGlicWebView(SidePanelEntryScope& scope); + + raw_ptr<GlicKeyedService> glic_service_; +}; + +} // namespace glic + +#endif // CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_GLIC_GLIC_SIDE_PANEL_COORDINATOR_H_
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 3fa4eb0..efefc8c 100644 --- a/chrome/browser/ui/views/side_panel/side_panel_util.cc +++ b/chrome/browser/ui/views/side_panel/side_panel_util.cc
@@ -33,7 +33,7 @@ #if BUILDFLAG(ENABLE_GLIC) #include "chrome/browser/glic/public/glic_enabling.h" -#include "chrome/browser/glic/widget/glic_side_panel_coordinator.h" +#include "chrome/browser/ui/views/side_panel/glic/glic_side_panel_coordinator.h" #endif // static
diff --git a/chrome/browser/ui/webui/feedback/feedback_dialog.cc b/chrome/browser/ui/webui/feedback/feedback_dialog.cc index a0dc384..4cbbc3f 100644 --- a/chrome/browser/ui/webui/feedback/feedback_dialog.cc +++ b/chrome/browser/ui/webui/feedback/feedback_dialog.cc
@@ -57,8 +57,7 @@ const extensions::api::feedback_private::FeedbackInfo& info) { if (current_instance_) { DCHECK(current_instance_->widget_); - const Profile* current_profile = - current_instance_->profile_keep_alive_.profile(); + Profile* current_profile = current_instance_->profile_keep_alive_.profile(); if (profile == current_profile) { // Focus the window hosting the dialog that has already been created. current_instance_->widget_->Show();
diff --git a/chrome/browser/ui/webui_browser/browser.mojom b/chrome/browser/ui/webui_browser/browser.mojom index e4700f1..ad622b6 100644 --- a/chrome/browser/ui/webui_browser/browser.mojom +++ b/chrome/browser/ui/webui_browser/browser.mojom
@@ -29,7 +29,10 @@ // Shows the side panel UI. The side panel contents is hosted by a guest // contents. - ShowSidePanel(int32 guest_contents_id); + ShowSidePanel(int32 guest_contents_id, string title); + + // Closes the side panel, destroys the webview. + CloseSidePanel(); }; // Browser-side handler for requests from WebUI page.
diff --git a/chrome/browser/ui/webui_browser/webui_browser_page_handler.cc b/chrome/browser/ui/webui_browser/webui_browser_page_handler.cc index c747b2c..7fd607b 100644 --- a/chrome/browser/ui/webui_browser/webui_browser_page_handler.cc +++ b/chrome/browser/ui/webui_browser/webui_browser_page_handler.cc
@@ -17,6 +17,7 @@ #include "chrome/browser/ui/views/profiles/profile_menu_coordinator.h" #include "chrome/browser/ui/views/tab_search_bubble_host.h" #include "chrome/browser/ui/views/toolbar/app_menu.h" +#include "chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.h" #include "chrome/browser/ui/webui_browser/webui_browser_ui.h" #include "components/guest_contents/browser/guest_contents_handle.h" #include "content/public/browser/devtools_agent_host.h" @@ -193,8 +194,7 @@ } void WebUIBrowserPageHandler::OnSidePanelClosed() { - // TODO(webium): Find side panel UI and call OnSidePanelClosed(). - NOTIMPLEMENTED(); + GetBrowserWindow()->GetWebUIBrowserSidePanelUI()->OnSidePanelClosed(); } void WebUIBrowserPageHandler::Minimize() {
diff --git a/chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.cc b/chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.cc index 24450ef..85c7cd4 100644 --- a/chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.cc +++ b/chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.cc
@@ -36,7 +36,19 @@ WebUIBrowserSidePanelUI::~WebUIBrowserSidePanelUI() = default; -void WebUIBrowserSidePanelUI::Close() {} +void WebUIBrowserSidePanelUI::Close() { + if (!current_key().has_value()) { + return; + } + + if (SidePanelEntry* entry = GetEntryForUniqueKey(*current_key())) { + entry->OnEntryWillHide(SidePanelEntryHideReason::kSidePanelClosed); + } + // Asynchronously close the side panel in webshell. + // WebUI then notifies the browser when the side panel is actually closed + // via OnSidePanelClosed(). + GetWebUIBrowserWindow()->CloseSidePanel(); +} void WebUIBrowserSidePanelUI::Toggle(SidePanelEntryKey key, SidePanelOpenTrigger open_trigger) {} @@ -55,7 +67,9 @@ void WebUIBrowserSidePanelUI::SetNoDelaysForTesting( bool no_delays_for_testing) {} -void WebUIBrowserSidePanelUI::Close(bool suppress_animations) {} +void WebUIBrowserSidePanelUI::Close(bool suppress_animations) { + Close(); +} content::WebContents* WebUIBrowserSidePanelUI::GetWebContentsForId( SidePanelEntryId entry_id) const { @@ -133,6 +147,35 @@ SidePanelRegistry* old_contextual_registry, SidePanelRegistry* new_contextual_registry) {} +void WebUIBrowserSidePanelUI::OnSidePanelClosed() { + if (!current_key()) { + return; + } + + const bool closing_global = !current_key()->tab_handle; + SidePanelEntry* previous_entry = GetEntryForUniqueKey(*current_key()); + set_current_key(std::nullopt); + previous_entry->OnEntryHidden(); + + // Reset active entry values for all observed registries and clear cache for + // everything except remaining active entries (i.e. if another tab has an + // active contextual entry). + if (auto* contextual_registry = GetActiveContextualRegistry()) { + contextual_registry->ResetActiveEntry(); + if (closing_global) { + // Reset last active entry in contextual registry as global entry should + // take precedence. + contextual_registry->ResetLastActiveEntry(); + } + } + + window_registry_->ResetActiveEntry(); + + current_side_panel_view_.reset(); + // TODO(webium): Clear cached views for registry entries for global and + // contextual registries. +} + WebUIBrowserWindow* WebUIBrowserSidePanelUI::GetWebUIBrowserWindow() { return static_cast<WebUIBrowserWindow*>(browser()->window()); }
diff --git a/chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.h b/chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.h index 38bbcdc2..6af90ce1 100644 --- a/chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.h +++ b/chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.h
@@ -33,6 +33,8 @@ content::WebContents* GetWebContentsForId(SidePanelEntryId entry_id) const; + void OnSidePanelClosed(); + private: // SidePanelUIBase: void Close(bool suppress_animations) override;
diff --git a/chrome/browser/ui/webui_browser/webui_browser_ui.cc b/chrome/browser/ui/webui_browser/webui_browser_ui.cc index f79ecfd..51697f6c 100644 --- a/chrome/browser/ui/webui_browser/webui_browser_ui.cc +++ b/chrome/browser/ui/webui_browser/webui_browser_ui.cc
@@ -11,6 +11,7 @@ #include "chrome/browser/ui/browser_window/public/browser_window_features.h" #include "chrome/browser/ui/interaction/browser_elements.h" #include "chrome/browser/ui/tabs/tab_strip_api/tab_strip_service_register.h" +#include "chrome/browser/ui/views/side_panel/side_panel_entry_id.h" #include "chrome/browser/ui/webui/searchbox/realbox_handler.h" #include "chrome/browser/ui/webui/searchbox/searchbox_handler.h" #include "chrome/browser/ui/webui_browser/bookmark_bar_page_handler.h" @@ -18,6 +19,7 @@ #include "chrome/browser/ui/webui_browser/webui_browser_page_handler.h" #include "chrome/browser/ui/webui_browser/webui_browser_side_panel_ui.h" #include "chrome/common/webui_url_constants.h" +#include "chrome/grit/generated_resources.h" #include "chrome/grit/tab_strip_api_resources_map.h" #include "chrome/grit/webui_browser_resources.h" #include "chrome/grit/webui_browser_resources_map.h" @@ -26,10 +28,38 @@ #include "content/public/browser/web_contents.h" #include "content/public/browser/web_ui.h" #include "content/public/browser/web_ui_data_source.h" +#include "ui/base/l10n/l10n_util.h" #include "ui/views/interaction/element_tracker_views.h" #include "ui/webui/tracked_element/tracked_element_handler.h" #include "ui/webui/webui_util.h" +namespace { + +std::string SidePanelEntryIdToTitle(SidePanelEntryId id) { + // TODO(webium): Ideally, the titles should be added to SIDE_PANEL_ENTRY_IDS + // macros in chrome/browser/ui/views/side_panel/side_panel_entry_id.h, and + // then this conversion function would be written in that same file, + // analogously to the other functions it contains. But it appears that some + // of the entry ids there are stale and no longer have a matching generated + // IDS_*_TITLE string that we can include. + // + // Since we are currently only explicitly supporting three side panel types, + // we do the id to title conversion here manually to minimize modification to + // existing code. + switch (id) { + case SidePanelEntryId::kCustomizeChrome: + return l10n_util::GetStringUTF8(IDS_SIDE_PANEL_CUSTOMIZE_CHROME_TITLE); + case SidePanelEntryId::kReadingList: + return l10n_util::GetStringUTF8(IDS_READ_LATER_TITLE); + case SidePanelEntryId::kBookmarks: + return l10n_util::GetStringUTF8(IDS_BOOKMARK_MANAGER_TITLE); + default: + NOTREACHED(); + } +} + +} // namespace + WebUIBrowserUIConfig::WebUIBrowserUIConfig() : DefaultWebUIConfig(content::kChromeUIScheme, chrome::kChromeUIWebuiBrowserHost) {} @@ -165,12 +195,12 @@ guest_contents::GuestContentsHandle::CreateForWebContents(web_contents); // Notify JS. - page_->ShowSidePanel(guest_handle->id()); + page_->ShowSidePanel(guest_handle->id(), + SidePanelEntryIdToTitle(side_panel_entry_key.id())); } void WebUIBrowserUI::CloseSidePanel() { - // TODO(webium): Create side panel and call page_->CloseSidePanel() - NOTIMPLEMENTED(); + page_->CloseSidePanel(); } WEB_UI_CONTROLLER_TYPE_IMPL(WebUIBrowserUI)
diff --git a/chrome/browser/ui/webui_browser/webui_browser_window.cc b/chrome/browser/ui/webui_browser/webui_browser_window.cc index 05b8412..f60a12a 100644 --- a/chrome/browser/ui/webui_browser/webui_browser_window.cc +++ b/chrome/browser/ui/webui_browser/webui_browser_window.cc
@@ -1079,6 +1079,10 @@ GetWebUIBrowserUI()->ShowSidePanel(side_panel_entry_key); } +void WebUIBrowserWindow::CloseSidePanel() { + GetWebUIBrowserUI()->CloseSidePanel(); +} + WebUIBrowserSidePanelUI* WebUIBrowserWindow::GetWebUIBrowserSidePanelUI() { return static_cast<WebUIBrowserSidePanelUI*>( browser_->browser_window_features()->side_panel_ui());
diff --git a/chrome/browser/ui/webui_browser/webui_browser_window.h b/chrome/browser/ui/webui_browser/webui_browser_window.h index e0f5dc45..78d128a 100644 --- a/chrome/browser/ui/webui_browser/webui_browser_window.h +++ b/chrome/browser/ui/webui_browser/webui_browser_window.h
@@ -261,6 +261,7 @@ ui::Accelerator* accelerator) const override; void ShowSidePanel(SidePanelEntryKey side_panel_entry_key); + void CloseSidePanel(); WebUIBrowserSidePanelUI* GetWebUIBrowserSidePanelUI();
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt index c7b19af..6c63f3e0 100644 --- a/chrome/build/android-arm64.pgo.txt +++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@ -chrome-android64-main-1756822069-de4bd7d7669f6757eebc12283731a9eadfb5bee8-28a1ee4d22e556527709ae0c833a2580a4f89f7e.profdata +chrome-android64-main-1756825951-f7fd9b0a650ff4846caaef4c59f4dec626487321-f77acfdc1b3b8ea06fc49db464df065c16664370.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt index 0127685..c6dd893b4 100644 --- a/chrome/build/win32.pgo.txt +++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@ -chrome-win32-main-1756803502-220b7b3c12588346e324a15b398e7a00e4fa5edb-dde15930c4bf0c6c6424b21021203dbd21508dfe.profdata +chrome-win32-main-1756814343-722eb9404d7007635e7225e5015e3d1054841f35-af4c4b4a345d0ad4a3f8aa22f873f21587a1a6e3.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt index 38203c0..ab0713ac 100644 --- a/chrome/build/win64.pgo.txt +++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@ -chrome-win64-main-1756803502-2c048ae3b0371bc55013ee081f39516aa2cf9532-dde15930c4bf0c6c6424b21021203dbd21508dfe.profdata +chrome-win64-main-1756814343-db57bdd082f29af593eb784b36bbd3d8f9c676e6-af4c4b4a345d0ad4a3f8aa22f873f21587a1a6e3.profdata
diff --git a/chrome/common/extensions/api/experimental_actor.idl b/chrome/common/extensions/api/experimental_actor.idl index 7f836ea..df72033 100644 --- a/chrome/common/extensions/api/experimental_actor.idl +++ b/chrome/common/extensions/api/experimental_actor.idl
@@ -10,22 +10,6 @@ callback TaskIdCallback = void(long taskId); interface Functions { - // Creates and starts a new task. By default, opens a new tab to - // about:blank that is ready for actions. The startTaskProto can - // optionally specify a tab_id to use an existing tab instead of creating - // a new one. - // startTaskProto: encoded optimization_guide.proto.BrowserStartTask - // startTaskcallback: - // encoded optimization_guide.proto.BrowserStartTaskResult - [deprecated="Use createTask instead"] static void startTask( - ArrayBuffer startTaskProto, - DataCallback startTaskcallback); - // Executes one or more actions according to request. - // request: encoded optimization_guide.proto.BrowserAction - // response: encoded optimization_guide.proto.BrowserActionResult - [deprecated="Use performActions instead"] static void executeAction( - ArrayBuffer browserActionProto, - DataCallback executeActioncallback); // Stops a task. // taskId: id of the task to stop. // stopTaskcallback: a closure that is called when the task is stopped.
diff --git a/chrome/test/data/webui/settings/privacy_page_index_test.ts b/chrome/test/data/webui/settings/privacy_page_index_test.ts index 5bf28e8..df61ed4 100644 --- a/chrome/test/data/webui/settings/privacy_page_index_test.ts +++ b/chrome/test/data/webui/settings/privacy_page_index_test.ts
@@ -18,6 +18,10 @@ loadTimeData.overrideValues(Object.assign( { + autoPictureInPictureEnabled: false, + capturedSurfaceControlEnabled: false, + enableExperimentalWebPlatformFeatures: false, + enableHandTrackingContentSetting: false, enableIncognitoTrackingProtections: false, enableKeyboardLockPrompt: false, enableLocalNetworkAccessSetting: false, @@ -89,16 +93,31 @@ parentViewId: 'old', }, { + route: routes.SITE_SETTINGS_AR, + viewId: 'siteSettingsAr', + parentViewId: 'old', + }, + { route: routes.SITE_SETTINGS_AUTOMATIC_FULLSCREEN, viewId: 'siteSettingsAutomaticFullscreen', parentViewId: 'old', }, { + route: routes.SITE_SETTINGS_IDLE_DETECTION, + viewId: 'siteSettingsIdleDetection', + parentViewId: 'old', + }, + { route: routes.SITE_SETTINGS_HANDLERS, viewId: 'siteSettingsHandlers', parentViewId: 'old', }, { + route: routes.SITE_SETTINGS_LOCAL_FONTS, + viewId: 'siteSettingsLocalFonts', + parentViewId: 'old', + }, + { route: routes.SITE_SETTINGS_LOCATION, viewId: 'siteSettingsLocation', parentViewId: 'old', @@ -119,6 +138,21 @@ parentViewId: 'old', }, { + route: routes.SITE_SETTINGS_STORAGE_ACCESS, + viewId: 'siteSettingsStorageAccess', + parentViewId: 'old', + }, + { + route: routes.SITE_SETTINGS_VR, + viewId: 'siteSettingsVr', + parentViewId: 'old', + }, + { + route: routes.SITE_SETTINGS_WINDOW_MANAGEMENT, + viewId: 'siteSettingsWindowManagement', + parentViewId: 'old', + }, + { route: routes.SITE_SETTINGS_ZOOM_LEVELS, viewId: 'siteSettingsZoomLevels', parentViewId: 'old', @@ -200,6 +234,58 @@ } }); + test('RoutingAutoPictureInPicture', async function() { + assertFalse(loadTimeData.getBoolean('autoPictureInPictureEnabled')); + await createPrivacyPageIndex({autoPictureInPictureEnabled: true}); + + const viewId = 'siteSettingsAutoPictureInPicture'; + await testActiveViewsForRoute( + routes.SITE_SETTINGS_AUTO_PICTURE_IN_PICTURE, [viewId]); + + // Test that data-parent-view is correctly populated. + assertTrue(!!index.$.viewManager.querySelector( + `#${viewId}[slot=view][data-parent-view-id=old]`)); + }); + + test('RoutingBluetoothScanning', async function() { + assertFalse( + loadTimeData.getBoolean('enableExperimentalWebPlatformFeatures')); + await createPrivacyPageIndex({enableExperimentalWebPlatformFeatures: true}); + + const viewId = 'siteSettingsBluetoothScanning'; + await testActiveViewsForRoute( + routes.SITE_SETTINGS_BLUETOOTH_SCANNING, [viewId]); + + // Test that data-parent-view is correctly populated. + assertTrue(!!index.$.viewManager.querySelector( + `#${viewId}[slot=view][data-parent-view-id=old]`)); + }); + + test('RoutingCapturedSurfaceControl', async function() { + assertFalse(loadTimeData.getBoolean('capturedSurfaceControlEnabled')); + await createPrivacyPageIndex({capturedSurfaceControlEnabled: true}); + + const viewId = 'siteSettingsCapturedSurfaceControl'; + await testActiveViewsForRoute( + routes.SITE_SETTINGS_CAPTURED_SURFACE_CONTROL, [viewId]); + + // Test that data-parent-view is correctly populated. + assertTrue(!!index.$.viewManager.querySelector( + `#${viewId}[slot=view][data-parent-view-id=old]`)); + }); + + test('RoutingHandTracking', async function() { + assertFalse(loadTimeData.getBoolean('enableHandTrackingContentSetting')); + await createPrivacyPageIndex({enableHandTrackingContentSetting: true}); + + const viewId = 'siteSettingsHandTracking'; + await testActiveViewsForRoute(routes.SITE_SETTINGS_HAND_TRACKING, [viewId]); + + // Test that data-parent-view is correctly populated. + assertTrue(!!index.$.viewManager.querySelector( + `#${viewId}[slot=view][data-parent-view-id=old]`)); + }); + test('RoutingKeyboardLock', async function() { assertFalse(loadTimeData.getBoolean('enableKeyboardLockPrompt')); await createPrivacyPageIndex({enableKeyboardLockPrompt: true});
diff --git a/chrome/test/data/webui/settings/privacy_page_test.ts b/chrome/test/data/webui/settings/privacy_page_test.ts index 11d5677..441d93b 100644 --- a/chrome/test/data/webui/settings/privacy_page_test.ts +++ b/chrome/test/data/webui/settings/privacy_page_test.ts
@@ -7,7 +7,7 @@ import {loadTimeData} from 'chrome://resources/js/load_time_data.js'; import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; import type {CrToastElement} from 'chrome://settings/lazy_load.js'; -import {ClearBrowsingDataBrowserProxyImpl, ContentSetting, CookieControlsMode, SiteSettingsPrefsBrowserProxyImpl} from 'chrome://settings/lazy_load.js'; +import {ClearBrowsingDataBrowserProxyImpl, CookieControlsMode, SiteSettingsPrefsBrowserProxyImpl} from 'chrome://settings/lazy_load.js'; import type {CrLinkRowElement, Route, SettingsPrefsElement, SettingsPrivacyPageElement, SyncStatus} from 'chrome://settings/settings.js'; import {CrSettingsPrefs, HatsBrowserProxyImpl, MetricsBrowserProxyImpl, PrivacyGuideInteractions, PrivacyPageBrowserProxyImpl, resetRouterForTesting, Router, routes, StatusAction, TrustSafetyInteraction} from 'chrome://settings/settings.js'; import {assertEquals, assertFalse, assertTrue, assertThrows} from 'chrome://webui-test/chai_assert.js'; @@ -195,24 +195,6 @@ settingsSubpage.learnMoreUrl, 'https://support.google.com/chrome?p=webusb&hl=en-US'); }); - - test('StorageAccessPage', async function() { - Router.getInstance().navigateTo(routes.SITE_SETTINGS_STORAGE_ACCESS); - await flushTasks(); - - const categorySettingExceptions = - page.shadowRoot!.querySelector('settings-storage-access-page')! - .shadowRoot!.querySelectorAll('storage-access-site-list'); - - assertEquals(2, categorySettingExceptions.length); - assertTrue(isVisible(categorySettingExceptions[0]!)); - assertEquals( - ContentSetting.BLOCK, categorySettingExceptions[0]!.categorySubtype); - - assertTrue(isVisible(categorySettingExceptions[1]!)); - assertEquals( - ContentSetting.ALLOW, categorySettingExceptions[1]!.categorySubtype); - }); }); // Isolated ContentSettingsVisibility test suite due to significantly higher
diff --git a/chrome/test/data/webui/side_panel/read_anything/node_store_test.ts b/chrome/test/data/webui/side_panel/read_anything/node_store_test.ts index d353af1..64fe0cc 100644 --- a/chrome/test/data/webui/side_panel/read_anything/node_store_test.ts +++ b/chrome/test/data/webui/side_panel/read_anything/node_store_test.ts
@@ -4,7 +4,7 @@ import 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; -import {AxReadAloudNode, BrowserProxy, ESTIMATED_WORDS_PER_MS, getWordCount, MIN_MS_TO_READ, NodeStore} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; +import {BrowserProxy, ESTIMATED_WORDS_PER_MS, getWordCount, MIN_MS_TO_READ, NodeStore, ReadAloudNode} from 'chrome-untrusted://read-anything-side-panel.top-chrome/read_anything.js'; import {assertArrayEquals, assertEquals, assertFalse, assertGT, assertNotEquals, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js'; import {MockTimer} from 'chrome-untrusted://webui-test/mock_timer.js'; @@ -18,7 +18,19 @@ function areNodesAllHidden(axNodeIds: number[]): boolean { return nodeStore.areNodesAllHidden( - axNodeIds.map(id => new AxReadAloudNode(id))); + axNodeIds.map(id => getReadAloudNode(id))); + } + + function getReadAloudNode(axNodeId: number): ReadAloudNode { + const domNode = nodeStore.getDomNode(axNodeId); + return ReadAloudNode.create(domNode!, nodeStore)!; + } + + function setDomNodes(axNodeIds: number[]) { + axNodeIds.forEach(id => { + const element = document.createElement('p'); + nodeStore.setDomNode(element, id); + }); } setup(() => { @@ -42,6 +54,19 @@ assertEquals(node, nodeStore.getDomNode(id)); }); + test('getDomNode', () => { + const node = document.createElement('p'); + const id = 308; + + // Before the node has been set, getDomNode returns undefined. + nodeStore.getDomNode(id); + assertEquals(undefined, nodeStore.getDomNode(id)); + + nodeStore.setDomNode(node, id); + assertEquals(id, nodeStore.getAxId(node)); + assertEquals(node, nodeStore.getDomNode(id)); + }); + test('removeDomNode after set', () => { const node1 = document.createElement('p'); const id1 = 308; @@ -101,6 +126,10 @@ test('hideImageNode', () => { const id = 216; nodeStore.hideImageNode(id); + assertFalse(areNodesAllHidden([id])); + + setDomNodes([id]); + nodeStore.hideImageNode(id); assertTrue(areNodesAllHidden([id])); }); @@ -108,6 +137,7 @@ const id1 = 216; const id2 = 218; const id3 = 219; + setDomNodes([id1, id2, id3]); nodeStore.hideImageNode(id1); nodeStore.hideImageNode(id2); @@ -120,19 +150,27 @@ const id1 = 216; const id2 = 218; const id3 = 219; - nodeStore.setDomNode(document.createElement('p'), id1); - nodeStore.setDomNode(document.createElement('p'), id2); + + // hasAnyNode returns false before there are associated DOM nodes. + assertFalse(nodeStore.hasAnyNode([ + getReadAloudNode(id1), + getReadAloudNode(id2), + getReadAloudNode(id3), + ])); + + setDomNodes([id1, id2]); assertTrue(nodeStore.hasAnyNode([ - new AxReadAloudNode(id1), - new AxReadAloudNode(id2), - new AxReadAloudNode(id3), + getReadAloudNode(id1), + getReadAloudNode(id2), + getReadAloudNode(id3), ])); - assertTrue(nodeStore.hasAnyNode( - [new AxReadAloudNode(id1), new AxReadAloudNode(id2)])); - assertTrue(nodeStore.hasAnyNode( - [new AxReadAloudNode(id1), new AxReadAloudNode(id3)])); - assertFalse(nodeStore.hasAnyNode([new AxReadAloudNode(id3)])); + + assertTrue( + nodeStore.hasAnyNode([getReadAloudNode(id1), getReadAloudNode(id2)])); + assertTrue( + nodeStore.hasAnyNode([getReadAloudNode(id1), getReadAloudNode(id3)])); + assertFalse(nodeStore.hasAnyNode([getReadAloudNode(id3)])); }); test('addImageToFetch', () => {
diff --git a/chrome/test/data/webui/side_panel/read_anything/read_anything_browsertest.cc b/chrome/test/data/webui/side_panel/read_anything/read_anything_browsertest.cc index 7390c373..75dbcf44 100644 --- a/chrome/test/data/webui/side_panel/read_anything/read_anything_browsertest.cc +++ b/chrome/test/data/webui/side_panel/read_anything/read_anything_browsertest.cc
@@ -340,3 +340,25 @@ RunSidePanelTest("side_panel/read_anything/phrase_highlighting_test.js", "mocha.run()"); } + +class ReadAnythingReadAloudTsSegmentationMochaTest + : public ReadAnythingMochaBrowserTest { + protected: + ReadAnythingReadAloudTsSegmentationMochaTest() { + scoped_feature_list_.InitWithFeatures( + {features::kReadAnythingReadAloud, + features::kReadAnythingReadAloudTSTextSegmentation}, + {}); + } + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +// NodeStore tests should pass regardless of whether or not the TsSegmentation +// flag is enabled without any special handling. +IN_PROC_BROWSER_TEST_F(ReadAnythingReadAloudTsSegmentationMochaTest, + NodeStore) { + RunSidePanelTest("side_panel/read_anything/node_store_test.js", + "mocha.run()"); +}
diff --git a/chrome/test/data/webui/side_panel/read_anything/speech_controller_test.ts b/chrome/test/data/webui/side_panel/read_anything/speech_controller_test.ts index ef18542a..d94df6d 100644 --- a/chrome/test/data/webui/side_panel/read_anything/speech_controller_test.ts +++ b/chrome/test/data/webui/side_panel/read_anything/speech_controller_test.ts
@@ -24,6 +24,13 @@ let voiceLanguageController: VoiceLanguageController; let readingMode: FakeReadingMode; + function setDomNodes(axNodeIds: number[]) { + axNodeIds.forEach(id => { + const element = document.createElement('p'); + nodeStore.setDomNode(element, id); + }); + } + setup(() => { // Clearing the DOM should always be done first. document.body.innerHTML = window.trustedTypes!.emptyHTML; @@ -179,9 +186,18 @@ assertFalse(speechController.isSpeechTreeInitialized()); }); + test('with no dom node does nothing', () => { + const id1 = 10; + speechController.initializeSpeechTree(id1); + + assertFalse(!!initAxPositionWithNode); + assertFalse(speechController.isSpeechTreeInitialized()); + }); + test('when already initialized does nothing', () => { const id1 = 10; const id2 = 12; + setDomNodes([10, 12]); speechController.initializeSpeechTree(id1); speechController.initializeSpeechTree(id2); assertEquals(id1, initAxPositionWithNode); @@ -189,6 +205,7 @@ test('initializes speech tree after content is set', () => { const id = 14; + setDomNodes([14]); speechController.initializeSpeechTree(id); assertEquals(id, initAxPositionWithNode);
diff --git a/components/autofill/core/browser/country_type.h b/components/autofill/core/browser/country_type.h index d2af9cfe..64754a6c 100644 --- a/components/autofill/core/browser/country_type.h +++ b/components/autofill/core/browser/country_type.h
@@ -9,12 +9,15 @@ #include "base/types/strong_alias.h" +namespace autofill { + // Country code in the format of uppercase ISO 3166-1 alpha-2. Example: US, BR, // IN. Empty if unknown. // StrongAlias to distinguish it from language codes and coutries specified in // address profiles or inferred from address forms. This country code is // estimated by the variations::VariationsService. -using GeoIpCountryCode = base::StrongAlias<class GeoIpCountryTag, std::string>; +using GeoIpCountryCode = + base::StrongAlias<class GeoIpCountryCodeTag, std::string>; // Country code in the format of uppercase ISO 3166-1 alpha-2. Example: US, BR, // IN. Empty if unknown. @@ -22,4 +25,6 @@ using AddressCountryCode = base::StrongAlias<class AutofillAddressCountryTag, std::string>; +} // namespace autofill + #endif // COMPONENTS_AUTOFILL_CORE_BROWSER_COUNTRY_TYPE_H_
diff --git a/components/autofill/core/browser/filling/form_filler.h b/components/autofill/core/browser/filling/form_filler.h index bfc0c598..b94fb12 100644 --- a/components/autofill/core/browser/filling/form_filler.h +++ b/components/autofill/core/browser/filling/form_filler.h
@@ -179,6 +179,10 @@ base::optional_ref<const FormFieldData> field = std::nullopt, base::optional_ref<const std::u16string> old_value = std::nullopt); + base::WeakPtr<FormFiller> GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); + } + private: friend class FormFillerTestApi; friend class TestFormFiller;
diff --git a/components/autofill/core/browser/foundations/browser_autofill_manager.cc b/components/autofill/core/browser/foundations/browser_autofill_manager.cc index c30e1dd..9f1a855f 100644 --- a/components/autofill/core/browser/foundations/browser_autofill_manager.cc +++ b/components/autofill/core/browser/foundations/browser_autofill_manager.cc
@@ -960,7 +960,7 @@ if (auto* save_and_fill_manager = client().GetPaymentsAutofillClient()->GetSaveAndFillManager()) { - save_and_fill_manager->OnCreditCardFormSubmitted(); + save_and_fill_manager->MaybeAddStrikeForSaveAndFill(); } } @@ -2986,70 +2986,40 @@ return metrics_->credit_card_form_event_logger; } +// TODO(crbug.com/409962888): Remove once the new suggestion generation logic is +// launched. std::vector<Suggestion> BrowserAutofillManager::GetProfileSuggestions( const FormData& form, const FormStructure& form_structure, const FormFieldData& trigger_field, const AutofillField& trigger_autofill_field, std::optional<std::string> plus_address_email_override) { -#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) - bool should_suppress = - client() - .GetPersonalDataManager() - .address_data_manager() - .AreAddressSuggestionsBlocked( - CalculateFormSignature(form), - CalculateFieldSignatureForField(trigger_field), form.url()); - base::UmaHistogramBoolean("Autofill.Suggestion.StrikeSuppression.Address", - should_suppress); - if (should_suppress && - !base::FeatureList::IsEnabled( - features::test::kAutofillDisableSuggestionStrikeDatabase)) { - LOG_AF(log_manager()) << LoggingScope::kFilling - << LogMessage::kSuggestionSuppressed - << " Reason: strike limit reached."; - // If the user already reached the strike limit on this particular field, - // address suggestions are suppressed. - return {}; - } -#endif + std::vector<Suggestion> suggestions; + AddressSuggestionGenerator address_suggestion_generator( + client(), plus_address_email_override, form_filler_->GetWeakPtr(), + log_manager()); - // If the user triggers suggestions on an autofilled field, field-by-field - // filling suggestions should be shown so that the user could easily correct - // values to something present in different stored addresses. - SuggestionType current_suggestion_type = - trigger_field.is_autofilled() - ? SuggestionType::kAddressFieldByFieldFilling - : SuggestionType::kAddressEntry; + auto on_suggestions_generated = + [&suggestions]( + SuggestionGenerator::ReturnedSuggestions returned_suggestions) { + suggestions = std::move(returned_suggestions.second); + }; - FieldTypeSet field_types = [&]() -> FieldTypeSet { - if (current_suggestion_type == - SuggestionType::kAddressFieldByFieldFilling) { - return {trigger_autofill_field.Type().GetAddressType()}; - } - // If the FormData and FormStructure do not have the same size, we assume - // as a fallback that all fields are fillable. - base::flat_map<FieldGlobalId, DenseSet<FieldFillingSkipReason>> - skip_reasons; - if (form.fields().size() == form_structure.field_count()) { - skip_reasons = form_filler_->GetFieldFillingSkipReasons( - form.fields(), form_structure, trigger_autofill_field, - FormFiller::RefillOptions::NotRefill(), FillingProduct::kAddress); - } - FieldTypeSet field_types; - for (size_t i = 0; i < form_structure.field_count(); ++i) { - if (auto it = skip_reasons.find(form_structure.field(i)->global_id()); - it == skip_reasons.end() || it->second.empty()) { - field_types.insert(form_structure.field(i)->Type().GetAddressType()); - } - } - return field_types; - }(); + auto on_suggestion_data_returned = + [&on_suggestions_generated, &form, &trigger_field, &form_structure, + &trigger_autofill_field, &address_suggestion_generator]( + std::pair<FillingProduct, + std::vector<SuggestionGenerator::SuggestionData>> + suggestion_data) { + address_suggestion_generator.GenerateSuggestions( + form, trigger_field, &form_structure, &trigger_autofill_field, + {std::move(suggestion_data)}, on_suggestions_generated); + }; - return GetSuggestionsForProfiles( - client(), field_types, trigger_field, - trigger_autofill_field.Type().GetAddressType(), current_suggestion_type, - std::move(plus_address_email_override)); + address_suggestion_generator.FetchSuggestionData( + form, trigger_field, &form_structure, &trigger_autofill_field, client(), + on_suggestion_data_returned); + return suggestions; } std::vector<Suggestion> BrowserAutofillManager::GetCreditCardSuggestions(
diff --git a/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc b/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc index 2a091e8..ea1c151 100644 --- a/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc +++ b/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
@@ -3998,15 +3998,31 @@ FormSubmitted(response_data); } -TEST_F(BrowserAutofillManagerTest, FormSubmission_NotifiesSaveAndFillManager) { +TEST_F(BrowserAutofillManagerTest, FormEvents_NotifiesSaveAndFillManager) { EXPECT_CALL(*client().GetPaymentsAutofillClient()->GetSaveAndFillManager(), - OnCreditCardFormSubmitted()); + LogCreditCardFormFilled()); + EXPECT_CALL(*client().GetPaymentsAutofillClient()->GetSaveAndFillManager(), + LogCreditCardFormSubmitted()); + EXPECT_CALL(*client().GetPaymentsAutofillClient()->GetSaveAndFillManager(), + MaybeAddStrikeForSaveAndFill()); + client().SetAutofillPaymentMethodsEnabled(true); FormData form = CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false); - FormsSeen({form}); - FormSubmitted(form); + + // Create a card and add it to the PDM. + personal_data().test_payments_data_manager().ClearCreditCards(); + CreditCard card = test::GetCreditCard(); + personal_data().payments_data_manager().AddCreditCard(card); + const CreditCard* pdm_card = + personal_data().payments_data_manager().GetCreditCardByGUID(card.guid()); + ASSERT_TRUE(pdm_card); + + FormData filled_form = FillAutofillFormDataAndGetResults( + form, form.fields()[0], pdm_card->guid(), + AutofillTriggerSource::kCreditCardSaveAndFill); + FormSubmitted(filled_form); } TEST_F(BrowserAutofillManagerTest,
diff --git a/components/autofill/core/browser/integrators/autofill_ai/autofill_ai_manager.cc b/components/autofill/core/browser/integrators/autofill_ai/autofill_ai_manager.cc index 9574fc7c..f6831d8 100644 --- a/components/autofill/core/browser/integrators/autofill_ai/autofill_ai_manager.cc +++ b/components/autofill/core/browser/integrators/autofill_ai/autofill_ai_manager.cc
@@ -198,10 +198,11 @@ ukm::SourceId ukm_source_id) { logger_.RecordFormMetrics(form, ukm_source_id, /*submission_state=*/true, GetAutofillAiOptInStatus(*client_)); - return MaybeImportForm(form); + return MaybeImportForm(form, ukm_source_id); } -bool AutofillAiManager::MaybeImportForm(const FormStructure& form) { +bool AutofillAiManager::MaybeImportForm(const FormStructure& form, + ukm::SourceId ukm_source_id) { if (!MayPerformAutofillAiAction(*client_, AutofillAiAction::kImport)) { return false; } @@ -230,7 +231,7 @@ form.main_frame_origin(), net::registry_controlled_domains:: EXCLUDE_PRIVATE_REGISTRIES), - old_entity->guid()) + ukm_source_id, old_entity->guid()) : BindOnce( &AutofillAiManager::HandleSavePromptResult, GetWeakPtr(), form.source_url(), @@ -239,7 +240,7 @@ form.main_frame_origin(), net::registry_controlled_domains:: EXCLUDE_PRIVATE_REGISTRIES), - new_entity); + ukm_source_id, new_entity); client_->ShowEntitySaveOrUpdateBubble(std::move(new_entity), std::move(old_entity), std::move(prompt_result_callback)); @@ -252,11 +253,12 @@ const GURL& form_url, uint64_t form_session_id, const std::string& domain, + ukm::SourceId ukm_source_id, const EntityInstance& entity, AutofillClient::EntitySaveOrUpdatePromptResult result) { logger_.OnSaveOrUpdatePromptResult( AutofillClient::AutofillAiPromptTypes::kSave, entity.type(), - entity.record_type(), form_session_id, domain, result); + entity.record_type(), form_session_id, domain, result, ukm_source_id); if (!result.entity) { if (result.did_user_decline) { AddStrikeForSaveAttempt(form_url, entity); @@ -276,6 +278,7 @@ void AutofillAiManager::HandleUpdatePromptResult( uint64_t form_session_id, const std::string& domain, + ukm::SourceId ukm_source_id, const EntityInstance::EntityId& entity_uuid, AutofillClient::EntitySaveOrUpdatePromptResult result) { if (const EntityDataManager* entity_manager = @@ -284,7 +287,8 @@ entity_manager->GetEntityInstance(entity_uuid)) { logger_.OnSaveOrUpdatePromptResult( AutofillClient::AutofillAiPromptTypes::kUpdate, entity->type(), - entity->record_type(), form_session_id, domain, result); + entity->record_type(), form_session_id, domain, result, + ukm_source_id); } }
diff --git a/components/autofill/core/browser/integrators/autofill_ai/autofill_ai_manager.h b/components/autofill/core/browser/integrators/autofill_ai/autofill_ai_manager.h index 2cb1f2e9..3bd1f160 100644 --- a/components/autofill/core/browser/integrators/autofill_ai/autofill_ai_manager.h +++ b/components/autofill/core/browser/integrators/autofill_ai/autofill_ai_manager.h
@@ -105,7 +105,7 @@ // Attempts to display an import bubble for `form` if Autofill AI is // interested in the form. Returns whether an import bubble will be shown. - bool MaybeImportForm(const FormStructure& form); + bool MaybeImportForm(const FormStructure& form, ukm::SourceId ukm_source_id); // Updates the `EntityDataManager` and the save strike database depending on // the prompt `result`. @@ -113,6 +113,7 @@ const GURL& form_url, uint64_t form_session_id, const std::string& domain, + ukm::SourceId ukm_source_id, const EntityInstance& entity, AutofillClient::EntitySaveOrUpdatePromptResult result); // Updates the `EntityDataManager` and the update strike database depending on @@ -120,6 +121,7 @@ void HandleUpdatePromptResult( uint64_t form_session_id, const std::string& domain, + ukm::SourceId ukm_source_id, const EntityInstance::EntityId& entity_uuid, AutofillClient::EntitySaveOrUpdatePromptResult result);
diff --git a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger.cc b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger.cc index 9bc8cda..a7584bb7 100644 --- a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger.cc +++ b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger.cc
@@ -222,9 +222,11 @@ EntityInstance::RecordType record_type, uint64_t form_session_id, const std::string& domain, - AutofillClient::EntitySaveOrUpdatePromptResult result) { + AutofillClient::EntitySaveOrUpdatePromptResult result, + ukm::SourceId ukm_source_id) { ukm_logger_.LogSaveOrUpdatePromptResult(prompt_type, entity_type, record_type, - form_session_id, domain, result); + form_session_id, domain, result, + ukm_source_id); } void AutofillAiLogger::RecordKeyMetrics(
diff --git a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger.h b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger.h index 786cf2a..0b5525b9 100644 --- a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger.h +++ b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger.h
@@ -52,7 +52,8 @@ EntityInstance::RecordType record_type, uint64_t form_session_id, const std::string& domain, - AutofillClient::EntitySaveOrUpdatePromptResult result); + AutofillClient::EntitySaveOrUpdatePromptResult result, + ukm::SourceId ukm_source_id); // Function that records the contents of `form_states` for `form` into // appropriate metrics. `submission_state` denotes whether the form was
diff --git a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger_unittest.cc b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger_unittest.cc index 1ed0d44..cbd3227 100644 --- a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger_unittest.cc +++ b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_logger_unittest.cc
@@ -58,6 +58,7 @@ constexpr auto kPassport = EntityType(EntityTypeName::kPassport); constexpr auto kNationalIdCard = EntityType(EntityTypeName::kNationalIdCard); constexpr char kDefaultUrl[] = "https://example.com"; +constexpr uint64_t kFormSession = 123456UL; class MockAutofillClient : public TestAutofillClient { public: @@ -727,19 +728,28 @@ const optimization_guide::proto::AutofillAiFieldEvent& GetLastFieldEventLogs() { - return *(logs_uploader_->uploaded_logs() - .back() - ->mutable_forms_classifications() - ->mutable_quality() - ->mutable_field_event()); + return logs_uploader_->uploaded_logs() + .back() + ->forms_classifications() + .quality() + .field_event(); } const optimization_guide::proto::AutofillAiKeyMetrics& GetKeyMetricsLogs() { - return *(logs_uploader_->uploaded_logs() - .back() - ->mutable_forms_classifications() - ->mutable_quality() - ->mutable_key_metrics()); + return logs_uploader_->uploaded_logs() + .back() + ->forms_classifications() + .quality() + .key_metrics(); + } + + const optimization_guide::proto::AutofillAiUserPromptMetrics& + GetUserPromptMetrics() { + return logs_uploader_->uploaded_logs() + .back() + ->forms_classifications() + .quality() + .user_prompt_metrics(); } void ExpectCorrectMqlsFieldEventLogging( @@ -846,6 +856,31 @@ /*event_order=*/3); } +TEST_F(AutofillAiMqlsMetricsTest, UserPrompts) { + test_api(manager()).logger().OnSaveOrUpdatePromptResult( + AutofillClient::AutofillAiPromptTypes::kUpdate, kPassport, + EntityInstance::RecordType::kLocal, /*form_session_id=*/kFormSession, + "myform_root.com", + AutofillClient::EntitySaveOrUpdatePromptResult( + /*did_user_decline=*/false, test::GetPassportEntityInstance()), + /*ukm_source_id=*/{}); + ASSERT_EQ(mqls_logs().size(), 1u); + + const optimization_guide::proto::AutofillAiUserPromptMetrics& + mqls_user_prompt = GetUserPromptMetrics(); + EXPECT_EQ(mqls_user_prompt.domain(), "myform_root.com"); + EXPECT_EQ(mqls_user_prompt.form_session_identifier(), kFormSession); + EXPECT_EQ(mqls_user_prompt.storage_type(), + optimization_guide::proto::AUTOFILL_AI_ENTITY_STORAGE_TYPE_LOCAL); + EXPECT_EQ(mqls_user_prompt.prompt_type(), + optimization_guide::proto::AUTOFILL_AI_PROMPT_TYPE_UPDATE_ENTITY); + EXPECT_EQ(mqls_user_prompt.entity_type(), + optimization_guide::proto::AUTOFILL_AI_ENTITY_TYPE_PASSPORT); + EXPECT_EQ( + mqls_user_prompt.result(), + optimization_guide::proto::AUTOFILL_AI_PROMPT_USER_DECISION_ACCEPTED); +} + TEST_F(AutofillAiMqlsMetricsTest, KeyMetrics) { std::unique_ptr<FormStructure> form = CreatePassportForm();
diff --git a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_ukm_logger.cc b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_ukm_logger.cc index 5588a50..c02e773 100644 --- a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_ukm_logger.cc +++ b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_ukm_logger.cc
@@ -120,6 +120,37 @@ NOTREACHED(); } +optimization_guide::proto::AutofillAiPromptUserDecision GetUserDecision( + AutofillClient::EntitySaveOrUpdatePromptResult decision) { + if (decision.did_user_decline) { + return optimization_guide::proto::AUTOFILL_AI_PROMPT_USER_DECISION_DECLINED; + } else if (!decision.did_user_decline && !decision.entity) { + return optimization_guide::proto::AUTOFILL_AI_PROMPT_USER_DECISION_IGNORED; + } else { + return optimization_guide::proto::AUTOFILL_AI_PROMPT_USER_DECISION_ACCEPTED; + } +} + +optimization_guide::proto::AutofillAiPromptType GetPromptType( + AutofillClient::AutofillAiPromptTypes prompt_type) { + switch (prompt_type) { + case AutofillClient::AutofillAiPromptTypes::kSave: + return optimization_guide::proto::AUTOFILL_AI_PROMPT_TYPE_SAVE_ENTITY; + case AutofillClient::AutofillAiPromptTypes::kUpdate: + return optimization_guide::proto::AUTOFILL_AI_PROMPT_TYPE_UPDATE_ENTITY; + } +} + +optimization_guide::proto::AutofillAiEntityStorageType GetStorageType( + EntityInstance::RecordType record_type) { + switch (record_type) { + case EntityInstance::RecordType::kLocal: + return optimization_guide::proto::AUTOFILL_AI_ENTITY_STORAGE_TYPE_LOCAL; + case EntityInstance::RecordType::kServerWallet: + return optimization_guide::proto::AUTOFILL_AI_ENTITY_STORAGE_TYPE_WALLET; + } +} + } // namespace AutofillAiUkmLogger::AutofillAiUkmLogger(AutofillClient* client) @@ -225,8 +256,43 @@ EntityInstance::RecordType record_type, uint64_t form_session_id, const std::string& domain, - AutofillClient::EntitySaveOrUpdatePromptResult result) { - // TODO(crbug.com/439779727): Log to MQLS and UKM. + AutofillClient::EntitySaveOrUpdatePromptResult result, + ukm::SourceId ukm_source_id) { + if (optimization_guide::ModelQualityLogsUploaderService* uploader_ = + client_->GetMqlsUploadService(); + uploader_ && + MayPerformAutofillAiAction(*client_, AutofillAiAction::kLogToMqls)) { + // Note that the actual logging of the metric happens when `log_entry` goes + // out of scope and is destroyed. Also note that in this case it is not + // necessary to check if the user is opted in because it is assumed that all + // field event types can only occur if the user is opted in for Autofill AI. + optimization_guide::ModelQualityLogEntry log_entry( + client_->GetMqlsUploadService()->GetWeakPtr()); + + optimization_guide::proto::AutofillAiUserPromptMetrics* + mqls_user_prompt_event = log_entry.log_ai_data_request() + ->mutable_forms_classifications() + ->mutable_quality() + ->mutable_user_prompt_metrics(); + + mqls_user_prompt_event->set_domain(domain); + mqls_user_prompt_event->set_form_session_identifier(form_session_id); + mqls_user_prompt_event->set_storage_type(GetStorageType(record_type)); + mqls_user_prompt_event->set_prompt_type(GetPromptType(prompt_type)); + mqls_user_prompt_event->set_entity_type(GetEntityType(entity_type)); + mqls_user_prompt_event->set_result(GetUserDecision(result)); + } + + if (!CanLogUkm(ukm_source_id)) { + return; + } + ukm::builders::AutofillAi_UserPromptMetrics(ukm_source_id) + .SetFormSessionIdentifier(form_session_id) + .SetEntityType(base::to_underlying(entity_type.name())) + .SetStorageType(GetStorageType(record_type)) + .SetPromptType(GetPromptType(prompt_type)) + .SetResult(GetUserDecision(result)) + .Record(client_->GetUkmRecorder()); } void AutofillAiUkmLogger::LogFieldEvent(ukm::SourceId ukm_source_id,
diff --git a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_ukm_logger.h b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_ukm_logger.h index af3f19d..92c65f1 100644 --- a/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_ukm_logger.h +++ b/components/autofill/core/browser/integrators/autofill_ai/metrics/autofill_ai_ukm_logger.h
@@ -40,7 +40,8 @@ EntityInstance::RecordType record_type, uint64_t form_session_id, const std::string& domain, - AutofillClient::EntitySaveOrUpdatePromptResult result); + AutofillClient::EntitySaveOrUpdatePromptResult result, + ukm::SourceId ukm_source_id); // These values are persisted to logs. Entries should not be renumbered and // numeric values should never be reused.
diff --git a/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc b/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc index 3d0d2c3d..7badfd2 100644 --- a/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc +++ b/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.cc
@@ -31,6 +31,7 @@ #include "components/autofill/core/browser/payments/constants.h" #include "components/autofill/core/browser/payments/credit_card_access_manager.h" #include "components/autofill/core/browser/payments/payments_autofill_client.h" +#include "components/autofill/core/browser/payments/save_and_fill_manager.h" #include "components/autofill/core/common/autofill_internals/log_message.h" #include "components/autofill/core/common/autofill_internals/logging_scope.h" #include "components/autofill/core/common/autofill_payments_features.h" @@ -335,6 +336,7 @@ signin_state_for_metrics_ = signin_state_for_metrics; filled_credit_card_ = credit_card; + trigger_source_ = trigger_source; client().GetFormInteractionsUkmLogger().LogDidFillSuggestion( driver().GetPageUkmSourceId(), form, field, record_type); @@ -348,6 +350,21 @@ .newly_filled_fields = newly_filled_fields, .safe_fields = safe_filled_fields}); + if (trigger_source_ == AutofillTriggerSource::kCreditCardSaveAndFill) { + // If the fill is triggered by the Save and Fill flow. We log form filling + // separately as the it is not triggered by regular Autofill credit card + // suggestions. Also Save and Fill flow is offered only on full credit card + // forms. These factors could pollute the existing card + // retrieval / filling / submission metrics. + auto* save_and_fill_manager = + client().GetPaymentsAutofillClient()->GetSaveAndFillManager(); + // If the `trigger_source` is kCreditCardSaveAndFill, then + // `save_and_fill_manager` must exist. + CHECK(save_and_fill_manager); + save_and_fill_manager->LogCreditCardFormFilled(); + return; + } + latest_filled_card_was_masked_server_card_ = false; latest_filled_card_was_card_info_retrieval_enrolled_ = false; switch (record_type) { @@ -509,7 +526,7 @@ base::RecordAction( base::UserMetricsAction("Autofill_FilledCreditCardSuggestion")); - if (trigger_source != AutofillTriggerSource::kFastCheckout) { + if (trigger_source_ != AutofillTriggerSource::kFastCheckout) { ++form_interaction_counts_.autofill_fills; } UpdateFlowId(); @@ -600,6 +617,11 @@ } void CreditCardFormEventLogger::LogWillSubmitForm(const FormStructure& form) { + if (trigger_source_ == AutofillTriggerSource::kCreditCardSaveAndFill) { + // If it is a Save and Fill flow. Don't log any will-submit metrics. + return; + } + if (!has_logged_form_filling_suggestion_filled_) { Log(FORM_EVENT_NO_SUGGESTION_WILL_SUBMIT_ONCE, form); } else if (logged_suggestion_filled_was_masked_server_card_) { @@ -653,6 +675,14 @@ } void CreditCardFormEventLogger::LogFormSubmitted(const FormStructure& form) { + if (trigger_source_ == AutofillTriggerSource::kCreditCardSaveAndFill) { + auto* save_and_fill_manager = + client().GetPaymentsAutofillClient()->GetSaveAndFillManager(); + CHECK(save_and_fill_manager); + save_and_fill_manager->LogCreditCardFormSubmitted(); + return; + } + if (!has_logged_form_filling_suggestion_filled_) { Log(FORM_EVENT_NO_SUGGESTION_SUBMITTED_ONCE, form); } else if (logged_suggestion_filled_was_masked_server_card_) {
diff --git a/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.h b/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.h index e28babe..da0cac7 100644 --- a/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.h +++ b/components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.h
@@ -256,6 +256,8 @@ AutofillMetrics::PaymentsSigninState signin_state_for_metrics_ = AutofillMetrics::PaymentsSigninState::kUnknown; + AutofillTriggerSource trigger_source_ = AutofillTriggerSource::kNone; + // Weak references. raw_ptr<PersonalDataManager> personal_data_manager_;
diff --git a/components/autofill/core/browser/metrics/payments/save_and_fill_metrics.cc b/components/autofill/core/browser/metrics/payments/save_and_fill_metrics.cc index 60882bf..0db4aa32 100644 --- a/components/autofill/core/browser/metrics/payments/save_and_fill_metrics.cc +++ b/components/autofill/core/browser/metrics/payments/save_and_fill_metrics.cc
@@ -63,4 +63,14 @@ /*sample=*/true); } +void LogSaveAndFillFunnelMetrics(bool succeeded, + bool is_for_upload, + SaveAndFillFormEvent event) { + base::UmaHistogramEnumeration( + base::StrCat({"Autofill.SaveAndFill.Funnel", + is_for_upload ? ".Upload" : ".Local", + succeeded ? ".Success" : ".Failure"}), + event); +} + } // namespace autofill::autofill_metrics
diff --git a/components/autofill/core/browser/metrics/payments/save_and_fill_metrics.h b/components/autofill/core/browser/metrics/payments/save_and_fill_metrics.h index e3921c2..276bb5d 100644 --- a/components/autofill/core/browser/metrics/payments/save_and_fill_metrics.h +++ b/components/autofill/core/browser/metrics/payments/save_and_fill_metrics.h
@@ -19,7 +19,11 @@ kSuggestionShown = 0, // The Save and Fill suggestion was accepted by the user. kSuggestionAccepted = 1, - kMaxValue = kSuggestionAccepted, + // The form was filled after Save and Fill finished. + kFormFilled = 2, + // The form was submitted after Save and Fill finished. + kFormSubmitted = 3, + kMaxValue = kFormSubmitted, }; // LINT.ThenChange(/tools/metrics/histograms/metadata/autofill/enums.xml:SaveAndFillFormEvent) @@ -82,6 +86,13 @@ // Logs that the Save and Fill dialog was shown. void LogSaveAndFillDialogShown(bool is_upload); +// Logs the form being filled and form being submitted event. Broken down by +// whether the Save and Fill attempt succeeded and whether it was for upload +// Save and Fill. +void LogSaveAndFillFunnelMetrics(bool succeeded, + bool is_for_upload, + SaveAndFillFormEvent event); + } // namespace autofill::autofill_metrics #endif // COMPONENTS_AUTOFILL_CORE_BROWSER_METRICS_PAYMENTS_SAVE_AND_FILL_METRICS_H_
diff --git a/components/autofill/core/browser/ml_model/field_classification_model_handler.cc b/components/autofill/core/browser/ml_model/field_classification_model_handler.cc index b366d2d..6665397 100644 --- a/components/autofill/core/browser/ml_model/field_classification_model_handler.cc +++ b/components/autofill/core/browser/ml_model/field_classification_model_handler.cc
@@ -251,8 +251,7 @@ for (size_t i = 0; i < model_output.size() && i < prediction_log.field_predictions.size(); ++i) { - prediction_log.field_predictions[i]->probabilities = std::vector( - model_output[i].begin(), model_output[i].end()); + prediction_log.field_predictions[i]->probabilities = model_output[i]; } } @@ -272,10 +271,20 @@ prediction_log = CreateMlPredictionLog(form); } - // TODO(crbug.com/428686605) Set tokenized representation in `prediction_log`. FieldClassificationModelEncoder::ModelInput encoded_input = state_->encoder.EncodeForm(form); + if (prediction_log) { + for (auto [encoded_field, field_prediction] : + base::zip(encoded_input, prediction_log.value()->field_predictions)) { + field_prediction->tokenized_field_representation = base::ToVector( + encoded_field, + [this](FieldClassificationModelEncoder::TokenId token_id) { + return TokenIdToString(token_id); + }); + } + } + std::optional<ModelInputHash> input_hash; if (base::FeatureList::IsEnabled( features::kFieldClassificationModelCaching)) { @@ -489,4 +498,26 @@ return ModelInputHash(base::FastHash(base::as_byte_span(flattened_data))); } +std::string FieldClassificationModelHandler::TokenIdToString( + FieldClassificationModelEncoder::TokenId token_id) const { + const int token = static_cast<int>(token_id.value()); + if (token == 0) { + // Padding token, always encoded as 0. + return ""; + } else if (token == 1) { + // Unknown, out-of-vocabulary token, always encoded as 1. + return "[UNK]"; + } else if (token == state_->metadata.input_token_size() + 2) { + // Special "classification" token used by the model, encoded as + // `vocabulary_size` (where the vocab size includes the two special tokens, + // hence the +2). + return "[CLS]"; + } else if (token < 2 || token >= state_->metadata.input_token_size() + 2) { + return "[INVALID]"; + } else { + // Indexing starts at 2 because of the special tokens. + return state_->metadata.input_token(token_id.value() - 2); + } +} + } // namespace autofill
diff --git a/components/autofill/core/browser/ml_model/field_classification_model_handler.h b/components/autofill/core/browser/ml_model/field_classification_model_handler.h index 67251ac..69570d1 100644 --- a/components/autofill/core/browser/ml_model/field_classification_model_handler.h +++ b/components/autofill/core/browser/ml_model/field_classification_model_handler.h
@@ -135,6 +135,10 @@ autofill_ml_internals::mojom::MlPredictionLogPtr CreateMlPredictionLog( const FormData& form_structure) const; + // Converts a `TokenId` to the string representation. + std::string TokenIdToString( + FieldClassificationModelEncoder::TokenId token_id) const; + struct ModelState { optimization_guide::proto::AutofillFieldClassificationModelMetadata metadata;
diff --git a/components/autofill/core/browser/ml_model/logging/autofill_ml_internals.mojom b/components/autofill/core/browser/ml_model/logging/autofill_ml_internals.mojom index 391769e..cb76995 100644 --- a/components/autofill/core/browser/ml_model/logging/autofill_ml_internals.mojom +++ b/components/autofill/core/browser/ml_model/logging/autofill_ml_internals.mojom
@@ -29,6 +29,7 @@ array<SelectOption> select_options; array<float>? probabilities; + array<string> tokenized_field_representation; }; // Represents a single ML prediction log entry.
diff --git a/components/autofill/core/browser/payments/save_and_fill_manager.h b/components/autofill/core/browser/payments/save_and_fill_manager.h index f618a38..458a7eea 100644 --- a/components/autofill/core/browser/payments/save_and_fill_manager.h +++ b/components/autofill/core/browser/payments/save_and_fill_manager.h
@@ -29,15 +29,19 @@ FillCardCallback fill_card_callback) = 0; // Called when the Save and Fill suggestion is shown to the user. virtual void OnSuggestionOffered() = 0; - // Called when the form is submitted. This is used to check if a strike should - // be added if the suggestion was shown but not selected. - virtual void OnCreditCardFormSubmitted() = 0; + // If the strike database exists, add a strike if the suggestion was shown but + // not selected. + virtual void MaybeAddStrikeForSaveAndFill() = 0; // Returns true if the feature offer should be blocked. virtual bool ShouldBlockFeature() = 0; // Logs the reason why the Save and Fill suggestion was not shown if this // metric has not yet been recorded, as this is logged once per page load. virtual void MaybeLogSaveAndFillSuggestionNotShownReason( autofill_metrics::SaveAndFillSuggestionNotShownReason reason) = 0; + // Logs when the credit card form was filled / submitted with the + // Save-and-Fill candidate card. + virtual void LogCreditCardFormFilled() = 0; + virtual void LogCreditCardFormSubmitted() = 0; }; } // namespace autofill::payments
diff --git a/components/autofill/core/browser/payments/save_and_fill_manager_impl.cc b/components/autofill/core/browser/payments/save_and_fill_manager_impl.cc index 3d9779d5..fbe7cb7 100644 --- a/components/autofill/core/browser/payments/save_and_fill_manager_impl.cc +++ b/components/autofill/core/browser/payments/save_and_fill_manager_impl.cc
@@ -68,7 +68,7 @@ save_and_fill_suggestion_offered_ = true; } -void SaveAndFillManagerImpl::OnCreditCardFormSubmitted() { +void SaveAndFillManagerImpl::MaybeAddStrikeForSaveAndFill() { if (save_and_fill_suggestion_offered_ && !save_and_fill_suggestion_selected_) { GetSaveAndFillStrikeDatabase()->AddStrike(); @@ -101,11 +101,36 @@ void SaveAndFillManagerImpl::MaybeLogSaveAndFillSuggestionNotShownReason( autofill_metrics::SaveAndFillSuggestionNotShownReason reason) { - if (has_logged_save_and_fill_suggestion_not_shown_reason_) { + if (logging_context_.has_logged_save_and_fill_suggestion_not_shown_reason) { return; } autofill_metrics::LogSaveAndFillSuggestionNotShownReason(reason); - has_logged_save_and_fill_suggestion_not_shown_reason_ = true; + logging_context_.has_logged_save_and_fill_suggestion_not_shown_reason = true; +} + +void SaveAndFillManagerImpl::LogCreditCardFormFilled() { + if (!logging_context_.has_logged_form_filled) { + CHECK(logging_context_.last_attempt_succeeded.has_value()); + CHECK(logging_context_.last_attempt_was_for_upload.has_value()); + autofill_metrics::LogSaveAndFillFunnelMetrics( + logging_context_.last_attempt_succeeded.value(), + logging_context_.last_attempt_was_for_upload.value(), + autofill_metrics::SaveAndFillFormEvent::kFormFilled); + logging_context_.has_logged_form_filled = true; + } +} + +void SaveAndFillManagerImpl::LogCreditCardFormSubmitted() { + if (!logging_context_.has_logged_form_submitted && + logging_context_.has_logged_form_filled) { + CHECK(logging_context_.last_attempt_succeeded.has_value()); + CHECK(logging_context_.last_attempt_was_for_upload.has_value()); + autofill_metrics::LogSaveAndFillFunnelMetrics( + logging_context_.last_attempt_succeeded.value(), + logging_context_.last_attempt_was_for_upload.value(), + autofill_metrics::SaveAndFillFormEvent::kFormSubmitted); + logging_context_.has_logged_form_submitted = true; + } } void SaveAndFillManagerImpl::OnUserDidDecideOnLocalSave( @@ -114,6 +139,8 @@ user_provided_card_save_and_fill_details) { switch (user_decision) { case CardSaveAndFillDialogUserDecision::kAccepted: { + logging_context_.last_attempt_was_for_upload = false; + logging_context_.last_attempt_succeeded = true; if (auto* strike_database = GetSaveAndFillStrikeDatabase()) { autofill_metrics::LogSaveAndFillNumOfStrikesPresentWhenDialogAccepted( strike_database->GetStrikes()); @@ -294,6 +321,7 @@ switch (user_decision) { case CardSaveAndFillDialogUserDecision::kAccepted: upload_save_and_fill_dialog_accepted_ = true; + logging_context_.last_attempt_was_for_upload = true; if (auto* strike_database = GetSaveAndFillStrikeDatabase()) { autofill_metrics::LogSaveAndFillNumOfStrikesPresentWhenDialogAccepted( strike_database->GetStrikes()); @@ -301,9 +329,6 @@ } PopulateCreditCardInfo(upload_details_.card, user_provided_card_save_and_fill_details); - if (fill_card_callback_) { - std::move(fill_card_callback_).Run(upload_details_.card); - } // If risk data has already been loaded, send the request now. Otherwise, // continue to wait and let OnDidLoadRiskData handle it. if (!upload_details_.risk_data.empty()) { @@ -346,6 +371,8 @@ autofill_metrics::LogSaveAndFillCreateCardResultAndLatency( result == PaymentsRpcResult::kSuccess, base::TimeTicks::Now() - request_sent_timestamp); + logging_context_.last_attempt_succeeded = + result == PaymentsRpcResult::kSuccess; if (result != PaymentsAutofillClient::PaymentsRpcResult::kSuccess) { // If card creation fails, save the card locally instead. All card @@ -365,6 +392,9 @@ parsed_instrument_id, upload_details_.card.cvc()); } } + if (fill_card_callback_) { + std::move(fill_card_callback_).Run(upload_details_.card); + } payments_autofill_client()->HideCreditCardSaveAndFillDialog(); // Invoke feedback bubble. No callback needed (virtual card enrollment is not // eligible for card saved via the Save and Fill flow).
diff --git a/components/autofill/core/browser/payments/save_and_fill_manager_impl.h b/components/autofill/core/browser/payments/save_and_fill_manager_impl.h index f08abd92..a62a073 100644 --- a/components/autofill/core/browser/payments/save_and_fill_manager_impl.h +++ b/components/autofill/core/browser/payments/save_and_fill_manager_impl.h
@@ -19,6 +19,25 @@ // Contents. This class manages the flow for the Save and Fill dialog. class SaveAndFillManagerImpl : public SaveAndFillManager { public: + // A struct containing a few logging related info. This is not reset when flow + // ends as metrics loggings are once per page load. + struct LoggingContext { + // Set to true after the first time the Save and Fill suggestion not being + // shown is logged. Ensures that logging occurs only once per page load. + bool has_logged_save_and_fill_suggestion_not_shown_reason = false; + + // Set when the Save and Fill dialog was accepted by the user. Records + // whether the attempt was for upload / local save and whether the attempt + // succeeded. + std::optional<bool> last_attempt_succeeded; + std::optional<bool> last_attempt_was_for_upload; + + // Whether credit card form filling / submission event has been logged. + // Ensures that logging occurs only once per page load. + bool has_logged_form_filled = false; + bool has_logged_form_submitted = false; + }; + explicit SaveAndFillManagerImpl(AutofillClient* autofill_client); SaveAndFillManagerImpl(const SaveAndFillManagerImpl& other) = delete; SaveAndFillManagerImpl& operator=(const SaveAndFillManagerImpl& other) = @@ -29,10 +48,12 @@ void OnDidAcceptCreditCardSaveAndFillSuggestion( FillCardCallback fill_card_callback) override; void OnSuggestionOffered() override; - void OnCreditCardFormSubmitted() override; + void MaybeAddStrikeForSaveAndFill() override; bool ShouldBlockFeature() override; void MaybeLogSaveAndFillSuggestionNotShownReason( autofill_metrics::SaveAndFillSuggestionNotShownReason reason) override; + void LogCreditCardFormFilled() override; + void LogCreditCardFormSubmitted() override; // Called when the user makes a decision on the local Save and Fill dialog. // The `user_provided_card_save_and_fill_details` holds the data entered by @@ -48,9 +69,13 @@ private: FRIEND_TEST_ALL_PREFIXES(SaveAndFillManagerImplTest, + OnUserDidDecideOnUploadSave_Accepted); + FRIEND_TEST_ALL_PREFIXES(SaveAndFillManagerImplTest, ResetOnFlowEnds_ServerSave); FRIEND_TEST_ALL_PREFIXES(SaveAndFillManagerImplTest, ResetOnFlowEnds_LocalSave); + FRIEND_TEST_ALL_PREFIXES(SaveAndFillManagerImplTest, + LogFunnelMetrics_ServerSave); // Begins the process to show the local Save and Fill dialog. void OfferLocalSaveAndFill(); @@ -127,9 +152,8 @@ bool save_and_fill_suggestion_offered_ = false; // True if the user accepted the Save and Fill suggestion. bool save_and_fill_suggestion_selected_ = false; - // Set to true after the first time the Save and Fill suggestion not being - // shown is logged. Ensures that logging occurs only once per page load. - bool has_logged_save_and_fill_suggestion_not_shown_reason_ = false; + + LoggingContext logging_context_; // StrikeDatabase used to check whether to show the Save and Fill suggestion. std::unique_ptr<SaveAndFillStrikeDatabase> save_and_fill_strike_database_;
diff --git a/components/autofill/core/browser/payments/save_and_fill_manager_impl_unittest.cc b/components/autofill/core/browser/payments/save_and_fill_manager_impl_unittest.cc index 703afd3a..5877a45 100644 --- a/components/autofill/core/browser/payments/save_and_fill_manager_impl_unittest.cc +++ b/components/autofill/core/browser/payments/save_and_fill_manager_impl_unittest.cc
@@ -13,6 +13,7 @@ #include "base/time/time.h" #include "components/autofill/core/browser/form_import/form_data_importer_test_api.h" #include "components/autofill/core/browser/foundations/test_autofill_client.h" +#include "components/autofill/core/browser/metrics/payments/save_and_fill_metrics.h" #include "components/autofill/core/browser/payments/client_behavior_constants.h" #include "components/autofill/core/browser/payments/payments_util.h" #include "components/autofill/core/browser/payments/test/mock_multiple_request_payments_network_interface.h" @@ -659,6 +660,10 @@ save_and_fill_manager_impl_->OnDidAcceptCreditCardSaveAndFillSuggestion( fill_card_callback_.Get()); + save_and_fill_manager_impl_->OnDidCreateCard( + base::TimeTicks::Now(), + PaymentsAutofillClient::PaymentsRpcResult::kSuccess, + /*instrument_id=*/"1122334455"); EXPECT_EQ(u"1111222233334444", card_to_fill.GetRawInfo(CREDIT_CARD_NUMBER)); EXPECT_EQ(u"Jane Smith", card_to_fill.GetRawInfo(CREDIT_CARD_NAME_FULL)); @@ -750,7 +755,7 @@ SaveAndFillStrikeDatabase save_and_fill_strike_database(strike_database_); save_and_fill_manager_impl_->OnSuggestionOffered(); - save_and_fill_manager_impl_->OnCreditCardFormSubmitted(); + save_and_fill_manager_impl_->MaybeAddStrikeForSaveAndFill(); EXPECT_EQ(1, save_and_fill_strike_database.GetStrikes()); } @@ -764,7 +769,7 @@ save_and_fill_manager_impl_->OnSuggestionOffered(); save_and_fill_manager_impl_->OnDidAcceptCreditCardSaveAndFillSuggestion( fill_card_callback_.Get()); - save_and_fill_manager_impl_->OnCreditCardFormSubmitted(); + save_and_fill_manager_impl_->MaybeAddStrikeForSaveAndFill(); EXPECT_EQ(0, save_and_fill_strike_database.GetStrikes()); } @@ -1103,4 +1108,105 @@ fill_card_callback_.Get()); } +TEST_F(SaveAndFillManagerImplTest, LogFunnelMetrics_ServerSave) { + base::HistogramTester histogram_tester; + + save_and_fill_manager_impl_->SetCreditCardUploadEnabledOverrideForTesting( + true); + SetUpGetDetailsForCreateCardResponse( + PaymentsAutofillClient::PaymentsRpcResult::kSuccess, + /*create_valid_legal_message=*/true); + + EXPECT_CALL(*payments_autofill_client_, ShowCreditCardUploadSaveAndFillDialog) + .WillOnce([&](const LegalMessageLines&, + TestPaymentsAutofillClient::CardSaveAndFillDialogCallback + callback) { + std::move(callback).Run(CardSaveAndFillDialogUserDecision::kAccepted, + CreateUserProvidedCardDetails( + /*card_number=*/u"1111222233334444", + /*cardholder_name=*/u"Jane Smith", + /*expiration_date_month=*/u"06", + /*expiration_date_year=*/u"2035", + /*security_code=*/u"456")); + }); + + EXPECT_CALL(*payments_autofill_client_, LoadRiskData) + .WillOnce([](base::OnceCallback<void(const std::string&)> callback) { + std::move(callback).Run("some risk data"); + }); + EXPECT_CALL(*mock_network_interface_, CreateCard) + .WillOnce(testing::Return(RequestId("11223344"))); + + save_and_fill_manager_impl_->OnDidAcceptCreditCardSaveAndFillSuggestion( + fill_card_callback_.Get()); + save_and_fill_manager_impl_->OnDidCreateCard( + base::TimeTicks::Now(), + PaymentsAutofillClient::PaymentsRpcResult::kPermanentFailure, + /*instrument_id=*/""); + + save_and_fill_manager_impl_->LogCreditCardFormFilled(); + save_and_fill_manager_impl_->LogCreditCardFormSubmitted(); + + histogram_tester.ExpectBucketCount( + "Autofill.SaveAndFill.Funnel.Upload.Failure", + autofill_metrics::SaveAndFillFormEvent::kFormFilled, + /*expected_count=*/1); + histogram_tester.ExpectBucketCount( + "Autofill.SaveAndFill.Funnel.Upload.Failure", + autofill_metrics::SaveAndFillFormEvent::kFormSubmitted, + /*expected_count=*/1); + + // Make sure calling it multiple times has no effect. + save_and_fill_manager_impl_->LogCreditCardFormFilled(); + save_and_fill_manager_impl_->LogCreditCardFormSubmitted(); + + histogram_tester.ExpectBucketCount( + "Autofill.SaveAndFill.Funnel.Upload.Failure", + autofill_metrics::SaveAndFillFormEvent::kFormFilled, + /*expected_count=*/1); + histogram_tester.ExpectBucketCount( + "Autofill.SaveAndFill.Funnel.Upload.Failure", + autofill_metrics::SaveAndFillFormEvent::kFormSubmitted, + /*expected_count=*/1); +} + +TEST_F(SaveAndFillManagerImplTest, LogFunnelMetrics_LocalSave) { + base::HistogramTester histogram_tester; + save_and_fill_manager_impl_->OnDidAcceptCreditCardSaveAndFillSuggestion( + fill_card_callback_.Get()); + save_and_fill_manager_impl_->OnUserDidDecideOnLocalSave( + CardSaveAndFillDialogUserDecision::kAccepted, + CreateUserProvidedCardDetails( + /*card_number=*/u"1111222233334444", + /*cardholder_name=*/u"Jane Smith", + /*expiration_date_month=*/u"06", + /*expiration_date_year=*/u"2035", + /*security_code=*/u"456")); + + save_and_fill_manager_impl_->LogCreditCardFormFilled(); + save_and_fill_manager_impl_->LogCreditCardFormSubmitted(); + + histogram_tester.ExpectBucketCount( + "Autofill.SaveAndFill.Funnel.Local.Success", + autofill_metrics::SaveAndFillFormEvent::kFormFilled, + /*expected_count=*/1); + histogram_tester.ExpectBucketCount( + "Autofill.SaveAndFill.Funnel.Local.Success", + autofill_metrics::SaveAndFillFormEvent::kFormSubmitted, + /*expected_count=*/1); + + // Make sure calling it multiple times has no effect. + save_and_fill_manager_impl_->LogCreditCardFormFilled(); + save_and_fill_manager_impl_->LogCreditCardFormSubmitted(); + + histogram_tester.ExpectBucketCount( + "Autofill.SaveAndFill.Funnel.Local.Success", + autofill_metrics::SaveAndFillFormEvent::kFormFilled, + /*expected_count=*/1); + histogram_tester.ExpectBucketCount( + "Autofill.SaveAndFill.Funnel.Local.Success", + autofill_metrics::SaveAndFillFormEvent::kFormSubmitted, + /*expected_count=*/1); +} + } // namespace autofill::payments
diff --git a/components/autofill/core/browser/payments/test/mock_save_and_fill_manager.h b/components/autofill/core/browser/payments/test/mock_save_and_fill_manager.h index d974f27..529bed30 100644 --- a/components/autofill/core/browser/payments/test/mock_save_and_fill_manager.h +++ b/components/autofill/core/browser/payments/test/mock_save_and_fill_manager.h
@@ -21,12 +21,14 @@ (FillCardCallback fill_card_callback), (override)); MOCK_METHOD(void, OnSuggestionOffered, (), (override)); - MOCK_METHOD(void, OnCreditCardFormSubmitted, (), (override)); + MOCK_METHOD(void, MaybeAddStrikeForSaveAndFill, (), (override)); MOCK_METHOD(bool, ShouldBlockFeature, (), (override)); MOCK_METHOD(void, MaybeLogSaveAndFillSuggestionNotShownReason, (autofill_metrics::SaveAndFillSuggestionNotShownReason reason), (override)); + MOCK_METHOD(void, LogCreditCardFormFilled, (), (override)); + MOCK_METHOD(void, LogCreditCardFormSubmitted, (), (override)); }; } // namespace autofill
diff --git a/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator.cc b/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator.cc index f232931..7e8a1d39 100644 --- a/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator.cc +++ b/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator.cc
@@ -12,10 +12,12 @@ #include "base/check_deref.h" #include "base/containers/contains.h" +#include "base/containers/extend.h" #include "base/containers/to_vector.h" #include "base/feature_list.h" #include "base/i18n/case_conversion.h" #include "base/memory/raw_ptr.h" +#include "base/metrics/histogram_functions.h" #include "base/notreached.h" #include "base/strings/strcat.h" #include "base/strings/string_util.h" @@ -37,12 +39,15 @@ #include "components/autofill/core/browser/foundations/autofill_client.h" #include "components/autofill/core/browser/geo/address_i18n.h" #include "components/autofill/core/browser/geo/phone_number_i18n.h" +#include "components/autofill/core/browser/logging/log_manager.h" #include "components/autofill/core/browser/metrics/autofill_metrics.h" #include "components/autofill/core/browser/suggestions/suggestion.h" #include "components/autofill/core/browser/suggestions/suggestion_type.h" #include "components/autofill/core/common/autofill_clock.h" #include "components/autofill/core/common/autofill_constants.h" #include "components/autofill/core/common/autofill_features.h" +#include "components/autofill/core/common/autofill_internals/log_message.h" +#include "components/autofill/core/common/autofill_internals/logging_scope.h" #include "components/autofill/core/common/autofill_util.h" #include "components/autofill/core/common/form_field_data.h" #include "components/feature_engagement/public/feature_constants.h" @@ -264,6 +269,7 @@ Suggestion::Acceptability::kUnacceptableWithDeactivatedStyle; suggestion.children.emplace_back(SuggestionType::kSeparator); for (const AutofillProfile& test_address : test_addresses) { + CHECK(test_address.is_devtools_testing_profile()); const std::u16string test_address_country = test_address.GetInfo(ADDRESS_HOME_COUNTRY, locale); suggestion.children.emplace_back(test_address_country, @@ -499,6 +505,7 @@ std::vector<Suggestion::AutofillProfilePayload> payloads; payloads.reserve(profiles.size()); for (AutofillProfile& profile : profiles) { + CHECK(!profile.is_devtools_testing_profile()); std::u16string email_override; // If the following conditions are met: // - A plus address override is available @@ -606,8 +613,18 @@ return suggestions; } +SuggestionType GetSuggestionType(FormFieldData trigger_field) { + // If the user triggers suggestions on an autofilled field, field-by-field + // filling suggestions should be shown so that the user could easily correct + // values to something present in different stored addresses. + return trigger_field.is_autofilled() + ? SuggestionType::kAddressFieldByFieldFilling + : SuggestionType::kAddressEntry; +} + } // namespace +// TODO(crbug.com/409962888): Handle address suggestions on typing. std::vector<Suggestion> GetSuggestionsOnTypingForProfile( const AddressDataManager& address_data_manager, const std::u16string& field_contents) { @@ -728,42 +745,6 @@ return suggestions; } -std::vector<Suggestion> GetSuggestionsForProfiles( - const AutofillClient& client, - const FieldTypeSet& field_types, - const FormFieldData& trigger_field, - FieldType trigger_field_type, - SuggestionType suggestion_type, - std::optional<std::string> plus_address_email_override) { - CHECK(trigger_field.is_autofilled() || - suggestion_type != SuggestionType::kAddressFieldByFieldFilling); - std::vector<AutofillProfile> profiles_to_suggest = GetProfilesToSuggest( - client.GetPersonalDataManager().address_data_manager(), trigger_field, - trigger_field_type, field_types); - const std::string gaia_email = - client.GetIdentityManager() - ->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin) - .email; - std::vector<Suggestion> suggestions = CreateSuggestionsFromProfiles( - std::move(profiles_to_suggest), gaia_email, field_types, suggestion_type, - trigger_field_type, trigger_field, std::move(plus_address_email_override), - client.GetAppLocale()); - - // Add devtools test addresses suggestion if it exists. A suggestion will - // exist if devtools is open and therefore test addresses were set. - if (std::optional<Suggestion> test_addresses_suggestion = - GetSuggestionForTestAddresses(client.GetTestAddresses(), - client.GetAppLocale())) { - suggestions.push_back(std::move(*test_addresses_suggestion)); - } - if (suggestions.empty()) { - return suggestions; - } - std::ranges::move(GetAddressFooterSuggestions(trigger_field.is_autofilled()), - std::back_inserter(suggestions)); - return suggestions; -} - Suggestion CreateManageAddressesSuggestion() { Suggestion suggestion( l10n_util::GetStringUTF16(IDS_AUTOFILL_MANAGE_ADDRESSES), @@ -796,4 +777,186 @@ plus_address_email_override, app_locale); } +AddressSuggestionGenerator::AddressSuggestionGenerator( + const AutofillClient& client, + const std::optional<std::string>& plus_address_email_override, + base::WeakPtr<FormFiller> form_filler, + LogManager* log_manager) + : plus_address_email_override_(plus_address_email_override), + client_(client), + form_filler_(form_filler), + log_manager_(log_manager) {} + +AddressSuggestionGenerator::~AddressSuggestionGenerator() = default; + +void AddressSuggestionGenerator::FetchSuggestionData( + const FormData& form, + const FormFieldData& trigger_field, + const FormStructure* form_structure, + const AutofillField* trigger_autofill_field, + const AutofillClient& client, + base::OnceCallback< + void(std::pair<FillingProduct, + std::vector<SuggestionGenerator::SuggestionData>>)> + callback) { + FetchSuggestionData( + form, trigger_field, form_structure, trigger_autofill_field, client, + [&callback](std::pair<FillingProduct, + std::vector<SuggestionGenerator::SuggestionData>> + suggestion_data) { + std::move(callback).Run(std::move(suggestion_data)); + }); +} + +void AddressSuggestionGenerator::GenerateSuggestions( + const FormData& form, + const FormFieldData& trigger_field, + const FormStructure* form_structure, + const AutofillField* trigger_autofill_field, + const std::vector<std::pair<FillingProduct, std::vector<SuggestionData>>>& + all_suggestion_data, + base::OnceCallback<void(ReturnedSuggestions)> callback) { + GenerateSuggestions( + form, trigger_field, form_structure, trigger_autofill_field, + all_suggestion_data, + [&callback](ReturnedSuggestions returned_suggestions) { + std::move(callback).Run(std::move(returned_suggestions)); + }); +} + +void AddressSuggestionGenerator::FetchSuggestionData( + const FormData& form, + const FormFieldData& trigger_field, + const FormStructure* form_structure, + const AutofillField* trigger_autofill_field, + const AutofillClient& client, + base::FunctionRef< + void(std::pair<FillingProduct, + std::vector<SuggestionGenerator::SuggestionData>>)> + callback) { + if (!form_structure || !trigger_autofill_field) { + callback({FillingProduct::kAddress, {}}); + return; + } +#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) + bool should_suppress = + client.GetPersonalDataManager() + .address_data_manager() + .AreAddressSuggestionsBlocked( + CalculateFormSignature(form), + CalculateFieldSignatureForField(trigger_field), form.url()); + base::UmaHistogramBoolean("Autofill.Suggestion.StrikeSuppression.Address", + should_suppress); + if (should_suppress && + !base::FeatureList::IsEnabled( + features::test::kAutofillDisableSuggestionStrikeDatabase)) { + if (log_manager_) { + LOG_AF(log_manager_) << LoggingScope::kFilling + << LogMessage::kSuggestionSuppressed + << " Reason: strike limit reached."; + } + // If the user already reached the strike limit on this particular field, + // address suggestions are suppressed. + callback({FillingProduct::kAddress, {}}); + return; + } +#endif + + field_types_ = [&]() -> FieldTypeSet { + if (GetSuggestionType(trigger_field) == + SuggestionType::kAddressFieldByFieldFilling) { + return {trigger_autofill_field->Type().GetAddressType()}; + } + // If the FormData `form_data` and FormStructure `form` do not have the same + // size, we assume as a fallback that all fields are fillable. + base::flat_map<FieldGlobalId, DenseSet<FieldFillingSkipReason>> + skip_reasons; + if (form_filler_ && form.fields().size() == form_structure->field_count()) { + skip_reasons = form_filler_->GetFieldFillingSkipReasons( + form.fields(), *form_structure, *trigger_autofill_field, + FormFiller::RefillOptions::NotRefill(), FillingProduct::kAddress); + } + FieldTypeSet field_types; + for (size_t i = 0; i < form_structure->field_count(); ++i) { + if (auto it = skip_reasons.find(form_structure->field(i)->global_id()); + it == skip_reasons.end() || it->second.empty()) { + field_types.insert(form_structure->field(i)->Type().GetAddressType()); + } + } + return field_types; + }(); + + std::vector<AutofillProfile> profiles_to_suggest = GetProfilesToSuggest( + client.GetPersonalDataManager().address_data_manager(), trigger_field, + trigger_autofill_field->Type().GetAddressType(), field_types_); + + std::vector<SuggestionGenerator::SuggestionData> suggestion_data = + base::ToVector( + std::move(profiles_to_suggest), [](AutofillProfile& profile) { + return SuggestionGenerator::SuggestionData(std::move(profile)); + }); + + // Add devtools test addresses if it exists. A test addresses will + // exist if devtools is open and therefore test addresses were set. + for (AutofillProfile test_address : client.GetTestAddresses()) { + suggestion_data.emplace_back(test_address); + } + + callback({FillingProduct::kAddress, std::move(suggestion_data)}); +} + +void AddressSuggestionGenerator::GenerateSuggestions( + const FormData& form, + const FormFieldData& trigger_field, + const FormStructure* form_structure, + const AutofillField* trigger_autofill_field, + const std::vector<std::pair<FillingProduct, std::vector<SuggestionData>>>& + all_suggestion_data, + base::FunctionRef<void(ReturnedSuggestions)> callback) { + if (!form_structure || !trigger_autofill_field || + !client_->GetIdentityManager()) { + callback({FillingProduct::kAddress, {}}); + return; + } + + std::vector<SuggestionData> address_suggestion_data = + ExtractSuggestionDataForFillingProduct(all_suggestion_data, + FillingProduct::kAddress); + + std::vector<AutofillProfile> profiles_to_suggest = base::ToVector( + std::move(address_suggestion_data), [](SuggestionData& suggestion_data) { + return std::get<AutofillProfile>(std::move(suggestion_data)); + }); + + // Testing profiles were added last. + auto partition_it = std::ranges::find_if( + profiles_to_suggest, &AutofillProfile::is_devtools_testing_profile); + + std::vector<Suggestion> suggestions = CreateSuggestionsFromProfiles( + std::vector(std::make_move_iterator(profiles_to_suggest.begin()), + std::make_move_iterator(partition_it)), + client_->GetIdentityManager() + ->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin) + .email, + field_types_, GetSuggestionType(trigger_field), + trigger_autofill_field->Type().GetAddressType(), trigger_field, + plus_address_email_override_, client_->GetAppLocale()); + + // Add devtools test addresses suggestion if it exists. + if (std::optional<Suggestion> test_addresses_suggestion = + GetSuggestionForTestAddresses( + std::vector(std::make_move_iterator(partition_it), + std::make_move_iterator(profiles_to_suggest.end())), + client_->GetAppLocale())) { + suggestions.push_back(std::move(*test_addresses_suggestion)); + } + if (suggestions.empty()) { + callback({FillingProduct::kAddress, {}}); + return; + } + base::Extend(suggestions, + GetAddressFooterSuggestions(trigger_field.is_autofilled())); + callback({FillingProduct::kAddress, std::move(suggestions)}); +} + } // namespace autofill
diff --git a/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator.h b/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator.h index f8eade0..2ee6ebc6 100644 --- a/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator.h +++ b/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator.h
@@ -12,11 +12,15 @@ #include "base/check_deref.h" #include "base/containers/flat_map.h" #include "base/memory/raw_ptr.h" +#include "base/memory/raw_ref.h" #include "components/autofill/core/browser/data_manager/personal_data_manager.h" #include "components/autofill/core/browser/data_model/addresses/autofill_profile.h" #include "components/autofill/core/browser/field_types.h" +#include "components/autofill/core/browser/filling/form_filler.h" +#include "components/autofill/core/browser/logging/log_manager.h" #include "components/autofill/core/browser/metrics/log_event.h" #include "components/autofill/core/browser/suggestions/suggestion.h" +#include "components/autofill/core/browser/suggestions/suggestion_generator.h" #include "components/autofill/core/browser/suggestions/suggestion_type.h" #include "components/autofill/core/common/aliases.h" @@ -35,18 +39,6 @@ const AddressDataManager& adress_data_manager, const std::u16string& field_contents); -// Generates suggestions for a form containing the given `field_types`. It -// considers all available profiles, deduplicates them based on the types and -// returns one suggestion per remaining profile. -// `field_types` are the relevant types for the current suggestions. -std::vector<Suggestion> GetSuggestionsForProfiles( - const AutofillClient& client, - const FieldTypeSet& field_types, - const FormFieldData& trigger_field, - FieldType trigger_field_type, - SuggestionType suggestion_type, - std::optional<std::string> plus_address_email_override); - // Generates a footer suggestion "Manage addresses..." menu item which will // redirect to Chrome address settings page. Suggestion CreateManageAddressesSuggestion(); @@ -69,6 +61,82 @@ std::optional<std::string> plus_address_email_override = std::nullopt, const std::string& gaia_email = ""); +// `SuggestionGenerator` implementation for addresses. Obtaining the address +// suggestions should be done only through this class. +class AddressSuggestionGenerator : public SuggestionGenerator { + public: + // TODO(crbug.com/409962888): `plus_address_email_override` + // has to be removed once the plus address suggestion generator and + // suggestions merging are implemented. + AddressSuggestionGenerator( + const AutofillClient& client, + const std::optional<std::string>& plus_address_email_override, + base::WeakPtr<FormFiller> form_filler, + LogManager* log_manager); + ~AddressSuggestionGenerator() override; + + void FetchSuggestionData( + const FormData& form, + const FormFieldData& trigger_field, + const FormStructure* form_structure, + const AutofillField* trigger_autofill_field, + const AutofillClient& client, + base::OnceCallback< + void(std::pair<FillingProduct, + std::vector<SuggestionGenerator::SuggestionData>>)> + callback) override; + + void GenerateSuggestions( + const FormData& form, + const FormFieldData& trigger_field, + const FormStructure* form_structure, + const AutofillField* trigger_autofill_field, + const std::vector<std::pair<FillingProduct, std::vector<SuggestionData>>>& + all_suggestion_data, + base::OnceCallback<void(ReturnedSuggestions)> callback) override; + + // Like SuggestionGenerator override, but takes a base::FunctionRef instead of + // a base::OnceCallback. Calls that callback exactly once. + void FetchSuggestionData( + const FormData& form, + const FormFieldData& trigger_field, + const FormStructure* form_structure, + const AutofillField* trigger_autofill_field, + const AutofillClient& client, + base::FunctionRef< + void(std::pair<FillingProduct, + std::vector<SuggestionGenerator::SuggestionData>>)> + callback); + + // Like SuggestionGenerator override, but takes a base::FunctionRef instead of + // a base::OnceCallback. Calls that callback exactly once. + void GenerateSuggestions( + const FormData& form, + const FormFieldData& trigger_field, + const FormStructure* form_structure, + const AutofillField* trigger_autofill_field, + const std::vector<std::pair<FillingProduct, std::vector<SuggestionData>>>& + all_suggestion_data, + base::FunctionRef<void(ReturnedSuggestions)> callback); + + private: + // Used to change the emails matching the GAIA email in suggestions with + // the `plus_address_email_override_`. + // TODO(crbug.com/409962888): `plus_address_email_override_` has to be removed + // once the plus address suggestion generator and suggestions merging are + // implemented. + const std::optional<std::string> plus_address_email_override_; + const raw_ref<const AutofillClient> client_; + + // Used to obtain field filling skip reasons. + base::WeakPtr<FormFiller> form_filler_; + + raw_ptr<LogManager> log_manager_; + + // Stores a set of types of fillable fields that are in the form. + FieldTypeSet field_types_; +}; + } // namespace autofill #endif // COMPONENTS_AUTOFILL_CORE_BROWSER_SUGGESTIONS_ADDRESSES_ADDRESS_SUGGESTION_GENERATOR_H_
diff --git a/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator_unittest.cc b/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator_unittest.cc index e3ad32f47..42a96748 100644 --- a/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator_unittest.cc +++ b/components/autofill/core/browser/suggestions/addresses/address_suggestion_generator_unittest.cc
@@ -4,6 +4,7 @@ #include "components/autofill/core/browser/suggestions/addresses/address_suggestion_generator.h" +#include <memory> #include <optional> #include <string> #include <vector> @@ -12,6 +13,7 @@ #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.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" #include "base/time/time.h" @@ -22,6 +24,7 @@ #include "components/autofill/core/browser/data_model/addresses/autofill_profile.h" #include "components/autofill/core/browser/data_model/addresses/autofill_profile_test_api.h" #include "components/autofill/core/browser/field_types.h" +#include "components/autofill/core/browser/form_structure_test_api.h" #include "components/autofill/core/browser/foundations/test_autofill_client.h" #include "components/autofill/core/browser/geo/phone_number_i18n.h" #include "components/autofill/core/browser/suggestions/suggestion.h" @@ -32,6 +35,7 @@ #include "components/autofill/core/common/autofill_clock.h" #include "components/autofill/core/common/autofill_constants.h" #include "components/autofill/core/common/autofill_features.h" +#include "components/autofill/core/common/form_data_test_api.h" #include "components/autofill/core/common/form_field_data.h" #include "components/feature_engagement/public/feature_constants.h" #include "components/strings/grit/components_strings.h" @@ -117,6 +121,46 @@ const std::string& app_locale() { return address_data().app_locale(); } TestAutofillClient* autofill_client() { return &autofill_client_; } + AutofillField& field() { return *form_structure_->fields().front(); } + + std::vector<Suggestion> GetSuggestionsForProfiles( + const FormFieldData& field_data, + FieldType field_type) { + // Preparing the test form and field. + FormData form_data; + test_api(form_data).Append(field_data); + form_structure_ = std::make_unique<FormStructure>(form_data); + test_api(*form_structure_).SetFieldTypes({field_type}); + + std::vector<Suggestion> suggestions; + AddressSuggestionGenerator address_suggestion_generator( + autofill_client_, /*plus_address_email_override=*/std::nullopt, + /*form_filler=*/nullptr, /*log_manager=*/nullptr); + + auto on_suggestions_generated = + [&suggestions]( + SuggestionGenerator::ReturnedSuggestions returned_suggestions) { + suggestions = std::move(returned_suggestions.second); + }; + + auto on_suggestion_data_returned = + [this, &on_suggestions_generated, &form_data, &field_data, + &address_suggestion_generator]( + std::pair<FillingProduct, + std::vector<SuggestionGenerator::SuggestionData>> + suggestion_data) { + address_suggestion_generator.GenerateSuggestions( + form_data, field_data, form_structure_.get(), &field(), + {std::move(suggestion_data)}, on_suggestions_generated); + }; + + // Since the `on_suggestions_generated` callback is called synchronously, + // we can assume that `suggestions` will hold correct value. + address_suggestion_generator.FetchSuggestionData( + form_data, field_data, form_structure_.get(), &field(), + autofill_client_, on_suggestion_data_returned); + return suggestions; + } private: base::test::TaskEnvironment task_environment_{ @@ -124,6 +168,7 @@ test::AutofillUnitTestEnvironment autofill_test_environment_; TestAutofillClient autofill_client_; syncer::TestSyncService sync_service_; + std::unique_ptr<FormStructure> form_structure_; }; // Tests that `SuggestionType::AddressEntryOnTyping` suggestions are returned @@ -770,9 +815,8 @@ .starts_with(profile1.GetRawInfo(NAME_FIRST))); // Expect that suggestions are prefix matched. - std::vector<Suggestion> address_suggestions = GetSuggestionsForProfiles( - *autofill_client(), {NAME_FIRST}, triggering_field, NAME_FIRST, - SuggestionType::kAddressEntry, std::nullopt); + std::vector<Suggestion> address_suggestions = + GetSuggestionsForProfiles(triggering_field, NAME_FIRST); EXPECT_EQ(address_suggestions.size(), 3ul); EXPECT_THAT(address_suggestions, ContainsAddressFooterSuggestions()); } @@ -798,9 +842,7 @@ // Expect that only the second address yields a suggestion because the first // one would be removed for exactly matching the field's content. EXPECT_THAT( - GetSuggestionsForProfiles( - *autofill_client(), {NAME_FULL}, triggering_field, NAME_FULL, - SuggestionType::kAddressFieldByFieldFilling, std::nullopt), + GetSuggestionsForProfiles(triggering_field, NAME_FULL), ElementsAre(EqualsSuggestion(SuggestionType::kAddressFieldByFieldFilling, profile2.GetRawInfo(NAME_FULL)), EqualsSuggestion(SuggestionType::kSeparator), @@ -815,9 +857,7 @@ // otherwise there would be no address suggestions at all and we would not // show the popup, making the user unable to use the footer suggestions. EXPECT_THAT( - GetSuggestionsForProfiles( - *autofill_client(), {NAME_FULL}, triggering_field, NAME_FULL, - SuggestionType::kAddressFieldByFieldFilling, std::nullopt), + GetSuggestionsForProfiles(triggering_field, NAME_FULL), ElementsAre(EqualsSuggestion(SuggestionType::kAddressFieldByFieldFilling, profile1.GetRawInfo(NAME_FULL)), EqualsSuggestion(SuggestionType::kSeparator), @@ -848,9 +888,7 @@ // one would be removed for exactly matching the field's content, even though // the two values are equal up to normalization. EXPECT_THAT( - GetSuggestionsForProfiles( - *autofill_client(), {NAME_FULL}, triggering_field, NAME_FULL, - SuggestionType::kAddressFieldByFieldFilling, std::nullopt), + GetSuggestionsForProfiles(triggering_field, NAME_FULL), ElementsAre(EqualsSuggestion(SuggestionType::kAddressFieldByFieldFilling, u"Tést Name"), EqualsSuggestion(SuggestionType::kSeparator), @@ -967,14 +1005,14 @@ address_data().AddProfile(test::GetFullProfile()); FormFieldData field; field.set_is_autofilled(true); - std::vector<Suggestion> suggestions = GetSuggestionsForProfiles( - *autofill_client(), {NAME_FIRST}, field, NAME_FIRST, - SuggestionType::kAddressEntry, std::nullopt); - EXPECT_THAT(suggestions, - ElementsAre(EqualsSuggestion(SuggestionType::kAddressEntry), - EqualsSuggestion(SuggestionType::kSeparator), - EqualsUndoAutofillSuggestion(), - EqualsManageAddressesSuggestion())); + std::vector<Suggestion> suggestions = + GetSuggestionsForProfiles(field, NAME_FIRST); + EXPECT_THAT( + suggestions, + ElementsAre(EqualsSuggestion(SuggestionType::kAddressFieldByFieldFilling), + EqualsSuggestion(SuggestionType::kSeparator), + EqualsUndoAutofillSuggestion(), + EqualsManageAddressesSuggestion())); } #endif @@ -982,9 +1020,8 @@ TestAddressSuggestion_AddressField_ReturnSuggestion) { AutofillProfile profile = test::GetFullProfile(); autofill_client()->set_test_addresses({profile}); - std::vector<Suggestion> suggestions = GetSuggestionsForProfiles( - *autofill_client(), /*field_types=*/{NAME_FIRST}, FormFieldData(), - NAME_FIRST, SuggestionType::kAddressEntry, std::nullopt); + std::vector<Suggestion> suggestions = + GetSuggestionsForProfiles(FormFieldData(), NAME_FIRST); // There should be one `SuggestionType::kDevtoolsTestAddresses`, one // `SuggestionType::kSeparator` and one `SuggestionType::kManageAddress`. @@ -1221,5 +1258,52 @@ } #endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) +TEST_F(AddressSuggestionGeneratorTest, GeneratesSuggestions) { + base::MockCallback<base::OnceCallback<void( + std::pair<FillingProduct, + std::vector<SuggestionGenerator::SuggestionData>>)>> + suggestion_data_callback; + base::MockCallback< + base::OnceCallback<void(SuggestionGenerator::ReturnedSuggestions)>> + suggestions_generated_callback; + + AutofillProfile profile1 = test::GetFullProfile(); + address_data().AddProfile(profile1); + + // Create a form with one field, that expects a full name. + FormFieldData field; + FormData form_data; + test_api(form_data).Append(field); + std::unique_ptr<FormStructure> form_structure = + std::make_unique<FormStructure>(form_data); + test_api(*form_structure).SetFieldTypes({NAME_FULL}); + + AddressSuggestionGenerator generator( + *autofill_client(), /*plus_address_email_override=*/std::nullopt, + /*form_filler=*/nullptr, /*log_manager=*/nullptr); + std::pair<FillingProduct, std::vector<SuggestionGenerator::SuggestionData>> + savedCallbackArgument; + + EXPECT_CALL(suggestion_data_callback, + Run(testing::Pair(FillingProduct::kAddress, + testing::ElementsAre(profile1)))) + .WillOnce(testing::SaveArg<0>(&savedCallbackArgument)); + generator.FetchSuggestionData(form_data, field, form_structure.get(), + form_structure->field(0), *autofill_client(), + suggestion_data_callback.Get()); + + EXPECT_CALL( + suggestions_generated_callback, + Run(testing::Pair( + FillingProduct::kAddress, + testing::ElementsAre( + EqualsSuggestion(SuggestionType::kAddressEntry, u"John H. Doe"), + EqualsSuggestion(SuggestionType::kSeparator), + EqualsSuggestion(SuggestionType::kManageAddress))))); + generator.GenerateSuggestions( + form_data, field, form_structure.get(), form_structure->field(0), + {savedCallbackArgument}, suggestions_generated_callback.Get()); +} + } // namespace } // namespace autofill
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/ClipDrawableProgressBar.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/ClipDrawableProgressBar.java index f54c5b1..16bfac9 100644 --- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/ClipDrawableProgressBar.java +++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/ClipDrawableProgressBar.java
@@ -260,7 +260,7 @@ */ public void getDrawingInfo(DrawingInfo drawingInfoOut) { boolean visible = getVisibility() == VISIBLE; - if (ChromeFeatureList.sAndroidAnimatedProgressBarInViz.isEnabled()) { + if (shouldAnimateCompositedLayer()) { visible = mDesiredVisibility == VISIBLE; drawingInfoOut.visible = visible; } @@ -339,7 +339,7 @@ int newVisibility = mDesiredVisibility; if (getAlpha() == 0 && mDesiredVisibility == VISIBLE) newVisibility = INVISIBLE; if (oldVisibility != newVisibility) { - if (!ChromeFeatureList.sAndroidAnimatedProgressBarInViz.isEnabled()) { + if (!shouldAnimateCompositedLayer()) { super.setVisibility(newVisibility); } if (mProgressBarObserver != null) mProgressBarObserver.onVisibilityChanged(); @@ -418,7 +418,7 @@ @Override protected boolean onSetAlpha(int alpha) { - if (ChromeFeatureList.sAndroidAnimatedProgressBarInViz.isEnabled()) { + if (shouldAnimateCompositedLayer()) { if (alpha == 0) { mDesiredVisibility = INVISIBLE; } else { @@ -428,4 +428,9 @@ updateInternalVisibility(); return super.onSetAlpha(alpha); } + + private boolean shouldAnimateCompositedLayer() { + return ChromeFeatureList.sAndroidAnimatedProgressBarInViz.isEnabled() + || ChromeFeatureList.sAndroidAnimatedProgressBarInBrowser.isEnabled(); + } }
diff --git a/components/content_settings/core/common/features.cc b/components/content_settings/core/common/features.cc index 1f9115a..3ddbb3e 100644 --- a/components/content_settings/core/common/features.cc +++ b/components/content_settings/core/common/features.cc
@@ -105,6 +105,10 @@ "NativeUnpartitionedStoragePermittedWhen3PCOff", base::FEATURE_DISABLED_BY_DEFAULT); +BASE_FEATURE(kBlockV8OptimizerOnUnfamiliarSitesSetting, + "BlockV8OptimizerOnUnfamiliarSitesSetting", + base::FEATURE_DISABLED_BY_DEFAULT); + const char kTpcdReadHeuristicsGrantsName[] = "TpcdReadHeuristicsGrants"; const char kTpcdWriteRedirectHeuristicGrantsName[] =
diff --git a/components/content_settings/core/common/features.h b/components/content_settings/core/common/features.h index 0f57dee2..1cd3b34 100644 --- a/components/content_settings/core/common/features.h +++ b/components/content_settings/core/common/features.h
@@ -123,6 +123,11 @@ COMPONENT_EXPORT(CONTENT_SETTINGS_FEATURES) BASE_DECLARE_FEATURE(kNativeUnpartitionedStoragePermittedWhen3PCOff); +// Shows the option to disable the v8 optimizer for unfamiliar sites on the +// site settings page. +COMPONENT_EXPORT(CONTENT_SETTINGS_FEATURES) +BASE_DECLARE_FEATURE(kBlockV8OptimizerOnUnfamiliarSitesSetting); + //////////////////////////////////////////////////////////// // Start of third-party cookie access heuristics features // ////////////////////////////////////////////////////////////
diff --git a/components/input/input_router_impl.cc b/components/input/input_router_impl.cc index 3da5728..f6b06457 100644 --- a/components/input/input_router_impl.cc +++ b/components/input/input_router_impl.cc
@@ -253,7 +253,8 @@ // then no scrolling really ever occurs (even though we still send // GestureScrollBegin). touch_scroll_started_sent_ = true; - touch_event_queue_.PrependTouchScrollNotification(); + touch_event_queue_.PrependTouchScrollNotification( + gesture_event.event.primary_unique_touch_event_id); } }
diff --git a/components/input/passthrough_touch_event_queue.cc b/components/input/passthrough_touch_event_queue.cc index 2c34662..713b20d 100644 --- a/components/input/passthrough_touch_event_queue.cc +++ b/components/input/passthrough_touch_event_queue.cc
@@ -9,11 +9,13 @@ #include <utility> #include "base/auto_reset.h" +#include "base/feature_list.h" #include "base/metrics/field_trial_params.h" #include "base/trace_event/trace_event.h" #include "base/trace_event/typed_macros.h" #include "components/input/touch_timeout_handler.h" #include "components/input/web_touch_event_traits.h" +#include "third_party/blink/public/common/features.h" #include "ui/events/base_event_utils.h" #include "ui/gfx/geometry/point_f.h" @@ -117,10 +119,17 @@ SendTouchEventImmediately(&cloned_event, true, dispatch_callback); } -void PassthroughTouchEventQueue::PrependTouchScrollNotification() { +void PassthroughTouchEventQueue::PrependTouchScrollNotification( + uint32_t primary_unique_touch_event_id) { TRACE_EVENT0("input", "PassthroughTouchEventQueue::PrependTouchScrollNotification"); + if (base::FeatureList::IsEnabled( + blink::features::kAsyncTouchMovesImmediatelyAfterScroll)) { + send_touch_events_async_ = true; + SetAckStateForPendingTouchMovesFromSequence(primary_unique_touch_event_id); + } + TouchEventWithLatencyInfo touch( WebInputEvent::Type::kTouchScrollStarted, WebInputEvent::kNoModifiers, ui::EventTimeForNow(), LatencyInfo()); @@ -133,6 +142,35 @@ } } +void PassthroughTouchEventQueue::SetAckStateForPendingTouchMovesFromSequence( + uint32_t primary_unique_touch_event_id) { + if (curr_sequence_down_event_id_ != primary_unique_touch_event_id) { + // The touch sequence that started scroll has ended. + return; + } + + CHECK(processing_acks_, base::NotFatalUntil::M143); + + // Ack all outstanding touches in the current sequence to unblock the + // browser. + for (auto& it : outstanding_touches_) { + if (it.event.GetType() != WebInputEvent::Type::kTouchMove) { + break; + } + + auto& outstanding_touch = + const_cast<TouchEventWithLatencyInfoAndAckState&>(it); + if (it.ack_state() == blink::mojom::InputEventResultState::kUnknown) { + // Based on the CHECK above we are already in middle of processing acks, + // we can just set them ignored here and the existing loop can process + // acks for these events as well. + outstanding_touch.set_ack_info( + blink::mojom::InputEventResultSource::kBrowser, + blink::mojom::InputEventResultState::kIgnored); + } + } +} + void PassthroughTouchEventQueue::ProcessTouchAck( blink::mojom::InputEventResultSource ack_source, blink::mojom::InputEventResultState ack_result, @@ -305,6 +343,12 @@ last_sent_touchevent_ = std::make_unique<WebTouchEvent>(touch->event); } + if (touch->event.IsTouchSequenceStart()) { + curr_sequence_down_event_id_ = touch->event.unique_touch_event_id; + } else if (touch->event.IsTouchSequenceEnd()) { + curr_sequence_down_event_id_.reset(); + } + if (timeout_handler_) timeout_handler_->StartIfNecessary(*touch); touch->event.GetModifiableEventLatencyMetadata().dispatched_to_renderer =
diff --git a/components/input/passthrough_touch_event_queue.h b/components/input/passthrough_touch_event_queue.h index ccd3cd39..1337d564 100644 --- a/components/input/passthrough_touch_event_queue.h +++ b/components/input/passthrough_touch_event_queue.h
@@ -5,6 +5,7 @@ #ifndef COMPONENTS_INPUT_PASSTHROUGH_TOUCH_EVENT_QUEUE_H_ #define COMPONENTS_INPUT_PASSTHROUGH_TOUCH_EVENT_QUEUE_H_ +#include <optional> #include <set> #include <string> @@ -106,7 +107,7 @@ void QueueEvent(const TouchEventWithLatencyInfo& event, DispatchToRendererCallback& dispatch_callback); - void PrependTouchScrollNotification(); + void PrependTouchScrollNotification(uint32_t primary_unique_touch_event_id); void ProcessTouchAck(blink::mojom::InputEventResultSource ack_source, blink::mojom::InputEventResultState ack_result, @@ -229,6 +230,9 @@ PreFilterResult FilterBeforeForwardingImpl(const blink::WebTouchEvent& event); bool ShouldFilterForEvent(const blink::WebTouchEvent& event); + void SetAckStateForPendingTouchMovesFromSequence( + uint32_t primary_unique_touch_event_id); + void AckTouchEventToClient( const TouchEventWithLatencyInfo& acked_event, blink::mojom::InputEventResultSource ack_source, @@ -278,6 +282,8 @@ TouchEventWithLatencyInfoAndAckStateComparator> outstanding_touches_; + std::optional<uint32_t> curr_sequence_down_event_id_ = std::nullopt; + // Whether we should allow events to bypass normal queue filter rules. const bool skip_touch_filter_; // What events types are allowed to bypass the filter.
diff --git a/components/input/passthrough_touch_event_queue_unittest.cc b/components/input/passthrough_touch_event_queue_unittest.cc index 929024f8..b3c19ab 100644 --- a/components/input/passthrough_touch_event_queue_unittest.cc +++ b/components/input/passthrough_touch_event_queue_unittest.cc
@@ -90,6 +90,13 @@ } last_acked_event_ = event.event; last_acked_event_state_ = ack_result; + + if (will_start_scrolling_on_touch_move_ack_) { + DCHECK_NE(event_id_for_scroll_, -1); + PrependTouchScrollNotification(event_id_for_scroll_); + will_start_scrolling_on_touch_move_ack_ = false; + event_id_for_scroll_ = -1; + } } void OnFilteringTouchEvent(const blink::WebTouchEvent& touch_event) override { @@ -189,6 +196,11 @@ followup_touch_event_ = std::make_unique<WebTouchEvent>(event); } + void SetWillStartScrollingOnTouchMoveAck(int primary_unique_touch_event_id) { + event_id_for_scroll_ = primary_unique_touch_event_id; + will_start_scrolling_on_touch_move_ack_ = true; + } + void SetSyncAckResult(blink::mojom::InputEventResultState sync_ack_result) { sync_ack_result_ = std::make_unique<blink::mojom::InputEventResultState>(sync_ack_result); @@ -262,8 +274,8 @@ SendTouchEvent(); } - void PrependTouchScrollNotification() { - queue_->PrependTouchScrollNotification(); + void PrependTouchScrollNotification(int primary_unique_touch_event_id = 0) { + queue_->PrependTouchScrollNotification(primary_unique_touch_event_id); } void AdvanceTouchTime(double seconds) { @@ -353,6 +365,8 @@ std::vector<WebTouchEvent> sent_events_; blink::mojom::InputEventResultState last_acked_event_state_; SyntheticWebTouchEvent touch_event_; + int event_id_for_scroll_ = -1; + bool will_start_scrolling_on_touch_move_ack_ = false; std::unique_ptr<WebTouchEvent> followup_touch_event_; std::unique_ptr<blink::mojom::InputEventResultState> sync_ack_result_; double slop_length_dips_; @@ -1990,4 +2004,71 @@ FilterBeforeForwarding(event)); } +// Testing sequence: TouchDown1, TouchMove1, TouchMove2, TouchMove1Ack. +// Test that the TouchMove2 is immediately ack'd, when TouchMove1Ack starts a +// scroll. +TEST_F(PassthroughTouchEventQueueTest, + TouchMoveBetweenScrollStartingTouchMoveAndAckSentAsync) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitAndEnableFeature( + blink::features::kAsyncTouchMovesImmediatelyAfterScroll); + + // A touch sequence that will turn into a scroll. + PressTouchPoint(0, 1); + int primary_unique_touch_event_id = GetUniqueTouchEventID(); + SendTouchEventAck(blink::mojom::InputEventResultState::kNotConsumed); + EXPECT_EQ(1U, GetAndResetSentEventCount()); + EXPECT_EQ(1U, GetAndResetAckedEventCount()); + EXPECT_EQ(0U, queued_event_count()); + + // Send two touch moves. Both will be sent to the renderer. + MoveTouchPoint(0, 20, 5); + EXPECT_EQ(1U, GetAndResetSentEventCount()); + EXPECT_EQ(1U, queued_event_count()); + + MoveTouchPoint(0, 40, 10); + EXPECT_EQ(1U, GetAndResetSentEventCount()); + EXPECT_EQ(2U, queued_event_count()); + + // A scroll gesture is generated from the first touch move. The compositor + // consumes the gesture. + SetWillStartScrollingOnTouchMoveAck(primary_unique_touch_event_id); + SendTouchEventAck(blink::mojom::InputEventResultState::kNotConsumed); + + // The second touch move should be acked immediately as ignored. + EXPECT_EQ(2U, GetAndResetAckedEventCount()); +} + +// Testing sequence: TouchDown1, TouchMove1, TouchMove1Ack, TouchMove2, GSUAck. +// Test that the TouchMove2 is sent async. +TEST_F(PassthroughTouchEventQueueTest, TouchMoveGSUAckSentAsync) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitAndEnableFeature( + blink::features::kAsyncTouchMovesImmediatelyAfterScroll); + + // A touch sequence that will turn into a scroll. + PressTouchPoint(0, 1); + int primary_unique_touch_event_id = GetUniqueTouchEventID(); + SendTouchEventAck(blink::mojom::InputEventResultState::kNotConsumed); + EXPECT_EQ(1U, GetAndResetSentEventCount()); + EXPECT_EQ(1U, GetAndResetAckedEventCount()); + EXPECT_EQ(0U, queued_event_count()); + + // Send two touch moves. Both will be sent to the renderer. + MoveTouchPoint(0, 20, 5); + EXPECT_EQ(1U, GetAndResetSentEventCount()); + EXPECT_EQ(1U, queued_event_count()); + + // A scroll gesture is generated from the first touch move. The compositor + // consumes the gesture. + SetWillStartScrollingOnTouchMoveAck(primary_unique_touch_event_id); + SendTouchEventAck(blink::mojom::InputEventResultState::kNotConsumed); + + MoveTouchPoint(0, 40, 10); + EXPECT_EQ(sent_event().dispatch_type, + WebInputEvent::DispatchType::kEventNonBlocking); + EXPECT_EQ(sent_event().GetType(), WebInputEvent::Type::kTouchMove); + EXPECT_EQ(2U, GetAndResetSentEventCount()); +} + } // namespace input
diff --git a/components/input/render_input_router.mojom b/components/input/render_input_router.mojom index d4563b9..81eed30 100644 --- a/components/input/render_input_router.mojom +++ b/components/input/render_input_router.mojom
@@ -57,6 +57,7 @@ // Implemented by Viz, an interface between Browser and Viz. It defines methods // used to communicate input event handling related state from Browser to Viz. +[DirectReceiver] interface RenderInputRouterDelegate { // Send |TouchTransferState| from Browser upon a successful touch transfer. // Viz needs this to do input handling on it's side.
diff --git a/components/lens/BUILD.gn b/components/lens/BUILD.gn index f71d963..350cb188 100644 --- a/components/lens/BUILD.gn +++ b/components/lens/BUILD.gn
@@ -68,7 +68,6 @@ ":features", ":mime_type", ] - sources += [ "lens_bitmap_processing.cc", "lens_bitmap_processing.h", @@ -84,6 +83,7 @@ "tab_contextualization_controller.h", ] public_deps += [ "//ui/base/unowned_user_data" ] + deps += [ "//content/public/browser" ] } if (!is_android) {
diff --git a/components/lens/DEPS b/components/lens/DEPS index 0ec3383..12790b9 100644 --- a/components/lens/DEPS +++ b/components/lens/DEPS
@@ -7,6 +7,7 @@ "+components/tabs", "+components/variations", "+components/unified_consent", + "+content/public/browser", "+google_apis", "+net", "+services/metrics/public/cpp",
diff --git a/components/lens/tab_contextualization_controller.cc b/components/lens/tab_contextualization_controller.cc index 97d401014..22b014de0 100644 --- a/components/lens/tab_contextualization_controller.cc +++ b/components/lens/tab_contextualization_controller.cc
@@ -4,6 +4,7 @@ #include "components/lens/tab_contextualization_controller.h" +#include "content/public/browser/navigation_handle.h" #include "ui/base/unowned_user_data/scoped_unowned_user_data.h" namespace lens { @@ -12,15 +13,51 @@ TabContextualizationController::TabContextualizationController( tabs::TabInterface* tab) - : scoped_unowned_user_data_(tab->GetUnownedUserDataHost(), *this) {} + : content::WebContentsObserver(tab->GetContents()), + scoped_unowned_user_data_(tab->GetUnownedUserDataHost(), *this), + tab_(tab) { + tab_subscription_ = tab->RegisterWillDiscardContents( + base::BindRepeating(&TabContextualizationController::WillDiscardContents, + base::Unretained(this))); +} TabContextualizationController::~TabContextualizationController() = default; +void TabContextualizationController::WillDiscardContents( + tabs::TabInterface* tab, + content::WebContents* old_contents, + content::WebContents* new_contents) { + Observe(new_contents); +} + TabContextualizationController* TabContextualizationController::From( tabs::TabInterface* tab) { return tab ? Get(tab->GetUnownedUserDataHost()) : nullptr; } +void TabContextualizationController::PrimaryPageChanged(content::Page& page) { + is_page_context_eligible_ = false; +} + +void TabContextualizationController::DidFinishNavigation( + content::NavigationHandle* navigation_handle) { + if (!navigation_handle->IsInPrimaryMainFrame() || + !navigation_handle->HasCommitted() || + navigation_handle->IsSameDocument()) { + return; + } + + // Call the asynchronous eligibility check. + GetInitialPageContextEligibility( + base::BindOnce(&TabContextualizationController::OnEligibilityChecked, + base::Unretained(this))); +} + +void TabContextualizationController::OnEligibilityChecked( + bool is_page_context_eligible) { + is_page_context_eligible_ = is_page_context_eligible; +} + // TODO(crbug.com/439595898): Get contextual page content. void TabContextualizationController::GetPageContext( GetPageContextCallback callback) {} @@ -29,9 +66,8 @@ void TabContextualizationController::GetInitialPageContextEligibility( GetPageContextEligibilityCallback callback) {} -// TODO(crbug.com/439597165): Check tab eligibility. bool TabContextualizationController::GetCurrentPageContextEligibility() { - return false; + return is_page_context_eligible_; } } // namespace lens
diff --git a/components/lens/tab_contextualization_controller.h b/components/lens/tab_contextualization_controller.h index 5313a36..29a5b3e 100644 --- a/components/lens/tab_contextualization_controller.h +++ b/components/lens/tab_contextualization_controller.h
@@ -12,15 +12,16 @@ #include "base/memory/raw_ptr.h" #include "components/lens/contextual_input.h" #include "components/tabs/public/tab_interface.h" +#include "content/public/browser/web_contents_observer.h" #include "ui/base/unowned_user_data/scoped_unowned_user_data.h" namespace lens { -class TabContextualizationController { +class TabContextualizationController : public content::WebContentsObserver { public: TabContextualizationController(); explicit TabContextualizationController(tabs::TabInterface* tab); - ~TabContextualizationController(); + ~TabContextualizationController() override; DECLARE_USER_DATA(TabContextualizationController); static TabContextualizationController* From(tabs::TabInterface* tab); @@ -50,8 +51,26 @@ bool GetCurrentPageContextEligibility(); private: + // content::WebContentsObserver: + void PrimaryPageChanged(content::Page& page) override; + void DidFinishNavigation( + content::NavigationHandle* navigation_handle) override; + + // TabInterface::WillDiscardContentsCallback: + void WillDiscardContents(tabs::TabInterface* tab, + content::WebContents* old_contents, + content::WebContents* new_contents); + + void OnEligibilityChecked(bool is_page_context_eligible); + ::ui::ScopedUnownedUserData<TabContextualizationController> scoped_unowned_user_data_; + + raw_ptr<tabs::TabInterface> tab_; + + base::CallbackListSubscription tab_subscription_; + + bool is_page_context_eligible_ = false; }; } // namespace lens
diff --git a/components/optimization_guide/content/browser/page_content_proto_provider.cc b/components/optimization_guide/content/browser/page_content_proto_provider.cc index f228b5e..90dd6f1 100644 --- a/components/optimization_guide/content/browser/page_content_proto_provider.cc +++ b/components/optimization_guide/content/browser/page_content_proto_provider.cc
@@ -15,15 +15,20 @@ #include "components/optimization_guide/content/browser/media_transcript_provider.h" #include "components/optimization_guide/content/browser/page_content_proto_util.h" #include "components/optimization_guide/core/optimization_guide_features.h" +#include "content/public/browser/global_routing_id.h" #include "content/public/browser/media_session.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" #include "mojo/public/cpp/bindings/callback_helpers.h" #include "mojo/public/cpp/bindings/remote.h" +#include "net/base/schemeful_site.h" #include "services/metrics/public/cpp/metrics_utils.h" #include "services/metrics/public/cpp/ukm_builders.h" #include "services/metrics/public/cpp/ukm_recorder.h" #include "services/service_manager/public/cpp/interface_provider.h" +#include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom-data-view.h" +#include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom-forward.h" +#include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom.h" namespace optimization_guide { @@ -66,7 +71,7 @@ // TODO(crbug.com/389737599): There's a bug with scheduling idle tasks in an // OOPIF with site isolation if there are no other main frames in the process. // See crbug.com/40785325. - auto new_options = blink::mojom::AIPageContentOptions::New(input); + auto new_options = input.Clone(); new_options->on_critical_path = true; return new_options; } @@ -422,6 +427,9 @@ const auto* main_frame_rph = web_contents->GetPrimaryMainFrame()->GetProcess(); + const url::Origin& top_level_origin = + web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin(); + web_contents->GetPrimaryMainFrame()->ForEachRenderFrameHost( [&](content::RenderFrameHost* rfh) { if (!rfh->IsRenderFrameLive()) { @@ -429,12 +437,26 @@ } auto* parent_frame = rfh->GetParentOrOuterDocument(); + content::GlobalRenderFrameHostToken frame_token = + rfh->GetGlobalFrameToken(); + + const url::Origin& frame_origin = rfh->GetLastCommittedOrigin(); + if (options->include_same_site_only && + (!net::SchemefulSite::IsSameSite(top_level_origin, frame_origin) || + rfh->IsFencedFrameRoot())) { + CHECK(page_content_map->find(frame_token) == page_content_map->end()); + (*page_content_map)[frame_token] = + blink::mojom::RedactedFrameMetadata::New( + blink::mojom::RedactedFrameMetadata_Reason::kCrossSite); + return; + } // Skip dispatching IPCs for non-local root frames. The local root // provides data for itself and all child local frames. const bool is_local_root = !parent_frame || parent_frame->GetRenderWidgetHost() != rfh->GetRenderWidgetHost(); + if (!is_local_root) { return; } @@ -452,9 +474,8 @@ agent_ptr->GetAIPageContent( std::move(options_to_use), mojo::WrapCallbackWithDefaultInvokeIfNotRun( - base::BindOnce(&OnGotAIPageContentForFrame, - rfh->GetGlobalFrameToken(), std::move(agent), - page_content_map.get(), + base::BindOnce(&OnGotAIPageContentForFrame, frame_token, + std::move(agent), page_content_map.get(), concurrent.CreateClosure()), nullptr)); });
diff --git a/components/optimization_guide/content/browser/page_content_proto_provider_browsertest.cc b/components/optimization_guide/content/browser/page_content_proto_provider_browsertest.cc index 32a49a9..1a9f5daa 100644 --- a/components/optimization_guide/content/browser/page_content_proto_provider_browsertest.cc +++ b/components/optimization_guide/content/browser/page_content_proto_provider_browsertest.cc
@@ -10,6 +10,7 @@ #include "components/network_session_configurator/common/network_switches.h" #include "components/optimization_guide/content/browser/mock_media_transcript_provider.h" #include "components/optimization_guide/core/optimization_guide_features.h" +#include "components/optimization_guide/proto/features/common_quality_data.pb.h" #include "components/ukm/test_ukm_recorder.h" #include "content/public/browser/media_session.h" #include "content/public/browser/web_contents.h" @@ -95,9 +96,11 @@ return request; } -blink::mojom::AIPageContentOptionsPtr GetActionableAIPageContentOptions() { +blink::mojom::AIPageContentOptionsPtr GetActionableAIPageContentOptions( + bool include_same_site_only = false) { auto request = ActionableAIPageContentOptions( /*on_critical_path =*/true); + request->include_same_site_only = include_same_site_only; return request; } @@ -905,6 +908,67 @@ c_geometry.outer_bounding_box().x()); } +IN_PROC_BROWSER_TEST_P(PageContentProtoProviderBrowserTestMultiProcess, + AIPageContentMultipleMixedCrossSiteFrames) { + LoadPage(https_server()->GetURL("a.com", "/iframe_mixed_cross_site.html"), + GetActionableAIPageContentOptions(/*include_same_site_only=*/true)); + + const auto& root_node = ActionableContentRootNode(); + + EXPECT_EQ(root_node.children_nodes().size(), 2); + + const auto& same_site_frame = root_node.children_nodes()[0]; + EXPECT_EQ(same_site_frame.content_attributes().attribute_type(), + optimization_guide::proto::CONTENT_ATTRIBUTE_IFRAME); + const auto& same_site_frame_data = + same_site_frame.content_attributes().iframe_data(); + AssertValidOrigin(same_site_frame_data.frame_data().security_origin(), + ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0) + ->GetLastCommittedOrigin()); + EXPECT_FALSE(same_site_frame_data.likely_ad_frame()); + + const auto& same_site_frame_root = ContentRootNodeForFrameActionableMode( + same_site_frame.children_nodes()[0]); + EXPECT_EQ(same_site_frame_root.children_nodes().size(), 1); + AssertIsTextNode(same_site_frame_root.children_nodes()[0], + "This page has no title.\n\n"); + const auto& same_site_geometry = + same_site_frame.content_attributes().geometry(); + AssertRectsEqual(same_site_geometry.outer_bounding_box(), + same_site_geometry.visible_bounding_box()); + + const auto& cross_site_frame = root_node.children_nodes()[1]; + EXPECT_EQ(cross_site_frame.content_attributes().attribute_type(), + optimization_guide::proto::CONTENT_ATTRIBUTE_IFRAME); + const auto& cross_site_frame_data = + cross_site_frame.content_attributes().iframe_data(); + + // Ensure the frame data isn't populated and a redaction reason is included. + EXPECT_FALSE(cross_site_frame_data.likely_ad_frame()); + EXPECT_FALSE(cross_site_frame_data.has_frame_data()); + EXPECT_TRUE(cross_site_frame_data.has_redacted_frame_metadata()); + EXPECT_EQ(cross_site_frame_data.redacted_frame_metadata().reason(), + optimization_guide::proto::IframeData_RedactedFrameMetadata:: + REASON_CROSS_SITE); + + // The cross-site frame itself should have no children. + EXPECT_EQ(cross_site_frame.children_nodes().size(), 0); + + const auto& cross_site_frame_geometry = + cross_site_frame.content_attributes().geometry(); + AssertRectsEqual(cross_site_frame_geometry.outer_bounding_box(), + cross_site_frame_geometry.visible_bounding_box()); + + EXPECT_ALMOST_EQ(same_site_geometry.outer_bounding_box().width(), + cross_site_frame_geometry.outer_bounding_box().width()); + EXPECT_ALMOST_EQ(same_site_geometry.outer_bounding_box().height(), + cross_site_frame_geometry.outer_bounding_box().height()); + EXPECT_ALMOST_EQ(same_site_geometry.outer_bounding_box().y(), + cross_site_frame_geometry.outer_bounding_box().y()); + EXPECT_NE(same_site_geometry.outer_bounding_box().x(), + cross_site_frame_geometry.outer_bounding_box().x()); +} + INSTANTIATE_TEST_SUITE_P(All, PageContentProtoProviderBrowserTestMultiProcess, testing::Bool());
diff --git a/components/optimization_guide/content/browser/page_content_proto_util.cc b/components/optimization_guide/content/browser/page_content_proto_util.cc index 952a3d7..2f19bb754 100644 --- a/components/optimization_guide/content/browser/page_content_proto_util.cc +++ b/components/optimization_guide/content/browser/page_content_proto_util.cc
@@ -6,6 +6,7 @@ #include <optional> #include <string> +#include <variant> #include <vector> #include "base/notreached.h" @@ -15,6 +16,9 @@ #include "components/optimization_guide/core/optimization_guide_proto_util.h" #include "components/optimization_guide/core/page_content_proto_serializer.h" #include "components/optimization_guide/proto/features/common_quality_data.pb.h" +#include "third_party/abseil-cpp/absl/functional/overload.h" +#include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom-data-view.h" +#include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom-forward.h" #include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom.h" #include "third_party/blink/public/mojom/content_extraction/ai_page_content_metadata.mojom.h" #include "third_party/blink/public/mojom/forms/form_control_type.mojom-shared.h" @@ -703,6 +707,37 @@ frame_token_set); } +void ConvertRedactionReason( + const blink::mojom::RedactedFrameMetadata_Reason& mojom_reason, + optimization_guide::proto::IframeData::RedactedFrameMetadata* + proto_redacted_frame_metadata) { + switch (mojom_reason) { + case blink::mojom::RedactedFrameMetadata_Reason::kCrossSite: + proto_redacted_frame_metadata->set_reason( + optimization_guide::proto::IframeData::RedactedFrameMetadata::Reason:: + IframeData_RedactedFrameMetadata_Reason_REASON_CROSS_SITE); + break; + case blink::mojom::RedactedFrameMetadata_Reason::kCrossOrigin: + proto_redacted_frame_metadata->set_reason( + optimization_guide::proto::IframeData::RedactedFrameMetadata::Reason:: + IframeData_RedactedFrameMetadata_Reason_REASON_CROSS_ORIGIN); + break; + }; +} + +void ConvertRedactedIframeData( + const RenderFrameInfo& render_frame_info, + const blink::mojom::AIPageContentIframeData& mojom_iframe_data, + const blink::mojom::RedactedFrameMetadata& mojom_redacted_frame_metadata, + blink::mojom::PageMetadata& metadata, + FrameTokenSet& frame_token_set, + optimization_guide::proto::IframeData* proto_iframe_data) { + proto_iframe_data->set_likely_ad_frame(mojom_iframe_data.likely_ad_frame); + + ConvertRedactionReason(mojom_redacted_frame_metadata.reason, + proto_iframe_data->mutable_redacted_frame_metadata()); +} + base::expected<void, std::string> ConvertNode( content::GlobalRenderFrameHostToken source_frame_token, const blink::mojom::AIPageContentNode& mojom_node, @@ -735,7 +770,8 @@ return base::unexpected("could not find render_frame_info for iframe"); } - const blink::mojom::AIPageContentFrameData* frame_data = nullptr; + auto* proto_iframe_data = + proto_node->mutable_content_attributes()->mutable_iframe_data(); if (frame_token.Is<blink::RemoteFrameToken>()) { // RemoteFrame should have no child nodes since the content is out of // process. @@ -744,41 +780,75 @@ } // The embedder shouldn't be providing LocalFrameData for remote frames. - if (iframe_data.local_frame_data) { + if (iframe_data.content) { return base::unexpected( - "embedder incorrectly provided local_frame_data for iframe"); + "embedder incorrectly provided content for this iframe"); } auto it = page_content_map.find(render_frame_info->global_frame_token); if (it == page_content_map.end()) { + // This may happen either because the remote renderer responsible for + // this frame was destroyed before we were able to query it, or because + // the supplied frame token was manipulated by a compromised renderer. return base::ok(); } - const auto& frame_page_content = *it->second; - frame_data = frame_page_content.frame_data.get(); - auto* proto_child_frame_node = proto_node->add_children_nodes(); + return std::visit( + absl::Overload{ + [&](const blink::mojom::AIPageContentPtr& page_content) mutable + -> base::expected<void, std::string> { + auto* proto_child_frame_node = proto_node->add_children_nodes(); + if (auto result = + ConvertNode(render_frame_info->global_frame_token, + *page_content->root_node, page_content_map, + frame_token_set, get_render_frame_info, + metadata, proto_child_frame_node); + !result.has_value()) { + return result; + } - if (auto result = ConvertNode( - render_frame_info->global_frame_token, - *frame_page_content.root_node, page_content_map, frame_token_set, - get_render_frame_info, metadata, proto_child_frame_node); - !result.has_value()) { - return result; - } - } else { - if (!iframe_data.local_frame_data) { - return base::unexpected("local frame missing local_frame_data"); + ConvertIframeData( + *render_frame_info, iframe_data, + /*mojom_local_frame_data=*/*page_content->frame_data.get(), + metadata, frame_token_set, proto_iframe_data); + return base::ok(); + }, + [&](const blink::mojom::RedactedFrameMetadataPtr& r) mutable + -> base::expected<void, std::string> { + ConvertRedactedIframeData( + *render_frame_info, iframe_data, + /*mojom_redacted_frame_metadata*/ *r.get(), metadata, + frame_token_set, proto_iframe_data); + return base::ok(); + }}, + it->second); + } else /* this is a local frame */ { + if (!iframe_data.content) { + return base::unexpected( + "local frame missing local_frame_data or redacted_frame_metadata"); } - frame_data = iframe_data.local_frame_data.get(); + switch (iframe_data.content->which()) { + case blink::mojom::AIPageContentIframeContent::Tag::kLocalFrameData: + ConvertIframeData(*render_frame_info, iframe_data, + /*mojom_local_frame_data=*/ + *iframe_data.content->get_local_frame_data(), + metadata, frame_token_set, proto_iframe_data); + // Breaking instead of returning so we get to copy the child nodes. + break; + case blink::mojom::AIPageContentIframeContent::Tag:: + kRedactedFrameMetadata: + ConvertRedactedIframeData( + *render_frame_info, iframe_data, + *iframe_data.content->get_redacted_frame_metadata().get(), + metadata, frame_token_set, proto_iframe_data); + return base::ok(); + } } - - auto* proto_iframe_data = - proto_node->mutable_content_attributes()->mutable_iframe_data(); - ConvertIframeData(*render_frame_info, iframe_data, *frame_data, metadata, - frame_token_set, proto_iframe_data); } + // We should only get here if this is either a non-redacted local frame or a + // regular node. const auto source_frame_for_children = render_frame_info ? render_frame_info->global_frame_token : source_frame_token; @@ -806,10 +876,25 @@ optimization_guide::AIPageContentResult& page_content_result) { auto it = page_content_map.find(main_frame_token); if (it == page_content_map.end()) { - return base::unexpected("could not find AIPageContent for main frame"); + return base::unexpected( + "could not find AIPageContent or RedactedFrameMetadata for main frame"); } - const auto& main_frame_page_content = *it->second; + const blink::mojom::AIPageContent* main_frame_page_content = nullptr; + std::visit(absl::Overload{ + [&main_frame_page_content]( + const blink::mojom::AIPageContentPtr& p) mutable { + main_frame_page_content = p.get(); + }, + [](const blink::mojom::RedactedFrameMetadataPtr& r) mutable { + return; + }}, + it->second); + + if (!main_frame_page_content) { + return base::unexpected( + "Main content frame was redacted; this should not happen."); + } auto render_frame_info = get_render_frame_info.Run( main_frame_token.child_id, main_frame_token.frame_token); @@ -818,11 +903,11 @@ return base::unexpected("could not find RenderFrameInfo for main frame"); } - ConvertFrameData(*render_frame_info, *main_frame_page_content.frame_data, + ConvertFrameData(*render_frame_info, *main_frame_page_content->frame_data, page_content_result.proto.mutable_main_frame_data(), *page_content_result.metadata, frame_token_set); if (auto result = - ConvertNode(main_frame_token, *main_frame_page_content.root_node, + ConvertNode(main_frame_token, *main_frame_page_content->root_node, page_content_map, frame_token_set, get_render_frame_info, *page_content_result.metadata, page_content_result.proto.mutable_root_node()); @@ -830,9 +915,9 @@ return result; } - if (main_frame_page_content.page_interaction_info) { + if (main_frame_page_content->page_interaction_info) { ConvertPageInteractionInfo( - *main_frame_page_content.page_interaction_info, + *main_frame_page_content->page_interaction_info, page_content_result.proto.mutable_page_interaction_info()); }
diff --git a/components/optimization_guide/content/browser/page_content_proto_util.h b/components/optimization_guide/content/browser/page_content_proto_util.h index d52cec8..bd50369 100644 --- a/components/optimization_guide/content/browser/page_content_proto_util.h +++ b/components/optimization_guide/content/browser/page_content_proto_util.h
@@ -37,12 +37,18 @@ raw_ptr<const optimization_guide::proto::ContentNode> node = nullptr; }; -using AIPageContentMap = base::flat_map<content::GlobalRenderFrameHostToken, - blink::mojom::AIPageContentPtr>; +using AIPageContentMap = + base::flat_map<content::GlobalRenderFrameHostToken, + std::variant<blink::mojom::AIPageContentPtr, + blink::mojom::RedactedFrameMetadataPtr>>; // A set of frame tokens that have been seen during conversion. using FrameTokenSet = base::flat_set<content::GlobalRenderFrameHostToken>; +using FrameOrRedaction = + std::variant<const blink::mojom::AIPageContentFrameData*, + const blink::mojom::RedactedFrameMetadata*>; + // A callback to get the RenderFrameInfo for a given frame token. using GetRenderFrameInfo = base::RepeatingCallback<std::optional<RenderFrameInfo>(int child_process_id,
diff --git a/components/optimization_guide/content/browser/page_content_proto_util_unittest.cc b/components/optimization_guide/content/browser/page_content_proto_util_unittest.cc index fc4aebe..0b286577 100644 --- a/components/optimization_guide/content/browser/page_content_proto_util_unittest.cc +++ b/components/optimization_guide/content/browser/page_content_proto_util_unittest.cc
@@ -590,21 +590,21 @@ auto iframe_data = blink::mojom::AIPageContentIframeData::New(); iframe_data->frame_token = iframe_token.frame_token; iframe_data->likely_ad_frame = true; - iframe_data->local_frame_data = blink::mojom::AIPageContentFrameData::New(); - iframe_data->local_frame_data->frame_interaction_info = + auto frame_data = blink::mojom::AIPageContentFrameData::New(); + frame_data->frame_interaction_info = blink::mojom::AIPageContentFrameInteractionInfo::New(); - iframe_data->local_frame_data->frame_interaction_info->selection = + frame_data->frame_interaction_info->selection = blink::mojom::AIPageContentSelection::New(); - iframe_data->local_frame_data->frame_interaction_info->selection - ->selected_text = "selected text"; - iframe_data->local_frame_data->frame_interaction_info->selection - ->start_dom_node_id = 1; - iframe_data->local_frame_data->frame_interaction_info->selection - ->end_dom_node_id = 2; - iframe_data->local_frame_data->frame_interaction_info->selection - ->start_offset = 3; - iframe_data->local_frame_data->frame_interaction_info->selection->end_offset = - 4; + frame_data->frame_interaction_info->selection->selected_text = + "selected text"; + frame_data->frame_interaction_info->selection->start_dom_node_id = 1; + frame_data->frame_interaction_info->selection->end_dom_node_id = 2; + frame_data->frame_interaction_info->selection->start_offset = 3; + frame_data->frame_interaction_info->selection->end_offset = 4; + iframe_data->content = + blink::mojom::AIPageContentIframeContent::NewLocalFrameData( + std::move(frame_data)); + root_content->root_node->children_nodes.back() ->content_attributes->iframe_data = std::move(iframe_data);
diff --git a/components/paint_preview/browser/paint_preview_base_service_unittest.cc b/components/paint_preview/browser/paint_preview_base_service_unittest.cc index df666f98..ef7fe6d 100644 --- a/components/paint_preview/browser/paint_preview_base_service_unittest.cc +++ b/components/paint_preview/browser/paint_preview_base_service_unittest.cc
@@ -198,7 +198,7 @@ if (GetParam() == RecordingPersistence::kMemoryBuffer) { response->skp.emplace(mojo_base::BigBuffer()); } - recorder.SetResponse(mojom::PaintPreviewStatus::kOk, std::move(response)); + recorder.SetResponse(std::move(response)); OverrideInterface(&recorder); auto* service = GetService(); @@ -265,10 +265,7 @@ params->is_main_frame = true; params->max_capture_size = 0; recorder.SetExpectedParams(std::move(params)); - auto response = mojom::PaintPreviewCaptureResponse::New(); - response->geometry_metadata = mojom::GeometryMetadataResponse::New(); - response->embedding_token = std::nullopt; - recorder.SetResponse(mojom::PaintPreviewStatus::kFailed, std::move(response)); + recorder.SetResponse(base::unexpected(mojom::PaintPreviewStatus::kFailed)); OverrideInterface(&recorder); auto* service = GetService();
diff --git a/components/paint_preview/browser/paint_preview_client.cc b/components/paint_preview/browser/paint_preview_client.cc index de2cbb9..73c531c5 100644 --- a/components/paint_preview/browser/paint_preview_client.cc +++ b/components/paint_preview/browser/paint_preview_client.cc
@@ -175,8 +175,9 @@ base::File file) { if (!file.IsValid()) { DLOG(ERROR) << "File create failed: " << file.error_details(); - std::move(callback).Run(std::move(capture_params), - mojom::PaintPreviewStatus::kFileCreationError, {}); + std::move(callback).Run( + std::move(capture_params), + base::unexpected(mojom::PaintPreviewStatus::kFileCreationError)); } else if (callback.IsCancelled()) { // The weak pointer is invalid, we should close the file on a background // thread to avoid it being closed implicitly via the default dtor on the UI @@ -187,8 +188,7 @@ } else { mojom::PaintPreviewCaptureParamsPtr params = CreateRecordingRequestParams( RecordingPersistence::kFileSystem, capture_params, std::move(file)); - std::move(callback).Run(std::move(capture_params), - mojom::PaintPreviewStatus::kOk, std::move(params)); + std::move(callback).Run(std::move(capture_params), std::move(params)); } } @@ -215,7 +215,7 @@ return memory_stream.detachAsData(); } -std::pair<mojom::PaintPreviewStatus, mojom::PaintPreviewCaptureResponsePtr> +base::expected<mojom::PaintPreviewCaptureResponsePtr, mojom::PaintPreviewStatus> SerializeRedactedFrameToFile(const gfx::Size& size, std::optional<size_t> max_capture_size, const base::FilePath& file_path, @@ -225,16 +225,16 @@ base::span<const uint8_t> bytes = skia::as_byte_span(*data); if (max_capture_size && data->size() > max_capture_size) { - return {mojom::PaintPreviewStatus::kFileCreationError, std::move(response)}; + return base::unexpected(mojom::PaintPreviewStatus::kFileCreationError); } base::File file = CreateOrOverwriteFileForWriting(file_path); if (!file.IsValid() || !file.WriteAndCheck(/*offset=*/0, bytes)) { - return {mojom::PaintPreviewStatus::kFileCreationError, std::move(response)}; + return base::unexpected(mojom::PaintPreviewStatus::kFileCreationError); } - return {mojom::PaintPreviewStatus::kOk, std::move(response)}; + return std::move(response); } struct BufferAndMetadata { @@ -266,18 +266,6 @@ rfh->GetRoutingID()); } -// Converts a callback that accepts two args into a callback that accepts a -// single std::pair arg. -template <typename T, typename U> -base::OnceCallback<void(std::pair<T, U>)> ConvertToPairArgCallback( - base::OnceCallback<void(T, U)> callback) { - return base::BindOnce( - [](base::OnceCallback<void(T, U)> callback, std::pair<T, U> pair) { - std::move(callback).Run(std::move(pair.first), std::move(pair.second)); - }, - std::move(callback)); -} - } // namespace PaintPreviewClient::PaintPreviewParams::PaintPreviewParams( @@ -380,9 +368,7 @@ } else { mojom::PaintPreviewCaptureParamsPtr params = CreateRecordingRequestParams(persistence, capture_params, {}); - std::move(ready_callback) - .Run(std::move(capture_params), mojom::PaintPreviewStatus::kOk, - std::move(params)); + std::move(ready_callback).Run(std::move(capture_params), std::move(params)); } } @@ -609,9 +595,10 @@ const base::UnguessableToken& frame_guid, const content::GlobalRenderFrameHostId& render_frame_id, RecordingParams params, - base::OnceCallback<void(RecordingParams, - mojom::PaintPreviewStatus, - mojom::PaintPreviewCaptureResponsePtr)> callback, + base::OnceCallback< + void(RecordingParams, + base::expected<mojom::PaintPreviewCaptureResponsePtr, + mojom::PaintPreviewStatus>)> callback, mojom::GeometryMetadataResponsePtr response) { auto* document_data = base::FindOrNull(all_document_data_, params.get_document_guid()); @@ -625,8 +612,9 @@ render_frame_host->GetEmbeddingToken().value_or( base::UnguessableToken::Null()) != frame_guid || !response) { - std::move(callback).Run(std::move(params), - mojom::PaintPreviewStatus::kCaptureFailed, {}); + std::move(callback).Run( + std::move(params), + base::unexpected(mojom::PaintPreviewStatus::kCaptureFailed)); return; } @@ -647,30 +635,31 @@ max_capture_size, document_data->FilePathForFrame(frame_guid), std::move(capture_response)), - ConvertToPairArgCallback( - base::BindOnce(std::move(callback), std::move(params)))); + base::BindOnce(std::move(callback), std::move(params))); return; } case RecordingPersistence::kMemoryBuffer: { - mojom::PaintPreviewStatus capture_status = - mojom::PaintPreviewStatus::kCaptureFailed; std::optional<BufferAndMetadata> serialized = SerializeRedactedFrameToBuffer( params.clip_rect.size(), document_data->max_per_capture_size == 0 ? std::nullopt : std::make_optional(document_data->max_per_capture_size)); - if (serialized) { - capture_status = mojom::PaintPreviewStatus::kOk; - capture_response->skp.emplace(std::move(serialized->buffer)); - capture_response->serialized_size = serialized->buffer_size; + if (!serialized) { + std::move(callback).Run( + std::move(params), + base::unexpected(mojom::PaintPreviewStatus::kCaptureFailed)); + return; } - std::move(callback).Run(std::move(params), capture_status, - std::move(capture_response)); + capture_response->skp.emplace(std::move(serialized->buffer)); + capture_response->serialized_size = serialized->buffer_size; + + std::move(callback).Run(std::move(params), std::move(capture_response)); return; } } + NOTREACHED(); } void PaintPreviewClient::AwaitSubframeCapture( @@ -765,8 +754,8 @@ const base::UnguessableToken& frame_guid, const content::GlobalRenderFrameHostId& render_frame_id, RecordingParams params, - mojom::PaintPreviewStatus status, - mojom::PaintPreviewCaptureParamsPtr capture_params) { + base::expected<mojom::PaintPreviewCaptureParamsPtr, + mojom::PaintPreviewStatus> capture_params) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); auto* document_data = @@ -776,9 +765,9 @@ } CHECK(document_data->callback); - if (status != mojom::PaintPreviewStatus::kOk) { + if (!capture_params.has_value()) { std::move(document_data->callback) - .Run(params.get_document_guid(), status, {}); + .Run(params.get_document_guid(), capture_params.error(), {}); return; } @@ -789,7 +778,7 @@ if (!render_frame_host || render_frame_host->GetEmbeddingToken().value_or( base::UnguessableToken::Null()) != frame_guid || - !capture_params) { + !capture_params.value()) { std::move(document_data->callback) .Run(params.get_document_guid(), mojom::PaintPreviewStatus::kCaptureFailed, {}); @@ -800,12 +789,12 @@ // For the main frame, apply a clip rect if one is provided. if (params.is_main_frame) { - capture_params->geometry_metadata_params->clip_rect_is_hint = false; + capture_params.value()->geometry_metadata_params->clip_rect_is_hint = false; } GetOrInsertRecorder(frame_guid, *render_frame_host) ->CapturePaintPreview( - std::move(capture_params), + std::move(capture_params).value(), base::BindOnce(&PaintPreviewClient::OnPaintPreviewCapturedCallback, weak_ptr_factory_.GetWeakPtr(), frame_guid, render_frame_id, std::move(params))); @@ -834,8 +823,8 @@ const base::UnguessableToken& frame_guid, const content::GlobalRenderFrameHostId& render_frame_id, RecordingParams params, - mojom::PaintPreviewStatus status, - mojom::PaintPreviewCaptureResponsePtr response) { + base::expected<mojom::PaintPreviewCaptureResponsePtr, + mojom::PaintPreviewStatus> response) { auto* document_data = base::FindOrNull(all_document_data_, params.get_document_guid()); @@ -848,16 +837,16 @@ auto* render_frame_host = content::RenderFrameHost::FromID(render_frame_id); if (!render_frame_host || render_frame_host->GetEmbeddingToken().value_or( base::UnguessableToken::Null()) != frame_guid) { - status = mojom::PaintPreviewStatus::kCaptureFailed; + response = base::unexpected(mojom::PaintPreviewStatus::kCaptureFailed); } if (!document_data) { return; } - if (status == mojom::PaintPreviewStatus::kOk) { + if (response.has_value()) { document_data->RecordSuccessfulFrame(frame_guid, params.is_main_frame, - std::move(response)); + std::move(response).value()); } else { document_data->had_error = true;
diff --git a/components/paint_preview/browser/paint_preview_client.h b/components/paint_preview/browser/paint_preview_client.h index 4f56e46..6d2ee5a 100644 --- a/components/paint_preview/browser/paint_preview_client.h +++ b/components/paint_preview/browser/paint_preview_client.h
@@ -42,10 +42,10 @@ mojom::PaintPreviewStatus, std::unique_ptr<CaptureResult>)>; - using RecordingRequestParamsReadyCallback = - base::OnceCallback<void(RecordingParams, - mojom::PaintPreviewStatus, - mojom::PaintPreviewCaptureParamsPtr)>; + using RecordingRequestParamsReadyCallback = base::OnceCallback<void( + RecordingParams, + base::expected<mojom::PaintPreviewCaptureParamsPtr, + mojom::PaintPreviewStatus>)>; // Augmented version of mojom::PaintPreviewServiceParams. struct PaintPreviewParams { @@ -236,8 +236,8 @@ const base::UnguessableToken& frame_guid, const content::GlobalRenderFrameHostId& render_frame_id, RecordingParams params, - mojom::PaintPreviewStatus status, - mojom::PaintPreviewCaptureParamsPtr capture_params); + base::expected<mojom::PaintPreviewCaptureParamsPtr, + mojom::PaintPreviewStatus> capture_params); // Handles recording the frame and updating client state when capture is // complete. @@ -245,8 +245,8 @@ const base::UnguessableToken& frame_guid, const content::GlobalRenderFrameHostId& render_frame_id, RecordingParams params, - mojom::PaintPreviewStatus status, - mojom::PaintPreviewCaptureResponsePtr response); + base::expected<mojom::PaintPreviewCaptureResponsePtr, + mojom::PaintPreviewStatus> response); // Marks a frame as having been processed, this should occur regardless of // whether the processed frame is valid as there is no retry. @@ -267,14 +267,14 @@ // Synthesizes a redacted subframe and persists it appropriately, then resumes // the capture. - void RedactSubframe( - const base::UnguessableToken& frame_guid, - const content::GlobalRenderFrameHostId& render_frame_id, - RecordingParams params, - base::OnceCallback<void(RecordingParams, - mojom::PaintPreviewStatus, - mojom::PaintPreviewCaptureResponsePtr)> callback, - mojom::GeometryMetadataResponsePtr response); + void RedactSubframe(const base::UnguessableToken& frame_guid, + const content::GlobalRenderFrameHostId& render_frame_id, + RecordingParams params, + base::OnceCallback<void( + RecordingParams, + base::expected<mojom::PaintPreviewCaptureResponsePtr, + mojom::PaintPreviewStatus>)> callback, + mojom::GeometryMetadataResponsePtr response); // Performs bookkeeping to keep track of the fact that this frame's capture is // still pending.
diff --git a/components/paint_preview/browser/paint_preview_client_unittest.cc b/components/paint_preview/browser/paint_preview_client_unittest.cc index 4c5c93aa..796327ac 100644 --- a/components/paint_preview/browser/paint_preview_client_unittest.cc +++ b/components/paint_preview/browser/paint_preview_client_unittest.cc
@@ -241,7 +241,7 @@ MockPaintPreviewRecorder service; service.SetExpectedParams(ToMojoParams(params)); - service.SetResponse(mojom::PaintPreviewStatus::kOk, std::move(response)); + service.SetResponse(std::move(response)); OverrideInterface(rfh, &service); PaintPreviewClient::CreateForWebContents(web_contents()); auto* client = PaintPreviewClient::FromWebContents(web_contents()); @@ -290,13 +290,10 @@ params.root_dir = temp_dir_.GetPath(); params.inner.is_main_frame = true; - auto response = NewMockPaintPreviewCaptureResponse(); - response->skp = {mojo_base::BigBuffer()}; - MockPaintPreviewRecorder recorder; recorder.SetExpectedParams(ToMojoParams(params)); - recorder.SetResponse(mojom::PaintPreviewStatus::kCaptureFailed, - std::move(response)); + recorder.SetResponse( + base::unexpected(mojom::PaintPreviewStatus::kCaptureFailed)); OverrideInterface(main_rfh(), &recorder); PaintPreviewClient::CreateForWebContents(web_contents()); auto* client = PaintPreviewClient::FromWebContents(web_contents()); @@ -323,13 +320,10 @@ params.root_dir = temp_dir_.GetPath(); params.inner.is_main_frame = true; - auto response = mojom::PaintPreviewCaptureResponse::New(); - response->geometry_metadata = mojom::GeometryMetadataResponse::New(); - MockPaintPreviewRecorder recorder; recorder.SetExpectedParams(ToMojoParams(params)); - recorder.SetResponse(mojom::PaintPreviewStatus::kCaptureFailed, - std::move(response)); + recorder.SetResponse( + base::unexpected(mojom::PaintPreviewStatus::kCaptureFailed)); OverrideInterface(main_rfh(), &recorder); PaintPreviewClient::CreateForWebContents(web_contents()); @@ -389,7 +383,7 @@ MockPaintPreviewRecorder service; service.SetExpectedParams(ToMojoParams(params)); - service.SetResponse(mojom::PaintPreviewStatus::kOk, std::move(response)); + service.SetResponse(std::move(response)); OverrideInterface(rfh, &service); PaintPreviewClient::CreateForWebContents(web_contents()); auto* client = PaintPreviewClient::FromWebContents(web_contents()); @@ -479,8 +473,7 @@ MockPaintPreviewRecorder main_frame_recorder; main_frame_recorder.SetExpectedParams(ToMojoParams(main_frame_params)); - main_frame_recorder.SetResponse(mojom::PaintPreviewStatus::kOk, - std::move(main_frame_response)); + main_frame_recorder.SetResponse(std::move(main_frame_response)); base::test::TestFuture<void> main_frame_req; main_frame_recorder.SetReceivedRequestClosure(main_frame_req.GetCallback()); OverrideInterface(rfh, &main_frame_recorder); @@ -494,8 +487,7 @@ expected_subframe_params.inner.clip_rect = subframe_rect; MockPaintPreviewRecorder subframe_recorder; subframe_recorder.SetExpectedParams(ToMojoParams(expected_subframe_params)); - subframe_recorder.SetResponse(mojom::PaintPreviewStatus::kOk, - std::move(subframe_response)); + subframe_recorder.SetResponse(std::move(subframe_response)); base::test::TestFuture<void> subframe_req; subframe_recorder.SetReceivedRequestClosure(subframe_req.GetCallback()); OverrideInterface(subframe, &subframe_recorder); @@ -572,8 +564,7 @@ MockPaintPreviewRecorder main_frame_recorder; main_frame_recorder.SetExpectedParams(ToMojoParams(main_frame_params)); - main_frame_recorder.SetResponse(mojom::PaintPreviewStatus::kOk, - std::move(main_frame_response)); + main_frame_recorder.SetResponse(std::move(main_frame_response)); base::test::TestFuture<void> main_frame_req; main_frame_recorder.SetReceivedRequestClosure(main_frame_req.GetCallback()); OverrideInterface(rfh, &main_frame_recorder);
diff --git a/components/paint_preview/common/mock_paint_preview_recorder.cc b/components/paint_preview/common/mock_paint_preview_recorder.cc index 5dd63377..24d1c65e 100644 --- a/components/paint_preview/common/mock_paint_preview_recorder.cc +++ b/components/paint_preview/common/mock_paint_preview_recorder.cc
@@ -22,14 +22,11 @@ mojom::PaintPreviewRecorder::CapturePaintPreviewCallback callback) { CheckParams(params); + send_response_callback_ = std::move(callback); if (received_request_closure_) { - send_response_callback_ = std::move(callback); std::move(received_request_closure_).Run(); } else { - if (!response_) { - response_ = NewResponse(); - } - std::move(callback).Run(status_, std::move(response_)); + SendResponse(); } } @@ -38,14 +35,11 @@ mojom::PaintPreviewRecorder::GetGeometryMetadataCallback callback) { CheckGeometryParams(params); + send_geometry_response_callback_ = std::move(callback); if (received_request_closure_) { - send_geometry_response_callback_ = std::move(callback); std::move(received_request_closure_).Run(); } else { - if (!geometry_response_) { - geometry_response_ = mojom::GeometryMetadataResponse::New(); - } - std::move(callback).Run(std::move(geometry_response_)); + SendGeometryResponse(); } } @@ -60,9 +54,8 @@ } void MockPaintPreviewRecorder::SetResponse( - mojom::PaintPreviewStatus status, - mojom::PaintPreviewCaptureResponsePtr response) { - status_ = status; + base::expected<mojom::PaintPreviewCaptureResponsePtr, + mojom::PaintPreviewStatus> response) { response_ = std::move(response); } @@ -79,14 +72,22 @@ void MockPaintPreviewRecorder::SendResponse() { ASSERT_TRUE(send_response_callback_); - if (!response_) { + ASSERT_TRUE(response_) << "SetResponse() not called"; + if (response_.value().has_value() && response_.value().value().is_null()) { response_ = NewResponse(); } - std::move(send_response_callback_).Run(status_, std::move(response_)); + std::optional<base::expected<mojom::PaintPreviewCaptureResponsePtr, + mojom::PaintPreviewStatus>> + resp = std::nullopt; + resp.swap(response_); + std::move(send_response_callback_).Run(std::move(resp).value()); } void MockPaintPreviewRecorder::SendGeometryResponse() { ASSERT_TRUE(send_geometry_response_callback_); + if (!geometry_response_) { + geometry_response_ = mojom::GeometryMetadataResponse::New(); + } std::move(send_geometry_response_callback_) .Run(std::move(geometry_response_)); }
diff --git a/components/paint_preview/common/mock_paint_preview_recorder.h b/components/paint_preview/common/mock_paint_preview_recorder.h index ab45802..46c868c7 100644 --- a/components/paint_preview/common/mock_paint_preview_recorder.h +++ b/components/paint_preview/common/mock_paint_preview_recorder.h
@@ -32,10 +32,14 @@ void SetExpectedParams(mojom::PaintPreviewCaptureParamsPtr params); void SetExpectedGeometryParams(mojom::GeometryMetadataParamsPtr params); - // Sets the status and response that will be sent. If `response` is nullptr, a - // new response will be conructed on demand when needed. - void SetResponse(mojom::PaintPreviewStatus status, - mojom::PaintPreviewCaptureResponsePtr response = nullptr); + // Sets the status or response that will be sent. If `response` is + // `base::ok(nullptr)`, a new response will be conructed on demand when + // needed. Each response is consumed when it is sent. + void SetResponse( + base::expected<mojom::PaintPreviewCaptureResponsePtr, + mojom::PaintPreviewStatus> response = base::ok(nullptr)); + // Sets the response that will be sent. If `response` is `nullptr`, a new + // response will be conructed on demand when needed. void SetGeometryResponse( mojom::GeometryMetadataResponsePtr response = nullptr); @@ -72,9 +76,9 @@ mojom::PaintPreviewCaptureParamsPtr expected_params_; mojom::GeometryMetadataParamsPtr expected_geometry_params_; - mojom::PaintPreviewStatus status_; - - mojom::PaintPreviewCaptureResponsePtr response_; + std::optional<base::expected<mojom::PaintPreviewCaptureResponsePtr, + mojom::PaintPreviewStatus>> + response_ = std::nullopt; mojom::GeometryMetadataResponsePtr geometry_response_; mojo::AssociatedReceiver<mojom::PaintPreviewRecorder> binding_{this};
diff --git a/components/paint_preview/common/mojom/paint_preview_recorder.mojom b/components/paint_preview/common/mojom/paint_preview_recorder.mojom index ce15a4b..6e13eb5c 100644 --- a/components/paint_preview/common/mojom/paint_preview_recorder.mojom +++ b/components/paint_preview/common/mojom/paint_preview_recorder.mojom
@@ -153,11 +153,10 @@ // via the RenderFrameProxy. The browser handles dispatching these requests to // the correct RenderFrame and aggregating all the outputs. // - // Returns a status, and if |status| == kOK a valid instance of - // PaintPreviewCaptureResponse. + // Returns a valid instance of PaintPreviewCaptureResponse, or a status code + // on error. CapturePaintPreview(PaintPreviewCaptureParams params) => - (PaintPreviewStatus status, - PaintPreviewCaptureResponse response); + result<PaintPreviewCaptureResponse, PaintPreviewStatus>; // Computes the geometry that would be used for a capture with the given // geometry params. Returns null if the geometry couldn't be calculated.
diff --git a/components/paint_preview/renderer/paint_preview_recorder_browsertest.cc b/components/paint_preview/renderer/paint_preview_recorder_browsertest.cc index 1a8fd87b..50a4bbb6 100644 --- a/components/paint_preview/renderer/paint_preview_recorder_browsertest.cc +++ b/components/paint_preview/renderer/paint_preview_recorder_browsertest.cc
@@ -5,6 +5,7 @@ #include "base/files/file.h" #include "base/files/file_path.h" #include "base/files/scoped_temp_dir.h" +#include "base/test/gmock_expected_support.h" #include "base/test/scoped_feature_list.h" #include "base/test/test_future.h" #include "base/threading/thread_restrictions.h" @@ -111,15 +112,14 @@ params->file = std::move(skp_file); PaintPreviewRecorderImpl paint_preview_recorder(frame); - base::test::TestFuture<mojom::PaintPreviewStatus, - mojom::PaintPreviewCaptureResponsePtr> + base::test::TestFuture<base::expected<mojom::PaintPreviewCaptureResponsePtr, + mojom::PaintPreviewStatus>> future; paint_preview_recorder.CapturePaintPreview(std::move(params), future.GetCallback()); - auto [status, response] = future.Take(); - EXPECT_EQ(status, mojom::PaintPreviewStatus::kOk); - return {skp_path, std::move(response)}; + EXPECT_THAT(future.Get(), base::test::HasValue()); + return {skp_path, future.Take().value()}; } mojom::GeometryMetadataResponsePtr GetGeometryMetadata( @@ -739,14 +739,14 @@ params->file = std::move(skp_file); content::RenderFrame* frame = GetMainRenderFrame(); - base::test::TestFuture<mojom::PaintPreviewStatus, - mojom::PaintPreviewCaptureResponsePtr> + base::test::TestFuture<base::expected<mojom::PaintPreviewCaptureResponsePtr, + mojom::PaintPreviewStatus>> future; PaintPreviewRecorderImpl paint_preview_recorder(frame); paint_preview_recorder.CapturePaintPreview(std::move(params), future.GetCallback()); - auto [status, response] = future.Take(); - EXPECT_EQ(mojom::PaintPreviewStatus::kCaptureFailed, status); + EXPECT_THAT(future.Take(), + base::test::ErrorIs(mojom::PaintPreviewStatus::kCaptureFailed)); } TEST_P(PaintPreviewRecorderRenderViewTest, TestCaptureInvalidXYClip) { @@ -768,14 +768,14 @@ params->file = std::move(skp_file); content::RenderFrame* frame = GetMainRenderFrame(); - base::test::TestFuture<mojom::PaintPreviewStatus, - mojom::PaintPreviewCaptureResponsePtr> + base::test::TestFuture<base::expected<mojom::PaintPreviewCaptureResponsePtr, + mojom::PaintPreviewStatus>> future; PaintPreviewRecorderImpl paint_preview_recorder(frame); paint_preview_recorder.CapturePaintPreview(std::move(params), future.GetCallback()); - auto [status, response] = future.Take(); - EXPECT_EQ(mojom::PaintPreviewStatus::kCaptureFailed, status); + EXPECT_THAT(future.Take(), + base::test::ErrorIs(mojom::PaintPreviewStatus::kCaptureFailed)); } TEST_P(PaintPreviewRecorderRenderViewTest, TestCaptureMainFrameAndLocalFrame) {
diff --git a/components/paint_preview/renderer/paint_preview_recorder_impl.cc b/components/paint_preview/renderer/paint_preview_recorder_impl.cc index 5e0918d8..7455317a 100644 --- a/components/paint_preview/renderer/paint_preview_recorder_impl.cc +++ b/components/paint_preview/renderer/paint_preview_recorder_impl.cc
@@ -81,18 +81,19 @@ }; using CapturePaintPreviewCallback = - base::OnceCallback<void(mojom::PaintPreviewStatus, - mojom::PaintPreviewCaptureResponsePtr)>; + mojom::PaintPreviewRecorder::CapturePaintPreviewCallback; // Finishes building the PaintPreviewCaptureResponse mojo message and sends it // or sends an error if the status is not `PaintPreviewStatus::kOK` void BuildAndSendResponse(std::unique_ptr<PaintPreviewTracker> tracker, FinishedRecording out, CapturePaintPreviewCallback callback) { - if (out.status == mojom::PaintPreviewStatus::kOk) { - BuildResponse(tracker.get(), out.response.get()); + if (out.status != mojom::PaintPreviewStatus::kOk) { + std::move(callback).Run(base::unexpected(out.status)); + return; } - std::move(callback).Run(out.status, std::move(out.response)); + BuildResponse(tracker.get(), out.response.get()); + std::move(callback).Run(std::move(out.response)); } // Records `skp` to `skp_file` on the threadpool to avoid blocking the main @@ -165,8 +166,8 @@ TRACE_EVENT0("paint_preview", "FinishRecordingOnUIThread"); DCHECK(tracker); if (!tracker) { - std::move(callback).Run(mojom::PaintPreviewStatus::kCaptureFailed, - std::move(response)); + std::move(callback).Run( + base::unexpected(mojom::PaintPreviewStatus::kCaptureFailed)); return; } @@ -176,8 +177,8 @@ auto skp = PaintRecordToSkPicture(std::move(recording), tracker.get(), bounds); if (!skp) { - std::move(callback).Run(mojom::PaintPreviewStatus::kCaptureFailed, - std::move(response)); + std::move(callback).Run( + base::unexpected(mojom::PaintPreviewStatus::kCaptureFailed)); return; } TRACE_EVENT_BEGIN0("paint_preview", "ConvertToSkPicture"); @@ -329,8 +330,8 @@ // recoverable. auto response = mojom::PaintPreviewCaptureResponse::New(); if (is_painting_preview_) { - std::move(callback).Run(mojom::PaintPreviewStatus::kAlreadyCapturing, - std::move(response)); + std::move(callback).Run( + base::unexpected(mojom::PaintPreviewStatus::kAlreadyCapturing)); return; } const base::AutoReset<bool> resetter(&is_painting_preview_, true); @@ -359,26 +360,25 @@ // Ensure the a frame actually exists to avoid a possible crash. if (!frame) { DVLOG(1) << "Error: renderer has no frame yet!"; - std::move(callback).Run(mojom::PaintPreviewStatus::kFailed, - std::move(response)); + std::move(callback).Run( + base::unexpected(mojom::PaintPreviewStatus::kFailed)); return; } DCHECK_EQ(is_main_frame_, params->is_main_frame); - ASSIGN_OR_RETURN( - const CaptureGeometry geometry, - ComputeCaptureGeometry( - frame->GetScrollOffset(), frame->DocumentSize(), - params->geometry_metadata_params->clip_rect, - params->geometry_metadata_params->clip_x_coord_override, - params->geometry_metadata_params->clip_y_coord_override, - params->geometry_metadata_params->clip_rect_is_hint), - [&] { - std::move(callback).Run(mojom::PaintPreviewStatus::kCaptureFailed, - std::move(response)); - return; - }); + ASSIGN_OR_RETURN(const CaptureGeometry geometry, + ComputeCaptureGeometry( + frame->GetScrollOffset(), frame->DocumentSize(), + params->geometry_metadata_params->clip_rect, + params->geometry_metadata_params->clip_x_coord_override, + params->geometry_metadata_params->clip_y_coord_override, + params->geometry_metadata_params->clip_rect_is_hint), + [&] { + std::move(callback).Run(base::unexpected( + mojom::PaintPreviewStatus::kCaptureFailed)); + return; + }); response->geometry_metadata = mojom::GeometryMetadataResponse::New(); response->geometry_metadata->scroll_offsets = geometry.scroll_offsets; @@ -432,8 +432,8 @@ // Restore to before out-of-lifecycle paint phase. if (!success) { - std::move(callback).Run(mojom::PaintPreviewStatus::kCaptureFailed, - std::move(response)); + std::move(callback).Run( + base::unexpected(mojom::PaintPreviewStatus::kCaptureFailed)); return; }
diff --git a/components/password_manager/core/browser/export/BUILD.gn b/components/password_manager/core/browser/export/BUILD.gn index a8bcd0d..29b0e35e3 100644 --- a/components/password_manager/core/browser/export/BUILD.gn +++ b/components/password_manager/core/browser/export/BUILD.gn
@@ -22,22 +22,6 @@ "//components/password_manager/core/browser/ui", "//components/sync/base:features", ] - - if (is_android) { - sources += [ - "login_db_deprecation_password_exporter.cc", - "login_db_deprecation_password_exporter.h", - "login_db_deprecation_password_exporter_interface.h", - "login_db_deprecation_runner.cc", - "login_db_deprecation_runner.h", - ] - deps += [ - "//components/password_manager/core/browser/features:password_features", - "//components/password_manager/core/browser/password_store:password_store_interface", - "//components/password_manager/core/common", - "//components/prefs", - ] - } configs += [ "//build/config/compiler:wexit_time_destructors" ] } @@ -58,17 +42,6 @@ "//testing/gtest", ] - if (is_android) { - sources += [ - "login_db_deprecation_password_exporter_unittest.cc", - "login_db_deprecation_runner_unittest.cc", - ] - deps += [ - "//components/password_manager/core/browser/features:password_features", - "//components/prefs:test_support", - ] - } - # TODO(crbug.com/40031409): Fix code that adds exit-time destructors and # enable the diagnostic by removing this line. configs += [ "//build/config/compiler:no_exit_time_destructors" ]
diff --git a/components/password_manager/core/browser/export/login_db_deprecation_password_exporter.cc b/components/password_manager/core/browser/export/login_db_deprecation_password_exporter.cc deleted file mode 100644 index 529298c..0000000 --- a/components/password_manager/core/browser/export/login_db_deprecation_password_exporter.cc +++ /dev/null
@@ -1,148 +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. - -#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h" - -#include <variant> - -#include "base/files/file_path.h" -#include "base/functional/callback_helpers.h" -#include "base/metrics/histogram_functions.h" -#include "base/time/time.h" -#include "components/password_manager/core/browser/export/export_progress_status.h" -#include "components/password_manager/core/browser/export/password_manager_exporter.h" -#include "components/password_manager/core/browser/password_manager_metrics_util.h" -#include "components/password_manager/core/browser/password_store/password_store_interface.h" -#include "components/password_manager/core/browser/ui/credential_ui_entry.h" -#include "components/password_manager/core/common/password_manager_pref_names.h" -#include "components/prefs/pref_service.h" - -namespace password_manager { - -namespace { -void LogExportResult(LoginDbDeprecationExportResult result) { - base::UmaHistogramEnumeration( - "PasswordManager.UPM.LoginDbDeprecationExport.Result", result); -} - -void LogExportLatency(base::TimeDelta latency) { - base::UmaHistogramMediumTimes( - "PasswordManager.UPM.LoginDbDeprecationExport.Latency", latency); -} - -} // namespace - -LoginDbDeprecationPasswordExporter::LoginDbDeprecationPasswordExporter( - PrefService* pref_service, - base::FilePath export_dir_path) - : pref_service_(pref_service), - export_dir_path_(std::move(export_dir_path)) { - exporter_ = std::make_unique<PasswordManagerExporter>( - this, - base::BindRepeating(&LoginDbDeprecationPasswordExporter::OnExportProgress, - weak_factory_.GetWeakPtr()), - base::BindOnce(&LoginDbDeprecationPasswordExporter::OnExportComplete, - weak_factory_.GetWeakPtr())); -} - -LoginDbDeprecationPasswordExporter::~LoginDbDeprecationPasswordExporter() = - default; - -void LoginDbDeprecationPasswordExporter::Start( - scoped_refptr<PasswordStoreInterface> password_store, - base::OnceClosure export_cleanup_calback) { - password_store_ = password_store; - export_cleanup_callback_ = std::move(export_cleanup_calback); - start_time_ = base::Time::Now(); - password_store->GetAutofillableLogins(weak_factory_.GetWeakPtr()); -} - -std::vector<CredentialUIEntry> -LoginDbDeprecationPasswordExporter::GetSavedCredentials() const { - return passwords_; -} - -PasswordManagerExporter* -LoginDbDeprecationPasswordExporter::GetInternalExporterForTesting( - base::PassKey<LoginDbDeprecationPasswordExporterTest>) { - return exporter_.get(); -} - -void LoginDbDeprecationPasswordExporter::OnGetPasswordStoreResultsOrErrorFrom( - PasswordStoreInterface* store, - LoginsResultOrError logins_or_error) { - if (std::holds_alternative<PasswordStoreBackendError>(logins_or_error)) { - OnExportCompleteWithResult( - LoginDbDeprecationExportResult::kErrorFetchingPasswords); - return; - } - - // This is only invoked once, since the export flow goverened by this - // class is a one-time operation. - CHECK(passwords_.empty()); - passwords_.reserve(std::get<LoginsResult>(logins_or_error).size()); - for (const auto& password_form : std::get<LoginsResult>(logins_or_error)) { - passwords_.emplace_back(password_form); - } - if (passwords_.empty()) { - OnExportCompleteWithResult(LoginDbDeprecationExportResult::kNoPasswords); - return; - } - - exporter_->PreparePasswordsForExport(); - exporter_->SetDestination( - export_dir_path_.Append(FILE_PATH_LITERAL(kExportedPasswordsFileName))); -} - -void LoginDbDeprecationPasswordExporter::OnExportProgress( - const PasswordExportInfo& export_info) { - export_status_ = export_info.status; -} - -void LoginDbDeprecationPasswordExporter::OnExportComplete() { - LoginDbDeprecationExportResult result; - switch (export_status_) { - case ExportProgressStatus::kNotStarted: - case ExportProgressStatus::kInProgress: { - // `OnExportComplete` should only be called for completed export flows. - NOTREACHED(); - } - case ExportProgressStatus::kSucceeded: { - result = LoginDbDeprecationExportResult::kSuccess; - break; - } - case ExportProgressStatus::kFailedCancelled: { - // There is no option that cancels this export flow. - NOTREACHED(); - } - case ExportProgressStatus::kFailedWrite: { - result = LoginDbDeprecationExportResult::kFileWriteError; - break; - } - }; - OnExportCompleteWithResult(result); -} - -void LoginDbDeprecationPasswordExporter::OnExportCompleteWithResult( - LoginDbDeprecationExportResult result) { - LogExportResult(result); - - // If the export wasn't successful and there are passwords to export, - // it will be re-attempted on the next startup. - if (result == LoginDbDeprecationExportResult::kSuccess) { - LogExportLatency(base::Time::Now() - start_time_); - pref_service_->SetBoolean(prefs::kUpmUnmigratedPasswordsExported, true); - password_store_->RemoveLoginsCreatedBetween(FROM_HERE, base::Time(), - base::Time::Max()); - } else if (result == LoginDbDeprecationExportResult::kNoPasswords) { - // Nothing to export, so the export can be marked as done. - pref_service_->SetBoolean( - password_manager::prefs::kUpmUnmigratedPasswordsExported, true); - } - - std::move(export_cleanup_callback_).Run(); - // The callback above destroys `this`. -} - -} // namespace password_manager
diff --git a/components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h b/components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h deleted file mode 100644 index f4ccf982..0000000 --- a/components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h +++ /dev/null
@@ -1,112 +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. - -#ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_EXPORT_LOGIN_DB_DEPRECATION_PASSWORD_EXPORTER_H_ -#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_EXPORT_LOGIN_DB_DEPRECATION_PASSWORD_EXPORTER_H_ - -#include <vector> - -#include "base/files/file_path.h" -#include "base/functional/callback_forward.h" -#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter_interface.h" -#include "components/password_manager/core/browser/export/password_manager_exporter.h" -#include "components/password_manager/core/browser/password_store/password_store_consumer.h" -#include "components/password_manager/core/browser/password_store/password_store_interface.h" -#include "components/password_manager/core/browser/ui/credential_ui_entry.h" -#include "components/password_manager/core/browser/ui/passwords_provider.h" -#include "components/prefs/pref_service.h" - -namespace password_manager { - -// These values are persisted to logs. Entries should not be renumbered and -// numeric values should never be reused. -// -// LINT.IfChange(LoginDbDeprecationExportResult) -enum class LoginDbDeprecationExportResult { - kSuccess = 0, - kNoPasswords = 1, - kErrorFetchingPasswords = 2, - kFileWriteError = 3, - - kMaxValue = kFileWriteError, -}; -// LINT.ThenChange(/tools/metrics/histograms/metadata/password/enums.xml:LoginDbDeprecationExportResult) - -inline constexpr std::string_view kExportedPasswordsFileName = - "ChromePasswords.csv"; - -// Directs exporting the passwords from the `LoginDatabase` to a CSV stored -// in the same place to allow for database deprecation. -class LoginDbDeprecationPasswordExporter - : public LoginDbDeprecationPasswordExporterInterface, - public PasswordStoreConsumer, - public PasswordsProvider { - public: - explicit LoginDbDeprecationPasswordExporter(PrefService* pref_service, - base::FilePath export_dir_path); - LoginDbDeprecationPasswordExporter( - const LoginDbDeprecationPasswordExporter&) = delete; - LoginDbDeprecationPasswordExporter& operator=( - const LoginDbDeprecationPasswordExporter&) = delete; - ~LoginDbDeprecationPasswordExporter() override; - - void Start(scoped_refptr<PasswordStoreInterface> password_store, - base::OnceClosure export_cleanup_calback) override; - - // Allows the `PasswordManagerExporter` to retrieve the saved credentials - // after `this` receives them. Not a necessary pattern for this use-case - // but one which the `PasswordManagerExport` expects, since it's usually - // used in UI applications where another class holds the credentials. - std::vector<CredentialUIEntry> GetSavedCredentials() const override; - - PasswordManagerExporter* GetInternalExporterForTesting( - base::PassKey<class LoginDbDeprecationPasswordExporterTest>); - - private: - void OnGetPasswordStoreResultsOrErrorFrom( - PasswordStoreInterface* store, - LoginsResultOrError results_or_error) override; - - // Reports on the progress of the export flow. - void OnExportProgress(const PasswordExportInfo& export_info); - - // Called when the `exporter_` completes all the export operations, - // irrespective of whether the export succeeded. - void OnExportComplete(); - - // Called when the export finishes (from `OnExportComplete`), - // or before it starts if the passwords to export could not be fetched. - void OnExportCompleteWithResult(LoginDbDeprecationExportResult result); - - // Used to delete the passwords after successful export. - scoped_refptr<PasswordStoreInterface> password_store_; - - // Callback to invoke when ALL the export operations finished. It will clean - // up `this`. - base::OnceClosure export_cleanup_callback_; - - // Serializes the passwords and writes them to a CSV file. - std::unique_ptr<PasswordManagerExporter> exporter_; - - // Stores the saved credentials. - std::vector<CredentialUIEntry> passwords_; - - // Used to store the export completion status in a pref. - raw_ptr<PrefService> pref_service_; - - // Path where the exported CSV will be written. It should be the same as the - // login db path. - base::FilePath export_dir_path_; - - // The last reported status of the export flow. - ExportProgressStatus export_status_{ExportProgressStatus::kNotStarted}; - - base::Time start_time_; - - base::WeakPtrFactory<LoginDbDeprecationPasswordExporter> weak_factory_{this}; -}; - -} // namespace password_manager - -#endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_EXPORT_LOGIN_DB_DEPRECATION_PASSWORD_EXPORTER_H_
diff --git a/components/password_manager/core/browser/export/login_db_deprecation_password_exporter_interface.h b/components/password_manager/core/browser/export/login_db_deprecation_password_exporter_interface.h deleted file mode 100644 index 11cab84c..0000000 --- a/components/password_manager/core/browser/export/login_db_deprecation_password_exporter_interface.h +++ /dev/null
@@ -1,26 +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 COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_EXPORT_LOGIN_DB_DEPRECATION_PASSWORD_EXPORTER_INTERFACE_H_ -#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_EXPORT_LOGIN_DB_DEPRECATION_PASSWORD_EXPORTER_INTERFACE_H_ - -#include "components/password_manager/core/browser/password_store/password_store_interface.h" - -namespace password_manager { - -// Interface for `LoginDbDeprecationPasswordExporter` to allow mocking in tests. -class LoginDbDeprecationPasswordExporterInterface { - public: - virtual ~LoginDbDeprecationPasswordExporterInterface() = default; - - // Starts the export flow. It will first fetch the stored credentials - // and when fetching completes the actual export will be automatically - // started. - virtual void Start(scoped_refptr<PasswordStoreInterface> password_store, - base::OnceClosure export_cleanup_calback) = 0; -}; - -} // namespace password_manager - -#endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_EXPORT_LOGIN_DB_DEPRECATION_PASSWORD_EXPORTER_INTERFACE_H_
diff --git a/components/password_manager/core/browser/export/login_db_deprecation_password_exporter_unittest.cc b/components/password_manager/core/browser/export/login_db_deprecation_password_exporter_unittest.cc deleted file mode 100644 index 6823ba9..0000000 --- a/components/password_manager/core/browser/export/login_db_deprecation_password_exporter_unittest.cc +++ /dev/null
@@ -1,279 +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. - -#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h" - -#include <iterator> -#include <memory> - -#include "base/files/file_path.h" -#include "base/files/file_util.h" -#include "base/files/scoped_temp_dir.h" -#include "base/memory/scoped_refptr.h" -#include "base/memory/weak_ptr.h" -#include "base/strings/utf_string_conversions.h" -#include "base/test/bind.h" -#include "base/test/metrics/histogram_tester.h" -#include "base/test/mock_callback.h" -#include "base/test/run_until.h" -#include "base/test/task_environment.h" -#include "base/test/test_future.h" -#include "base/time/time.h" -#include "components/password_manager/core/browser/password_form.h" -#include "components/password_manager/core/browser/password_store/fake_password_store_backend.h" -#include "components/password_manager/core/browser/password_store/mock_password_store_backend.h" -#include "components/password_manager/core/browser/password_store/password_store.h" -#include "components/password_manager/core/browser/password_store/password_store_backend.h" -#include "components/password_manager/core/browser/password_store/password_store_backend_error.h" -#include "components/password_manager/core/browser/password_store/password_store_consumer.h" -#include "components/password_manager/core/browser/password_store/password_store_interface.h" -#include "components/password_manager/core/browser/password_store/password_store_results_observer.h" -#include "components/password_manager/core/browser/password_store/test_password_store.h" -#include "components/password_manager/core/common/password_manager_pref_names.h" -#include "components/prefs/pref_registry_simple.h" -#include "components/prefs/testing_pref_service.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace password_manager { - -namespace { -using ::testing::Return; -using ::testing::StrictMock; -using ::testing::WithArg; -using ::testing::WithArgs; - -const std::string kLineEnding = "\n"; -const std::string kExportFilename = "ChromePasswords.csv"; -const std::string kExportResultHistogram = - "PasswordManager.UPM.LoginDbDeprecationExport.Result"; -const std::string kExportLatencyHistogram = - "PasswordManager.UPM.LoginDbDeprecationExport.Latency"; - -std::pair<PasswordForm, std::string> GetTestFormWithExpectedExportData() { - const std::u16string kNoteValue = - base::UTF8ToUTF16("Note Line 1" + kLineEnding + "Note Line 2"); - PasswordForm form; - form.signon_realm = "https://example.com/"; - form.url = GURL("https://example.com"); - form.username_value = u"Someone"; - form.password_value = u"Secret"; - form.notes = {PasswordNote(kNoteValue, base::Time::Now())}; - - std::string expected_export_data = - "name,url,username,password,note" + kLineEnding + - "example.com,https://example.com/,Someone,Secret,\"Note Line " - "1" + - kLineEnding + "Note Line 2\"" + kLineEnding; - return {form, expected_export_data}; -} - -} // namespace - -class LoginDbDeprecationPasswordExporterTest : public testing::Test { - public: - LoginDbDeprecationPasswordExporterTest() = default; - - void SetUp() override { - ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); - pref_service_.registry()->RegisterBooleanPref( - prefs::kUpmUnmigratedPasswordsExported, false); - - password_store_ = base::MakeRefCounted<TestPasswordStore>(); - password_store_->Init(&pref_service_, /*affiliated_match_helper=*/nullptr); - exporter_ = std::make_unique<LoginDbDeprecationPasswordExporter>( - &pref_service_, temp_dir_.GetPath()); - } - - void TearDown() override { - password_store_->ShutdownOnUIThread(); - ASSERT_TRUE(temp_dir_.Delete()); - } - - const base::FilePath& export_dir() { return temp_dir_.GetPath(); } - - LoginDbDeprecationPasswordExporter* exporter() { return exporter_.get(); } - - TestingPrefServiceSimple* pref_service() { return &pref_service_; } - - PasswordStoreInterface* password_store() { return password_store_.get(); } - - protected: - base::test::TaskEnvironment task_env_{ - base::test::TaskEnvironment::TimeSource::MOCK_TIME, - base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED}; - base::PassKey<class LoginDbDeprecationPasswordExporterTest> passkey = - base::PassKey<class LoginDbDeprecationPasswordExporterTest>(); - - private: - std::unique_ptr<LoginDbDeprecationPasswordExporter> exporter_; - TestingPrefServiceSimple pref_service_; - scoped_refptr<TestPasswordStore> password_store_; - base::ScopedTempDir temp_dir_; -}; - -TEST_F(LoginDbDeprecationPasswordExporterTest, DoesntCreateFileIfNoPasswords) { - base::HistogramTester histogram_tester; - // No passwords in the backend. - exporter()->Start(password_store(), task_env_.QuitClosure()); - task_env_.RunUntilQuit(); - - base::FilePath expected_file_path = - export_dir().Append(FILE_PATH_LITERAL("ChromePasswords.csv")); - EXPECT_FALSE(base::PathExists(expected_file_path)); - histogram_tester.ExpectUniqueSample( - kExportResultHistogram, LoginDbDeprecationExportResult::kNoPasswords, 1); - histogram_tester.ExpectTotalCount(kExportLatencyHistogram, 0); - EXPECT_TRUE( - pref_service()->GetBoolean(prefs::kUpmUnmigratedPasswordsExported)); -} - -TEST_F(LoginDbDeprecationPasswordExporterTest, ExportFailedToFetchPasswords) { - base::HistogramTester histogram_tester; - // No passwords in the backend. - std::unique_ptr<MockPasswordStoreBackend> mock_backend = - std::make_unique<MockPasswordStoreBackend>(); - MockPasswordStoreBackend* weak_mock_backend = mock_backend.get(); - scoped_refptr<PasswordStore> password_store = - base::MakeRefCounted<PasswordStore>(std::move(mock_backend)); - - EXPECT_CALL(*weak_mock_backend, InitBackend) - .WillOnce(WithArgs<3>([](base::OnceCallback<void(bool)> completion) { - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(completion), /*success=*/true)); - })); - - password_store->Init(pref_service(), /*affiliation_match_service=*/nullptr); - - EXPECT_CALL(*weak_mock_backend, GetAutofillableLoginsAsync) - .WillOnce(WithArg<0>([password_store](LoginsOrErrorReply callback) { - PasswordStoreBackendError error{ - PasswordStoreBackendErrorType::kUncategorized}; - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), std::move(error))); - })); - - exporter()->Start(password_store, task_env_.QuitClosure()); - task_env_.RunUntilQuit(); - - base::FilePath expected_file_path = - export_dir().Append(FILE_PATH_LITERAL("ChromePasswords.csv")); - EXPECT_FALSE(base::PathExists(expected_file_path)); - histogram_tester.ExpectUniqueSample( - kExportResultHistogram, - LoginDbDeprecationExportResult::kErrorFetchingPasswords, 1); - histogram_tester.ExpectTotalCount(kExportLatencyHistogram, 0); - password_store->ShutdownOnUIThread(); -} - -TEST_F(LoginDbDeprecationPasswordExporterTest, ExportsBackendPasswords) { - base::HistogramTester histogram_tester; - std::pair<PasswordForm, std::string> form_expected_data_pair = - GetTestFormWithExpectedExportData(); - PasswordForm form = std::move(form_expected_data_pair.first); - std::string expected_exported_data = - std::move(form_expected_data_pair.second); - - password_store()->AddLogin(form, task_env_.QuitClosure()); - task_env_.RunUntilQuit(); - - const base::TimeDelta kExportLatency = base::Minutes(1); - exporter()->Start(password_store(), task_env_.QuitClosure()); - task_env_.AdvanceClock(kExportLatency); - task_env_.RunUntilQuit(); - - base::FilePath expected_file_path = - export_dir().Append(FILE_PATH_LITERAL(kExportFilename)); - - EXPECT_TRUE(base::PathExists(expected_file_path)); - EXPECT_TRUE( - pref_service()->GetBoolean(prefs::kUpmUnmigratedPasswordsExported)); - - std::string contents; - ASSERT_TRUE(base::ReadFileToString(expected_file_path, &contents)); - EXPECT_TRUE(!contents.empty()); - EXPECT_EQ(expected_exported_data, contents); - histogram_tester.ExpectUniqueSample( - kExportResultHistogram, LoginDbDeprecationExportResult::kSuccess, 1); - histogram_tester.ExpectUniqueTimeSample(kExportLatencyHistogram, - kExportLatency, 1); -} - -TEST_F(LoginDbDeprecationPasswordExporterTest, ExportFailure) { - base::HistogramTester histogram_tester; - PasswordForm form = GetTestFormWithExpectedExportData().first; - password_store()->AddLogin(form, task_env_.QuitClosure()); - task_env_.RunUntilQuit(); - - PasswordManagerExporter* internal_exporter = - exporter()->GetInternalExporterForTesting(passkey); - StrictMock<base::MockCallback<PasswordManagerExporter::WriteCallback>> - mock_write_callback; - - // Fake a failed write. - internal_exporter->SetWriteForTesting(mock_write_callback.Get()); - EXPECT_CALL(mock_write_callback, Run).WillOnce(Return(false)); - - exporter()->Start(password_store(), task_env_.QuitClosure()); - task_env_.RunUntilQuit(); - - EXPECT_FALSE( - pref_service()->GetBoolean(prefs::kUpmUnmigratedPasswordsExported)); - histogram_tester.ExpectUniqueSample( - kExportResultHistogram, LoginDbDeprecationExportResult::kFileWriteError, - 1); - histogram_tester.ExpectTotalCount(kExportLatencyHistogram, 0); -} - -TEST_F(LoginDbDeprecationPasswordExporterTest, RemovesLoginsOnExportSuccess) { - std::pair<PasswordForm, std::string> form_expected_data_pair = - GetTestFormWithExpectedExportData(); - PasswordForm form = std::move(form_expected_data_pair.first); - std::string expected_exported_data = - std::move(form_expected_data_pair.second); - - password_store()->AddLogin(form, task_env_.QuitClosure()); - task_env_.RunUntilQuit(); - - exporter()->Start(password_store(), task_env_.QuitClosure()); - task_env_.RunUntilQuit(); - - ASSERT_TRUE( - pref_service()->GetBoolean(prefs::kUpmUnmigratedPasswordsExported)); - - password_manager::PasswordStoreResultsObserver results_observer; - password_store()->GetAllLogins(results_observer.GetWeakPtr()); - std::vector<std::unique_ptr<PasswordForm>> forms = - results_observer.WaitForResults(); - EXPECT_TRUE(forms.empty()); -} - -TEST_F(LoginDbDeprecationPasswordExporterTest, - DoesntRemoveLoginsOnExportFailure) { - PasswordForm form = GetTestFormWithExpectedExportData().first; - password_store()->AddLogin(form, task_env_.QuitClosure()); - task_env_.RunUntilQuit(); - - PasswordManagerExporter* internal_exporter = - exporter()->GetInternalExporterForTesting(passkey); - StrictMock<base::MockCallback<PasswordManagerExporter::WriteCallback>> - mock_write_callback; - - // Fake a failed write. - internal_exporter->SetWriteForTesting(mock_write_callback.Get()); - EXPECT_CALL(mock_write_callback, Run).WillOnce(Return(false)); - - exporter()->Start(password_store(), task_env_.QuitClosure()); - task_env_.RunUntilQuit(); - - ASSERT_FALSE( - pref_service()->GetBoolean(prefs::kUpmUnmigratedPasswordsExported)); - - password_manager::PasswordStoreResultsObserver results_observer; - password_store()->GetAllLogins(results_observer.GetWeakPtr()); - std::vector<std::unique_ptr<PasswordForm>> forms = - results_observer.WaitForResults(); - EXPECT_FALSE(forms.empty()); -} - -} // namespace password_manager
diff --git a/components/password_manager/core/browser/export/login_db_deprecation_runner.cc b/components/password_manager/core/browser/export/login_db_deprecation_runner.cc deleted file mode 100644 index aec0347..0000000 --- a/components/password_manager/core/browser/export/login_db_deprecation_runner.cc +++ /dev/null
@@ -1,49 +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. - -#include "components/password_manager/core/browser/export/login_db_deprecation_runner.h" - -#include "base/memory/scoped_refptr.h" -#include "base/metrics/histogram_functions.h" -#include "base/time/time.h" -#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h" -#include "components/password_manager/core/browser/features/password_features.h" -namespace password_manager { - -void LogExportProgress(LoginDbDeprecationExportProgress progress) { - base::UmaHistogramEnumeration( - "PasswordManager.UPM.LoginDbDeprecationExport.Progress", progress); -} - -LoginDbDeprecationRunner::LoginDbDeprecationRunner( - std::unique_ptr<LoginDbDeprecationPasswordExporterInterface> exporter) - : exporter_(std::move(exporter)) {} - -LoginDbDeprecationRunner::~LoginDbDeprecationRunner() = default; - -void LoginDbDeprecationRunner::StartExportWithDelay( - scoped_refptr<PasswordStoreInterface> password_store) { - CHECK(exporter_); - LogExportProgress(LoginDbDeprecationExportProgress::kScheduled); - base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( - FROM_HERE, - base::BindOnce(&LoginDbDeprecationRunner::StartExport, - weak_ptr_factory_.GetWeakPtr(), password_store), - kLoginDbDeprecationExportDelay); -} - -void LoginDbDeprecationRunner::StartExport( - scoped_refptr<PasswordStoreInterface> password_store) { - LogExportProgress(LoginDbDeprecationExportProgress::kStarted); - exporter_->Start(password_store, - base::BindOnce(&LoginDbDeprecationRunner::ExportFinished, - weak_ptr_factory_.GetWeakPtr())); -} - -void LoginDbDeprecationRunner::ExportFinished() { - LogExportProgress(LoginDbDeprecationExportProgress::kFinished); - exporter_.reset(); -} - -} // namespace password_manager
diff --git a/components/password_manager/core/browser/export/login_db_deprecation_runner.h b/components/password_manager/core/browser/export/login_db_deprecation_runner.h deleted file mode 100644 index 0e2ed399..0000000 --- a/components/password_manager/core/browser/export/login_db_deprecation_runner.h +++ /dev/null
@@ -1,58 +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. - -#ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_EXPORT_LOGIN_DB_DEPRECATION_RUNNER_H_ -#define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_EXPORT_LOGIN_DB_DEPRECATION_RUNNER_H_ - -#include "base/time/time.h" -#include "components/keyed_service/core/keyed_service.h" -#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h" -#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter_interface.h" -#include "components/password_manager/core/browser/password_store/password_store_interface.h" - -namespace password_manager { - -inline constexpr base::TimeDelta kLoginDbDeprecationExportDelay = - base::Seconds(5); - -// These values are persisted to logs. Entries should not be renumbered and -// numeric values should never be reused. -// -// LINT.IfChange(LoginDbDeprecationExportProgress) -enum class LoginDbDeprecationExportProgress { - kScheduled = 0, - kStarted = 1, - kFinished = 2, - kMaxValue = kFinished, -}; -// LINT.ThenChange(/tools/metrics/histograms/metadata/password/enums.xml:LoginDbDeprecationExportProgress) - -// Owns and runs the pre-deprecation password export on Android. -// Once the export is done it will destroy the exporter and clear the memory. -// The service will only be instantiated for clients whose passwords couldn't -// be migrated to GMS Core. -class LoginDbDeprecationRunner : public KeyedService { - public: - explicit LoginDbDeprecationRunner( - std::unique_ptr<LoginDbDeprecationPasswordExporterInterface> exporter); - LoginDbDeprecationRunner(const LoginDbDeprecationRunner&) = delete; - LoginDbDeprecationRunner& operator=(const LoginDbDeprecationRunner&) = delete; - ~LoginDbDeprecationRunner() override; - - void StartExportWithDelay( - scoped_refptr<PasswordStoreInterface> password_store); - - private: - void StartExport(scoped_refptr<PasswordStoreInterface> password_store); - - void ExportFinished(); - - std::unique_ptr<LoginDbDeprecationPasswordExporterInterface> exporter_; - - base::WeakPtrFactory<LoginDbDeprecationRunner> weak_ptr_factory_{this}; -}; - -} // namespace password_manager - -#endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_EXPORT_LOGIN_DB_DEPRECATION_RUNNER_H_
diff --git a/components/password_manager/core/browser/export/login_db_deprecation_runner_unittest.cc b/components/password_manager/core/browser/export/login_db_deprecation_runner_unittest.cc deleted file mode 100644 index 7e271d04..0000000 --- a/components/password_manager/core/browser/export/login_db_deprecation_runner_unittest.cc +++ /dev/null
@@ -1,122 +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. - -#include "components/password_manager/core/browser/export/login_db_deprecation_runner.h" - -#include <memory> - -#include "base/functional/callback_forward.h" -#include "base/memory/scoped_refptr.h" -#include "base/test/metrics/histogram_tester.h" -#include "base/test/task_environment.h" -#include "base/time/time.h" -#include "components/password_manager/core/browser/export/login_db_deprecation_password_exporter_interface.h" -#include "components/password_manager/core/browser/password_store/mock_password_store_interface.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace password_manager { - -namespace { -using ::testing::WithArgs; - -const std::string kExportProgressHistogram = - "PasswordManager.UPM.LoginDbDeprecationExport.Progress"; - -class MockLoginDbDeprecationPasswordExporter - : public LoginDbDeprecationPasswordExporterInterface { - public: - MOCK_METHOD(void, - Start, - (scoped_refptr<PasswordStoreInterface> password_store, - base::OnceClosure export_cleanup_calback), - (override)); -}; -} // namespace - -class LoginDbDeprecationdRunnerTest : public testing::Test { - public: - LoginDbDeprecationdRunnerTest() = default; - - void SetUp() override { - std::unique_ptr<MockLoginDbDeprecationPasswordExporter> - exporter_unique_ptr = - std::make_unique<MockLoginDbDeprecationPasswordExporter>(); - mock_exporter_ = exporter_unique_ptr.get(); - db_export_runner_ = std::make_unique<LoginDbDeprecationRunner>( - std::move(exporter_unique_ptr)); - mock_password_store_ = base::MakeRefCounted<MockPasswordStoreInterface>(); - } - - LoginDbDeprecationRunner* db_export_runner() { - return db_export_runner_.get(); - } - - MockLoginDbDeprecationPasswordExporter* mock_exporter() { - return mock_exporter_; - } - - scoped_refptr<MockPasswordStoreInterface> mock_password_store() { - return mock_password_store_; - } - - protected: - base::test::SingleThreadTaskEnvironment task_env_{ - base::test::TaskEnvironment::TimeSource::MOCK_TIME, - base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED}; - - private: - raw_ptr<MockLoginDbDeprecationPasswordExporter> mock_exporter_; - std::unique_ptr<LoginDbDeprecationRunner> db_export_runner_; - scoped_refptr<MockPasswordStoreInterface> mock_password_store_; -}; - -TEST_F(LoginDbDeprecationdRunnerTest, ExportScheduledNotStartingBeforeDelay) { - base::HistogramTester histogram_tester; - EXPECT_CALL(*mock_exporter(), Start).Times(0); - db_export_runner()->StartExportWithDelay(mock_password_store()); - - // Fast forward by a less time than the task delay. - task_env_.FastForwardBy(kLoginDbDeprecationExportDelay / 2); - histogram_tester.ExpectUniqueSample( - kExportProgressHistogram, LoginDbDeprecationExportProgress::kScheduled, - 1); -} - -TEST_F(LoginDbDeprecationdRunnerTest, ExportRunsAfterDelayButDoesntFinish) { - base::HistogramTester histogram_tester; - - db_export_runner()->StartExportWithDelay(mock_password_store()); - EXPECT_CALL(*mock_exporter(), Start); - task_env_.FastForwardBy(kLoginDbDeprecationExportDelay); - - histogram_tester.ExpectBucketCount( - kExportProgressHistogram, LoginDbDeprecationExportProgress::kScheduled, - 1); - histogram_tester.ExpectBucketCount( - kExportProgressHistogram, LoginDbDeprecationExportProgress::kStarted, 1); - histogram_tester.ExpectBucketCount( - kExportProgressHistogram, LoginDbDeprecationExportProgress::kFinished, 0); -} - -TEST_F(LoginDbDeprecationdRunnerTest, ExportRunsAndFinishes) { - base::HistogramTester histogram_tester; - - db_export_runner()->StartExportWithDelay(mock_password_store()); - EXPECT_CALL(*mock_exporter(), Start) - .WillOnce(WithArgs<1>([](base::OnceClosure completion_callback) { - std::move(completion_callback).Run(); - })); - task_env_.FastForwardBy(kLoginDbDeprecationExportDelay); - - histogram_tester.ExpectBucketCount( - kExportProgressHistogram, LoginDbDeprecationExportProgress::kScheduled, - 1); - histogram_tester.ExpectBucketCount( - kExportProgressHistogram, LoginDbDeprecationExportProgress::kStarted, 1); - histogram_tester.ExpectBucketCount( - kExportProgressHistogram, LoginDbDeprecationExportProgress::kFinished, 1); -} - -} // namespace password_manager
diff --git a/components/password_manager/core/browser/password_manager.cc b/components/password_manager/core/browser/password_manager.cc index bfaa54b..26e89fc 100644 --- a/components/password_manager/core/browser/password_manager.cc +++ b/components/password_manager/core/browser/password_manager.cc
@@ -69,7 +69,6 @@ #include "components/password_manager/core/browser/first_cct_page_load_passwords_ukm_recorder.h" #include "components/password_manager/core/browser/password_feature_manager.h" #include "components/password_manager/core/browser/password_sync_util.h" -#include "components/password_manager/core/browser/split_stores_and_local_upm.h" #endif // BUILDFLAG(IS_ANDROID) #if BUILDFLAG(IS_WIN) @@ -535,18 +534,10 @@ #if BUILDFLAG(IS_ANDROID) registry->RegisterBooleanPref(prefs::kOfferToSavePasswordsEnabledGMS, true); registry->RegisterBooleanPref(prefs::kAutoSignInEnabledGMS, true); - RegisterLegacySplitStoresPref(registry); registry->RegisterStringPref(prefs::kUPMErrorUIShownTimestamp, "0"); registry->RegisterIntegerPref( prefs::kPasswordGenerationBottomSheetDismissCount, 0); - // This pref is used to decide whether the PasswordStore can be connected to - // the new Android backend without migrating existing entries in the - // LoginDatabase. In doubt, it's best to assume that's not the case, otherwise - // passwords might be left behind. In practice, the default value should make - // little difference, the pref is always written on startup. - registry->RegisterBooleanPref(prefs::kEmptyProfileStoreLoginDatabase, false); registry->RegisterBooleanPref(prefs::kUpmAutoExportCsvNeedsDeletion, false); - registry->RegisterBooleanPref(prefs::kUpmUnmigratedPasswordsExported, false); #endif // BUILDFLAG(IS_ANDROID) #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS)
diff --git a/components/password_manager/core/browser/password_store/login_database.cc b/components/password_manager/core/browser/password_store/login_database.cc index 913d1b8..250546cb 100644 --- a/components/password_manager/core/browser/password_store/login_database.cc +++ b/components/password_manager/core/browser/password_store/login_database.cc
@@ -1282,7 +1282,6 @@ return false; } - TriggerIsEmptyCb(); LogDatabaseInitError(INIT_OK); // Keep the database open if everything went well. @@ -1348,7 +1347,6 @@ PasswordStoreChangeList LoginDatabase::AddLogin(const PasswordForm& form, AddCredentialError* error) { TRACE_EVENT0("passwords", "LoginDatabase::AddLogin"); - absl::Cleanup is_empty_runner = [this] { TriggerIsEmptyCb(); }; if (error) { *error = AddCredentialError::kNone; } @@ -1586,7 +1584,6 @@ bool LoginDatabase::RemoveLogin(const PasswordForm& form, PasswordStoreChangeList* changes) { TRACE_EVENT0("passwords", "LoginDatabase::RemoveLogin"); - absl::Cleanup is_empty_runner = [this] { TriggerIsEmptyCb(); }; if (changes) { changes->clear(); } @@ -1624,7 +1621,6 @@ TRACE_EVENT0("passwords", "LoginDatabase::RemoveLoginByPrimaryKey"); CHECK(changes); - absl::Cleanup is_empty_runner = [this] { TriggerIsEmptyCb(); }; changes->clear(); sql::Statement s1(db_.GetCachedStatement( SQL_FROM_HERE, "SELECT * FROM logins WHERE id = ?")); @@ -1658,7 +1654,6 @@ base::Time delete_end, PasswordStoreChangeList* changes) { TRACE_EVENT0("passwords", "LoginDatabase::RemoveLoginsCreatedBetween"); - absl::Cleanup is_empty_runner = [this] { TriggerIsEmptyCb(); }; if (changes) { changes->clear(); } @@ -1909,12 +1904,6 @@ return true; } -bool LoginDatabase::IsEmpty() { - sql::Statement count_all_logins(db_.GetCachedStatement( - SQL_FROM_HERE, "SELECT EXISTS(SELECT 1 FROM logins)")); - return count_all_logins.Step() && count_all_logins.ColumnInt(0) == 0; -} - bool LoginDatabase::DeleteAndRecreateDatabaseFile() { TRACE_EVENT0("passwords", "LoginDatabase::DeleteAndRecreateDatabaseFile"); DCHECK(db_.is_open()); @@ -1941,7 +1930,6 @@ DatabaseCleanupResult LoginDatabase::DeleteUndecryptableLogins() { TRACE_EVENT0("passwords", "LoginDatabase::DeleteUndecryptableLogins"); - absl::Cleanup is_empty_runner = [this] { TriggerIsEmptyCb(); }; // If the Keychain in MacOS or the real secret key in Linux is unavailable, // don't delete any logins. if (!OSCrypt::IsEncryptionAvailable()) { @@ -2004,10 +1992,6 @@ return db_.CommitTransactionDeprecated(); } -void LoginDatabase::SetIsEmptyCb(IsEmptyCallback is_empty_cb) { - is_empty_cb_ = std::move(is_empty_cb); -} - LoginDatabase::SyncMetadataStore::SyncMetadataStore(sql::Database* db) : db_(db) { CHECK(db); @@ -2520,10 +2504,4 @@ return true; } -void LoginDatabase::TriggerIsEmptyCb() { - if (is_empty_cb_) { - is_empty_cb_.Run(IsEmpty()); - } -} - } // namespace password_manager
diff --git a/components/password_manager/core/browser/password_store/login_database.h b/components/password_manager/core/browser/password_store/login_database.h index f3b632d..7f2a6fa5 100644 --- a/components/password_manager/core/browser/password_store/login_database.h +++ b/components/password_manager/core/browser/password_store/login_database.h
@@ -50,7 +50,6 @@ // the login information. class LoginDatabase : public EncryptDecryptInterface { public: - using IsEmptyCallback = base::RepeatingCallback<void(bool)>; using DeletingUndecryptablePasswordsEnabled = base::StrongAlias<class DeletingUndecryptablePasswordsEnabledTag, bool>; using OnUndecryptablePasswordsRemoved = @@ -167,8 +166,6 @@ // whether further use of this login database will succeed is unspecified. bool DeleteAndRecreateDatabaseFile(); - bool IsEmpty(); - // On MacOS, it deletes all logins from the database that cannot be decrypted // when encryption key from Keychain is available. If the Keychain is locked, // it does nothing and returns ENCRYPTION_UNAVAILABLE. If it's not running on @@ -186,12 +183,6 @@ void RollbackTransaction(); bool CommitTransaction(); - // `is_empty_cb`is called to signal whether the database is empty (i.e. - // without any logins *or* blocklists) and whether there are any autofillable - // logins. The call happens when initializing the database and when - // adding/removing entries, regardless of success. - void SetIsEmptyCb(IsEmptyCallback is_empty_cb); - #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) void SetIsUserDataDirPolicySet(bool is_set) { is_user_data_dir_policy_set_ = is_set; @@ -359,13 +350,8 @@ bool UpdatePasswordNotes(FormPrimaryKey primary_key, const std::vector<PasswordNote>& notes); - // If a non-null `is_empty_cb` was passed on construction, computes whether - // the DB is empty (SQL statement) and invokes the callback with the result. - void TriggerIsEmptyCb(); - const base::FilePath db_path_; const IsAccountStore is_account_store_; - IsEmptyCallback is_empty_cb_ = base::NullCallback(); // `on_undecryptable_passwords_removed_`is called to signal whether user // interacted with the kClearUndecryptablePasswords experiment. It is needed
diff --git a/components/password_manager/core/browser/password_store/login_database_unittest.cc b/components/password_manager/core/browser/password_store/login_database_unittest.cc index 7fbbe18..7efac72 100644 --- a/components/password_manager/core/browser/password_store/login_database_unittest.cc +++ b/components/password_manager/core/browser/password_store/login_database_unittest.cc
@@ -137,33 +137,6 @@ return form; } -PasswordForm GenerateBlocklistedForm() { - PasswordForm form = GenerateExamplePasswordForm(); - form.url = GURL("http://accounts.blocklisted.com/LoginAuth"); - form.action = GURL("http://accounts.blocklisted.com/Login"); - form.signon_realm = "http://www.blocklisted.com/"; - form.blocked_by_user = true; - return form; -} - -PasswordForm GenerateFederatedCredentialForm() { - PasswordForm form = GenerateExamplePasswordForm(); - form.url = GURL("http://accounts.federated.com/LoginAuth"); - form.action = GURL("http://accounts.federated.com/Login"); - form.federation_origin = - url::SchemeHostPort(GURL("https://accounts.federated.com/")); - return form; -} - -PasswordForm GenerateUsernameOnlyForm() { - PasswordForm form = GenerateExamplePasswordForm(); - form.url = GURL("http://accounts.usernameonly.com/LoginAuth"); - form.action = GURL("http://accounts.usernameonly.com/Login"); - form.signon_realm = "http://www.usernameonly.com/"; - form.scheme = PasswordForm::Scheme::kUsernameOnly; - return form; -} - // Helper functions to read the value of the first column of an executed // statement if we know its type. You must implement a specialization for // every column type you use. @@ -296,7 +269,6 @@ OSCryptMocker::SetUp(); db_ = std::make_unique<LoginDatabase>(file_, IsAccountStore(false)); - db_->SetIsEmptyCb(is_empty_cb_.Get()); ASSERT_TRUE( db_->Init(/*on_undecryptable_passwords_removed=*/base::NullCallback(), /*encryptor=*/nullptr)); @@ -308,7 +280,6 @@ base::ScopedTempDir temp_dir_; base::FilePath file_; - NiceMock<base::MockCallback<LoginDatabase::IsEmptyCallback>> is_empty_cb_; std::unique_ptr<LoginDatabase> db_; // A full TaskEnvironment is required instead of only // SingleThreadTaskEnvironment because on iOS, @@ -327,7 +298,6 @@ OSCryptMocker::SetUp(); db_ = std::make_unique<LoginDatabase>(file_, IsAccountStore(false)); - db_->SetIsEmptyCb(is_empty_cb_.Get()); ASSERT_TRUE( db_->Init(/*on_undecryptable_passwords_removed=*/base::NullCallback(), /*encryptor=*/encryptor())); @@ -2421,8 +2391,6 @@ /*should_be_corrupted=*/true, /*blocklisted=*/true); LoginDatabase db(database_path(), IsAccountStore(false)); - NiceMock<base::MockCallback<LoginDatabase::IsEmptyCallback>> is_empty_cb; - db.SetIsEmptyCb(is_empty_cb.Get()); base::HistogramTester histogram_tester; ASSERT_TRUE( db.Init(/*on_undecryptable_passwords_removed=*/base::NullCallback(), @@ -2440,9 +2408,6 @@ EXPECT_TRUE(result.empty()); // Delete undecryptable logins and make sure we can get valid logins. - // `is_empty_cb_` is called more than once because DeleteUndecryptableLogins() - // internally calls RemoveLogin() for each form. - EXPECT_CALL(is_empty_cb, Run(false)).Times(AnyNumber()); EXPECT_EQ(DatabaseCleanupResult::kSuccess, db.DeleteUndecryptableLogins()); EXPECT_TRUE(db.GetAutofillableLogins(&result)); EXPECT_THAT(result, ElementsAre(HasPrimaryKeyAndEquals(form1))); @@ -3446,123 +3411,6 @@ EXPECT_EQ(error, AddCredentialError::kConstraintViolation); } -TEST_P(LoginDatabaseTest, IsEmptyCb_InitEmpty) { - LoginDatabase db(temp_dir_.GetPath().AppendASCII("DbDirectory"), - IsAccountStore(false)); - NiceMock<base::MockCallback<LoginDatabase::IsEmptyCallback>> is_empty_cb; - db.SetIsEmptyCb(is_empty_cb.Get()); - EXPECT_CALL(is_empty_cb, Run(true)); - db.Init(/*on_undecryptable_passwords_removed=*/base::NullCallback(), - /*encryptor=*/encryptor()); -} - -TEST_P(LoginDatabaseTest, IsEmptyCb_InitNonEmpty) { - base::FilePath directory = temp_dir_.GetPath().AppendASCII("DbDirectory"); - { - // Simulate the DB being populated in a previous startup. - auto db = std::make_unique<LoginDatabase>(directory, IsAccountStore(false)); - db->Init(/*on_undecryptable_passwords_removed=*/base::NullCallback(), - /*encryptor=*/encryptor()); - std::ignore = - db->AddLogin(GenerateExamplePasswordForm(), /*error=*/nullptr); - db.reset(); - } - - LoginDatabase db(directory, IsAccountStore(false)); - NiceMock<base::MockCallback<LoginDatabase::IsEmptyCallback>> is_empty_cb; - db.SetIsEmptyCb(is_empty_cb.Get()); - EXPECT_CALL(is_empty_cb, Run(false)); - db.Init(/*on_undecryptable_passwords_removed=*/base::NullCallback(), - /*encryptor=*/encryptor()); -} - -TEST_P(LoginDatabaseTest, IsEmptyCb_AddLogin) { - ASSERT_TRUE(db().IsEmpty()); - EXPECT_CALL(is_empty_cb_, Run(false)); - std::ignore = db().AddLogin(GenerateExamplePasswordForm(), /*error=*/nullptr); -} - -TEST_P(LoginDatabaseTest, IsEmptyCb_RemoveLogin) { - PasswordForm normal_form = GenerateExamplePasswordForm(); - PasswordForm blocklist_form = GenerateBlocklistedForm(); - PasswordForm federated_form = GenerateFederatedCredentialForm(); - PasswordForm username_only_form = GenerateUsernameOnlyForm(); - - ASSERT_EQ(db().AddLogin(normal_form, /*error=*/nullptr).size(), 1u); - ASSERT_EQ(db().AddLogin(blocklist_form, /*error=*/nullptr).size(), 1u); - ASSERT_EQ(db().AddLogin(federated_form, /*error=*/nullptr).size(), 1u); - ASSERT_EQ(db().AddLogin(username_only_form, /*error=*/nullptr).size(), 1u); - ASSERT_FALSE(db().IsEmpty()); - - testing::MockFunction<void(int)> check; - { - testing::InSequence in_sequence; - EXPECT_CALL(is_empty_cb_, Run(false)).Times(3); - EXPECT_CALL(check, Call(1)); - EXPECT_CALL(is_empty_cb_, Run(true)); - } - std::ignore = db().RemoveLogin(normal_form, /*changes=*/nullptr); - std::ignore = db().RemoveLogin(blocklist_form, /*changes=*/nullptr); - std::ignore = db().RemoveLogin(federated_form, /*changes=*/nullptr); - check.Call(1); - std::ignore = db().RemoveLogin(username_only_form, /*changes=*/nullptr); -} - -TEST_P(LoginDatabaseTest, IsEmptyCb_RemoveLoginByPrimaryKey) { - PasswordForm normal_form = GenerateExamplePasswordForm(); - PasswordForm blocklist_form = GenerateBlocklistedForm(); - PasswordForm federated_form = GenerateFederatedCredentialForm(); - PasswordForm username_only_form = GenerateUsernameOnlyForm(); - - PasswordStoreChangeList normal_form_changes = db().AddLogin(normal_form); - PasswordStoreChangeList blocklist_form_changes = - db().AddLogin(blocklist_form); - PasswordStoreChangeList federated_form_changes = - db().AddLogin(federated_form); - PasswordStoreChangeList username_only_form_changes = - db().AddLogin(username_only_form); - - ASSERT_EQ(normal_form_changes.size(), 1u); - ASSERT_EQ(blocklist_form_changes.size(), 1u); - ASSERT_EQ(federated_form_changes.size(), 1u); - ASSERT_EQ(username_only_form_changes.size(), 1u); - ASSERT_FALSE(db().IsEmpty()); - - testing::MockFunction<void(int)> check; - { - testing::InSequence in_sequence; - EXPECT_CALL(is_empty_cb_, Run(false)).Times(3); - EXPECT_CALL(check, Call(1)); - EXPECT_CALL(is_empty_cb_, Run(true)); - } - - std::ignore = db().RemoveLoginByPrimaryKey( - *normal_form_changes[0].form().primary_key, &normal_form_changes); - std::ignore = db().RemoveLoginByPrimaryKey( - *blocklist_form_changes[0].form().primary_key, &blocklist_form_changes); - std::ignore = db().RemoveLoginByPrimaryKey( - *federated_form_changes[0].form().primary_key, &federated_form_changes); - check.Call(1); - std::ignore = db().RemoveLoginByPrimaryKey( - *username_only_form_changes[0].form().primary_key, - &username_only_form_changes); -} - -TEST_P(LoginDatabaseTest, IsEmptyCb_RemoveLoginsCreatedBetween) { - std::ignore = db().AddLogin(GenerateExamplePasswordForm(), /*error=*/nullptr); - ASSERT_FALSE(db().IsEmpty()); - EXPECT_CALL(is_empty_cb_, Run(true)); - std::ignore = db().RemoveLoginsCreatedBetween(base::Time(), base::Time::Now(), - /*changes=*/nullptr); -} - -TEST_P(LoginDatabaseTest, IsEmptyCb_DeleteAndRecreateDatabaseFile) { - std::ignore = db().AddLogin(GenerateExamplePasswordForm(), /*error=*/nullptr); - ASSERT_FALSE(db().IsEmpty()); - EXPECT_CALL(is_empty_cb_, Run(true)); - db().DeleteAndRecreateDatabaseFile(); -} - class LoginDatabaseForAccountStoreTest : public testing::Test { protected: void SetUp() override {
diff --git a/components/password_manager/core/browser/password_store_factory_util.cc b/components/password_manager/core/browser/password_store_factory_util.cc index 40eb050..0a862ed 100644 --- a/components/password_manager/core/browser/password_store_factory_util.cc +++ b/components/password_manager/core/browser/password_store_factory_util.cc
@@ -18,7 +18,6 @@ #include "components/password_manager/core/browser/password_change_backup_password_cleaner.h" #include "components/password_manager/core/browser/password_manager_constants.h" #include "components/password_manager/core/browser/password_store/login_database.h" -#include "components/password_manager/core/browser/password_store/password_store_backend.h" #include "components/password_manager/core/browser/password_store/password_store_interface.h" #include "components/password_manager/core/common/password_manager_pref_names.h" #include "components/prefs/pref_service.h" @@ -107,15 +106,4 @@ } } -void IntermediateCallbackForSettingPrefs( - base::WeakPtr<PasswordStoreBackend> backend, - LoginDatabase::IsEmptyCallback set_prefs_callback, - bool value) { - // When a `PasswordStoreBackend` is shut down, the weak pointers are - // invalidated. - if (backend) { - set_prefs_callback.Run(value); - } -} - } // namespace password_manager
diff --git a/components/password_manager/core/browser/password_store_factory_util.h b/components/password_manager/core/browser/password_store_factory_util.h index 66fb856..b89d8a85 100644 --- a/components/password_manager/core/browser/password_store_factory_util.h +++ b/components/password_manager/core/browser/password_store_factory_util.h
@@ -9,7 +9,6 @@ #include "base/files/file_path.h" #include "base/functional/callback.h" -#include "base/memory/weak_ptr.h" #include "base/time/time.h" #include "components/password_manager/core/browser/password_store/login_database.h" #include "components/password_manager/core/browser/password_store/password_store_interface.h" @@ -23,7 +22,6 @@ namespace password_manager { class CredentialsCleanerRunner; -class PasswordStoreBackend; // Creates a LoginDatabase. Looks in |db_directory| for the database file. // Does not call LoginDatabase::Init() -- to avoid UI jank, that needs to be @@ -58,22 +56,6 @@ base::RepeatingCallback<network::mojom::NetworkContext*()> network_context_getter); -// Checks that the backend was not yet shut down (i.e. the weak pointer to the -// backend was not yet invalidated) before calling `set_prefs_callback`. -// -// Example of usage: -// -// base::BindRepeating( -// &password_manager::IntermediateCallbackForSettingPrefs, -// backend->AsWeakPtr(), base::BindRepeating( -// &password_manager::SetFooPref, pref_service, -// password_manager::prefs:: -// kEmptyProfileStoreLoginDatabase)) -void IntermediateCallbackForSettingPrefs( - base::WeakPtr<PasswordStoreBackend> backend, - LoginDatabase::IsEmptyCallback set_prefs_callback, - bool value); - } // namespace password_manager #endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_PASSWORD_STORE_FACTORY_UTIL_H_
diff --git a/components/password_manager/core/browser/sharing/password_sharing_recipients_downloader.cc b/components/password_manager/core/browser/sharing/password_sharing_recipients_downloader.cc index 4ffbede..450e7a3 100644 --- a/components/password_manager/core/browser/sharing/password_sharing_recipients_downloader.cc +++ b/components/password_manager/core/browser/sharing/password_sharing_recipients_downloader.cc
@@ -99,10 +99,6 @@ DVLOG(1) << "Access token fetch complete, error state: " << static_cast<int>(error.state()); - base::UmaHistogramEnumeration( - "PasswordManager.PasswordSharingRecipients.FetchAccessTokenResult", - error.state(), GoogleServiceAuthError::NUM_STATES); - CHECK(ongoing_access_token_fetch_); ongoing_access_token_fetch_.reset();
diff --git a/components/password_manager/core/browser/split_stores_and_local_upm.cc b/components/password_manager/core/browser/split_stores_and_local_upm.cc index f80fbe5..004ed734 100644 --- a/components/password_manager/core/browser/split_stores_and_local_upm.cc +++ b/components/password_manager/core/browser/split_stores_and_local_upm.cc
@@ -8,54 +8,18 @@ #include "base/notreached.h" #include "base/strings/string_number_conversions.h" #include "build/buildflag.h" -#include "components/password_manager/core/browser/features/password_features.h" #include "components/password_manager/core/browser/password_manager_buildflags.h" -#include "components/password_manager/core/common/password_manager_pref_names.h" -#include "components/prefs/pref_registry_simple.h" -#include "components/prefs/pref_service.h" namespace password_manager { namespace { -// DO NOT expose the enum nor the pref name in a header! This is a legacy pref -// and usages should be limited to GetLegacySplitStoresPref(). -// -// Do not renumber UseUpmLocalAndSeparateStoresState, values are persisted. -// Values are also used for metrics recording. -enum class UseUpmLocalAndSeparateStoresState { - kOff = 0, - kOffAndMigrationPending = 1, - kOn = 2, - kMaxValue = kOn -}; -constexpr char kPasswordsUseUPMLocalAndSeparateStores[] = - "passwords_use_upm_local_and_separate_stores"; - // Do not expose these constants! Use GetSplitStoresUpmMinVersion() instead. const int kSplitStoresUpmMinVersionForNonAuto = 240212000; const int kSplitStoresUpmMinVersionForAuto = 241512000; } // namespace -void RegisterLegacySplitStoresPref(PrefRegistrySimple* registry) { - registry->RegisterIntegerPref( - kPasswordsUseUPMLocalAndSeparateStores, - static_cast<int>(UseUpmLocalAndSeparateStoresState::kOff)); -} - -bool GetLegacySplitStoresPref(const PrefService* pref_service) { - switch (static_cast<UseUpmLocalAndSeparateStoresState>( - pref_service->GetInteger(kPasswordsUseUPMLocalAndSeparateStores))) { - case UseUpmLocalAndSeparateStoresState::kOff: - case UseUpmLocalAndSeparateStoresState::kOffAndMigrationPending: - return false; - case UseUpmLocalAndSeparateStoresState::kOn: - return true; - } - NOTREACHED(); -} - bool IsGmsCoreUpdateRequired() { #if BUILDFLAG(USE_LOGIN_DATABASE_AS_BACKEND) return false; @@ -78,11 +42,4 @@ : kSplitStoresUpmMinVersionForNonAuto; } -void SetLegacySplitStoresPrefForTest(PrefService* pref_service, bool enabled) { - pref_service->SetInteger( - kPasswordsUseUPMLocalAndSeparateStores, - static_cast<int>(enabled ? UseUpmLocalAndSeparateStoresState::kOn - : UseUpmLocalAndSeparateStoresState::kOff)); -} - } // namespace password_manager
diff --git a/components/password_manager/core/browser/split_stores_and_local_upm.h b/components/password_manager/core/browser/split_stores_and_local_upm.h index 3964f60..b1c06d85 100644 --- a/components/password_manager/core/browser/split_stores_and_local_upm.h +++ b/components/password_manager/core/browser/split_stores_and_local_upm.h
@@ -5,22 +5,12 @@ #ifndef COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_SPLIT_STORES_AND_LOCAL_UPM_H_ #define COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_SPLIT_STORES_AND_LOCAL_UPM_H_ -class PrefRegistrySimple; -class PrefService; +// TODO(crbug.com/442347616): Rename this file and the test fixture. Ideally +// IsPasswordManagerAvailable() should live next to these methods and they would +// all be named consistently. namespace password_manager { -void RegisterLegacySplitStoresPref(PrefRegistrySimple* registry); - -// WARNING: Do not add new callers without consulting with ioanap@. -// -// This returns the value of a certain pref that used to dictate whether a -// second PasswordStore should be created. As of 07/2025, Android always creates -// 2 stores, regardless of the pref. For now, the pref value still exists on -// disk and is read in specific places for migration reasons. But it is never -// written in production anymore. -bool GetLegacySplitStoresPref(const PrefService* pref_service); - // Returns if it is a requirement to update the GMSCore based on whether split // password stores are supported or not. bool IsGmsCoreUpdateRequired(); @@ -30,8 +20,6 @@ // non-auto and the form factor can only be checked in runtime. int GetSplitStoresUpmMinVersion(); -void SetLegacySplitStoresPrefForTest(PrefService* pref_service, bool enabled); - } // namespace password_manager #endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_SPLIT_STORES_AND_LOCAL_UPM_H_
diff --git a/components/password_manager/core/browser/split_stores_and_local_upm_unittest.cc b/components/password_manager/core/browser/split_stores_and_local_upm_unittest.cc index b10ec67..479504d 100644 --- a/components/password_manager/core/browser/split_stores_and_local_upm_unittest.cc +++ b/components/password_manager/core/browser/split_stores_and_local_upm_unittest.cc
@@ -5,12 +5,7 @@ #include "components/password_manager/core/browser/split_stores_and_local_upm.h" #include "base/android/device_info.h" -#include "base/test/scoped_feature_list.h" -#include "components/password_manager/core/browser/features/password_features.h" #include "components/password_manager/core/browser/password_manager_buildflags.h" -#include "components/password_manager/core/common/password_manager_pref_names.h" -#include "components/prefs/pref_registry_simple.h" -#include "components/prefs/testing_pref_service.h" #include "testing/gtest/include/gtest/gtest.h" namespace password_manager { @@ -19,28 +14,6 @@ constexpr std::string kSplitStoresUpmMinVersionForNonAuto = "240212000"; constexpr std::string kSplitStoresUpmMinVersionForAuto = "241512000"; -class SplitStoresAndLocalUpmTest : public ::testing::Test { - public: - SplitStoresAndLocalUpmTest() { - RegisterLegacySplitStoresPref(pref_service_.registry()); - } - - TestingPrefServiceSimple* pref_service() { return &pref_service_; } - - private: - TestingPrefServiceSimple pref_service_; -}; - -TEST_F(SplitStoresAndLocalUpmTest, UpmPrefOff) { - EXPECT_FALSE(GetLegacySplitStoresPref(pref_service())); -} - -TEST_F(SplitStoresAndLocalUpmTest, UpmPrefOn) { - SetLegacySplitStoresPrefForTest(pref_service(), true); - - EXPECT_TRUE(GetLegacySplitStoresPref(pref_service())); -} - struct IsGmsCoreUpdateRequiredTestCase { std::string test_case_desc; std::string gms_version; @@ -49,7 +22,7 @@ }; class SplitStoresAndLocalUpmTestIsGmsCoreUpdateRequired - : public SplitStoresAndLocalUpmTest, + : public ::testing::Test, public ::testing::WithParamInterface<IsGmsCoreUpdateRequiredTestCase> { public: SplitStoresAndLocalUpmTestIsGmsCoreUpdateRequired() = default;
diff --git a/components/password_manager/core/common/password_manager_pref_names.h b/components/password_manager/core/common/password_manager_pref_names.h index 3ef5890..b29bd8e 100644 --- a/components/password_manager/core/common/password_manager_pref_names.h +++ b/components/password_manager/core/common/password_manager_pref_names.h
@@ -76,11 +76,6 @@ inline constexpr char kAutoSignInEnabledGMS[] = "profile.auto_sign_in_enabled_gms"; -// A cache of whether the profile LoginDatabase is empty, so that can be checked -// early on startup. -inline constexpr char kEmptyProfileStoreLoginDatabase[] = - "password_manager.empty_profile_store_login_database"; - // Boolean controlling whether the password manager offers to save passwords. // If false, the password manager will not save credentials, but it will still // fill previously saved ones. This pref is not synced. Its value is set @@ -140,14 +135,6 @@ // that deletion should be retried. inline constexpr char kUpmAutoExportCsvNeedsDeletion[] = "profile.upm_auto_export_csv_needs_deletion"; - -// Whether the passwords who couldn't be migrated to UPM have been -// saved as a CSV. The user can then choose to export the CSV out of Chrome -// via a separate flow. The pref is also set to true if there were no -// saved passwords. The value is used as a signal that the login db -// can stop being used. -inline constexpr char kUpmUnmigratedPasswordsExported[] = - "profile.upm_unmigrated_passwords_exported"; #endif #if BUILDFLAG(IS_WIN)
diff --git a/components/payments/content/payment_request_state_unittest.cc b/components/payments/content/payment_request_state_unittest.cc index 7e2b96d..67c5d27d 100644 --- a/components/payments/content/payment_request_state_unittest.cc +++ b/components/payments/content/payment_request_state_unittest.cc
@@ -477,7 +477,7 @@ // Select an address, nothing should happen until the normalization is // completed and the merchant has validated the address. - autofill::AutofillProfile profile(AddressCountryCode("JP")); + autofill::AutofillProfile profile(autofill::AddressCountryCode("JP")); autofill::test::SetProfileInfo(&profile, "Jon", "V.", "Doe", "jon.doe@exampl.com", "Example Inc", "Roppongi", "6 Chrome-10-1", "Tokyo", "",
diff --git a/components/persistent_cache/backend_params.cc b/components/persistent_cache/backend_params.cc index 3e9797d..57c1576 100644 --- a/components/persistent_cache/backend_params.cc +++ b/components/persistent_cache/backend_params.cc
@@ -17,6 +17,7 @@ params.db_file_is_writable = db_file_is_writable; params.journal_file = journal_file.Duplicate(); params.journal_file_is_writable = journal_file_is_writable; + params.shared_lock = shared_lock.Duplicate(); params.type = type; return params; }
diff --git a/components/persistent_cache/backend_params.h b/components/persistent_cache/backend_params.h index 09125605..bcb64eff 100644 --- a/components/persistent_cache/backend_params.h +++ b/components/persistent_cache/backend_params.h
@@ -8,6 +8,7 @@ #include "base/component_export.h" #include "base/containers/flat_map.h" #include "base/files/file.h" +#include "base/memory/unsafe_shared_memory_region.h" namespace persistent_cache { @@ -38,6 +39,7 @@ bool db_file_is_writable = false; base::File journal_file; bool journal_file_is_writable = false; + base::UnsafeSharedMemoryRegion shared_lock; }; } // namespace persistent_cache
diff --git a/components/persistent_cache/backend_params_manager.cc b/components/persistent_cache/backend_params_manager.cc index 1090d90..f569ed7 100644 --- a/components/persistent_cache/backend_params_manager.cc +++ b/components/persistent_cache/backend_params_manager.cc
@@ -23,6 +23,7 @@ #include "base/strings/utf_string_conversions.h" #include "base/task/thread_pool.h" #include "components/persistent_cache/sqlite/sqlite_backend_impl.h" +#include "components/persistent_cache/sqlite/vfs/sandboxed_file.h" namespace { @@ -416,6 +417,9 @@ } } + params.shared_lock = + base::UnsafeSharedMemoryRegion::Create(sizeof(SharedMemoryLocks)); + return params; }
diff --git a/components/persistent_cache/sqlite/sqlite_backend_impl.cc b/components/persistent_cache/sqlite/sqlite_backend_impl.cc index bb6b4478..fbd4e520b 100644 --- a/components/persistent_cache/sqlite/sqlite_backend_impl.cc +++ b/components/persistent_cache/sqlite/sqlite_backend_impl.cc
@@ -9,6 +9,7 @@ #include "base/check_op.h" #include "base/containers/span.h" +#include "base/memory/unsafe_shared_memory_region.h" #include "base/strings/string_view_util.h" #include "base/trace_event/trace_event.h" #include "components/persistent_cache/backend_params.h" @@ -31,17 +32,24 @@ BackendParams backend_params) { CHECK_EQ(backend_params.type, BackendType::kSqlite); + base::UnsafeSharedMemoryRegion shared_lock = + std::move(backend_params.shared_lock); + + base::WritableSharedMemoryMapping mapped_shared_lock = shared_lock.Map(); + using AccessRights = SandboxedFile::AccessRights; std::unique_ptr<SandboxedFile> db_file = std::make_unique<SandboxedFile>( - std::move(backend_params.db_file), backend_params.db_file_is_writable - ? AccessRights::kReadWrite - : AccessRights::kReadOnly); + std::move(backend_params.db_file), + backend_params.db_file_is_writable ? AccessRights::kReadWrite + : AccessRights::kReadOnly, + std::move(mapped_shared_lock)); std::unique_ptr<SandboxedFile> journal_file = std::make_unique<SandboxedFile>( std::move(backend_params.journal_file), backend_params.journal_file_is_writable ? AccessRights::kReadWrite : AccessRights::kReadOnly); - return SqliteVfsFileSet(std::move(db_file), std::move(journal_file)); + return SqliteVfsFileSet(std::move(db_file), std::move(journal_file), + std::move(shared_lock)); } SqliteBackendImpl::SqliteBackendImpl(BackendParams backend_params) @@ -54,6 +62,7 @@ SqliteSandboxedVfsDelegate::GetInstance()->RegisterSandboxedFiles( vfs_file_set_)), db_(sql::DatabaseOptions() + .set_exclusive_locking(false) .set_vfs_name_discouraged( SqliteSandboxedVfsDelegate::kSqliteVfsName) // Prevent SQLite from trying to use mmap, as SandboxedVfs does
diff --git a/components/persistent_cache/sqlite/test_utils.cc b/components/persistent_cache/sqlite/test_utils.cc index 27a200a..a5f8c8d7 100644 --- a/components/persistent_cache/sqlite/test_utils.cc +++ b/components/persistent_cache/sqlite/test_utils.cc
@@ -11,6 +11,7 @@ #include "base/files/scoped_temp_dir.h" #include "components/persistent_cache/backend_params.h" #include "components/persistent_cache/sqlite/vfs/sqlite_database_vfs_file_set.h" +#include "sql/sandboxed_vfs_file.h" namespace persistent_cache::test_utils { @@ -31,6 +32,8 @@ backend_params.db_file_is_writable = true; backend_params.journal_file = CreateFile(temporary_subdir, "SECOND"); backend_params.journal_file_is_writable = true; + backend_params.shared_lock = + base::UnsafeSharedMemoryRegion::Create(sizeof(SharedMemoryLocks)); backend_params.type = type; return backend_params; } @@ -38,6 +41,10 @@ SqliteVfsFileSet TestHelper::CreateFilesAndBuildVfsFileSet() { base::FilePath temporary_subdir = CreateTemporaryDir(); + base::UnsafeSharedMemoryRegion shared_lock = + base::UnsafeSharedMemoryRegion::Create(sizeof(SharedMemoryLocks)); + base::WritableSharedMemoryMapping mapped_shared_lock = shared_lock.Map(); + // Note: Specifically give nonsensical names to the files here to examplify // that using a vfs allows for their use not through their actual names. base::File db_file = CreateFile(temporary_subdir, "FIRST"); @@ -45,9 +52,11 @@ return SqliteVfsFileSet( std::make_unique<SandboxedFile>(std::move(db_file), - SandboxedFile::AccessRights::kReadWrite), + SandboxedFile::AccessRights::kReadWrite, + std::move(mapped_shared_lock)), std::make_unique<SandboxedFile>(std::move(journal_file), - SandboxedFile::AccessRights::kReadWrite)); + SandboxedFile::AccessRights::kReadWrite), + std::move(shared_lock)); } base::FilePath TestHelper::CreateTemporaryDir() {
diff --git a/components/persistent_cache/sqlite/vfs/sandboxed_file.cc b/components/persistent_cache/sqlite/vfs/sandboxed_file.cc index 92600df..48085b2 100644 --- a/components/persistent_cache/sqlite/vfs/sandboxed_file.cc +++ b/components/persistent_cache/sqlite/vfs/sandboxed_file.cc
@@ -9,10 +9,21 @@ namespace persistent_cache { -SandboxedFile::SandboxedFile(base::File file, AccessRights access_rights) +namespace { +constexpr uint32_t kMaxReaders = 0x8000000; +constexpr uint32_t kSharedMask = 0xFFFFFFF; +constexpr uint32_t kReservedBit = 0x20000000; +constexpr uint32_t kPendingBit = 0x40000000; +} // namespace + +SandboxedFile::SandboxedFile( + base::File file, + AccessRights access_rights, + base::WritableSharedMemoryMapping mapped_shared_lock) : underlying_file_(std::move(file)), access_rights_(access_rights), - sqlite_lock_mode_(SQLITE_LOCK_NONE) {} + sqlite_lock_mode_(SQLITE_LOCK_NONE), + mapped_shared_lock_(std::move(mapped_shared_lock)) {} SandboxedFile::~SandboxedFile() = default; base::File SandboxedFile::TakeUnderlyingFile() { @@ -120,25 +131,206 @@ return SQLITE_OK; } +// This function implements the database locking mechanism as defined by the +// SQLite VFS (Virtual File System) interface. It is responsible for escalating +// locks on the database file to ensure that multiple processes can access the +// database in a controlled and serialized manner, preventing data corruption. +// +// In this shared memory implementation, the lock states are managed directly +// in a shared memory region accessible by all client processes, rather than +// relying on traditional file-system locks (like fcntl on Unix or LockFileEx +// on Windows). +// +// The lock implementation mirrors the state transitions of the standard SQLite +// locking mechanism: +// +// SHARED: Allows multiple readers. +// RESERVED: A process signals its intent to write. +// PENDING: A writer is waiting for readers to finish. +// EXCLUSIVE: A single process has exclusive write access. +// +// The valid transitions are: +// +// UNLOCKED -> SHARED +// SHARED -> RESERVED +// SHARED -> (PENDING) -> EXCLUSIVE +// RESERVED -> (PENDING) -> EXCLUSIVE +// PENDING -> EXCLUSIVE +// +// See original implementation: +// https://source.chromium.org/chromium/chromium/src/+/main:third_party/sqlite/src/src/os_win.c;l=3514;drc=4a0b7a332f3aeb27814cfa12dc0ebdbbd994a928 +// +// Some issues related to file system locks: +// https://source.chromium.org/chromium/chromium/src/+/main:third_party/sqlite/src/src/os_unix.c;l=1077;drc=5d60f47001bf64b48abac68ed59621e528144ea4 +// +// The SQLite core uses two distinct strategies to acquire an EXCLUSIVE lock. +// This VFS implementation must correctly handle lock requests from both paths. +// +// 1. Normal transaction path +// The standard database operations (INSERT, UPDATE, BEGIN COMMIT, etc.) on a +// healthy database will escalate the lock sequentially: +// SHARED -> RESERVED -> PENDING -> EXCLUSIVE. +// The intermediate RESERVED lock is mandatory. It signals an intent to write +// while still permitting other connections to hold SHARED locks for reading. +// +// 2. Hot-journal recovery path +// A special case that occurs upon initial connection when a hot-journal is +// detected, indicating a previous crash or power loss. A direct request for +// an EXCLUSIVE lock is required. In this state, the database is known to be +// inconsistent. The RESERVED lock is intentionally skipped because its +// purpose is to allow concurrent readers, which would be disastrous. A direct +// EXCLUSIVE lock acts as an emergency lockdown, preventing ALL other +// connections from reading corrupt data until the recovery process is +// complete. +// +// see: +// https://source.chromium.org/chromium/chromium/src/+/main:third_party/sqlite/src/src/pager.c;l=5260;drc=65d0312c96cd23958372fac8940314c782a6b03c int SandboxedFile::Lock(int mode) { - // TODO(https://crbug.com/377475540): Implement a cross-process lock. - if (mode > sqlite_lock_mode_) { - sqlite_lock_mode_ = mode; + // Ensures valid lock states are used (see: sqlite3OsLock(...) assertions). + CHECK(mode == SQLITE_LOCK_SHARED || mode == SQLITE_LOCK_RESERVED || + mode == SQLITE_LOCK_EXCLUSIVE); + CHECK(mapped_shared_lock_.IsValid()); + + // Do nothing if there is already a lock of this type or more restrictive. + if (sqlite_lock_mode_ >= mode) { + return SQLITE_OK; } - return SQLITE_OK; + + switch (mode) { + case SQLITE_LOCK_SHARED: { + // Check the PENDING lock to prevent adding readers (as readers are banned + // once we've granted the PENDING_LOCK to a writer). + uint32_t shared_state = GetSharedLock()->shared_state.load(); + if ((shared_state & kPendingBit) != 0) { + return SQLITE_BUSY; + } + + // Add this connection to the shared readers. + uint32_t acquired_shared_state = (shared_state + 1); + CHECK_LT(shared_state & kSharedMask, kMaxReaders); + if (!GetSharedLock()->shared_state.compare_exchange_weak( + shared_state, acquired_shared_state)) { + return SQLITE_BUSY; + } + + // The SHARED lock was successfully acquired. + sqlite_lock_mode_ = SQLITE_LOCK_SHARED; + return SQLITE_OK; + } + + case SQLITE_LOCK_RESERVED: { + // To acquire a RESERVED lock, the current connection must already have + // a shared access to it. + CHECK_EQ(sqlite_lock_mode_, SQLITE_LOCK_SHARED); + + // Acquire a RESERVED lock to prevent a different writer to declare its + // intention to modify the database. At this point, readers are still + // allowed to get a SHARED lock on the database. + const uint32_t acquired_shared_state = + GetSharedLock()->shared_state.fetch_or(kReservedBit); + if ((acquired_shared_state & kReservedBit) != 0) { + return SQLITE_BUSY; + } + + // The RESERVED lock was successfully acquired. + sqlite_lock_mode_ = SQLITE_LOCK_RESERVED; + return SQLITE_OK; + } + + case SQLITE_LOCK_EXCLUSIVE: { + // Acquiring an EXCLUSIVE lock may happen through multiple calls to + // SandboxedFile::Lock(...) and the PENDING lock may be kept between these + // calls. + + // To acquire an EXCLUSIVE lock, the current connection must already have + // at least SHARED lock. Owning RESERVED lock not mandatory. + CHECK_GE(sqlite_lock_mode_, SQLITE_LOCK_SHARED); + + // Acquire a PENDING lock, if not already acquired. Keep it until the + // EXCLUSIVE lock is obtained to let readers leaving the critical section + // while avoiding new readers to get into it. + if (sqlite_lock_mode_ < SQLITE_LOCK_PENDING) { + const uint32_t acquired_shared_state = + GetSharedLock()->shared_state.fetch_or(kPendingBit); + if ((acquired_shared_state & kPendingBit) != 0) { + // This connection is not the owner of the PENDING lock. + return SQLITE_BUSY; + } + // The PENDING lock was acquired. Keep it for subsequent calls until + // every SHARED locks are released. + sqlite_lock_mode_ = SQLITE_LOCK_PENDING; + } + + // Do not grant the EXCLUSIVE lock until other readers released their + // SHARED locks. This connection still owns and keeps a SHARED lock. + const uint32_t readers_shared_state = + GetSharedLock()->shared_state.load(); + if ((readers_shared_state & kSharedMask) != 1) { + return SQLITE_BUSY; + } + + // There is no active SHARED locks except for this connection. The PENDING + // lock is owned by this connection so it is valid to grant the EXCLUSIVE + // lock. + sqlite_lock_mode_ = SQLITE_LOCK_EXCLUSIVE; + return SQLITE_OK; + } + } + + return SQLITE_IOERR_LOCK; } +// This function is the counterpart to Lock and is responsible for reducing the +// lock level on the database file. This typically happens after a transaction +// is committed or rolled back, or when a process holding a write lock is +// ready to allow other readers in. +// +// The valid transitions are: +// +// SHARED -> UNLOCKED +// EXCLUSIVE -> UNLOCKED +// EXCLUSIVE -> SHARED +// +// It is also valid to release any pending state (PENDING or RESERVED) even if +// the state never went to EXCLUSIVE. This can happen when a connection give up +// on trying to get an EXCLUSIVE lock. int SandboxedFile::Unlock(int mode) { - // TODO(https://crbug.com/377475540): Implement a cross-process lock. - if (mode < sqlite_lock_mode_) { - sqlite_lock_mode_ = mode; + // Ensures valid lock states are used (see: sqlite3OsUnlock(...) assertions). + CHECK(mode == SQLITE_LOCK_NONE || mode == SQLITE_LOCK_SHARED); + CHECK(mapped_shared_lock_.IsValid()); + + // Do nothing if there is already a lock of this type or less restrictive. + if (sqlite_lock_mode_ <= mode) { + return SQLITE_OK; } + + // Release the RESERVED lock if we had it. + if (sqlite_lock_mode_ >= SQLITE_LOCK_RESERVED) { + // It is valid to have a PENDING or EXCLUSIVE lock without the RESERVED. + GetSharedLock()->shared_state.fetch_and(~kReservedBit); + } + + // Release the SHARED lock if no longer needed. + if (mode == SQLITE_LOCK_NONE) { + const uint32_t readers_shared_state = + GetSharedLock()->shared_state.fetch_sub(1); + CHECK_GE(readers_shared_state & 0xFFFFFFF, 1u); + } + + // Release the PENDING lock if we had it. + if (sqlite_lock_mode_ >= SQLITE_LOCK_PENDING) { + GetSharedLock()->shared_state.fetch_and(~kPendingBit); + } + + // Lock was successfully released. + sqlite_lock_mode_ = mode; return SQLITE_OK; } int SandboxedFile::CheckReservedLock(int* has_reserved_lock) { - // TODO(https://crbug.com/377475540): Implement a cross-process lock. - *has_reserved_lock = sqlite_lock_mode_ >= SQLITE_LOCK_RESERVED; + CHECK(mapped_shared_lock_.IsValid()); + uint32_t shared_state = GetSharedLock()->shared_state.load(); + *has_reserved_lock = (shared_state & kReservedBit) != 0; return SQLITE_OK; } @@ -187,4 +379,9 @@ return SQLITE_IOERR; } +SharedMemoryLocks* SandboxedFile::GetSharedLock() { + CHECK(mapped_shared_lock_.IsValid()); + return mapped_shared_lock_.GetMemoryAs<SharedMemoryLocks>(); +} + } // namespace persistent_cache
diff --git a/components/persistent_cache/sqlite/vfs/sandboxed_file.h b/components/persistent_cache/sqlite/vfs/sandboxed_file.h index 317eeedf..c59b7692 100644 --- a/components/persistent_cache/sqlite/vfs/sandboxed_file.h +++ b/components/persistent_cache/sqlite/vfs/sandboxed_file.h
@@ -5,12 +5,37 @@ #ifndef COMPONENTS_PERSISTENT_CACHE_SQLITE_VFS_SANDBOXED_FILE_H_ #define COMPONENTS_PERSISTENT_CACHE_SQLITE_VFS_SANDBOXED_FILE_H_ +#include <cstdint> + #include "base/component_export.h" #include "base/files/file.h" +#include "base/memory/shared_memory_safety_checker.h" +#include "base/memory/unsafe_shared_memory_region.h" #include "sql/sandboxed_vfs_file.h" namespace persistent_cache { +// The lock shared state is encoded over 32-bits: +// [ pending ][ reserved ][ readers ] +// +// Readers : Number of clients holding a shared lock. +// +// Reserved : Whether a client holds a reserved lock. Clients may acquire a +// shared lock while a client holds a reserved lock. Only one client +// can hold a reserved lock. +// +// Pending : Whether a client holds a pending lock. No new client may acquire +// a shared lock when a client holds a pending lock (but clients +// that had a shared lock before may not have released it yet when a +// client acquires the pending lock). The client may also hold the +// reserved lock, but that's not always the case. +// +// Exclusive : A client is considered to have "exclusive" locking when it holds +// the pending lock and it is the only one holding a shared lock. +struct SharedMemoryLocks { + base::subtle::SharedAtomic<uint32_t> shared_state; +}; + // Represents a file to be exposed to sql::Database via // SqliteSandboxedVfsDelegate. // @@ -22,7 +47,10 @@ public: enum class AccessRights { kReadWrite, kReadOnly }; - SandboxedFile(base::File file, AccessRights access_rights); + SandboxedFile(base::File file, + AccessRights access_rights, + base::WritableSharedMemoryMapping mapped_shared_lock = + base::WritableSharedMemoryMapping()); SandboxedFile(SandboxedFile& other) = delete; SandboxedFile& operator=(const SandboxedFile& other) = delete; SandboxedFile(SandboxedFile&& other) = delete; @@ -76,13 +104,24 @@ int Fetch(sqlite3_int64 offset, int size, void** result) override; int Unfetch(sqlite3_int64 offset, void* fetch_result) override; + int LockModeForTesting() const { return sqlite_lock_mode_; } + private: + // Returns a pointer to the SharedMemoryLocks struct, which is shared across + // other instances of SandboxedFile via shared memory + SharedMemoryLocks* GetSharedLock(); + base::File underlying_file_; base::File opened_file_; AccessRights access_rights_; - // One of the SQLite locking mode constants. + // One of the SQLite locking mode constants which represent the current lock + // state of this connection (see: https://www.sqlite.org/lockingv3.html). int sqlite_lock_mode_; + + // The actual shared locks across processes to implement the SQLite algorithm + // and from which `sqlite_lock_mode_` is coming from. + base::WritableSharedMemoryMapping mapped_shared_lock_; }; } // namespace persistent_cache
diff --git a/components/persistent_cache/sqlite/vfs/sandboxed_file_unittest.cc b/components/persistent_cache/sqlite/vfs/sandboxed_file_unittest.cc index d7dfada9..7e05b54 100644 --- a/components/persistent_cache/sqlite/vfs/sandboxed_file_unittest.cc +++ b/components/persistent_cache/sqlite/vfs/sandboxed_file_unittest.cc
@@ -7,6 +7,7 @@ #include "base/containers/span.h" #include "base/files/scoped_temp_dir.h" #include "testing/gtest/include/gtest/gtest.h" +#include "third_party/sqlite/sqlite3.h" namespace persistent_cache { @@ -18,14 +19,18 @@ public: void SetUp() override { ASSERT_TRUE(temporary_directory_.CreateUniqueTempDir()); + shared_region_ = + base::UnsafeSharedMemoryRegion::Create(sizeof(SharedMemoryLocks)); } std::unique_ptr<SandboxedFile> CreateEmptyFile(const std::string& file_name) { + base::WritableSharedMemoryMapping mapped_shared_lock = shared_region_.Map(); + return std::make_unique<SandboxedFile>( base::File(temporary_directory_.GetPath().AppendASCII(file_name), base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_READ | base::File::FLAG_WRITE), - SandboxedFile::AccessRights::kReadWrite); + SandboxedFile::AccessRights::kReadWrite, std::move(mapped_shared_lock)); } // Simulate an OpenFile from the VFS delegate. @@ -54,6 +59,7 @@ private: base::ScopedTempDir temporary_directory_; std::vector<uint8_t> buffer_; + base::UnsafeSharedMemoryRegion shared_region_; }; TEST_F(SandboxedFileTest, OpenClose) { @@ -230,6 +236,188 @@ EXPECT_EQ(GetReadBuffer(), base::span(expected_buffer)); } +TEST_F(SandboxedFileTest, LockBasics) { + std::unique_ptr<SandboxedFile> file = CreateEmptyFile("lock"); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_NONE); + + EXPECT_EQ(file->Lock(SQLITE_LOCK_SHARED), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_SHARED); + + EXPECT_EQ(file->Lock(SQLITE_LOCK_RESERVED), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_RESERVED); + + EXPECT_EQ(file->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE); + + EXPECT_EQ(file->Unlock(SQLITE_LOCK_SHARED), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_SHARED); + + EXPECT_EQ(file->Unlock(SQLITE_LOCK_NONE), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_NONE); +} + +TEST_F(SandboxedFileTest, AcquireSameLockLevel) { + std::unique_ptr<SandboxedFile> file = CreateEmptyFile("lock"); + + EXPECT_EQ(file->Lock(SQLITE_LOCK_SHARED), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_SHARED); + EXPECT_EQ(file->Lock(SQLITE_LOCK_RESERVED), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_RESERVED); + EXPECT_EQ(file->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE); + + // Re-acquire EXCLUSIVE must be valid. + EXPECT_EQ(file->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE); +} + +TEST_F(SandboxedFileTest, AcquireLowerLockLevel) { + std::unique_ptr<SandboxedFile> file = CreateEmptyFile("lock"); + + EXPECT_EQ(file->Lock(SQLITE_LOCK_SHARED), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_SHARED); + EXPECT_EQ(file->Lock(SQLITE_LOCK_RESERVED), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_RESERVED); + EXPECT_EQ(file->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE); + + // Acquiring the lower SHARED lock when the connection is at EXCLUSIVE must be + // valid. + EXPECT_EQ(file->Lock(SQLITE_LOCK_SHARED), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE); +} + +TEST_F(SandboxedFileTest, UnlockWhenNone) { + std::unique_ptr<SandboxedFile> file = CreateEmptyFile("lock"); + + EXPECT_EQ(file->Unlock(SQLITE_LOCK_NONE), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_NONE); +} + +TEST_F(SandboxedFileTest, UnlockToNone) { + std::unique_ptr<SandboxedFile> file = CreateEmptyFile("lock"); + + EXPECT_EQ(file->Lock(SQLITE_LOCK_SHARED), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_SHARED); + EXPECT_EQ(file->Lock(SQLITE_LOCK_RESERVED), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_RESERVED); + EXPECT_EQ(file->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE); + + // Downgrade the connection from EXCLUSIVE to NONE. + EXPECT_EQ(file->Unlock(SQLITE_LOCK_NONE), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_NONE); +} + +TEST_F(SandboxedFileTest, UnlockToShared) { + std::unique_ptr<SandboxedFile> file = CreateEmptyFile("lock"); + + EXPECT_EQ(file->Lock(SQLITE_LOCK_SHARED), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_SHARED); + EXPECT_EQ(file->Lock(SQLITE_LOCK_RESERVED), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_RESERVED); + EXPECT_EQ(file->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE); + + // Downgrade the connection from EXCLUSIVE to SHARED. + EXPECT_EQ(file->Unlock(SQLITE_LOCK_SHARED), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_SHARED); +} + +TEST_F(SandboxedFileTest, MultipleLocks) { + std::unique_ptr<SandboxedFile> reader1 = CreateEmptyFile("multi-lock"); + std::unique_ptr<SandboxedFile> reader2 = CreateEmptyFile("multi-lock"); + std::unique_ptr<SandboxedFile> reader3 = CreateEmptyFile("multi-lock"); + std::unique_ptr<SandboxedFile> writer1 = CreateEmptyFile("multi-lock"); + std::unique_ptr<SandboxedFile> writer2 = CreateEmptyFile("multi-lock"); + + // Take SHARED lock for the first reader. + EXPECT_EQ(reader1->Lock(SQLITE_LOCK_SHARED), SQLITE_OK); + EXPECT_EQ(reader1->LockModeForTesting(), SQLITE_LOCK_SHARED); + + // First writer is getting the RESERVED lock. + EXPECT_EQ(writer1->Lock(SQLITE_LOCK_SHARED), SQLITE_OK); + EXPECT_EQ(writer1->LockModeForTesting(), SQLITE_LOCK_SHARED); + EXPECT_EQ(writer1->Lock(SQLITE_LOCK_RESERVED), SQLITE_OK); + EXPECT_EQ(writer1->LockModeForTesting(), SQLITE_LOCK_RESERVED); + + // Second reader should be able to get a SHARED lock even with the RESERVED + // lock. + EXPECT_EQ(reader2->Lock(SQLITE_LOCK_SHARED), SQLITE_OK); + EXPECT_EQ(reader2->LockModeForTesting(), SQLITE_LOCK_SHARED); + + // Second writer can't get the RESERVED lock. + EXPECT_EQ(writer2->Lock(SQLITE_LOCK_SHARED), SQLITE_OK); + EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_SHARED); + EXPECT_EQ(writer2->Lock(SQLITE_LOCK_RESERVED), SQLITE_BUSY); + EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_SHARED); + + // Try to upgrade the lock to EXCLUSIVE but fails since there are pending + // readers. + EXPECT_EQ(writer1->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_BUSY); + EXPECT_EQ(writer1->LockModeForTesting(), SQLITE_LOCK_PENDING); + + // No new writer can get the lock EXCLUSIVE. + EXPECT_EQ(writer2->Lock(SQLITE_LOCK_RESERVED), SQLITE_BUSY); + EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_SHARED); + EXPECT_EQ(writer2->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_BUSY); + EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_SHARED); + EXPECT_EQ(writer2->Unlock(SQLITE_LOCK_NONE), SQLITE_OK); + EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_NONE); + + // No new reader can enter while a writer has the PENDING lock. + EXPECT_EQ(reader3->Lock(SQLITE_LOCK_SHARED), SQLITE_BUSY); + + // Release a SHARED lock. + EXPECT_EQ(reader1->Unlock(SQLITE_LOCK_NONE), SQLITE_OK); + EXPECT_EQ(reader1->LockModeForTesting(), SQLITE_LOCK_NONE); + + // Try to upgrade the lock to EXCLUSIVE but fails since there are still + // pending readers. + EXPECT_EQ(writer1->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_BUSY); + EXPECT_EQ(writer1->LockModeForTesting(), SQLITE_LOCK_PENDING); + + // Release the last SHARED lock. + EXPECT_EQ(reader2->Unlock(SQLITE_LOCK_NONE), SQLITE_OK); + EXPECT_EQ(reader2->LockModeForTesting(), SQLITE_LOCK_NONE); + + // The write lock can now be upgraded to EXCLUSIVE. + EXPECT_EQ(writer1->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_OK); + EXPECT_EQ(writer1->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE); + + // No other writer can get the lock. + EXPECT_EQ(writer2->Lock(SQLITE_LOCK_SHARED), SQLITE_BUSY); + EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_NONE); + + // Unlock the writer. + EXPECT_EQ(writer1->Unlock(SQLITE_LOCK_NONE), SQLITE_OK); + EXPECT_EQ(writer1->LockModeForTesting(), SQLITE_LOCK_NONE); + + // Second writer can now get the lock. + EXPECT_EQ(writer2->Lock(SQLITE_LOCK_SHARED), SQLITE_OK); + EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_SHARED); + EXPECT_EQ(writer2->Lock(SQLITE_LOCK_RESERVED), SQLITE_OK); + EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_RESERVED); + EXPECT_EQ(writer2->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_OK); + EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE); + EXPECT_EQ(writer2->Unlock(SQLITE_LOCK_NONE), SQLITE_OK); + EXPECT_EQ(writer2->LockModeForTesting(), SQLITE_LOCK_NONE); +} + +TEST_F(SandboxedFileTest, LockHotJournal) { + std::unique_ptr<SandboxedFile> file = CreateEmptyFile("lock"); + + EXPECT_EQ(file->Lock(SQLITE_LOCK_SHARED), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_SHARED); + + // It is valid to skip the RESERVED lock in the presence of am hot-journal. + // There can't be any RESERVED on any connection at that point since any + // attempt to open the database will get into the same state (an hot-journal + // that forced an exclusive lock). + EXPECT_EQ(file->Lock(SQLITE_LOCK_EXCLUSIVE), SQLITE_OK); + EXPECT_EQ(file->LockModeForTesting(), SQLITE_LOCK_EXCLUSIVE); +} + } // namespace } // namespace persistent_cache
diff --git a/components/persistent_cache/sqlite/vfs/sqlite_database_vfs_file_set.cc b/components/persistent_cache/sqlite/vfs/sqlite_database_vfs_file_set.cc index c9e2e397..f42b374 100644 --- a/components/persistent_cache/sqlite/vfs/sqlite_database_vfs_file_set.cc +++ b/components/persistent_cache/sqlite/vfs/sqlite_database_vfs_file_set.cc
@@ -22,11 +22,13 @@ namespace persistent_cache { SqliteVfsFileSet::SqliteVfsFileSet(std::unique_ptr<SandboxedFile> db_file, - std::unique_ptr<SandboxedFile> journal_file) + std::unique_ptr<SandboxedFile> journal_file, + base::UnsafeSharedMemoryRegion shared_lock) : db_file_(std::move(db_file)), journal_file_(std::move(journal_file)), - virtual_fs_path_(base::NumberToString( - g_file_set_id_generator.fetch_add(1, std::memory_order_relaxed))), + shared_lock_(std::move(shared_lock)), + virtual_fs_path_( + base::NumberToString(g_file_set_id_generator.fetch_add(1))), read_only_(db_file_->access_rights() == SandboxedFile::AccessRights::kReadOnly) { // It makes no sense to have a file writeable and not the other.
diff --git a/components/persistent_cache/sqlite/vfs/sqlite_database_vfs_file_set.h b/components/persistent_cache/sqlite/vfs/sqlite_database_vfs_file_set.h index e999f6c..09ac282 100644 --- a/components/persistent_cache/sqlite/vfs/sqlite_database_vfs_file_set.h +++ b/components/persistent_cache/sqlite/vfs/sqlite_database_vfs_file_set.h
@@ -11,6 +11,7 @@ #include "base/component_export.h" #include "base/files/file.h" #include "base/files/file_path.h" +#include "base/memory/unsafe_shared_memory_region.h" #include "components/persistent_cache/sqlite/vfs/sandboxed_file.h" namespace persistent_cache { @@ -22,7 +23,8 @@ class COMPONENT_EXPORT(PERSISTENT_CACHE) SqliteVfsFileSet { public: SqliteVfsFileSet(std::unique_ptr<SandboxedFile> db_file, - std::unique_ptr<SandboxedFile> journal_file); + std::unique_ptr<SandboxedFile> journal_file, + base::UnsafeSharedMemoryRegion shared_lock); SqliteVfsFileSet(SqliteVfsFileSet& other) = delete; SqliteVfsFileSet& operator=(const SqliteVfsFileSet& other) = delete; SqliteVfsFileSet(SqliteVfsFileSet&& other); @@ -44,6 +46,7 @@ std::unique_ptr<SandboxedFile> db_file_; std::unique_ptr<SandboxedFile> journal_file_; + base::UnsafeSharedMemoryRegion shared_lock_; // SQLite databases use standard naming for their files. Since the vfs might // register files for many databases at once it needs some way to
diff --git a/components/persistent_cache/sqlite/vfs/sqlite_sandboxed_vfs.h b/components/persistent_cache/sqlite/vfs/sqlite_sandboxed_vfs.h index 3a9b049d..9e95ad5 100644 --- a/components/persistent_cache/sqlite/vfs/sqlite_sandboxed_vfs.h +++ b/components/persistent_cache/sqlite/vfs/sqlite_sandboxed_vfs.h
@@ -35,7 +35,7 @@ // // Register the file set for use by any sql::Database in this process // // that uses the `SqliteSandboxedVfsDelegate`. // auto unregister_runner = SqliteSandboxedVfsDelelegate::GetInstance() -// ->RegisterSandboxedFiles(vfs_file_set.Copy()); +// ->RegisterSandboxedFiles(vfs_file_set); // // // Create an `sql::Database` which uses the // // `SqliteSandboxedVfsDelegate`.
diff --git a/components/policy/core/common/generate_policy_source_unittest.cc b/components/policy/core/common/generate_policy_source_unittest.cc index bf3b3a49..cd472b8 100644 --- a/components/policy/core/common/generate_policy_source_unittest.cc +++ b/components/policy/core/common/generate_policy_source_unittest.cc
@@ -137,7 +137,7 @@ EXPECT_TRUE(*next == nullptr); #endif // !BUILDFLAG(IS_IOS) -#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) +#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA) subschema = schema.GetProperty(key::kExtensionSettings); ASSERT_TRUE(subschema.valid()); ASSERT_EQ(base::Value::Type::DICT, subschema.type());
diff --git a/components/policy/core/common/policy_loader_common.cc b/components/policy/core/common/policy_loader_common.cc index fec1b1c..a9d2f87 100644 --- a/components/policy/core/common/policy_loader_common.cc +++ b/components/policy/core/common/policy_loader_common.cc
@@ -16,7 +16,7 @@ namespace policy { namespace { -#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) +#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA) // Duplicate the extension constants in order to avoid extension dependency. // However, those values below must be synced with files in extension folders. // In long term, we can refactor the code and create an interface for sensitive @@ -83,7 +83,7 @@ base::UmaHistogramSparse("EnterpriseCheck.InvalidPolicies", details->id); } -#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) +#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA) // Marks the sensitive ExtensionInstallForceList policy entries, returns true if // there is any sensitive entries in the policy. bool FilterSensitiveExtensionsInstallForcelist(PolicyMap::Entry* map_entry) { @@ -183,7 +183,7 @@ void FilterSensitivePolicies(PolicyMap* policy) { int invalid_policies = 0; -#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) +#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA) if (FilterSensitiveExtensionsInstallForcelist( policy->GetMutable(key::kExtensionInstallForcelist))) { invalid_policies++;
diff --git a/components/policy/core/common/policy_merger.cc b/components/policy/core/common/policy_merger.cc index 18732f0..4b99691 100644 --- a/components/policy/core/common/policy_merger.cc +++ b/components/policy/core/common/policy_merger.cc
@@ -19,7 +19,7 @@ namespace { -#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID) +#if !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_FUCHSIA) constexpr const char* kDictionaryPoliciesToMerge[] = { #if BUILDFLAG(IS_CHROMEOS) key::kExtensionSettings, key::kDeviceLoginScreenPowerManagement, @@ -29,7 +29,8 @@ key::kExtensionSettings, #endif // BUILDFLAG(IS_CHROMEOS) }; -#endif // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID) +#endif // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_ANDROID) && + // !BUILDFLAG(IS_FUCHSIA) } // namespace @@ -186,7 +187,7 @@ PolicyDictionaryMerger::PolicyDictionaryMerger( base::flat_set<std::string> policies_to_merge) -#if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_ANDROID) +#if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_FUCHSIA) : policies_to_merge_(std::move(policies_to_merge)){} #else : policies_to_merge_(std::move(policies_to_merge)),
diff --git a/components/policy/core/common/policy_service_impl_unittest.cc b/components/policy/core/common/policy_service_impl_unittest.cc index 4e687b6..3682796 100644 --- a/components/policy/core/common/policy_service_impl_unittest.cc +++ b/components/policy/core/common/policy_service_impl_unittest.cc
@@ -1245,7 +1245,8 @@ policy_service_->RemoveObserver(POLICY_DOMAIN_SIGNIN_EXTENSIONS, &observer); } -#if !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) +#if !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && \ + !BUILDFLAG(IS_FUCHSIA) TEST_F(PolicyServiceTest, DictionaryPoliciesMerging) { const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string()); @@ -1462,7 +1463,7 @@ EXPECT_TRUE(VerifyPolicies(chrome_namespace, expected_chrome)); } #endif // !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID) && - // !BUILDFLAG(IS_IOS) + // !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA) TEST_F(PolicyServiceTest, ListsPoliciesMerging) { const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/BlockExternalExtensions.yaml b/components/policy/resources/templates/policy_definitions/Extensions/BlockExternalExtensions.yaml index a06e3c2..ca36934 100644 --- a/components/policy/resources/templates/policy_definitions/Extensions/BlockExternalExtensions.yaml +++ b/components/policy/resources/templates/policy_definitions/Extensions/BlockExternalExtensions.yaml
@@ -13,8 +13,6 @@ features: dynamic_refresh: false per_profile: true -future_on: -- fuchsia items: - caption: Block installation of external extensions value: true
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionAllowedTypes.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionAllowedTypes.yaml index f7f6039..e3d92463 100644 --- a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionAllowedTypes.yaml +++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionAllowedTypes.yaml
@@ -28,8 +28,6 @@ features: dynamic_refresh: true per_profile: true -future_on: -- fuchsia items: - caption: Extension name: extension
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionExtendedBackgroundLifetimeForPortConnectionsToUrls.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionExtendedBackgroundLifetimeForPortConnectionsToUrls.yaml index cfe942c..8fa5a17 100644 --- a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionExtendedBackgroundLifetimeForPortConnectionsToUrls.yaml +++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionExtendedBackgroundLifetimeForPortConnectionsToUrls.yaml
@@ -24,8 +24,6 @@ supported_on: - chrome.*:112- - chrome_os:112- -future_on: -- fuchsia owners: - mpetrisor@chromium.org - chromeos-commercial-identity@google.com
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallAllowlist.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallAllowlist.yaml index 20a826c..68d09b6 100644 --- a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallAllowlist.yaml +++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallAllowlist.yaml
@@ -13,7 +13,6 @@ per_profile: true future_on: - android -- fuchsia label: Extension IDs to exempt from the blocklist owners: - rdevlin.cronin@chromium.org
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallBlocklist.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallBlocklist.yaml index 58db0d8..771d045 100644 --- a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallBlocklist.yaml +++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallBlocklist.yaml
@@ -13,7 +13,6 @@ per_profile: true future_on: - android -- fuchsia label: Extension IDs the user should be prevented from installing (or * for all) owners: - lazyboy@chromium.org
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallForcelist.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallForcelist.yaml index 4b7aacfa..4bc3f295 100644 --- a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallForcelist.yaml +++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallForcelist.yaml
@@ -27,7 +27,6 @@ per_profile: true future_on: - android -- fuchsia label: Extension/App IDs and update URLs to be silently installed owners: - karandeepb@chromium.org
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallSources.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallSources.yaml index b72ca78..47ef3289 100644 --- a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallSources.yaml +++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionInstallSources.yaml
@@ -10,8 +10,6 @@ features: dynamic_refresh: true per_profile: true -future_on: -- fuchsia label: URL patterns to allow extension, app, and user script installs from owners: - dbertoni@chromium.org
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionManifestV2Availability.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionManifestV2Availability.yaml index 7077a77..ebc2a63 100644 --- a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionManifestV2Availability.yaml +++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionManifestV2Availability.yaml
@@ -20,8 +20,6 @@ - chrome.*:110-138 - chrome_os:110-138 deprecated: true -future_on: -- fuchsia features: dynamic_refresh: true per_profile: true
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionSettings.yaml b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionSettings.yaml index 7b2d947d..01164cc6 100644 --- a/components/policy/resources/templates/policy_definitions/Extensions/ExtensionSettings.yaml +++ b/components/policy/resources/templates/policy_definitions/Extensions/ExtensionSettings.yaml
@@ -62,7 +62,6 @@ per_profile: true future_on: - android -- fuchsia owners: - finnur@chromium.org - file://extensions/OWNERS
diff --git a/components/policy/resources/templates/policy_definitions/Extensions/MandatoryExtensionsForIncognitoNavigation.yaml b/components/policy/resources/templates/policy_definitions/Extensions/MandatoryExtensionsForIncognitoNavigation.yaml index 8dd7ffd..709f9659 100644 --- a/components/policy/resources/templates/policy_definitions/Extensions/MandatoryExtensionsForIncognitoNavigation.yaml +++ b/components/policy/resources/templates/policy_definitions/Extensions/MandatoryExtensionsForIncognitoNavigation.yaml
@@ -14,7 +14,6 @@ per_profile: true future_on: - android -- fuchsia - chrome.* supported_on: - chrome_os:114-
diff --git a/components/policy/test/data/pref_mapping/BlockExternalExtensions.json b/components/policy/test/data/pref_mapping/BlockExternalExtensions.json index 9efdd12..6e536a34 100644 --- a/components/policy/test/data/pref_mapping/BlockExternalExtensions.json +++ b/components/policy/test/data/pref_mapping/BlockExternalExtensions.json
@@ -3,8 +3,7 @@ "os": [ "win", "linux", - "mac", - "fuchsia" + "mac" ], "simple_policy_pref_mapping_test": { "pref_name": "extensions.block_external_extensions",
diff --git a/components/policy/test/data/pref_mapping/ExtensionAllowedTypes.json b/components/policy/test/data/pref_mapping/ExtensionAllowedTypes.json index c8b8759..9ac33e7 100644 --- a/components/policy/test/data/pref_mapping/ExtensionAllowedTypes.json +++ b/components/policy/test/data/pref_mapping/ExtensionAllowedTypes.json
@@ -4,8 +4,7 @@ "win", "linux", "mac", - "chromeos", - "fuchsia" + "chromeos" ], "policy_pref_mapping_tests": [ {
diff --git a/components/policy/test/data/pref_mapping/ExtensionExtendedBackgroundLifetimeForPortConnectionsToUrls.json b/components/policy/test/data/pref_mapping/ExtensionExtendedBackgroundLifetimeForPortConnectionsToUrls.json index 0b89bf4..b0e4aac 100644 --- a/components/policy/test/data/pref_mapping/ExtensionExtendedBackgroundLifetimeForPortConnectionsToUrls.json +++ b/components/policy/test/data/pref_mapping/ExtensionExtendedBackgroundLifetimeForPortConnectionsToUrls.json
@@ -4,8 +4,7 @@ "win", "linux", "mac", - "chromeos", - "fuchsia" + "chromeos" ], "simple_policy_pref_mapping_test": { "pref_name": "extensions.extended_background_lifetime_urls",
diff --git a/components/policy/test/data/pref_mapping/ExtensionInstallAllowlist.json b/components/policy/test/data/pref_mapping/ExtensionInstallAllowlist.json index 4d30a2a..af70ae0 100644 --- a/components/policy/test/data/pref_mapping/ExtensionInstallAllowlist.json +++ b/components/policy/test/data/pref_mapping/ExtensionInstallAllowlist.json
@@ -5,8 +5,7 @@ "linux", "mac", "chromeos", - "desktop_android", - "fuchsia" + "desktop_android" ], "simple_policy_pref_mapping_test": { "pref_name": "extensions.install.allowlist",
diff --git a/components/policy/test/data/pref_mapping/ExtensionInstallBlocklist.json b/components/policy/test/data/pref_mapping/ExtensionInstallBlocklist.json index 33c4520..505c70c 100644 --- a/components/policy/test/data/pref_mapping/ExtensionInstallBlocklist.json +++ b/components/policy/test/data/pref_mapping/ExtensionInstallBlocklist.json
@@ -5,8 +5,7 @@ "linux", "mac", "chromeos", - "desktop_android", - "fuchsia" + "desktop_android" ], "simple_policy_pref_mapping_test": { "pref_name": "extensions.install.denylist",
diff --git a/components/policy/test/data/pref_mapping/ExtensionInstallForcelist.json b/components/policy/test/data/pref_mapping/ExtensionInstallForcelist.json index f06aa88..4d98a1b 100644 --- a/components/policy/test/data/pref_mapping/ExtensionInstallForcelist.json +++ b/components/policy/test/data/pref_mapping/ExtensionInstallForcelist.json
@@ -5,8 +5,7 @@ "linux", "mac", "chromeos", - "desktop_android", - "fuchsia" + "desktop_android" ], "policy_pref_mapping_tests": [ {
diff --git a/components/policy/test/data/pref_mapping/ExtensionInstallSources.json b/components/policy/test/data/pref_mapping/ExtensionInstallSources.json index 2da5ab1..8d6daa85 100644 --- a/components/policy/test/data/pref_mapping/ExtensionInstallSources.json +++ b/components/policy/test/data/pref_mapping/ExtensionInstallSources.json
@@ -4,8 +4,7 @@ "win", "linux", "mac", - "chromeos", - "fuchsia" + "chromeos" ], "simple_policy_pref_mapping_test": { "pref_name": "extensions.allowed_install_sites",
diff --git a/components/policy/test/data/pref_mapping/ExtensionInstallTypeBlocklist.json b/components/policy/test/data/pref_mapping/ExtensionInstallTypeBlocklist.json index b040741..77c8bae5 100644 --- a/components/policy/test/data/pref_mapping/ExtensionInstallTypeBlocklist.json +++ b/components/policy/test/data/pref_mapping/ExtensionInstallTypeBlocklist.json
@@ -3,8 +3,7 @@ "os": [ "win", "linux", - "mac", - "fuchsia" + "mac" ], "simple_policy_pref_mapping_test": { "pref_name": "extensions.extension_install_type_blocklist",
diff --git a/components/policy/test/data/pref_mapping/ExtensionSettings.json b/components/policy/test/data/pref_mapping/ExtensionSettings.json index 46265cb..a347241d 100644 --- a/components/policy/test/data/pref_mapping/ExtensionSettings.json +++ b/components/policy/test/data/pref_mapping/ExtensionSettings.json
@@ -5,8 +5,7 @@ "linux", "mac", "chromeos", - "desktop_android", - "fuchsia" + "desktop_android" ], "simple_policy_pref_mapping_test": { "pref_name": "extensions.management",
diff --git a/components/policy/test_support/request_handler_for_policy_unittest.cc b/components/policy/test_support/request_handler_for_policy_unittest.cc index 415f36c..71e6c08 100644 --- a/components/policy/test_support/request_handler_for_policy_unittest.cc +++ b/components/policy/test_support/request_handler_for_policy_unittest.cc
@@ -28,10 +28,11 @@ constexpr char kMachineName[] = "machine_name"; constexpr char kPolicyInvalidationTopic[] = "policy_invalidation_topic"; constexpr char kUsername[] = "user-for-policy@example.com"; -#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) +#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA) constexpr char kPublicAccountEntityId[] = "test_user"; constexpr char kExtensionId[] = "extension_id"; -#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) +#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && + // !BUILDFLAG(IS_FUCHSIA) } // namespace @@ -372,7 +373,7 @@ GetSignatureTypeParam()); } -#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) +#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && !BUILDFLAG(IS_FUCHSIA) TEST_F(RequestHandlerForPolicyTest, HandleRequest_Success_UnsignedExtensionPolicies) { ClientStorage::ClientInfo client_info; @@ -515,7 +516,8 @@ EXPECT_EQ(public_account_fetch_response.policy_data_signature_type(), GetSignatureTypeParam()); } -#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) +#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS) && + // !BUILDFLAG(IS_FUCHSIA) INSTANTIATE_TEST_SUITE_P( SignatureType,
diff --git a/components/safe_browsing/core/common/safe_browsing_prefs.cc b/components/safe_browsing/core/common/safe_browsing_prefs.cc index 637e113b..0b8b86e 100644 --- a/components/safe_browsing/core/common/safe_browsing_prefs.cc +++ b/components/safe_browsing/core/common/safe_browsing_prefs.cc
@@ -205,6 +205,8 @@ } void RegisterProfilePrefs(PrefRegistrySimple* registry) { + registry->RegisterBooleanPref( + prefs::kJavascriptOptimizerBlockedForUnfamiliarSites, false); // TODO(crbug.com/422747384): Implement correct logic to set bundle level // based on user's safe browsing status. registry->RegisterIntegerPref(prefs::kSecuritySettingsBundle,
diff --git a/components/safe_browsing/core/common/safe_browsing_prefs.h b/components/safe_browsing/core/common/safe_browsing_prefs.h index c0bd977..c9f874e3 100644 --- a/components/safe_browsing/core/common/safe_browsing_prefs.h +++ b/components/safe_browsing/core/common/safe_browsing_prefs.h
@@ -283,6 +283,13 @@ // the user is in. inline constexpr char kSecuritySettingsBundle[] = "safebrowsing.bundle"; +// A boolean indicating whether the user selected on chrome://settings to +// disable the JavaScript optimizer on unfamiliar sites for improved security. +// The site-familiarity computation is done locally based on the user's +// browsing habits. +inline constexpr char kJavascriptOptimizerBlockedForUnfamiliarSites[] = + "safebrowsing.javascript_optimizer_blocked_for_unfamiliar_sites"; + } // namespace prefs namespace safe_browsing {
diff --git a/components/test/data/optimization_guide/iframe_mixed_cross_site.html b/components/test/data/optimization_guide/iframe_mixed_cross_site.html new file mode 100644 index 0000000..88a88fa --- /dev/null +++ b/components/test/data/optimization_guide/iframe_mixed_cross_site.html
@@ -0,0 +1,5 @@ +<html><head><title>mixed cross-site iframe test</title></head> +<body> +<iframe src="/title1.html" id="frame1"></iframe> +<iframe src="/cross-site/b.com/title1.html" id="frame2"></iframe> +</body></html>
diff --git a/components/test/data/optimization_guide/unit_tests_bundle_data.filelist b/components/test/data/optimization_guide/unit_tests_bundle_data.filelist index f510bb0..a109b37 100644 --- a/components/test/data/optimization_guide/unit_tests_bundle_data.filelist +++ b/components/test/data/optimization_guide/unit_tests_bundle_data.filelist
@@ -20,6 +20,7 @@ //components/test/data/optimization_guide/for_label.html //components/test/data/optimization_guide/i18n_visibility_test_model.tflite //components/test/data/optimization_guide/iframe_cross_site.html +//components/test/data/optimization_guide/iframe_mixed_cross_site.html //components/test/data/optimization_guide/iframe_same_site.html //components/test/data/optimization_guide/invalid_model.crx3 //components/test/data/optimization_guide/label_not_actionable.html
diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn index 4017fef..d38cb3a 100644 --- a/components/viz/service/BUILD.gn +++ b/components/viz/service/BUILD.gn
@@ -172,6 +172,8 @@ "display_embedder/skia_output_surface_impl_on_gpu.h", "display_embedder/skia_output_surface_impl_on_gpu_debug_capture.cc", "display_embedder/skia_output_surface_impl_on_gpu_debug_capture.h", + "display_embedder/skia_output_surface_shared_image_interface.cc", + "display_embedder/skia_output_surface_shared_image_interface.h", "display_embedder/skia_render_copy_results.cc", "display_embedder/skia_render_copy_results.h", "display_embedder/software_output_surface.cc",
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.cc b/components/viz/service/display_embedder/skia_output_surface_impl.cc index 7c13c5a..8c31acc 100644 --- a/components/viz/service/display_embedder/skia_output_surface_impl.cc +++ b/components/viz/service/display_embedder/skia_output_surface_impl.cc
@@ -18,6 +18,7 @@ #include "base/functional/callback_forward.h" #include "base/functional/callback_helpers.h" #include "base/memory/memory_pressure_listener.h" +#include "base/memory/scoped_refptr.h" #include "base/no_destructor.h" #include "base/observer_list.h" #include "base/strings/stringprintf.h" @@ -45,6 +46,7 @@ #include "components/viz/service/display_embedder/image_context_impl.h" #include "components/viz/service/display_embedder/skia_output_surface_dependency.h" #include "components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h" +#include "components/viz/service/display_embedder/skia_output_surface_shared_image_interface.h" #include "gpu/command_buffer/client/shared_image_interface.h" #include "gpu/command_buffer/common/swap_buffers_complete_params.h" #include "gpu/command_buffer/common/sync_token.h" @@ -405,6 +407,11 @@ } DCHECK(render_pass_image_cache_.empty()); + // Break reference cycle. + if (shared_image_interface_) { + shared_image_interface_->DetachOutputSurfaceOnHostThread(); + } + // Save a copy of this pointer before moving it into the task. Tasks that are // already enqueud may need to use it before |impl_on_gpu_| is destroyed. SkiaOutputSurfaceImplOnGpu* impl_on_gpu = impl_on_gpu_.get(); @@ -1090,6 +1097,17 @@ // |capabilities_| will be initialized in InitializeOnGpuThread(), so have to // wait. FlushGpuTasks(SyncMode::kWaitForTasksFinished); + // Exit early if creation of `impl_on_gpu` fails. + if (!result) { + return false; + } + + // We need to wait for `impl_on_gpu_` to be initialized to initialize + // `shared_image_interface_`, so can't do that before now. + DCHECK(impl_on_gpu_); + shared_image_interface_ = + base::MakeRefCounted<SkiaOutputSurfaceSharedImageInterface>( + *this, *impl_on_gpu_); if (capabilities_.damage_area_from_skia_output_device) { damage_of_current_buffer_.emplace(); @@ -1104,7 +1122,7 @@ graphite_recorder_, graphite_cache_controller_.get(), dependency_->GetClientTaskRunner()); } - return result; + return true; } void SkiaOutputSurfaceImpl::InitializeOnGpuThread(bool* result) { @@ -1548,9 +1566,8 @@ } gpu::SyncToken SkiaOutputSurfaceImpl::Flush() { - gpu::SyncToken sync_token( - gpu::CommandBufferNamespace::VIZ_SKIA_OUTPUT_SURFACE, - impl_on_gpu_->command_buffer_id(), ++sync_fence_release_); + DCHECK(shared_image_interface_); + gpu::SyncToken sync_token = shared_image_interface_->GenNextSyncToken(); sync_token.SetVerifyFlush(); FlushGpuTasks(SyncMode::kNoWait, sync_token); return sync_token;
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.h b/components/viz/service/display_embedder/skia_output_surface_impl.h index 7c0d00e..5bf966a 100644 --- a/components/viz/service/display_embedder/skia_output_surface_impl.h +++ b/components/viz/service/display_embedder/skia_output_surface_impl.h
@@ -67,6 +67,7 @@ class ImageContextImpl; class SkiaOutputSurfaceDependency; class SkiaOutputSurfaceImplOnGpu; +class SkiaOutputSurfaceSharedImageInterface; // The SkiaOutputSurface implementation. It is the output surface for // SkiaRenderer. It lives on the compositor thread, but it will post tasks @@ -217,6 +218,8 @@ CopyOutputRequest::CopyOutputRequestCallback result_callback) override; private: + friend class SkiaOutputSurfaceSharedImageInterface; + bool Initialize(); void InitializeOnGpuThread(bool* result); GrSurfaceCharacterization CreateGrSurfaceCharacterizationRenderPass( @@ -288,7 +291,7 @@ // Observers for context lost. base::ObserverList<ContextLostObserver>::Unchecked observers_; - uint64_t sync_fence_release_ = 0; + scoped_refptr<SkiaOutputSurfaceSharedImageInterface> shared_image_interface_; raw_ptr<SkiaOutputSurfaceDependency> dependency_; UpdateVSyncParametersCallback update_vsync_parameters_callback_;
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h index e615ffb2..bf30a2d 100644 --- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h +++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
@@ -286,6 +286,10 @@ return context_state_.get(); } + gpu::SharedImageFactory* shared_image_factory() const { + return shared_image_factory_.get(); + } + #if BUILDFLAG(ENABLE_VULKAN) && BUILDFLAG(IS_CHROMEOS) && \ BUILDFLAG(USE_V4L2_CODEC) void DetileOverlay(gpu::Mailbox input,
diff --git a/components/viz/service/display_embedder/skia_output_surface_shared_image_interface.cc b/components/viz/service/display_embedder/skia_output_surface_shared_image_interface.cc new file mode 100644 index 0000000..1cf7aec --- /dev/null +++ b/components/viz/service/display_embedder/skia_output_surface_shared_image_interface.cc
@@ -0,0 +1,112 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/viz/service/display_embedder/skia_output_surface_shared_image_interface.h" + +#include <cstddef> +#include <utility> + +#include "base/check.h" +#include "base/functional/bind.h" +#include "base/task/sequenced_task_runner.h" +#include "components/viz/service/display_embedder/skia_output_surface_impl.h" +#include "components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h" +#include "gpu/command_buffer/common/constants.h" +#include "gpu/command_buffer/common/sync_token.h" +#include "gpu/command_buffer/service/shared_image_interface_in_process_base.h" + +namespace viz { + +SkiaOutputSurfaceSharedImageInterface::SkiaOutputSurfaceSharedImageInterface( + SkiaOutputSurfaceImpl& output_surface, + SkiaOutputSurfaceImplOnGpu& output_surface_on_gpu) + : gpu::SharedImageInterfaceInProcessBase( + gpu::CommandBufferNamespace::VIZ_SKIA_OUTPUT_SURFACE, + output_surface_on_gpu.command_buffer_id(), + /*verify_creation_sync_token=*/true), + output_surface_(&output_surface), + output_surface_on_gpu_(&output_surface_on_gpu), + host_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()) {} + +SkiaOutputSurfaceSharedImageInterface:: + ~SkiaOutputSurfaceSharedImageInterface() = default; + +void SkiaOutputSurfaceSharedImageInterface::DetachOutputSurfaceOnHostThread() { + DCHECK(host_task_runner_->RunsTasksInCurrentSequence()); + + if (!output_surface_) { + return; + } + output_surface_->EnqueueGpuTask( + base::BindOnce(&SkiaOutputSurfaceSharedImageInterface:: + DetachOutputSurfaceOnGpuThread, + this), + /*sync_tokens=*/{}, /*make_current=*/true, /*need_framebuffer=*/false); + output_surface_ = nullptr; +} + +void SkiaOutputSurfaceSharedImageInterface::DetachOutputSurfaceOnGpuThread() { + DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_); + output_surface_on_gpu_ = nullptr; +} + +void SkiaOutputSurfaceSharedImageInterface::ScheduleGpuTask( + base::OnceClosure task, + std::vector<gpu::SyncToken> sync_token_fences, + const gpu::SyncToken& release) { + // Here we assume that either this is a rendering task scheduled by + // `SkiaOutputSurfaceImpl`, in which case the output surface will eventually + // call `FlushGpuTasks()` and signal the `release` token (or one that subsumes + // it), or the `release` token is empty coming from another task. + + if (host_task_runner_->RunsTasksInCurrentSequence()) { + ScheduleGpuTaskOnHostThread(std::move(task), std::move(sync_token_fences)); + } else { + DCHECK(!release.HasData()); + host_task_runner_->PostTask( + FROM_HERE, + base::BindOnce( + &SkiaOutputSurfaceSharedImageInterface::ScheduleGpuTaskOnHostThread, + this, std::move(task), std::move(sync_token_fences))); + } +} + +void SkiaOutputSurfaceSharedImageInterface::ScheduleGpuTaskOnHostThread( + base::OnceClosure task, + std::vector<gpu::SyncToken> sync_token_fences) { + DCHECK(host_task_runner_->RunsTasksInCurrentSequence()); + + if (!output_surface_) { + return; + } + output_surface_->EnqueueGpuTask(std::move(task), std::move(sync_token_fences), + /*make_current=*/true, + /*need_framebuffer=*/false); +} + +gpu::SharedImageFactory* +SkiaOutputSurfaceSharedImageInterface::GetSharedImageFactoryOnGpuThread() { + DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_); + if (!output_surface_on_gpu_) { + return nullptr; + } + return output_surface_on_gpu_->shared_image_factory(); +} + +bool SkiaOutputSurfaceSharedImageInterface::MakeContextCurrentOnGpuThread( + bool needs_gl) { + DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_); + // Passing `make_current=true` in `ScheduleGpuTask()` will call + // `MakeCurrent()` at an appropriate time so it doesn't need to be done here. + return output_surface_on_gpu_; +} + +void SkiaOutputSurfaceSharedImageInterface::MarkContextLostOnGpuThread() { + DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_); + if (output_surface_on_gpu_) { + output_surface_on_gpu_->OnContextLost(); + } +} + +} // namespace viz
diff --git a/components/viz/service/display_embedder/skia_output_surface_shared_image_interface.h b/components/viz/service/display_embedder/skia_output_surface_shared_image_interface.h new file mode 100644 index 0000000..b4509d86 --- /dev/null +++ b/components/viz/service/display_embedder/skia_output_surface_shared_image_interface.h
@@ -0,0 +1,65 @@ +// Copyright 2025 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SKIA_OUTPUT_SURFACE_SHARED_IMAGE_INTERFACE_H_ +#define COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SKIA_OUTPUT_SURFACE_SHARED_IMAGE_INTERFACE_H_ + +#include <vector> + +#include "base/memory/raw_ptr.h" +#include "base/memory/scoped_refptr.h" +#include "base/task/sequenced_task_runner.h" +#include "components/viz/service/viz_service_export.h" +#include "gpu/command_buffer/service/shared_image_interface_in_process_base.h" + +namespace gpu { +class SharedImageFactory; +struct SyncToken; +} // namespace gpu + +namespace viz { +class SkiaOutputSurfaceImpl; +class SkiaOutputSurfaceImplOnGpu; + +// This wraps `SkiaOutputSurfaceImpl` to implement `SharedImageInterface`. +class VIZ_SERVICE_EXPORT SkiaOutputSurfaceSharedImageInterface + : public gpu::SharedImageInterfaceInProcessBase { + public: + explicit SkiaOutputSurfaceSharedImageInterface( + SkiaOutputSurfaceImpl& output_surface, + SkiaOutputSurfaceImplOnGpu& output_surface_on_gpu); + + // Call from destructor of `output_surface_` to break reference cycle. + // This will also break the reference to `output_surface_on_gpu_`. This can + // only be called from the host thread. + void DetachOutputSurfaceOnHostThread(); + + protected: + ~SkiaOutputSurfaceSharedImageInterface() override; + + // SharedImageInterfaceInProcessBase: + void ScheduleGpuTask(base::OnceClosure task, + std::vector<gpu::SyncToken> sync_token_fences, + const gpu::SyncToken& release) override; + gpu::SharedImageFactory* GetSharedImageFactoryOnGpuThread() override; + bool MakeContextCurrentOnGpuThread(bool needs_gl) override; + void MarkContextLostOnGpuThread() override; + + private: + // This breaks the reference to `output_surface_on_gpu_`. + void DetachOutputSurfaceOnGpuThread(); + + // This schedules a GPU task from the host thread. + void ScheduleGpuTaskOnHostThread( + base::OnceClosure task, + std::vector<gpu::SyncToken> sync_token_fences); + + raw_ptr<SkiaOutputSurfaceImpl> output_surface_; + raw_ptr<SkiaOutputSurfaceImplOnGpu> output_surface_on_gpu_; + scoped_refptr<base::SequencedTaskRunner> host_task_runner_; +}; + +} // namespace viz + +#endif // COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SKIA_OUTPUT_SURFACE_SHARED_IMAGE_INTERFACE_H_
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc index 5f819f1..603ec936 100644 --- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc +++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -943,17 +943,8 @@ RunAriaTest(FILE_PATH_LITERAL("aria-controls-reference-target.html")); } -// TODO(crbug.com/405117678): Re-enable this test on Mac. -#if BUILDFLAG(IS_MAC) -#define MAYBE_AccessibilityAriaControlsManyParagraphsBetween \ - DISABLED_AccessibilityAriaControlsManyParagraphsBetween -#else -#define MAYBE_AccessibilityAriaControlsManyParagraphsBetween \ - AccessibilityAriaControlsManyParagraphsBetween -#endif - IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, - MAYBE_AccessibilityAriaControlsManyParagraphsBetween) { + AccessibilityAriaControlsManyParagraphsBetween) { RunAriaTest(FILE_PATH_LITERAL("aria-controls-many-paragraphs-between.html")); }
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.cc b/content/browser/attribution_reporting/attribution_manager_impl.cc index abc43b6..64a9d3f 100644 --- a/content/browser/attribution_reporting/attribution_manager_impl.cc +++ b/content/browser/attribution_reporting/attribution_manager_impl.cc
@@ -1212,7 +1212,6 @@ for (auto& report : reports) { SendReport(base::NullCallback(), now, std::move(report)); } - report_sender_->SetInFirstBatch(/*in_first_batch=*/false); } // If `web_ui_callback` is null, assumes that `report` is being sent at its
diff --git a/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc b/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc index ce150e5..a8333032 100644 --- a/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc +++ b/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc
@@ -226,7 +226,6 @@ class MockReportSender : public AttributionReportSender { public: - MOCK_METHOD(void, SetInFirstBatch, (bool in_first_batch), (override)); MOCK_METHOD(void, SendReport, (AttributionReport report,
diff --git a/content/browser/attribution_reporting/attribution_report_network_sender.cc b/content/browser/attribution_reporting/attribution_report_network_sender.cc index 2d75b347..1351c55 100644 --- a/content/browser/attribution_reporting/attribution_report_network_sender.cc +++ b/content/browser/attribution_reporting/attribution_report_network_sender.cc
@@ -95,10 +95,6 @@ AttributionReportNetworkSender::~AttributionReportNetworkSender() = default; -void AttributionReportNetworkSender::SetInFirstBatch(bool in_first_batch) { - in_first_batch_ = in_first_batch; -} - void AttributionReportNetworkSender::SendReport( AttributionReport report, bool is_debug_report, @@ -258,12 +254,6 @@ loader->GetNumRetries() > 0 ? std::make_optional<bool>(net_ok_and_http_ok) : std::nullopt; - if (in_first_batch_) { - base::UmaHistogramSparse( - "Conversions.FirstBatch.HttpResponseOrNetErrorCode", - response_or_net_error); - } - #if BUILDFLAG(IS_ANDROID) std::string_view suffix; switch (app_state_) {
diff --git a/content/browser/attribution_reporting/attribution_report_network_sender.h b/content/browser/attribution_reporting/attribution_report_network_sender.h index 7adfa4f..3a7ea75 100644 --- a/content/browser/attribution_reporting/attribution_report_network_sender.h +++ b/content/browser/attribution_reporting/attribution_report_network_sender.h
@@ -56,8 +56,6 @@ ~AttributionReportNetworkSender() override; // AttributionReportSender: - void SetInFirstBatch(bool in_first_batch) override; - void SendReport(AttributionReport report, bool is_debug_report, ReportSentCallback sent_callback) override; @@ -105,9 +103,6 @@ // Used for network requests. scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_; - // Used for metric logging. - bool in_first_batch_ = true; - #if BUILDFLAG(IS_ANDROID) // Callback invoked when the application state changes. void OnApplicationStateChanged(base::android::ApplicationState state);
diff --git a/content/browser/attribution_reporting/attribution_report_network_sender_unittest.cc b/content/browser/attribution_reporting/attribution_report_network_sender_unittest.cc index 1a0653d..b8507377 100644 --- a/content/browser/attribution_reporting/attribution_report_network_sender_unittest.cc +++ b/content/browser/attribution_reporting/attribution_report_network_sender_unittest.cc
@@ -540,8 +540,6 @@ base::DoNothing()); EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest( kEventLevelReportUrl, "")); - histograms.ExpectUniqueSample( - "Conversions.FirstBatch.HttpResponseOrNetErrorCode", net::HTTP_OK, 1); histograms.ExpectUniqueSample(kErrorCodeMetric, net::HTTP_OK, 1); histograms.ExpectTotalCount(kReportSizeMetric, 1); } @@ -556,9 +554,6 @@ EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest( GURL(kEventLevelReportUrl), completion_status, network::mojom::URLResponseHead::New(), "")); - histograms.ExpectUniqueSample( - "Conversions.FirstBatch.HttpResponseOrNetErrorCode", net::ERR_FAILED, - 1); histograms.ExpectUniqueSample(kErrorCodeMetric, net::ERR_FAILED, 1); histograms.ExpectTotalCount(kReportSizeMetric, 1); } @@ -570,9 +565,6 @@ base::DoNothing()); EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest( kEventLevelReportUrl, "", net::HTTP_UNAUTHORIZED)); - histograms.ExpectUniqueSample( - "Conversions.FirstBatch.HttpResponseOrNetErrorCode", - net::HTTP_UNAUTHORIZED, 1); histograms.ExpectUniqueSample(kErrorCodeMetric, net::HTTP_UNAUTHORIZED, 1); histograms.ExpectTotalCount(kReportSizeMetric, 1); } @@ -697,8 +689,6 @@ base::DoNothing()); EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest( kAggregatableReportUrl, "")); - histograms.ExpectUniqueSample( - "Conversions.FirstBatch.HttpResponseOrNetErrorCode", net::HTTP_OK, 1); verify_histogram(histograms, kErrorCodeMetric, has_trigger_context_id, net::HTTP_OK, 1); histograms.ExpectTotalCount(kReportSizeMetric, 1); @@ -714,9 +704,6 @@ EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest( GURL(kAggregatableReportUrl), completion_status, network::mojom::URLResponseHead::New(), "")); - histograms.ExpectUniqueSample( - "Conversions.FirstBatch.HttpResponseOrNetErrorCode", net::ERR_FAILED, - 1); verify_histogram(histograms, kErrorCodeMetric, has_trigger_context_id, net::ERR_FAILED, 1); histograms.ExpectTotalCount(kReportSizeMetric, 1); @@ -729,9 +716,6 @@ base::DoNothing()); EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest( kAggregatableReportUrl, "", net::HTTP_UNAUTHORIZED)); - histograms.ExpectUniqueSample( - "Conversions.FirstBatch.HttpResponseOrNetErrorCode", - net::HTTP_UNAUTHORIZED, 1); verify_histogram(histograms, kErrorCodeMetric, has_trigger_context_id, net::HTTP_UNAUTHORIZED, 1); histograms.ExpectTotalCount(kReportSizeMetric, 1);
diff --git a/content/browser/attribution_reporting/attribution_report_sender.h b/content/browser/attribution_reporting/attribution_report_sender.h index 08e1c20..41495881 100644 --- a/content/browser/attribution_reporting/attribution_report_sender.h +++ b/content/browser/attribution_reporting/attribution_report_sender.h
@@ -39,8 +39,6 @@ using AggregatableDebugReportSentCallback = base::OnceCallback< void(const AggregatableDebugReport&, base::ValueView, int status)>; - virtual void SetInFirstBatch(bool in_first_batch) = 0; - // Sends `report` and runs `sent_callback` when done. virtual void SendReport(AttributionReport report, bool is_debug_report,
diff --git a/content/browser/media/webaudio/audio_context_manager_impl.cc b/content/browser/media/webaudio/audio_context_manager_impl.cc index 51a41ae..48538cf 100644 --- a/content/browser/media/webaudio/audio_context_manager_impl.cc +++ b/content/browser/media/webaudio/audio_context_manager_impl.cc
@@ -66,7 +66,7 @@ void AudioContextManagerImpl::AudioContextAudiblePlaybackStarted( uint32_t audio_context_id) { - DCHECK(pending_audible_durations_[audio_context_id].is_null()); + CHECK(pending_audible_durations_[audio_context_id].is_null()); // Keeps track of the start audible time for this context. pending_audible_durations_[audio_context_id] = clock_->NowTicks();
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc index 817b5da..fdb69cd 100644 --- a/content/browser/renderer_host/render_widget_host_impl.cc +++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -3724,6 +3724,16 @@ return true; } +bool RenderWidgetHostImpl::ShouldWaitForInputProcessed() { + for (auto& it : GetHostFrameSinkManager()->GetDisplayHitTestQuery()) { + if (it.second->ContainsActiveFrameSinkId(GetFrameSinkId())) { + // Browser already has hit test data for frame sink. + return false; + } + } + return true; +} + bool RenderWidgetHostImpl::IsHidden() const { return is_hidden_; }
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h index 43c8402..04cc38e 100644 --- a/content/browser/renderer_host/render_widget_host_impl.h +++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -816,6 +816,8 @@ // resolving any ambiguity that could arise from multiple inheritance. bool IsHidden() const override; + bool ShouldWaitForInputProcessed() override; + // Signals that a frame with token |frame_token| was finished processing. If // there are any queued messages belonging to it, they will be processed. void DidProcessFrame(uint32_t frame_token, base::TimeTicks activation_time); @@ -1029,6 +1031,10 @@ // cpu-priority boosted to run discard logic. void SetIsDiscarding(bool is_discarding); + SyntheticGestureController* SyntheticGestureControllerForTesting() { + return synthetic_gesture_controller_.get(); + } + protected: // |routing_id| must not be IPC::mojom::kRoutingIdNone. // If this object outlives |delegate|, DetachDelegate() must be called when
diff --git a/content/browser/site_per_process_scroll_browsertest.cc b/content/browser/site_per_process_scroll_browsertest.cc index 95d2335..45c3c45 100644 --- a/content/browser/site_per_process_scroll_browsertest.cc +++ b/content/browser/site_per_process_scroll_browsertest.cc
@@ -7,11 +7,13 @@ #include "base/task/single_thread_task_runner.h" #include "base/test/bind.h" #include "base/test/run_until.h" +#include "base/test/scoped_feature_list.h" #include "base/test/test_timeouts.h" #include "build/build_config.h" #include "content/browser/renderer_host/cross_process_frame_connector.h" #include "content/browser/renderer_host/frame_tree.h" #include "content/browser/renderer_host/render_frame_proxy_host.h" +#include "content/browser/renderer_host/render_widget_host_impl.h" #include "content/browser/site_per_process_browsertest.h" #include "content/common/input/synthetic_smooth_scroll_gesture.h" #include "content/public/common/content_switches.h" @@ -24,6 +26,8 @@ #include "content/test/render_document_feature.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/common/features.h" +#include "third_party/blink/public/common/input/web_touch_event.h" #include "ui/events/gesture_detection/gesture_configuration.h" #include "ui/events/keycodes/dom/keycode_converter.h" #include "ui/native_theme/features/native_theme_features.h" @@ -919,10 +923,166 @@ ack_observer.Wait(); } +class OOPIFScrollBubblingTest : public SitePerProcessBrowserTest { + public: + OOPIFScrollBubblingTest() { + scoped_feature_list_.InitWithFeatureState( + blink::features::kAsyncTouchMovesImmediatelyAfterScroll, + /* enabled= */ true); + } + ~OOPIFScrollBubblingTest() override = default; + + private: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +class TouchMoveInjectingObserver : public RenderWidgetHost::InputEventObserver { + public: + TouchMoveInjectingObserver( + RenderWidgetHost* target_widget, + base::WeakPtr<SyntheticGestureController> synthetic_gesture_controller, + base::RunLoop* run_loop) + : target_rwh_(static_cast<RenderWidgetHostImpl*>(target_widget)), + gesture_controller_(synthetic_gesture_controller), + run_loop_(run_loop) {} + + TouchMoveInjectingObserver(const TouchMoveInjectingObserver&) = delete; + TouchMoveInjectingObserver& operator=(const TouchMoveInjectingObserver&) = + delete; + + void OnInputEvent(const RenderWidgetHost& widget, + const blink::WebInputEvent& event) override { + const RenderWidgetHostViewBase* view = + static_cast<const RenderWidgetHostViewBase*>(widget.GetView()); + bool is_child_view = view->IsRenderWidgetHostViewChildFrame(); + if (is_child_view) { + OnInputEventOnChildFrame(event); + return; + } + OnInputEventOnRootFrame(event); + } + + bool root_view_has_seen_gsu_for_async_touch_move() const { + return root_view_has_seen_gsu_for_async_touch_move_; + } + + private: + void OnInputEventOnChildFrame(const blink::WebInputEvent& event) { + if (event.GetType() == blink::WebInputEvent::Type::kGestureScrollBegin) { + GetUIThreadTaskRunner({BrowserTaskType::kUserInput}) + ->PostTask(FROM_HERE, base::BindOnce( + [](base::WeakPtr<SyntheticGestureController> + gesture_controller) { + if (!gesture_controller) { + return; + } + gesture_controller->DispatchNextEvent( + base::TimeTicks::Now()); + }, + gesture_controller_)); + has_seen_gesture_scroll_begin_ = true; + return; + } + if (event.GetType() == blink::WebInputEvent::Type::kTouchMove && + has_seen_gesture_scroll_begin_ && + first_touch_move_after_scroll_begin_id_ == -1) { + first_touch_move_after_scroll_begin_id_ = + static_cast<const blink::WebTouchEvent&>(event).unique_touch_event_id; + return; + } + } + + void OnInputEventOnRootFrame(const blink::WebInputEvent& event) { + if (event.GetType() != blink::WebInputEvent::Type::kGestureScrollUpdate) { + return; + } + const blink::WebGestureEvent& gesture_event = + static_cast<const blink::WebGestureEvent&>(event); + if (gesture_event.unique_touch_event_id == + first_touch_move_after_scroll_begin_id_) { + ASSERT_FALSE(root_view_has_seen_gsu_for_async_touch_move_); + root_view_has_seen_gsu_for_async_touch_move_ = true; + run_loop_->Quit(); + } + } + int first_touch_move_after_scroll_begin_id_ = -1; + bool root_view_has_seen_gsu_for_async_touch_move_ = false; + bool has_seen_gesture_scroll_begin_ = false; + raw_ptr<RenderWidgetHostImpl> target_rwh_; + base::WeakPtr<SyntheticGestureController> gesture_controller_; + raw_ptr<base::RunLoop> run_loop_; +}; + +IN_PROC_BROWSER_TEST_P(OOPIFScrollBubblingTest, + ScrollBubblingWithTouchMoveInjection) { + GURL main_url(embedded_test_server()->GetURL( + "a.com", "/scrollable_page_with_iframe.html")); + EXPECT_TRUE(NavigateToURL(shell(), main_url)); + RenderFrameSubmissionObserver frame_observer(shell()->web_contents()); + + FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); + RenderWidgetHostImpl* root_rwh = + root->current_frame_host()->GetRenderWidgetHost(); + root_rwh->EnsureReadyForSyntheticGestures(base::DoNothing()); + RenderWidgetHostViewBase* root_view = + static_cast<RenderWidgetHostViewBase*>(root_rwh->GetView()); + + FrameTreeNode* iframe_node = root->child_at(0); + GURL url_domain_b(embedded_test_server()->GetURL( + "b.com", "/body_overflow_hidden_blocking_touch_handlers.html")); + EXPECT_TRUE(NavigateToURLFromRenderer(iframe_node, url_domain_b)); + WaitForHitTestData(iframe_node->current_frame_host()); + RenderWidgetHostImpl* iframe_rwh = + iframe_node->current_frame_host()->GetRenderWidgetHost(); + + gfx::Rect iframe_bounds = + iframe_node->current_frame_host()->GetView()->GetViewBounds(); + float scale_factor = + frame_observer.LastRenderFrameMetadata().page_scale_factor; + gfx::PointF scroll_pos( + std::ceil((iframe_bounds.x() - root_view->GetViewBounds().x() + 10) * + scale_factor), + std::ceil((iframe_bounds.y() - root_view->GetViewBounds().y() + 10) * + scale_factor)); + + SyntheticSmoothScrollGestureParams params; + params.gesture_source_type = content::mojom::GestureSourceType::kTouchInput; + params.anchor = scroll_pos; + params.distances.push_back(gfx::Vector2d(0, -200)); // Scroll down. + params.granularity = ui::ScrollGranularity::kScrollByPrecisePixel; + + auto gesture = std::make_unique<SyntheticSmoothScrollGesture>(params); + + base::RunLoop run_loop; + root_rwh->QueueSyntheticGesture(std::move(gesture), base::DoNothing()); + + TouchMoveInjectingObserver observer( + iframe_rwh, + root->current_frame_host() + ->GetRenderWidgetHost() + ->SyntheticGestureControllerForTesting() + ->GetWeakPtr(), + &run_loop); + + root->current_frame_host()->GetRenderWidgetHost()->AddInputEventObserver( + &observer); + iframe_rwh->AddInputEventObserver(&observer); + run_loop.Run(); + + EXPECT_TRUE(observer.root_view_has_seen_gsu_for_async_touch_move()); + + root->current_frame_host()->GetRenderWidgetHost()->RemoveInputEventObserver( + &observer); + iframe_rwh->RemoveInputEventObserver(&observer); +} + INSTANTIATE_TEST_SUITE_P(All, ScrollingIntegrationTest, testing::ValuesIn(RenderDocumentFeatureLevelValues())); INSTANTIATE_TEST_SUITE_P(All, + OOPIFScrollBubblingTest, + testing::ValuesIn(RenderDocumentFeatureLevelValues())); +INSTANTIATE_TEST_SUITE_P(All, SitePerProcessScrollAnchorTest, testing::ValuesIn(RenderDocumentFeatureLevelValues())); INSTANTIATE_TEST_SUITE_P(All,
diff --git a/content/common/input/synthetic_gesture_controller.cc b/content/common/input/synthetic_gesture_controller.cc index 8e8270ab..a3a877d 100644 --- a/content/common/input/synthetic_gesture_controller.cc +++ b/content/common/input/synthetic_gesture_controller.cc
@@ -38,6 +38,12 @@ if (renderer_known_to_be_initialized_) return; + if (!delegate_->ShouldWaitForInputProcessed()) { + renderer_known_to_be_initialized_ = true; + std::move(on_completed).Run(); + return; + } + base::OnceClosure wrapper = base::BindOnce( [](base::WeakPtr<SyntheticGestureController> weak_ptr, base::OnceClosure on_completed) {
diff --git a/content/common/input/synthetic_gesture_controller.h b/content/common/input/synthetic_gesture_controller.h index 4c47e58..35a3b77 100644 --- a/content/common/input/synthetic_gesture_controller.h +++ b/content/common/input/synthetic_gesture_controller.h
@@ -38,6 +38,8 @@ virtual bool HasGestureStopped() = 0; virtual bool IsHidden() const = 0; + + virtual bool ShouldWaitForInputProcessed() = 0; }; SyntheticGestureController( Delegate* delegate,
diff --git a/content/common/input/synthetic_gesture_controller_unittest.cc b/content/common/input/synthetic_gesture_controller_unittest.cc index 14438a4..d9842793 100644 --- a/content/common/input/synthetic_gesture_controller_unittest.cc +++ b/content/common/input/synthetic_gesture_controller_unittest.cc
@@ -805,6 +805,7 @@ // SyntheticGestureController::Delegate: bool HasGestureStopped() override { return true; } bool IsHidden() const override { return false; } + bool ShouldWaitForInputProcessed() override { return true; } }; } // namespace
diff --git a/content/common/input/synthetic_pointer_action_unittest.cc b/content/common/input/synthetic_pointer_action_unittest.cc index 6d5c1e64..5d323b27 100644 --- a/content/common/input/synthetic_pointer_action_unittest.cc +++ b/content/common/input/synthetic_pointer_action_unittest.cc
@@ -421,6 +421,7 @@ // SyntheticGestureController::Delegate: bool HasGestureStopped() override { return true; } bool IsHidden() const override { return false; } + bool ShouldWaitForInputProcessed() override { return true; } }; class SyntheticPointerActionTest : public testing::Test {
diff --git a/content/test/content_test_bundle_data.filelist b/content/test/content_test_bundle_data.filelist index 484ace75..39e953bb 100644 --- a/content/test/content_test_bundle_data.filelist +++ b/content/test/content_test_bundle_data.filelist
@@ -2196,10 +2196,10 @@ data/accessibility/css/table-display.html data/accessibility/css/table-incomplete-expected-blink.txt data/accessibility/css/table-incomplete.html -data/accessibility/css/target-current-html-anchor-role-expected-blink.txt -data/accessibility/css/target-current-html-anchor-role.html data/accessibility/css/target-current-html-anchor-role-dynamic-expected-blink.txt data/accessibility/css/target-current-html-anchor-role-dynamic.html +data/accessibility/css/target-current-html-anchor-role-expected-blink.txt +data/accessibility/css/target-current-html-anchor-role.html data/accessibility/css/text-overflow-ellipsis-expected-blink.txt data/accessibility/css/text-overflow-ellipsis.html data/accessibility/css/transform-expected-blink.txt @@ -6808,6 +6808,7 @@ data/blob_storage/common.js data/blue.html data/body_overflow_hidden.html +data/body_overflow_hidden_blocking_touch_handlers.html data/browsing_data/embedded_site_data.html data/browsing_data/embeds_storage_accessor.html data/browsing_data/empty.js
diff --git a/content/test/data/accessibility/aria/aria-controls-many-paragraphs-between.html b/content/test/data/accessibility/aria/aria-controls-many-paragraphs-between.html index 272b15f8..eb9cec9 100644 --- a/content/test/data/accessibility/aria/aria-controls-many-paragraphs-between.html +++ b/content/test/data/accessibility/aria/aria-controls-many-paragraphs-between.html
@@ -498,1207 +498,6 @@ <p>The quick brown fox jumps over the lazy dog.</p> <p>The quick brown fox jumps over the lazy dog.</p> <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> - <p>The quick brown fox jumps over the lazy dog.</p> </div> <p id="last-para">Last paragraph</p> </body>
diff --git a/content/test/data/body_overflow_hidden_blocking_touch_handlers.html b/content/test/data/body_overflow_hidden_blocking_touch_handlers.html new file mode 100644 index 0000000..087540c --- /dev/null +++ b/content/test/data/body_overflow_hidden_blocking_touch_handlers.html
@@ -0,0 +1,21 @@ +<html> +<head> + <meta name='viewport' content='width=device-width, minimum-scale=1'> +</head> +<style> +body { + height: 10000px; + overflow: hidden; +} +</style> +<div style="width:100px; height:300px; overflow: scroll;"> + <div style="width:150px; height:250px; overflow: scroll"> + <div style="width:300px; height:200px; overflow: scroll"> + </div> + </div> +</div> +<script> + document.addEventListener('touchstart', (e) => {}, {passive: false}); + document.addEventListener('touchmove', (e) => {}, {passive: false}); +</script> +</html>
diff --git a/docs/unsafe_buffers.md b/docs/unsafe_buffers.md index 4a4d5e4..52c0cdcf 100644 --- a/docs/unsafe_buffers.md +++ b/docs/unsafe_buffers.md
@@ -17,7 +17,7 @@ error: unsafe buffer access [-Werror,-Wunsafe-buffer-usage] ``` and directs developers to this file for more information. Several common -[Techniques](#container-based-ecosystem) for fixing these issues are presented +[Techniques](#container_based-ecosystem) for fixing these issues are presented later in this document. Clang documentation includes a guide to working with unsafe-buffer-usage
diff --git a/docs/website b/docs/website index c6edf98..4f4404f 160000 --- a/docs/website +++ b/docs/website
@@ -1 +1 @@ -Subproject commit c6edf98e7fab2385c90caac6211f00b62e9b773d +Subproject commit 4f4404f68b21aefa24a86abad8fb0cf01a0db7fc
diff --git a/extensions/browser/events/event_dispatch_helper.cc b/extensions/browser/events/event_dispatch_helper.cc index e96bcd32..2ad3b92 100644 --- a/extensions/browser/events/event_dispatch_helper.cc +++ b/extensions/browser/events/event_dispatch_helper.cc
@@ -44,7 +44,7 @@ EventListenerMap& listeners, DispatchFunction dispatch_function, DispatchToProcessFunction dispatch_to_process_function, - const std::string& restrict_to_extension_id, + const ExtensionId& restrict_to_extension_id, const GURL& restrict_to_url, std::unique_ptr<Event> event) { const ExtensionRegistry* extension_registry = @@ -58,7 +58,7 @@ } void EventDispatchHelper::DispatchEventImpl( - const std::string& restrict_to_extension_id, + const ExtensionId& restrict_to_extension_id, const GURL& restrict_to_url, std::unique_ptr<Event> event) { std::set<const EventListener*> listeners( @@ -71,66 +71,77 @@ // the event we are dispatching here, we dispatch to the lazy listeners here // first. for (const EventListener* listener : listeners) { - if (!restrict_to_extension_id.empty() && - restrict_to_extension_id != listener->extension_id()) { - continue; - } - if (!restrict_to_url.is_empty() && - !url::IsSameOriginWith(restrict_to_url, listener->listener_url())) { - continue; - } - if (!listener->IsLazy()) { - continue; - } - - // TODO(richardzh): Move cross browser context check (by calling - // EventRouter::CanDispatchEventToBrowserContext) to here. So the check - // happens before instead of during the dispatch. - - // Lazy listeners don't have a process, take the stored browser context - // for lazy context. - TryQueueEventForLazyListener( - *event, LazyContextIdForListener(listener, *browser_context_), - listener->filter()); - - // Dispatch to lazy listener in the incognito context. - // We need to use the incognito context in the case of split-mode - // extensions. - BrowserContext* incognito_context = - GetIncognitoContextIfAccessible(listener->extension_id()); - if (incognito_context) { - TryQueueEventForLazyListener( - *event, LazyContextIdForListener(listener, *incognito_context), - listener->filter()); + if (listener->IsLazy()) { + DispatchEventToLazyListener(restrict_to_extension_id, restrict_to_url, + *event, listener); } } for (const EventListener* listener : listeners) { - if (!restrict_to_extension_id.empty() && - restrict_to_extension_id != listener->extension_id()) { - continue; + if (!listener->IsLazy()) { + DispatchEventToActiveListener(restrict_to_extension_id, restrict_to_url, + *event, listener); } - if (!restrict_to_url.is_empty() && - !url::IsSameOriginWith(restrict_to_url, listener->listener_url())) { - continue; - } - if (listener->IsLazy()) { - continue; - } - // Non-lazy listeners take the process browser context for - // lazy context - if (IsAlreadyQueued(LazyContextIdForListener( - listener, *listener->process()->GetBrowserContext()))) { - continue; - } - - dispatch_to_process_function_.Run( - listener->extension_id(), listener->listener_url(), listener->process(), - listener->service_worker_version_id(), listener->worker_thread_id(), - *event, listener->filter(), false /* did_enqueue */); } } +void EventDispatchHelper::DispatchEventToLazyListener( + const ExtensionId& restrict_to_extension_id, + const GURL& restrict_to_url, + Event& event, + const EventListener* listener) { + DCHECK(listener->IsLazy()); + if (!ListenerMeetsRestrictions(listener, restrict_to_extension_id, + restrict_to_url)) { + return; + } + + // TODO(richardzh): Move cross browser context check (by calling + // EventRouter::CanDispatchEventToBrowserContext) to here. So the check + // happens before instead of during the dispatch. + + // Lazy listeners don't have a process, take the stored browser context + // for lazy context. + TryQueueEventForLazyListener( + event, LazyContextIdForListener(listener, *browser_context_), + listener->filter()); + + // Dispatch to lazy listener in the incognito context. + // We need to use the incognito context in the case of split-mode + // extensions. + BrowserContext* incognito_context = + GetIncognitoContextIfAccessible(listener->extension_id()); + if (incognito_context) { + TryQueueEventForLazyListener( + event, LazyContextIdForListener(listener, *incognito_context), + listener->filter()); + } +} + +void EventDispatchHelper::DispatchEventToActiveListener( + const ExtensionId& restrict_to_extension_id, + const GURL& restrict_to_url, + const Event& event, + const EventListener* listener) { + DCHECK(!listener->IsLazy()); + if (!ListenerMeetsRestrictions(listener, restrict_to_extension_id, + restrict_to_url)) { + return; + } + + // Non-lazy listeners take the process browser context for + // lazy context. + if (IsAlreadyQueued(LazyContextIdForListener( + listener, *listener->process()->GetBrowserContext()))) { + return; + } + + dispatch_to_process_function_.Run( + listener->extension_id(), listener->listener_url(), listener->process(), + listener->service_worker_version_id(), listener->worker_thread_id(), + event, listener->filter(), false /* did_enqueue */); +} + void EventDispatchHelper::TryQueueEventForLazyListener( Event& event, const LazyContextId& dispatch_context, @@ -218,6 +229,23 @@ return base::Contains(dispatched_ids_, dispatch_context); } +bool EventDispatchHelper::ListenerMeetsRestrictions( + const EventListener* listener, + const ExtensionId& restrict_to_extension_id, + const GURL& restrict_to_url) const { + if (!restrict_to_extension_id.empty() && + restrict_to_extension_id != listener->extension_id()) { + return false; + } + + if (!restrict_to_url.is_empty() && + !url::IsSameOriginWith(restrict_to_url, listener->listener_url())) { + return false; + } + + return true; +} + BrowserContext* EventDispatchHelper::GetIncognitoContextIfAccessible( const ExtensionId& extension_id) const { DCHECK(!extension_id.empty());
diff --git a/extensions/browser/events/event_dispatch_helper.h b/extensions/browser/events/event_dispatch_helper.h index 15750d8..689aec8 100644 --- a/extensions/browser/events/event_dispatch_helper.h +++ b/extensions/browser/events/event_dispatch_helper.h
@@ -70,10 +70,31 @@ ~EventDispatchHelper(); - void DispatchEventImpl(const std::string& restrict_to_extension_id, + void DispatchEventImpl(const ExtensionId& restrict_to_extension_id, const GURL& restrict_to_url, std::unique_ptr<Event> event); + // Attempts to dispatch the given `event` to the specified lazy `listener`. + // This will queue the event to be dispatched later if the lazy context + // is not currently running. + // + // NOTE: this method will not dispatch to a lazy listener if the context + // is active, so that it can be dispatched to the corresponding active + // (non-lazy) listener instead. + void DispatchEventToLazyListener(const ExtensionId& restrict_to_extension_id, + const GURL& restrict_to_url, + Event& event, + const EventListener* listener); + + // Dispatches the given `event` to the specified active `listener`. Avoids + // dispatching if an event has already been queued for a lazy listener with + // the same context. + void DispatchEventToActiveListener( + const ExtensionId& restrict_to_extension_id, + const GURL& restrict_to_url, + const Event& event, + const EventListener* listener); + // Possibly queues the lazy `event` for dispatch to `dispatch_context`. // // If [dispatch_context| is for an event page, it ensures all of the pages @@ -105,6 +126,16 @@ // is already queued for dispatch to a lazy listener. bool IsAlreadyQueued(const LazyContextId& dispatch_context) const; + // Returns true if the given `listener` meets dispatch restrictions. Events + // may be restricted to a particular extension ID or URL context. + // + // If `restrict_to_extension_id` is non-empty, the listener's extension ID + // must match it. If `restrict_to_url` is non-empty, the listener's URL must + // be same-origin with it. + bool ListenerMeetsRestrictions(const EventListener* listener, + const ExtensionId& restrict_to_extension_id, + const GURL& restrict_to_url) const; + // Gets off-the-record browser context if // - The extension has incognito mode set to "split" // - The on-the-record browser context has an off-the-record context
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h index 9585e3b..70f32c88 100644 --- a/extensions/browser/extension_function_histogram_value.h +++ b/extensions/browser/extension_function_histogram_value.h
@@ -1994,8 +1994,8 @@ AUTOFILLPRIVATE_GETPAYOVERTIMEISSUERLIST = 1931, AUTOFILLPRIVATE_SETAUTOFILLAIOPTINSTATUS = 1932, AUTOFILLPRIVATE_GETAUTOFILLAIOPTINSTATUS = 1933, - EXPERIMENTALACTOR_STARTTASK = 1934, - EXPERIMENTALACTOR_EXECUTEACTION = 1935, + DELETED_EXPERIMENTALACTOR_STARTTASK = 1934, + DELETED_EXPERIMENTALACTOR_EXECUTEACTION = 1935, EXPERIMENTALACTOR_STOPTASK = 1936, BOOKMARKMANAGERPRIVATE_OPENINNEWTABGROUP = 1937, CONTROLLEDFRAMEINTERNAL_CONTEXTMENUSUPDATE = 1938,
diff --git a/extensions/common/extension_resource.cc b/extensions/common/extension_resource.cc index 84ee5fd8..a694c7af 100644 --- a/extensions/common/extension_resource.cc +++ b/extensions/common/extension_resource.cc
@@ -5,20 +5,10 @@ #include "extensions/common/extension_resource.h" #include "base/check.h" -#include "base/feature_list.h" #include "base/files/file_util.h" namespace extensions { -namespace { - -#if BUILDFLAG(IS_WIN) -BASE_FEATURE(WindowsNormalizeFilePathFallbackOnError, - base::FEATURE_DISABLED_BY_DEFAULT); -#endif - -} // namespace - ExtensionResource::ExtensionResource() : follow_symlinks_anywhere_(false) {} ExtensionResource::ExtensionResource(const ExtensionId& extension_id, @@ -70,17 +60,20 @@ base::FilePath normalized_extension_root; if (!base::NormalizeFilePath(extension_root, &normalized_extension_root)) { #if BUILDFLAG(IS_WIN) - // TODO(crbug.com/410059474): Remove this if-check and the Windows-specific - // fallback logic in M143. - if (!base::FeatureList::IsEnabled( - kWindowsNormalizeFilePathFallbackOnError)) { - return base::FilePath(); - } - - // On Windows, `NormalizeFilePath` fails if the path doesn't start with a - // drive letter (e.g. a network path) or if it exceeds `MAX_PATH` characters - // in length. Fall back to `MakeAbsoluteFilePath` and proceed if the path - // exists. + // On Windows, `NormalizeFilePath` is implemented via + // `GetFinalPathNameByHandle`, which can fail in some cases. One such case + // which was reported by users is with paths on a ramdisk. For example, from + // the author of ImDisk: + // + // > ImDisk is a rather old product. It is designed to be as small and + // > simple as possible and compatible with Windows versions as old as NT + // > 3.51. By design it lacks support for certain "modern" OS features like + // > plug-and-play and Volume Mount Manager. The mentioned API function, + // > GetFinalPathNameByHandle, uses Volume Mount Manager and is therefore + // > not supported for ImDisk virtual disks. + // + // Since we can't normalize the path, fall back to `MakeAbsoluteFilePath` + // and proceed if the file exists. normalized_extension_root = base::MakeAbsoluteFilePath(extension_root); if (normalized_extension_root.empty() || !base::PathExists(normalized_extension_root)) {
diff --git a/gpu/command_buffer/service/shared_image/shared_image_factory.cc b/gpu/command_buffer/service/shared_image/shared_image_factory.cc index e5bc99e2..0879c5b 100644 --- a/gpu/command_buffer/service/shared_image/shared_image_factory.cc +++ b/gpu/command_buffer/service/shared_image/shared_image_factory.cc
@@ -917,11 +917,11 @@ // TODO(crbug.com/423037052): Handle offscreen canvas case for WebView where // we fail to find a shared image factory. // Suppress crashes due to this client for now. - if (new_debug_label == "CanvasResourceRasterGmb") { + if (new_debug_label.find("CanvasResourceRasterGmb") != std::string::npos) { return; } #endif - SCOPED_CRASH_KEY_STRING32("SIFactory", "DebugLabel", new_debug_label); + SCOPED_CRASH_KEY_STRING64("SIFactory", "DebugLabel", new_debug_label); SCOPED_CRASH_KEY_STRING64("SIFactory", "Format", format.ToString()); SCOPED_CRASH_KEY_NUMBER("SIFactory", "Usage", usage); SCOPED_CRASH_KEY_STRING64("SIFactory", "GMBType", GmbTypeToString(gmb_type)); @@ -930,7 +930,7 @@ IsSharedBetweenThreads(usage)); // DumpWithoutCrashing to get crash reports for failure to find a shared image // backing factory. - // base::debug::DumpWithoutCrashing(); + base::debug::DumpWithoutCrashing(); } bool SharedImageFactory::RegisterBacking(
diff --git a/gpu/command_buffer/service/shared_image_interface_in_process.cc b/gpu/command_buffer/service/shared_image_interface_in_process.cc index 7138d39..df2ee96 100644 --- a/gpu/command_buffer/service/shared_image_interface_in_process.cc +++ b/gpu/command_buffer/service/shared_image_interface_in_process.cc
@@ -9,6 +9,7 @@ #include "base/functional/bind.h" #include "base/memory/raw_ptr.h" #include "base/memory/scoped_refptr.h" +#include "base/sequence_checker.h" #include "base/synchronization/waitable_event.h" #include "base/task/single_thread_task_runner.h" #include "build/build_config.h" @@ -153,6 +154,7 @@ void SharedImageInterfaceInProcess::DestroyOnGpu( base::WaitableEvent* completion) { + DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_); bool have_context = MakeContextCurrentOnGpuThread(); if (shared_image_factory_) { shared_image_factory_->DestroyAllSharedImages(have_context); @@ -167,6 +169,7 @@ SharedImageFactory* SharedImageInterfaceInProcess::GetSharedImageFactoryOnGpuThread() { + DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_); if (shared_image_factory_) { return shared_image_factory_.get(); } @@ -183,6 +186,7 @@ bool SharedImageInterfaceInProcess::MakeContextCurrentOnGpuThread( bool needs_gl) { + DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_); if (gl::GetGLImplementation() == gl::kGLImplementationDisabled) { return true; } @@ -202,6 +206,7 @@ } void SharedImageInterfaceInProcess::MarkContextLostOnGpuThread() { + DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_); context_state_->MarkContextLost(); }
diff --git a/gpu/command_buffer/service/shared_image_interface_in_process_base.h b/gpu/command_buffer/service/shared_image_interface_in_process_base.h index 8bc222b7..a3fdce45 100644 --- a/gpu/command_buffer/service/shared_image_interface_in_process_base.h +++ b/gpu/command_buffer/service/shared_image_interface_in_process_base.h
@@ -141,7 +141,7 @@ CommandBufferId command_buffer_id() const { return command_buffer_id_; } - // Sequence checker for tasks that run on the gpu "thread". + // Sequence checker for tasks that run on the gpu thread. SEQUENCE_CHECKER(gpu_sequence_checker_); private:
diff --git a/gpu/ipc/service/gpu_channel_shared_image_interface.cc b/gpu/ipc/service/gpu_channel_shared_image_interface.cc index bd3b7afd..3a4334a 100644 --- a/gpu/ipc/service/gpu_channel_shared_image_interface.cc +++ b/gpu/ipc/service/gpu_channel_shared_image_interface.cc
@@ -47,9 +47,7 @@ SchedulingPriority::kLow, shared_image_stub->channel()->task_runner(), CommandBufferNamespace::GPU_CHANNEL_SHARED_IMAGE_INTERFACE, - command_buffer_id())) { - DETACH_FROM_SEQUENCE(gpu_sequence_checker_); -} + command_buffer_id())) {} GpuChannelSharedImageInterface::~GpuChannelSharedImageInterface() { scheduler_->DestroySequence(sequence_); @@ -152,6 +150,7 @@ SharedImageFactory* GpuChannelSharedImageInterface::GetSharedImageFactoryOnGpuThread() { + DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_); if (!shared_image_stub_) { return nullptr; } @@ -169,6 +168,7 @@ } void GpuChannelSharedImageInterface::MarkContextLostOnGpuThread() { + DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_); shared_image_stub_->shared_context_state()->MarkContextLost(); }
diff --git a/infra/config/generated/builders/try/ios-simulator/properties.json b/infra/config/generated/builders/try/ios-simulator/properties.json index b0c6b26..3350b72 100644 --- a/infra/config/generated/builders/try/ios-simulator/properties.json +++ b/infra/config/generated/builders/try/ios-simulator/properties.json
@@ -57,6 +57,10 @@ ], "use_clang_coverage": true }, + "$build/flakiness": { + "check_for_flakiness": true, + "check_for_flakiness_with_resultdb": true + }, "$build/siso": { "configs": [ "builder" @@ -77,6 +81,7 @@ ] }, "builder_group": "tryserver.chromium.mac", + "cq": "required", "recipe": "chromium/orchestrator", "xcode_build_version": "17a5305f" } \ No newline at end of file
diff --git a/infra/config/generated/cq-builders.md b/infra/config/generated/cq-builders.md index 5c16382..5ca69e9 100644 --- a/infra/config/generated/cq-builders.md +++ b/infra/config/generated/cq-builders.md
@@ -45,6 +45,8 @@ * [fuchsia-x64-cast-receiver-rel](https://ci.chromium.org/p/chromium/builders/try/fuchsia-x64-cast-receiver-rel) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""fuchsia-x64-cast-receiver-rel"")) +* [ios-simulator](https://ci.chromium.org/p/chromium/builders/try/ios-simulator) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""ios-simulator"")) + * [linux-chromeos-compile-dbg](https://ci.chromium.org/p/chromium/builders/try/linux-chromeos-compile-dbg) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""linux-chromeos-compile-dbg"")) * [linux-chromeos-rel](https://ci.chromium.org/p/chromium/builders/try/linux-chromeos-rel) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""linux-chromeos-rel"")) @@ -878,7 +880,7 @@ * Experiment percentage: 10.0 * [mac15-arm64-rel](https://ci.chromium.org/p/chromium/builders/try/mac15-arm64-rel) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""mac15-arm64-rel"")) - * Experiment percentage: 30.0 + * Experiment percentage: 1.0 * [tricium-clang-tidy](https://ci.chromium.org/p/chromium/builders/try/tricium-clang-tidy) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""tricium-clang-tidy"")) * Experiment percentage: 100.0
diff --git a/infra/config/generated/cq-usage/default.cfg b/infra/config/generated/cq-usage/default.cfg index 7c519c7..e6c4d6393 100644 --- a/infra/config/generated/cq-usage/default.cfg +++ b/infra/config/generated/cq-usage/default.cfg
@@ -62,6 +62,9 @@ name: "chromium/try/fuchsia-x64-cast-receiver-rel" } builders { + name: "chromium/try/ios-simulator" + } + builders { name: "chromium/try/linux-chromeos-compile-dbg" } builders {
diff --git a/infra/config/generated/cq-usage/full.cfg b/infra/config/generated/cq-usage/full.cfg index 2d43bc0..ec3c387 100644 --- a/infra/config/generated/cq-usage/full.cfg +++ b/infra/config/generated/cq-usage/full.cfg
@@ -2572,6 +2572,29 @@ } } builders { + name: "chromium/try/ios-simulator" + location_filters { + gerrit_host_regexp: ".*" + gerrit_project_regexp: ".*" + gerrit_ref_regexp: ".*" + path_regexp: "infra/config/.+" + exclude: true + } + location_filters { + gerrit_host_regexp: ".*" + gerrit_project_regexp: ".*" + gerrit_ref_regexp: ".*" + path_regexp: "docs/.+" + exclude: true + } + location_filters { + gerrit_host_regexp: ".*" + gerrit_project_regexp: ".*" + gerrit_ref_regexp: ".*" + path_regexp: "infra/config/generated/builders/try/ios-simulator/.+" + } + } + builders { name: "chromium/try/ios-simulator-full-configs" location_filters { gerrit_host_regexp: ".*"
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg index 16763a8..3506658 100644 --- a/infra/config/generated/luci/commit-queue.cfg +++ b/infra/config/generated/luci/commit-queue.cfg
@@ -4402,8 +4402,29 @@ } builders { name: "chromium/try/ios-simulator" - includable_only: true disable_reuse_footers: "Include-Ci-Only-Tests" + location_filters { + gerrit_host_regexp: ".*" + gerrit_project_regexp: ".*" + gerrit_ref_regexp: ".*" + path_regexp: "infra/config/.+" + exclude: true + } + location_filters { + gerrit_host_regexp: ".*" + gerrit_project_regexp: ".*" + gerrit_ref_regexp: ".*" + path_regexp: "docs/.+" + exclude: true + } + location_filters { + gerrit_host_regexp: ".*" + gerrit_project_regexp: ".*" + gerrit_ref_regexp: ".*" + path_regexp: "infra/config/generated/builders/try/ios-simulator/.+" + } + mode_allowlist: "DRY_RUN" + mode_allowlist: "FULL_RUN" } builders { name: "chromium/try/ios-simulator-code-coverage" @@ -6723,7 +6744,7 @@ builders { name: "chromium/try/mac15-arm64-rel" disable_reuse_footers: "Include-Ci-Only-Tests" - experiment_percentage: 30 + experiment_percentage: 1 location_filters { gerrit_host_regexp: ".*" gerrit_project_regexp: ".*"
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg index 22be070..3ae6792 100644 --- a/infra/config/generated/luci/cr-buildbucket.cfg +++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -99234,6 +99234,7 @@ ' }' ' },' ' "builder_group": "tryserver.chromium.mac",' + ' "cq": "required",' ' "led_builder_is_bootstrapped": true,' ' "recipe": "chromium/orchestrator"' '}'
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star index 95d2d6bb..358e7ef 100644 --- a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star +++ b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
@@ -340,7 +340,7 @@ # TODO (crbug.com/415099984): change to 100, # then move out of experimental CQ after, # mac15-arm64-rel replaces mac14-arm64-rel on CQ. - experiment_percentage = 30, + experiment_percentage = 1, ), ) @@ -627,7 +627,7 @@ "chromium.add_one_test_shard": 10, }, main_list_view = "try", - #tryjob = try_.job(), + tryjob = try_.job(), use_clang_coverage = True, xcode = xcode.xcode_default, )
diff --git a/internal b/internal index 3ddd578..603b258 160000 --- a/internal +++ b/internal
@@ -1 +1 @@ -Subproject commit 3ddd5788bd026e3ed8a3f674c273a6030f54e656 +Subproject commit 603b258ff4ab92ff387e07b3f47c447a52595968
diff --git a/ios/chrome/browser/autofill/ui_bundled/address_editor/autofill_profile_edit_mediator.mm b/ios/chrome/browser/autofill/ui_bundled/address_editor/autofill_profile_edit_mediator.mm index 883c1f71..2899b968 100644 --- a/ios/chrome/browser/autofill/ui_bundled/address_editor/autofill_profile_edit_mediator.mm +++ b/ios/chrome/browser/autofill/ui_bundled/address_editor/autofill_profile_edit_mediator.mm
@@ -401,9 +401,9 @@ const variations::VariationsService* variations_service = GetApplicationContext()->GetVariationsService(); countryModel.SetCountries( - GeoIpCountryCode(variations_service - ? variations_service->GetLatestCountry() - : std::string()), + autofill::GeoIpCountryCode(variations_service + ? variations_service->GetLatestCountry() + : std::string()), GetApplicationContext()->GetApplicationLocaleStorage()->Get()); const autofill::CountryComboboxModel::CountryVector& countriesVector = countryModel.countries();
diff --git a/ios/chrome/browser/autofill/ui_bundled/address_editor/autofill_profile_edit_mediator_unittest.mm b/ios/chrome/browser/autofill/ui_bundled/address_editor/autofill_profile_edit_mediator_unittest.mm index a5c5f302..751d2f33 100644 --- a/ios/chrome/browser/autofill/ui_bundled/address_editor/autofill_profile_edit_mediator_unittest.mm +++ b/ios/chrome/browser/autofill/ui_bundled/address_editor/autofill_profile_edit_mediator_unittest.mm
@@ -139,9 +139,9 @@ const variations::VariationsService* variations_service = GetApplicationContext()->GetVariationsService(); country_model_.SetCountries( - GeoIpCountryCode(variations_service - ? variations_service->GetLatestCountry() - : std::string()), + autofill::GeoIpCountryCode(variations_service + ? variations_service->GetLatestCountry() + : std::string()), GetApplicationContext()->GetApplicationLocaleStorage()->Get()); return country_model_.countries(); }
diff --git a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/settings_autofill_edit_profile_bottom_sheet_handler.mm b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/settings_autofill_edit_profile_bottom_sheet_handler.mm index de31a5b..e172645 100644 --- a/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/settings_autofill_edit_profile_bottom_sheet_handler.mm +++ b/ios/chrome/browser/autofill/ui_bundled/bottom_sheet/settings_autofill_edit_profile_bottom_sheet_handler.mm
@@ -48,7 +48,7 @@ - (std::unique_ptr<autofill::AutofillProfile>)autofillProfile { // Since this is creating a new (empty) address, use the app's locale country // code as the default value. - AddressCountryCode countryCode = + autofill::AddressCountryCode countryCode = _addressDataManager->GetDefaultCountryCodeForNewAddress(); std::unique_ptr<autofill::AutofillProfile> autofillProfile =
diff --git a/ios/chrome/browser/home_customization/coordinator/home_customization_coordinator.mm b/ios/chrome/browser/home_customization/coordinator/home_customization_coordinator.mm index 559f54e..8d49ce8 100644 --- a/ios/chrome/browser/home_customization/coordinator/home_customization_coordinator.mm +++ b/ios/chrome/browser/home_customization/coordinator/home_customization_coordinator.mm
@@ -12,6 +12,7 @@ #import "ios/chrome/browser/home_customization/coordinator/home_customization_background_picker_action_sheet_coordinator.h" #import "ios/chrome/browser/home_customization/coordinator/home_customization_delegate.h" #import "ios/chrome/browser/home_customization/coordinator/home_customization_mediator.h" +#import "ios/chrome/browser/home_customization/model/home_background_customization_service.h" #import "ios/chrome/browser/home_customization/model/home_background_customization_service_factory.h" #import "ios/chrome/browser/home_customization/model/user_uploaded_image_manager_factory.h" #import "ios/chrome/browser/home_customization/ui/home_customization_background_color_picker_view_controller.h" @@ -65,6 +66,10 @@ // before their configuration requests return. NSMutableDictionary<NSString*, SearchEngineLogoMediator*>* _activeSearchEngineLogoMediator; + + // The Background customization service for getting current and recently used + // backgrounds. + raw_ptr<HomeBackgroundCustomizationService> _backgroundService; } // The main page of the customization menu. @@ -102,14 +107,14 @@ _activeSearchEngineLogoMediator = [NSMutableDictionary dictionary]; UserUploadedImageManager* userUploadedImageManager = UserUploadedImageManagerFactory::GetForProfile(self.profile); + _backgroundService = + HomeBackgroundCustomizationServiceFactory::GetForProfile(self.profile); _mediator = [[HomeCustomizationMediator alloc] initWithPrefService:self.profile->GetPrefs() discoverFeedVisibilityBrowserAgent:DiscoverFeedVisibilityBrowserAgent:: FromBrowser(self.browser) - backgroundService: - HomeBackgroundCustomizationServiceFactory:: - GetForProfile(self.profile) + backgroundService:_backgroundService imageFetcherService:imageFetcherService userUploadedImageManager:userUploadedImageManager]; _mediator.navigationDelegate = self; @@ -207,9 +212,8 @@ self.mainViewController.backgroundPickerPresentationDelegate = self; self.mainViewController.mutator = _mediator; self.mainViewController.searchEngineLogoMediatorProvider = self; - self.mainViewController.isNTPCustomBackgroundEnabledByPolicy = - self.profile->GetPrefs()->GetBoolean( - prefs::kNTPCustomBackgroundEnabledByPolicy); + self.mainViewController.customizationDisabledByPolicy = + _backgroundService->IsCustomizationDisabledByPolicy(); self.mediator.mainPageConsumer = self.mainViewController; [self.mediator configureMainPageData]; menuPage = self.mainViewController;
diff --git a/ios/chrome/browser/home_customization/coordinator/home_customization_mediator.mm b/ios/chrome/browser/home_customization/coordinator/home_customization_mediator.mm index 01d23b75..49dde665 100644 --- a/ios/chrome/browser/home_customization/coordinator/home_customization_mediator.mm +++ b/ios/chrome/browser/home_customization/coordinator/home_customization_mediator.mm
@@ -94,7 +94,7 @@ [self.mainPageConsumer populateToggles:toggleMap]; if (IsNTPBackgroundCustomizationEnabled() && - _prefService->GetBoolean(prefs::kNTPCustomBackgroundEnabledByPolicy)) { + !_backgroundService->IsCustomizationDisabledByPolicy()) { BackgroundCollectionConfiguration* collectionConfiguration = [[BackgroundCollectionConfiguration alloc] init];
diff --git a/ios/chrome/browser/home_customization/model/BUILD.gn b/ios/chrome/browser/home_customization/model/BUILD.gn index 25d24ae..980fe96 100644 --- a/ios/chrome/browser/home_customization/model/BUILD.gn +++ b/ios/chrome/browser/home_customization/model/BUILD.gn
@@ -30,6 +30,7 @@ "//components/pref_registry", "//components/sync/protocol", "//components/themes", + "//components/themes:prefs", "//ios/chrome/browser/home_customization/utils", "//ios/chrome/browser/shared/model/application_context", "//ios/chrome/browser/shared/model/prefs:pref_names",
diff --git a/ios/chrome/browser/home_customization/model/home_background_customization_service.h b/ios/chrome/browser/home_customization/model/home_background_customization_service.h index 59960be2..cad36bd6 100644 --- a/ios/chrome/browser/home_customization/model/home_background_customization_service.h +++ b/ios/chrome/browser/home_customization/model/home_background_customization_service.h
@@ -13,6 +13,7 @@ #import "base/task/sequenced_task_runner.h" #import "base/values.h" #import "components/keyed_service/core/keyed_service.h" +#import "components/prefs/pref_change_registrar.h" #import "components/sync/protocol/theme_specifics_ios.pb.h" #import "components/sync/protocol/theme_types.pb.h" #import "ios/chrome/browser/home_customization/model/home_background_data.h" @@ -200,6 +201,9 @@ // to disk. void ClearCurrentUserUploadedBackground(); + // Return whether the NTP custom background is disabled by enterprise policy. + bool IsCustomizationDisabledByPolicy(); + private: // Alerts observers when the background changes. void NotifyObserversOfBackgroundChange(); @@ -250,6 +254,11 @@ void DeleteUserBackgroundImage( HomeUserUploadedBackground user_background_image); + // Observes changes to enterprise policy prefs for theme color + // (kPolicyThemeColor) and custom backgrounds + // (kNTPCustomBackgroundEnabledByPolicy). + void OnPolicyPrefsChanged(const std::string& name); + // Handles the loaded images. void DefaultRecentlyUsedBackgroundsLoaded( const HomeBackgroundImageService::CollectionImageMap& collection_map); @@ -271,6 +280,9 @@ // Service used to load lists of recently used images. raw_ptr<HomeBackgroundImageService> home_background_image_service_; + // Registrar for prefs change. + PrefChangeRegistrar pref_change_registrar_; + base::ObserverList<HomeBackgroundCustomizationServiceObserver> observers_; base::WeakPtrFactory<HomeBackgroundCustomizationService> weak_ptr_factory_;
diff --git a/ios/chrome/browser/home_customization/model/home_background_customization_service.mm b/ios/chrome/browser/home_customization/model/home_background_customization_service.mm index d1e81f7..68ee3ba 100644 --- a/ios/chrome/browser/home_customization/model/home_background_customization_service.mm +++ b/ios/chrome/browser/home_customization/model/home_background_customization_service.mm
@@ -14,6 +14,7 @@ #import "components/prefs/pref_registry_simple.h" #import "components/prefs/pref_service.h" #import "components/sync/protocol/theme_types.pb.h" +#import "components/themes/pref_names.h" #import "ios/chrome/browser/home_customization/model/home_background_customization_service_observer.h" #import "ios/chrome/browser/home_customization/model/home_background_data.h" #import "ios/chrome/browser/home_customization/model/home_background_image_service.h" @@ -74,9 +75,14 @@ user_image_manager_(user_image_manager), home_background_image_service_(home_background_image_service), weak_ptr_factory_{this} { - if (!IsNTPBackgroundCustomizationEnabled()) { - return; - } + pref_change_registrar_.Init(pref_service_); + PrefChangeRegistrar::NamedChangeCallback callback = base::BindRepeating( + &HomeBackgroundCustomizationService::OnPolicyPrefsChanged, + weak_ptr_factory_.GetWeakPtr()); + pref_change_registrar_.Add(themes::prefs::kPolicyThemeColor, callback); + pref_change_registrar_.Add(prefs::kNTPCustomBackgroundEnabledByPolicy, + callback); + LoadCurrentTheme(); const base::Value::List& recently_used_backgrounds_list = @@ -143,6 +149,10 @@ std::optional<HomeCustomBackground> HomeBackgroundCustomizationService::GetCurrentCustomBackground() { + if (IsCustomizationDisabledByPolicy()) { + return std::nullopt; + } + std::optional<HomeUserUploadedBackground> user_uploaded_background = GetCurrentUserUploadedBackground(); if (user_uploaded_background) { @@ -153,6 +163,10 @@ std::optional<sync_pb::NtpCustomBackground> HomeBackgroundCustomizationService::GetCurrentNtpCustomBackground() { + if (IsCustomizationDisabledByPolicy()) { + return std::nullopt; + } + if (!current_theme_.has_ntp_background()) { return std::nullopt; } @@ -161,6 +175,10 @@ std::optional<sync_pb::UserColorTheme> HomeBackgroundCustomizationService::GetCurrentColorTheme() { + if (IsCustomizationDisabledByPolicy()) { + return std::nullopt; + } + if (!current_theme_.has_user_color_theme()) { return std::nullopt; } @@ -346,6 +364,11 @@ if (!IsNTPBackgroundCustomizationEnabled()) { return; } + + if (IsCustomizationDisabledByPolicy()) { + return; + } + current_theme_ = DecodeThemeSpecificsIos( pref_service_->GetString(prefs::kIosSavedThemeSpecificsIos)); @@ -401,6 +424,12 @@ current_user_uploaded_background_ = std::nullopt; } +bool HomeBackgroundCustomizationService::IsCustomizationDisabledByPolicy() { + return !pref_service_->GetBoolean( + prefs::kNTPCustomBackgroundEnabledByPolicy) || + pref_service_->IsManagedPreference(themes::prefs::kPolicyThemeColor); +} + RecentlyUsedBackground HomeBackgroundCustomizationService::ConvertBackgroundRepresentation( RecentlyUsedBackgroundInternal background) { @@ -520,3 +549,16 @@ StoreRecentlyUsedBackgroundsList(); } + +void HomeBackgroundCustomizationService::OnPolicyPrefsChanged( + const std::string& name) { + CHECK(themes::prefs::kPolicyThemeColor == name || + prefs::kNTPCustomBackgroundEnabledByPolicy == name); + if (IsCustomizationDisabledByPolicy()) { + ClearCurrentBackground(); + return; + } + + RestoreCurrentTheme(); + NotifyObserversOfBackgroundChange(); +}
diff --git a/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.h b/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.h index 61e258f..ca73fcf 100644 --- a/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.h +++ b/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.h
@@ -34,8 +34,8 @@ @property(nonatomic, weak) id<HomeCustomizationSearchEngineLogoMediatorProvider> searchEngineLogoMediatorProvider; -// Whether the NTP custom background is enabled by enterprise policy. -@property(nonatomic, assign) BOOL isNTPCustomBackgroundEnabledByPolicy; +// Whether the NTP custom background is disabled by enterprise policy. +@property(nonatomic, assign) BOOL customizationDisabledByPolicy; // Whether interaction with the background customization section is enabled. // Prevents the background from changing when it should not change.
diff --git a/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.mm b/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.mm index 903e22f..ab8fd1a 100644 --- a/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.mm +++ b/ios/chrome/browser/home_customization/ui/home_customization_main_view_controller.mm
@@ -111,8 +111,11 @@ // TODO(crbug.com/439549295): Update the UI to show a message when NTP // customization is blocked by enterprise policy. - if (IsNTPBackgroundCustomizationEnabled() && - self.isNTPCustomBackgroundEnabledByPolicy) { + if (IsNTPBackgroundCustomizationEnabled()) { + if (self.customizationDisabledByPolicy) { + return; + } + _backgroundCellRegistration = [UICollectionViewCellRegistration registrationWithCellClass:[HomeCustomizationBackgroundCell class] configurationHandler:^(HomeCustomizationBackgroundCell* cell, @@ -141,7 +144,7 @@ [[NSDiffableDataSourceSnapshot alloc] init]; if (IsNTPBackgroundCustomizationEnabled() && - self.isNTPCustomBackgroundEnabledByPolicy) { + !self.customizationDisabledByPolicy) { // Create background customization section and add items to it. [snapshot appendSectionsWithIdentifiers:@[ kCustomizationSectionBackground ]];
diff --git a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator.mm b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator.mm index 73ba615..9028977 100644 --- a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator.mm +++ b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_coordinator.mm
@@ -381,9 +381,7 @@ [self configureNTPViewController]; [self configureTabGroupIndicator]; - if (IsNTPBackgroundCustomizationEnabled() && - self.prefService->GetBoolean( - prefs::kNTPCustomBackgroundEnabledByPolicy)) { + if (IsNTPBackgroundCustomizationEnabled()) { // Ensure the initial background is applied after all components have been // set up. [self.NTPMediator updateBackground];
diff --git a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_mediator.mm b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_mediator.mm index a1cac9d..e091f6b 100644 --- a/ios/chrome/browser/ntp/ui_bundled/new_tab_page_mediator.mm +++ b/ios/chrome/browser/ntp/ui_bundled/new_tab_page_mediator.mm
@@ -327,8 +327,7 @@ _browserViewVisibilityNotifierBrowserAgent->AddObserver( _browserViewVisibilityObserverBridge.get()); _discoverFeedVisibilityBrowserAgent->AddObserver(self.feedVisibilityObserver); - if (IsNTPBackgroundCustomizationEnabled() && - _prefService->GetBoolean(prefs::kNTPCustomBackgroundEnabledByPolicy)) { + if (IsNTPBackgroundCustomizationEnabled()) { _backgroundCustomizationServiceObserverBridge = std::make_unique<HomeBackgroundCustomizationServiceObserverBridge>( _backgroundCustomizationService, self);
diff --git a/ios/web_view/internal/autofill/cwv_autofill_form_unittest.mm b/ios/web_view/internal/autofill/cwv_autofill_form_unittest.mm index 8d070d2..9cf63a3d 100644 --- a/ios/web_view/internal/autofill/cwv_autofill_form_unittest.mm +++ b/ios/web_view/internal/autofill/cwv_autofill_form_unittest.mm
@@ -38,11 +38,12 @@ std::unique_ptr<autofill::FormStructure> form_structure = std::make_unique<autofill::FormStructure>(form_data); const autofill::HeuristicPredictions heuristic_predictions = - DetermineHeuristicTypes(GeoIpCountryCode(""), autofill::LanguageCode(""), - *form_structure, nullptr); + DetermineHeuristicTypes(autofill::GeoIpCountryCode(""), + autofill::LanguageCode(""), *form_structure, + nullptr); heuristic_predictions.ApplyTo(form_structure->fields()); form_structure->RationalizeAndAssignSections( - GeoIpCountryCode(""), autofill::LanguageCode(""), nullptr); + autofill::GeoIpCountryCode(""), autofill::LanguageCode(""), nullptr); CWVAutofillForm* form = [[CWVAutofillForm alloc] initWithFormStructure:*form_structure]; EXPECT_NSEQ(base::SysUTF16ToNSString(form_data.name()), form.name);
diff --git a/ios_internal b/ios_internal index 6842118..3001825 160000 --- a/ios_internal +++ b/ios_internal
@@ -1 +1 @@ -Subproject commit 684211821a75efa2da3fad3310385a55308a3da6 +Subproject commit 30018259e2511654b23013f465ac3d119a7e4a35
diff --git a/media/capture/video/android/video_capture_device_android.cc b/media/capture/video/android/video_capture_device_android.cc index f049013..f8acb54c 100644 --- a/media/capture/video/android/video_capture_device_android.cc +++ b/media/capture/video/android/video_capture_device_android.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/capture/video/android/video_capture_device_android.h" #include <stdint.h> @@ -17,6 +12,7 @@ #include "base/android/jni_android.h" #include "base/android/jni_array.h" #include "base/android/jni_string.h" +#include "base/containers/heap_array.h" #include "base/functional/bind.h" #include "base/numerics/safe_conversions.h" #include "base/strings/string_number_conversions.h" @@ -364,15 +360,20 @@ const int y_plane_length = width * height; const int uv_plane_length = y_plane_length / 4; const int buffer_length = y_plane_length + uv_plane_length * 2; - auto buffer = std::make_unique<uint8_t[]>(buffer_length); + auto buffer = base::HeapArray<uint8_t>::Uninit( + base::checked_cast<size_t>(buffer_length)); + + auto dst_y_span = buffer.subspan(0, y_plane_length); + auto dst_u_span = buffer.subspan(y_plane_length, uv_plane_length); + auto dst_v_span = + buffer.subspan(y_plane_length + uv_plane_length, uv_plane_length); libyuv::Android420ToI420(y_src, y_stride, u_src, uv_row_stride, v_src, - uv_row_stride, uv_pixel_stride, buffer.get(), width, - buffer.get() + y_plane_length, width / 2, - buffer.get() + y_plane_length + uv_plane_length, - width / 2, width, height); + uv_row_stride, uv_pixel_stride, dst_y_span.data(), + width, dst_u_span.data(), width / 2, + dst_v_span.data(), width / 2, width, height); - SendIncomingDataToClient(buffer.get(), buffer_length, rotation, current_time, + SendIncomingDataToClient(buffer.data(), buffer_length, rotation, current_time, capture_time); }
diff --git a/mojo/public/cpp/bindings/direct_receiver.h b/mojo/public/cpp/bindings/direct_receiver.h index e57565a..e74c60a7 100644 --- a/mojo/public/cpp/bindings/direct_receiver.h +++ b/mojo/public/cpp/bindings/direct_receiver.h
@@ -155,6 +155,10 @@ // DirectReceiver, passing pipes to your DirectReceiver is likely a BAD IDEA. template <typename T> class DirectReceiver { + static_assert( + T::kSupportsDirectReceiver, + "This interface must be marked with the [DirectReceiver] attribute."); + public: DirectReceiver(DirectReceiverKey, T* impl) : receiver_(impl) {} ~DirectReceiver() = default;
diff --git a/mojo/public/cpp/bindings/tests/direct_receiver_unittest.test-mojom b/mojo/public/cpp/bindings/tests/direct_receiver_unittest.test-mojom index d86b8f1..f1c0033d 100644 --- a/mojo/public/cpp/bindings/tests/direct_receiver_unittest.test-mojom +++ b/mojo/public/cpp/bindings/tests/direct_receiver_unittest.test-mojom
@@ -4,7 +4,7 @@ module mojo.test.direct_receiver_unittest.mojom; +[DirectReceiver] interface Service { Ping() => (); }; -
diff --git a/mojo/public/tools/bindings/BUILD.gn b/mojo/public/tools/bindings/BUILD.gn index b99a4911..6216ac9 100644 --- a/mojo/public/tools/bindings/BUILD.gn +++ b/mojo/public/tools/bindings/BUILD.gn
@@ -40,7 +40,6 @@ "$mojom_generator_root/generators/cpp_templates/struct_declaration.tmpl", "$mojom_generator_root/generators/cpp_templates/struct_definition.tmpl", "$mojom_generator_root/generators/cpp_templates/struct_macros.tmpl", - "$mojom_generator_root/generators/cpp_templates/struct_macros.tmpl", "$mojom_generator_root/generators/cpp_templates/struct_serialization_declaration.tmpl", "$mojom_generator_root/generators/cpp_templates/struct_traits_declaration.tmpl", "$mojom_generator_root/generators/cpp_templates/struct_traits_definition.tmpl", @@ -132,6 +131,7 @@ data = [ mojom_generator_script, "checks/mojom_attributes_check_unittest.py", + "checks/mojom_interface_direct_receiver_check_unittest.py", "checks/mojom_interface_feature_check_unittest.py", "checks/mojom_restrictions_checks_unittest.py", "mojom_bindings_generator_unittest.py",
diff --git a/mojo/public/tools/bindings/checks/mojom_attributes_check.py b/mojo/public/tools/bindings/checks/mojom_attributes_check.py index e1b7a42d..7f4e1d1 100644 --- a/mojo/public/tools/bindings/checks/mojom_attributes_check.py +++ b/mojo/public/tools/bindings/checks/mojom_attributes_check.py
@@ -35,6 +35,7 @@ _INTERFACE_ATTRIBUTES = _COMMON_ATTRIBUTES | { 'DispatchDebugAlias', + 'DirectReceiver', 'RenamedFrom', 'RequireContext', 'RuntimeFeature',
diff --git a/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py b/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py index 0498faf..e0dc5b2 100644 --- a/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py +++ b/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py
@@ -103,6 +103,11 @@ [SendValidation=test.mojom.FeatureName] MethodWithSendValidation(Thingy thing); }; + + [DirectReceiver] + interface FooWithDirectReceiver { + Method(); + }; """) def testWrongModuleStable(self):
diff --git a/mojo/public/tools/bindings/checks/mojom_interface_direct_receiver_check.py b/mojo/public/tools/bindings/checks/mojom_interface_direct_receiver_check.py new file mode 100644 index 0000000..225f4a6 --- /dev/null +++ b/mojo/public/tools/bindings/checks/mojom_interface_direct_receiver_check.py
@@ -0,0 +1,60 @@ +# 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. +"""Validate mojo interfaces passed by [DirectReceiver] interfaces are also +[DirectReceiver] interfaces.""" + +import mojom.generate.check as check +import mojom.generate.module as module + + +class Check(check.Check): + + def __init__(self, *args, **kwargs): + super(Check, self).__init__(*args, **kwargs) + + # `param` can be a lot of things so check if it is a remote/receiver. + # Array/Map must be recursed into. + def _CheckFieldOrParam(self, kind): + if module.IsAnyInterfaceKind(kind): + if module.IsPendingRemoteKind(kind): + return + if module.IsPendingAssociatedRemoteKind(kind): + return + if not kind.kind.direct_receiver: + interface = kind.kind.mojom_name + raise check.CheckException( + self.module, f"interface {interface} must be a DirectReceiver") + if module.IsStructKind(kind): + self._CheckStruct(kind) + if module.IsUnionKind(kind): + self._CheckUnion(kind) + if module.IsArrayKind(kind): + self._CheckFieldOrParam(kind.kind) + if module.IsMapKind(kind): + self._CheckFieldOrParam(kind.key_kind) + self._CheckFieldOrParam(kind.value_kind) + + def _CheckInterface(self, interface): + if not interface.direct_receiver: + return + for method in interface.methods: + for param in method.parameters: + self._CheckFieldOrParam(param.kind) + if method.response_parameters: + for param in method.response_parameters: + self._CheckFieldOrParam(param.kind) + + def _CheckStruct(self, struct): + for field in struct.fields: + self._CheckFieldOrParam(field.kind) + + def _CheckUnion(self, union): + for field in union.fields: + self._CheckFieldOrParam(field.kind) + + def CheckModule(self): + """Validate that any runtime feature guarded interfaces that might be passed + over mojo are nullable.""" + for interface in self.module.interfaces: + self._CheckInterface(interface)
diff --git a/mojo/public/tools/bindings/checks/mojom_interface_direct_receiver_check_unittest.py b/mojo/public/tools/bindings/checks/mojom_interface_direct_receiver_check_unittest.py new file mode 100644 index 0000000..cbf671b --- /dev/null +++ b/mojo/public/tools/bindings/checks/mojom_interface_direct_receiver_check_unittest.py
@@ -0,0 +1,154 @@ +# Copyright 2025 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import unittest + +import mojom.generate.check as check +from mojom_bindings_generator import LoadChecks, _Generate +from mojom_parser_test_case import MojomParserTestCase + + +class FakeArgs: + """Fakes args to _Generate - intention is to do just enough to run checks""" + + def __init__(self, tester, files=None): + """ `tester` is MojomParserTestCase for paths. + `files` will have tester path added.""" + self.checks_string = 'direct_receiver' + self.depth = tester.GetPath('') + self.filelist = None + self.filename = [tester.GetPath(x) for x in files] + self.gen_directories = tester.GetPath('gen') + self.generators_string = '' + self.import_directories = [] + self.output_dir = tester.GetPath('out') + self.scrambled_message_id_salt_paths = None + self.typemaps = [] + self.variant = 'none' + + +class MojoBindingsCheckTest(MojomParserTestCase): + + def _ParseAndGenerate(self, mojoms): + self.ParseMojoms(mojoms) + args = FakeArgs(self, files=mojoms) + _Generate(args, {}) + + def assertValid(self, filename, content): + self.WriteFile(filename, content) + self._ParseAndGenerate([filename]) + + def assertThrows(self, filename, content, regexp): + mojoms = [] + self.WriteFile(filename, content) + mojoms.append(filename) + with self.assertRaisesRegex(check.CheckException, regexp): + self._ParseAndGenerate(mojoms) + + def testLoads(self): + """Validate that the check is registered under the expected name.""" + check_modules = LoadChecks('direct_receiver') + self.assertTrue(check_modules['direct_receiver']) + + def testAllowedWhenAnnotated(self): + self.assertValid( + "a.mojom", """ + module a; + struct B { + pending_associated_receiver<MyInterface3> passed; + pending_remote<Normal> normal; + }; + interface Normal {}; + [DirectReceiver] + interface MyInterface { + Method(pending_receiver<MyInterface2> passed); + Method2(B b, pending_associated_remote<Normal> normal); + }; + [DirectReceiver] + interface MyInterface2 { + }; + [DirectReceiver] + interface MyInterface3 { + }; + """) + + def testMissingViaMethod(self): + contents = """ + module a; + interface A {}; + + [DirectReceiver] + interface B { + Method(pending_receiver<A> a); + }; + """ + self.assertThrows('b.mojom', contents, + 'interface A must be a DirectReceiver') + + def testMissingViaStruct(self): + contents = """ + module a; + interface A {}; + struct C { + pending_receiver<A> a; + }; + + [DirectReceiver] + interface B { + Method(C c); + }; + """ + self.assertThrows('b.mojom', contents, + 'interface A must be a DirectReceiver') + + def testMissingViaUnion(self): + contents = """ + module a; + interface A {}; + union C { + pending_receiver<A> a; + bool other; + }; + + [DirectReceiver] + interface B { + Method(C c); + }; + """ + self.assertThrows('b.mojom', contents, + 'interface A must be a DirectReceiver') + + def testMissingViaArray(self): + contents = """ + module a; + interface A {}; + struct C { + array<pending_receiver<A>> a; + bool other; + }; + + [DirectReceiver] + interface B { + Method(C c); + }; + """ + self.assertThrows('b.mojom', contents, + 'interface A must be a DirectReceiver') + + def testMissingViaMap(self): + contents = """ + module a; + interface A {}; + struct C { + map<string, pending_receiver<A>> a; + bool other; + }; + + [DirectReceiver] + interface B { + Method(C c); + }; + """ + self.assertThrows('b.mojom', contents, + 'interface A must be a DirectReceiver')
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl index fac7c13b..b9ef3fb6 100644 --- a/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl +++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl
@@ -29,6 +29,9 @@ static constexpr base::Token Uuid_{ {{interface.uuid[0]}}ULL, {{interface.uuid[1]}}ULL }; {%- endif %} +{%- if interface.direct_receiver %} + static constexpr bool kSupportsDirectReceiver = true; +{%- endif %} {%- if interface.service_sandbox %} {%- set sandbox_enum = "%s"|format(interface.service_sandbox.GetSpec()|replace(".","::")) %} static constexpr auto kServiceSandbox = {{ sandbox_enum }};
diff --git a/mojo/public/tools/bindings/mojom.gni b/mojo/public/tools/bindings/mojom.gni index b8d5bad..87b3bbe9 100644 --- a/mojo/public/tools/bindings/mojom.gni +++ b/mojo/public/tools/bindings/mojom.gni
@@ -107,6 +107,7 @@ "$mojom_generator_root/checks/__init__.py", "$mojom_generator_root/checks/mojom_attributes_check.py", "$mojom_generator_root/checks/mojom_definitions_check.py", + "$mojom_generator_root/checks/mojom_interface_direct_receiver_check.py", "$mojom_generator_root/checks/mojom_interface_feature_check.py", "$mojom_generator_root/checks/mojom_restrictions_check.py", "$mojom_generator_root/generators/__init__.py",
diff --git a/mojo/public/tools/bindings/mojom_bindings_generator.py b/mojo/public/tools/bindings/mojom_bindings_generator.py index fc327e89..229d5889 100755 --- a/mojo/public/tools/bindings/mojom_bindings_generator.py +++ b/mojo/public/tools/bindings/mojom_bindings_generator.py
@@ -57,6 +57,7 @@ _BUILTIN_CHECKS = { "attributes": "mojom_attributes_check", "definitions": "mojom_definitions_check", + "direct_receiver": "mojom_interface_direct_receiver_check", "features": "mojom_interface_feature_check", "restrictions": "mojom_restrictions_check", }
diff --git a/mojo/public/tools/mojom/mojom/generate/module.py b/mojo/public/tools/mojom/mojom/generate/module.py index 33eb6e0..46d4e31 100644 --- a/mojo/public/tools/mojom/mojom/generate/module.py +++ b/mojo/public/tools/mojom/mojom/generate/module.py
@@ -389,6 +389,7 @@ ATTRIBUTE_ALLOWED_CONTEXT = 'AllowedContext' ATTRIBUTE_DEFAULT = 'Default' +ATTRIBUTE_DIRECT_RECEIVER = 'DirectReceiver' ATTRIBUTE_DISPATCH_DEBUG_ALIAS = 'DispatchDebugAlias' ATTRIBUTE_ESTIMATE_SIZE = 'EstimateSize' ATTRIBUTE_EXTENSIBLE = 'Extensible' @@ -1167,6 +1168,11 @@ constant.Stylize(stylizer) @property + def direct_receiver(self): + return self.attributes.get(ATTRIBUTE_DIRECT_RECEIVER, False) \ + if self.attributes else False + + @property def service_sandbox(self): if not self.attributes: return None
diff --git a/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java b/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java index ad753250..63291e4 100644 --- a/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java +++ b/net/android/java/src/org/chromium/net/NetworkChangeNotifier.java
@@ -38,7 +38,7 @@ * The alert is fired on the UI thread. */ public interface ConnectionTypeObserver { - public void onConnectionTypeChanged(int connectionType); + void onConnectionTypeChanged(int connectionType); } private final ArrayList<Long> mNativeChangeNotifiers;
diff --git a/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java b/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java index 15dd657..2cac8c2 100644 --- a/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java +++ b/net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java
@@ -995,48 +995,45 @@ private boolean mRegisterNetworkCallbackFailed; /** Observer interface by which observer is notified of network changes. */ - public static interface Observer { + public interface Observer { /** Called when default network changes. */ - public void onConnectionTypeChanged(@ConnectionType int newConnectionType); + void onConnectionTypeChanged(@ConnectionType int newConnectionType); /** Called when connection cost of default network changes. */ - public void onConnectionCostChanged(int newConnectionCost); + void onConnectionCostChanged(int newConnectionCost); /** Called when connection subtype of default network changes. */ - public void onConnectionSubtypeChanged(int newConnectionSubtype); + void onConnectionSubtypeChanged(int newConnectionSubtype); /** - * Called when device connects to network with NetID netId. For - * example device associates with a WiFi access point. - * connectionType is the type of the network; a member of + * Called when device connects to network with NetID netId. For example device associates + * with a WiFi access point. connectionType is the type of the network; a member of * ConnectionType. Only called on Android L and above. */ - public void onNetworkConnect(long netId, int connectionType); + void onNetworkConnect(long netId, int connectionType); /** - * Called when device determines the connection to the network with - * NetID netId is no longer preferred, for example when a device - * transitions from cellular to WiFi it might deem the cellular - * connection no longer preferred. The device will disconnect from - * the network in 30s allowing network communications on that network - * to wrap up. Only called on Android L and above. + * Called when device determines the connection to the network with NetID netId is no longer + * preferred, for example when a device transitions from cellular to WiFi it might deem the + * cellular connection no longer preferred. The device will disconnect from the network in + * 30s allowing network communications on that network to wrap up. Only called on Android L + * and above. */ - public void onNetworkSoonToDisconnect(long netId); + void onNetworkSoonToDisconnect(long netId); /** - * Called when device disconnects from network with NetID netId. - * Only called on Android L and above. + * Called when device disconnects from network with NetID netId. Only called on Android L + * and above. */ - public void onNetworkDisconnect(long netId); + void onNetworkDisconnect(long netId); /** - * Called to cause a purge of cached lists of active networks, of any - * networks not in the accompanying list of active networks. This is - * issued if a period elapsed where disconnected notifications may have - * been missed, and acts to keep cached lists of active networks - * accurate. Only called on Android L and above. + * Called to cause a purge of cached lists of active networks, of any networks not in the + * accompanying list of active networks. This is issued if a period elapsed where + * disconnected notifications may have been missed, and acts to keep cached lists of active + * networks accurate. Only called on Android L and above. */ - public void purgeActiveNetworkList(long[] activeNetIds); + void purgeActiveNetworkList(long[] activeNetIds); } /**
diff --git a/net/android/java/src/org/chromium/net/ProxyChangeListener.java b/net/android/java/src/org/chromium/net/ProxyChangeListener.java index 2ade51b..a4af8a0e 100644 --- a/net/android/java/src/org/chromium/net/ProxyChangeListener.java +++ b/net/android/java/src/org/chromium/net/ProxyChangeListener.java
@@ -113,7 +113,7 @@ /** The delegate for ProxyChangeListener. Use for testing. */ public interface Delegate { - public void proxySettingsChanged(); + void proxySettingsChanged(); } private ProxyChangeListener() {
diff --git a/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java b/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java index 01a9311..d6646ea2 100644 --- a/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java +++ b/net/android/javatests/src/org/chromium/net/NetworkChangeNotifierTest.java
@@ -369,7 +369,7 @@ // Types of network changes. Each is associated with a NetworkChangeNotifierAutoDetect.Observer // callback, and NONE is provided to indicate no callback observed. - private static enum ChangeType { + private enum ChangeType { NONE, CONNECT, SOON_TO_DISCONNECT, @@ -459,7 +459,7 @@ private MockConnectivityManagerDelegate mConnectivityDelegate; private MockWifiManagerDelegate mWifiDelegate; - private static enum WatchForChanges { + private enum WatchForChanges { ALWAYS, ONLY_WHEN_APP_IN_FOREGROUND, }
diff --git a/net/base/features.h b/net/base/features.h index fca06d14..df5d808a 100644 --- a/net/base/features.h +++ b/net/base/features.h
@@ -587,9 +587,12 @@ NET_EXPORT extern const base::FeatureParam<bool> kIpPrivacyEnableIppInDevTools; // Enables the ability for IP protection features to be gated in the Privacy -// and Security Panel within DevTools. When this flag is disabled, the IP -// Protection section will not be shown in the DevTools panel, allowing testing -// and development of the IP Protection features before public release. +// and Security Panel within DevTools. When this flag is enabled, the IP +// Protection section will be shown in the Privacy and Security section of the +// DevTools panel allowing users to view proxied requests and bypass IP +// Protection locally. +// Do not remove or enable this flag for all users until crbug.com/442349180 +// is resolved. NET_EXPORT extern const base::FeatureParam<bool> kIpPrivacyEnableIppPanelInDevTools;
diff --git a/net/extras/sqlite/sqlite_persistent_cookie_store.cc b/net/extras/sqlite/sqlite_persistent_cookie_store.cc index 05bfa24c..9eeffeef 100644 --- a/net/extras/sqlite/sqlite_persistent_cookie_store.cc +++ b/net/extras/sqlite/sqlite_persistent_cookie_store.cc
@@ -197,14 +197,14 @@ // Version 21 - 2023/11/22 - https://crrev.com/c/5049032 // Version 20 - 2023/11/14 - https://crrev.com/c/5030577 // Version 19 - 2023/09/22 - https://crrev.com/c/4704672 -// Version 18 - 2022/04/19 - https://crrev.com/c/3594203 // // Versions older than two years should be removed and marked as unsupported. -// This was last done in February 2024. https://crrev.com/c/5300252 +// This was last done in September 2025. https://crrev.com/c/6819011 // Be sure to update SQLitePersistentCookieStoreTest.TestInvalidVersionRecovery // to test the latest unsupported version number. // // Unsupported versions: +// Version 18 - 2022/04/19 - https://crrev.com/c/3594203 // Version 17 - 2022/01/25 - https://crrev.com/c/3416230 // Version 16 - 2021/09/10 - https://crrev.com/c/3152897 // Version 15 - 2021/07/01 - https://crrev.com/c/3001822 @@ -1148,38 +1148,6 @@ SQLitePersistentCookieStore::Backend::DoMigrateDatabaseSchema() { int cur_version = meta_table()->GetVersionNumber(); - if (cur_version == 18) { - SCOPED_UMA_HISTOGRAM_TIMER("Cookie.TimeDatabaseMigrationToV19"); - - sql::Statement update_statement( - db()->GetCachedStatement(SQL_FROM_HERE, - "UPDATE cookies SET expires_utc = ? WHERE " - "has_expires = 1 AND expires_utc > ?")); - if (!update_statement.is_valid()) { - return std::nullopt; - } - - sql::Transaction transaction(db()); - if (!transaction.Begin()) { - return std::nullopt; - } - - base::Time expires_cap = base::Time::Now() + base::Days(400); - update_statement.BindTime(0, expires_cap); - update_statement.BindTime(1, expires_cap); - if (!update_statement.Run()) { - return std::nullopt; - } - - ++cur_version; - if (!meta_table()->SetVersionNumber(cur_version) || - !meta_table()->SetCompatibleVersionNumber( - std::min(cur_version, kCompatibleVersionNumber)) || - !transaction.Commit()) { - return std::nullopt; - } - } - if (cur_version == 19) { SCOPED_UMA_HISTOGRAM_TIMER("Cookie.TimeDatabaseMigrationToV20");
diff --git a/net/extras/sqlite/sqlite_persistent_cookie_store_unittest.cc b/net/extras/sqlite/sqlite_persistent_cookie_store_unittest.cc index 28a6428..6b7181a 100644 --- a/net/extras/sqlite/sqlite_persistent_cookie_store_unittest.cc +++ b/net/extras/sqlite/sqlite_persistent_cookie_store_unittest.cc
@@ -370,7 +370,7 @@ ASSERT_TRUE(meta_table.Init(&db, 1, 1)); // Keep in sync with latest unsupported version from: // net/extras/sqlite/sqlite_persistent_cookie_store.cc - ASSERT_TRUE(meta_table.SetVersionNumber(17)); + ASSERT_TRUE(meta_table.SetVersionNumber(18)); } // Upon loading, the database should be reset to a good, blank state. @@ -1694,13 +1694,13 @@ sql::SqliteLoggedResultCode::kNotADatabase, 1); } -bool CreateV18Schema(sql::Database* db) { +bool CreateV19Schema(sql::Database* db) { sql::MetaTable meta_table; - if (!meta_table.Init(db, 18, 18)) { + if (!meta_table.Init(db, 19, 19)) { return false; } - // Version 18 schema + // Version 19 schema static constexpr char kCreateSql[] = "CREATE TABLE cookies(" "creation_utc INTEGER NOT NULL," @@ -1938,11 +1938,7 @@ return cookies; } -// Versions 18, 19, and 20 use the same schema so they can reuse this function. -// AddV20CookiesToDB (and future versions) need to set max_expiration_delta to -// base::Days(400) to simulate expiration limits introduced in version 19. -bool AddV18CookiesToDB(sql::Database* db, - base::TimeDelta max_expiration_delta) { +bool AddV19or20CookiesToDB(sql::Database* db) { std::vector<CanonicalCookie> cookies = CookiesForMigrationTest(); sql::Statement statement(db->GetCachedStatement( SQL_FROM_HERE, @@ -1959,7 +1955,7 @@ return false; } for (const CanonicalCookie& cookie : cookies) { - base::Time max_expiration(cookie.CreationDate() + max_expiration_delta); + base::Time max_expiration(cookie.CreationDate() + base::Days(400)); statement.Reset(true); statement.BindTime(0, cookie.CreationDate()); @@ -2000,10 +1996,6 @@ return transaction.Commit(); } -bool AddV20CookiesToDB(sql::Database* db) { - return AddV18CookiesToDB(db, base::Days(400)); -} - bool AddV21CookiesToDB(sql::Database* db) { std::vector<CanonicalCookie> cookies = CookiesForMigrationTest(); sql::Statement statement(db->GetCachedStatement( @@ -2320,29 +2312,6 @@ ASSERT_GE(GetDBCurrentVersionNumber(&connection), version); } -TEST_F(SQLitePersistentCookieStoreTest, UpgradeToSchemaVersion19) { - // Open db. - const base::FilePath database_path = - temp_dir_.GetPath().Append(kCookieFilename); - { - sql::Database connection(sql::test::kTestTag); - ASSERT_TRUE(connection.Open(database_path)); - ASSERT_TRUE(CreateV18Schema(&connection)); - ASSERT_EQ(GetDBCurrentVersionNumber(&connection), 18); - ASSERT_TRUE(AddV18CookiesToDB(&connection, base::TimeDelta::Max())); - } - - CanonicalCookieVector read_in_cookies = CreateAndLoad( - /*crypt_cookies=*/false, /*restore_old_session_cookies=*/false); - ASSERT_NO_FATAL_FAILURE( - ConfirmCookiesAfterMigrationTest(std::move(read_in_cookies), - /*expect_last_update_date=*/true)); - DestroyStore(); - - ASSERT_NO_FATAL_FAILURE( - ConfirmDatabaseVersionAfterMigration(database_path, 19)); -} - TEST_F(SQLitePersistentCookieStoreTest, UpgradeToSchemaVersion20) { // Open db. const base::FilePath database_path = @@ -2350,10 +2319,9 @@ { sql::Database connection(sql::test::kTestTag); ASSERT_TRUE(connection.Open(database_path)); - // V19's schema is the same as V18, so we can reuse the creation function. - ASSERT_TRUE(CreateV18Schema(&connection)); - ASSERT_EQ(GetDBCurrentVersionNumber(&connection), 18); - ASSERT_TRUE(AddV18CookiesToDB(&connection, base::TimeDelta::Max())); + ASSERT_TRUE(CreateV19Schema(&connection)); + ASSERT_EQ(GetDBCurrentVersionNumber(&connection), 19); + ASSERT_TRUE(AddV19or20CookiesToDB(&connection)); } CanonicalCookieVector read_in_cookies = CreateAndLoad( @@ -2376,7 +2344,7 @@ ASSERT_TRUE(connection.Open(database_path)); ASSERT_TRUE(CreateV20Schema(&connection)); ASSERT_EQ(GetDBCurrentVersionNumber(&connection), 20); - ASSERT_TRUE(AddV20CookiesToDB(&connection)); + ASSERT_TRUE(AddV19or20CookiesToDB(&connection)); } CanonicalCookieVector read_in_cookies = CreateAndLoad(
diff --git a/pdf/pdfium/pdfium_engine.cc b/pdf/pdfium/pdfium_engine.cc index a04a920..b574eae 100644 --- a/pdf/pdfium/pdfium_engine.cc +++ b/pdf/pdfium/pdfium_engine.cc
@@ -1509,7 +1509,7 @@ caret_->SetChar( PageCharacterIndex(point_data.page_index, point_data.char_index)); } - } else if (click_count == 2 || click_count == 3) { + } else if (click_count >= 2) { OnMultipleClick(click_count, point_data.page_index, point_data.char_index); } }
diff --git a/pdf/pdfium/pdfium_engine_unittest.cc b/pdf/pdfium/pdfium_engine_unittest.cc index 638e10d..750057dec 100644 --- a/pdf/pdfium/pdfium_engine_unittest.cc +++ b/pdf/pdfium/pdfium_engine_unittest.cc
@@ -143,6 +143,24 @@ #endif // BUILDFLAG(ENABLE_PDF_INK2) }; +void SimulateMultiClick(PDFiumEngine& engine, + const gfx::PointF& position, + int click_count) { + for (int i = 0, click = 1; i < click_count; ++i, ++click) { +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) + // On both Linux and ChromeOS `click_count` is only 1, 2 or 3. On MacOS and + // Windows `click_count` just keeps increasing as the user keeps clicking. + if (click > 3) { + click = 1; + } +#endif + EXPECT_TRUE(engine.HandleInputEvent(MouseEventBuilder() + .CreateLeftClickAtPosition(position) + .SetClickCount(click) + .Build())); + } +} + } // namespace class PDFiumEngineTest : public PDFiumTestBase { @@ -866,10 +884,7 @@ EXPECT_THAT(engine->GetSelectedText(), IsEmpty()); constexpr gfx::PointF kPosition(100, 120); - EXPECT_TRUE(engine->HandleInputEvent(MouseEventBuilder() - .CreateLeftClickAtPosition(kPosition) - .SetClickCount(2) - .Build())); + SimulateMultiClick(*engine, kPosition, 2); EXPECT_EQ("Goodbye", engine->GetSelectedText()); } @@ -885,10 +900,63 @@ EXPECT_THAT(engine->GetSelectedText(), IsEmpty()); constexpr gfx::PointF kPosition(100, 120); - EXPECT_TRUE(engine->HandleInputEvent(MouseEventBuilder() - .CreateLeftClickAtPosition(kPosition) - .SetClickCount(3) - .Build())); + SimulateMultiClick(*engine, kPosition, 3); + EXPECT_EQ("Goodbye, world!", engine->GetSelectedText()); +} + +TEST_P(PDFiumEngineTest, SelectTextWithFourClicks) { + TestClient client; + std::unique_ptr<PDFiumEngine> engine = + InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf")); + ASSERT_TRUE(engine); + + // Plugin size chosen so all pages of the document are visible. + engine->PluginSizeUpdated({1024, 4096}); + + EXPECT_THAT(engine->GetSelectedText(), IsEmpty()); + + constexpr gfx::PointF kPosition(100, 120); + SimulateMultiClick(*engine, kPosition, 4); +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) + EXPECT_THAT(engine->GetSelectedText(), IsEmpty()); +#else + EXPECT_EQ("Goodbye, world!", engine->GetSelectedText()); +#endif +} + +TEST_P(PDFiumEngineTest, SelectTextFiveClicks) { + TestClient client; + std::unique_ptr<PDFiumEngine> engine = + InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf")); + ASSERT_TRUE(engine); + + // Plugin size chosen so all pages of the document are visible. + engine->PluginSizeUpdated({1024, 4096}); + + EXPECT_THAT(engine->GetSelectedText(), IsEmpty()); + + constexpr gfx::PointF kPosition(100, 120); + SimulateMultiClick(*engine, kPosition, 5); +#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) + EXPECT_EQ("Goodbye", engine->GetSelectedText()); +#else + EXPECT_EQ("Goodbye, world!", engine->GetSelectedText()); +#endif +} + +TEST_P(PDFiumEngineTest, SelectTextWithSixClicks) { + TestClient client; + std::unique_ptr<PDFiumEngine> engine = + InitializeEngine(&client, FILE_PATH_LITERAL("hello_world2.pdf")); + ASSERT_TRUE(engine); + + // Plugin size chosen so all pages of the document are visible. + engine->PluginSizeUpdated({1024, 4096}); + + EXPECT_THAT(engine->GetSelectedText(), IsEmpty()); + + constexpr gfx::PointF kPosition(100, 120); + SimulateMultiClick(*engine, kPosition, 6); EXPECT_EQ("Goodbye, world!", engine->GetSelectedText()); }
diff --git a/services/network/BUILD.gn b/services/network/BUILD.gn index 9bd750e44..7e605af 100644 --- a/services/network/BUILD.gn +++ b/services/network/BUILD.gn
@@ -61,8 +61,8 @@ "host_resolver.h", "host_resolver_mdns_listener.cc", "host_resolver_mdns_listener.h", - "http_auth_cache_copier.cc", - "http_auth_cache_copier.h", + "http_auth_cache_proxy_copier.cc", + "http_auth_cache_proxy_copier.h", "http_cache_data_counter.cc", "http_cache_data_counter.h", "http_cache_data_remover.cc",
diff --git a/services/network/http_auth_cache_copier.cc b/services/network/http_auth_cache_copier.cc deleted file mode 100644 index 3aff7b57..0000000 --- a/services/network/http_auth_cache_copier.cc +++ /dev/null
@@ -1,42 +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 "services/network/http_auth_cache_copier.h" - -#include "base/logging.h" -#include "net/http/http_auth_cache.h" - -namespace network { - -HttpAuthCacheCopier::HttpAuthCacheCopier() = default; -HttpAuthCacheCopier::~HttpAuthCacheCopier() = default; - -base::UnguessableToken HttpAuthCacheCopier::SaveHttpAuthCache( - const net::HttpAuthCache& cache) { - base::UnguessableToken key = base::UnguessableToken::Create(); - auto cache_it = caches_.emplace( - key, std::make_unique<net::HttpAuthCache>( - cache.key_server_entries_by_network_anonymization_key())); - DCHECK(cache_it.second); - cache_it.first->second->CopyProxyEntriesFrom(cache); - return key; -} - -void HttpAuthCacheCopier::LoadHttpAuthCache(const base::UnguessableToken& key, - net::HttpAuthCache* cache) { - auto it = caches_.find(key); - if (it == caches_.end()) { - DLOG(ERROR) << "Unknown HttpAuthCache key: " << key; - return; - } - - // Source and destination caches must have the same configuration. - DCHECK_EQ(cache->key_server_entries_by_network_anonymization_key(), - it->second->key_server_entries_by_network_anonymization_key()); - - cache->CopyProxyEntriesFrom(*it->second); - caches_.erase(it); -} - -} // namespace network
diff --git a/services/network/http_auth_cache_proxy_copier.cc b/services/network/http_auth_cache_proxy_copier.cc new file mode 100644 index 0000000..62a07c9 --- /dev/null +++ b/services/network/http_auth_cache_proxy_copier.cc
@@ -0,0 +1,41 @@ +// 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 "services/network/http_auth_cache_proxy_copier.h" + +#include "base/logging.h" +#include "net/http/http_auth_cache.h" + +namespace network { + +HttpAuthCacheProxyCopier::HttpAuthCacheProxyCopier() = default; +HttpAuthCacheProxyCopier::~HttpAuthCacheProxyCopier() = default; + +base::UnguessableToken HttpAuthCacheProxyCopier::SaveHttpAuthCache( + const net::HttpAuthCache& cache) { + base::UnguessableToken key = base::UnguessableToken::Create(); + auto cache_it = caches_.emplace( + key, std::make_unique<net::HttpAuthCache>( + // It doesn't matter what value we pass here because these + // caches are only used for copying proxy entries (not + // server entries). + /*key_server_entries_by_network_anonymization_key=*/true)); + DCHECK(cache_it.second); + cache_it.first->second->CopyProxyEntriesFrom(cache); + return key; +} + +void HttpAuthCacheProxyCopier::LoadHttpAuthCache( + const base::UnguessableToken& key, + net::HttpAuthCache* cache) { + auto it = caches_.find(key); + if (it == caches_.end()) { + DLOG(ERROR) << "Unknown HttpAuthCache key: " << key; + return; + } + cache->CopyProxyEntriesFrom(*it->second); + caches_.erase(it); +} + +} // namespace network
diff --git a/services/network/http_auth_cache_copier.h b/services/network/http_auth_cache_proxy_copier.h similarity index 74% rename from services/network/http_auth_cache_copier.h rename to services/network/http_auth_cache_proxy_copier.h index 885169fc..31aef198 100644 --- a/services/network/http_auth_cache_copier.h +++ b/services/network/http_auth_cache_proxy_copier.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef SERVICES_NETWORK_HTTP_AUTH_CACHE_COPIER_H_ -#define SERVICES_NETWORK_HTTP_AUTH_CACHE_COPIER_H_ +#ifndef SERVICES_NETWORK_HTTP_AUTH_CACHE_PROXY_COPIER_H_ +#define SERVICES_NETWORK_HTTP_AUTH_CACHE_PROXY_COPIER_H_ #include <map> #include <memory> @@ -20,14 +20,14 @@ // an intermediate cache. This allows copying between two HttpAuthCache // instances that cannot both be accessed at the same time, such as the // HttpAuthCaches in two NetworkContexts. -class HttpAuthCacheCopier { +class HttpAuthCacheProxyCopier { public: - HttpAuthCacheCopier(); + HttpAuthCacheProxyCopier(); - HttpAuthCacheCopier(const HttpAuthCacheCopier&) = delete; - HttpAuthCacheCopier& operator=(const HttpAuthCacheCopier&) = delete; + HttpAuthCacheProxyCopier(const HttpAuthCacheProxyCopier&) = delete; + HttpAuthCacheProxyCopier& operator=(const HttpAuthCacheProxyCopier&) = delete; - ~HttpAuthCacheCopier(); + ~HttpAuthCacheProxyCopier(); // Saves the proxy entries of the given HttpAuthCache in an intermediate // HttpAuthCache and returns a key that can be used to load the saved contents @@ -46,4 +46,4 @@ } // namespace network -#endif // SERVICES_NETWORK_HTTP_AUTH_CACHE_COPIER_H_ +#endif // SERVICES_NETWORK_HTTP_AUTH_CACHE_PROXY_COPIER_H_
diff --git a/services/network/network_context.cc b/services/network/network_context.cc index 26019db9..88417e9 100644 --- a/services/network/network_context.cc +++ b/services/network/network_context.cc
@@ -111,7 +111,7 @@ #include "services/network/devtools_durable_msg_collector.h" #include "services/network/disk_cache/mojo_backend_file_operations_factory.h" #include "services/network/host_resolver.h" -#include "services/network/http_auth_cache_copier.h" +#include "services/network/http_auth_cache_proxy_copier.h" #include "services/network/http_server_properties_pref_delegate.h" #include "services/network/ignore_errors_cert_verifier.h" #include "services/network/is_browser_initiated.h" @@ -2472,7 +2472,7 @@ ->GetSession() ->http_auth_cache(); base::UnguessableToken cache_key = - network_service_->http_auth_cache_copier()->SaveHttpAuthCache( + network_service_->http_auth_cache_proxy_copier()->SaveHttpAuthCache( *http_auth_cache); std::move(callback).Run(cache_key); } @@ -2484,7 +2484,7 @@ url_request_context_->http_transaction_factory() ->GetSession() ->http_auth_cache(); - network_service_->http_auth_cache_copier()->LoadHttpAuthCache( + network_service_->http_auth_cache_proxy_copier()->LoadHttpAuthCache( cache_key, http_auth_cache); std::move(callback).Run(); }
diff --git a/services/network/network_service.cc b/services/network/network_service.cc index 9463331..c44a9801 100644 --- a/services/network/network_service.cc +++ b/services/network/network_service.cc
@@ -84,7 +84,7 @@ #include "net/url_request/url_request_context.h" #include "services/network/dns_config_change_manager.h" #include "services/network/first_party_sets/first_party_sets_manager.h" -#include "services/network/http_auth_cache_copier.h" +#include "services/network/http_auth_cache_proxy_copier.h" #include "services/network/net_log_exporter.h" #include "services/network/net_log_proxy_sink.h" #include "services/network/network_context.h" @@ -484,7 +484,7 @@ net::NetworkChangeNotifier::GetSystemDnsConfigNotifier(), net_log_); host_resolver_factory_ = std::make_unique<net::HostResolver::Factory>(); - http_auth_cache_copier_ = std::make_unique<HttpAuthCacheCopier>(); + http_auth_cache_proxy_copier_ = std::make_unique<HttpAuthCacheProxyCopier>(); doh_probe_activator_ = std::make_unique<DelayedDohProbeActivator>(this);
diff --git a/services/network/network_service.h b/services/network/network_service.h index 5646097..769a2e8 100644 --- a/services/network/network_service.h +++ b/services/network/network_service.h
@@ -81,7 +81,7 @@ namespace network { class DnsConfigChangeManager; -class HttpAuthCacheCopier; +class HttpAuthCacheProxyCopier; class NetLogProxySink; class NetworkContext; class NetworkService; @@ -313,8 +313,8 @@ net::HostResolver::Factory* host_resolver_factory() { return host_resolver_factory_.get(); } - HttpAuthCacheCopier* http_auth_cache_copier() { - return http_auth_cache_copier_.get(); + HttpAuthCacheProxyCopier* http_auth_cache_proxy_copier() { + return http_auth_cache_proxy_copier_.get(); } FirstPartySetsManager* first_party_sets_manager() const { @@ -472,7 +472,7 @@ std::unique_ptr<net::HostResolverManager> host_resolver_manager_; std::unique_ptr<net::HostResolver::Factory> host_resolver_factory_; - std::unique_ptr<HttpAuthCacheCopier> http_auth_cache_copier_; + std::unique_ptr<HttpAuthCacheProxyCopier> http_auth_cache_proxy_copier_; // Members that would store the http auth network_service related params. // These Params are later used by NetworkContext to create
diff --git a/services/viz/privileged/mojom/compositing/display_private.mojom b/services/viz/privileged/mojom/compositing/display_private.mojom index e063835e..78a96bff 100644 --- a/services/viz/privileged/mojom/compositing/display_private.mojom +++ b/services/viz/privileged/mojom/compositing/display_private.mojom
@@ -20,6 +20,7 @@ import "services/viz/privileged/mojom/compositing/vsync_parameter_observer.mojom"; // The DisplayPrivate is used by privileged clients to talk to Display. +[DirectReceiver] interface DisplayPrivate { SetDisplayVisible(bool visible);
diff --git a/services/viz/privileged/mojom/compositing/external_begin_frame_controller.mojom b/services/viz/privileged/mojom/compositing/external_begin_frame_controller.mojom index 904f262a..75a64840 100644 --- a/services/viz/privileged/mojom/compositing/external_begin_frame_controller.mojom +++ b/services/viz/privileged/mojom/compositing/external_begin_frame_controller.mojom
@@ -8,6 +8,7 @@ import "services/viz/public/mojom/compositing/begin_frame_args.mojom"; // Exposes a way to manually issue BeginFrames to a Display. +[DirectReceiver] interface ExternalBeginFrameController { // Request a frame. The callback is invoked when frame has either been // produced or can not be produced at this time.
diff --git a/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom b/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom index 4f1e9c6..5c5381c 100644 --- a/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom +++ b/services/viz/privileged/mojom/compositing/frame_sink_manager.mojom
@@ -75,6 +75,7 @@ // Thus, a client will typically simply request a CompositorFrameSink from the // frame sink manager host which will forward the request to the frame sink // manager. +[DirectReceiver] interface FrameSinkManager { // Starts the scope of temporary references tied to this |frame_sink_id|. // Temporary references tied to this |frame_sink_id| will be dropped on @@ -282,6 +283,7 @@ // RenderInputRouterDelegate[Client] interface to use the same mojo pipe to // send/receive information for input handling between the CrBrowserMain and the // VizCompositorThread. +[DirectReceiver] interface RendererInputRouterDelegateRegistry { // Setup the connection between the Browser (at RenderWidgetHost level) and // the VizCompositor thread (at InputManager level) in both directions to
diff --git a/services/viz/privileged/mojom/compositing/frame_sink_manager_test_api.mojom b/services/viz/privileged/mojom/compositing/frame_sink_manager_test_api.mojom index b3fe71e..a3b5fc5c 100644 --- a/services/viz/privileged/mojom/compositing/frame_sink_manager_test_api.mojom +++ b/services/viz/privileged/mojom/compositing/frame_sink_manager_test_api.mojom
@@ -10,6 +10,7 @@ // Note: These mojo definitions are intended for exclusive use in testing. // FrameSinkManager's test api. +[DirectReceiver] interface FrameSinkManagerTestApi { // Returns true if there are any unclaimed ViewTransition resources. [Sync]
diff --git a/services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom b/services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom index 3e9144c3..00277d0 100644 --- a/services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom +++ b/services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom
@@ -114,6 +114,7 @@ // should suspend, Stop() is called. After that point, Start() can be called // again to resume capture; or the mojo binding can be dropped to auto-terminate // the capturer. +[DirectReceiver] interface FrameSinkVideoCapturer { // Specifies the pixel format to use. Please note when format is // PIXEL_FORMAT_ARGB, the frame might be BGRA or RGBA depends on platform @@ -233,6 +234,7 @@ // Control interface for a small image to be composited on top of each captured // video frame. This allows clients to, for example, have the capturer render // mouse cursors or debug info boxes on top of the captured content. +[DirectReceiver] interface FrameSinkVideoCaptureOverlay { // Sets/Changes the overlay |image| and its position and size, relative to the // source content. |bounds| consists of coordinates where the range [0.0,1.0)
diff --git a/services/viz/privileged/mojom/compositing/frame_sinks_metrics_recorder.mojom b/services/viz/privileged/mojom/compositing/frame_sinks_metrics_recorder.mojom index 4df5405..743b851 100644 --- a/services/viz/privileged/mojom/compositing/frame_sinks_metrics_recorder.mojom +++ b/services/viz/privileged/mojom/compositing/frame_sinks_metrics_recorder.mojom
@@ -37,6 +37,7 @@ }; // The interface allows clients to collect metrics related to frame sinks. +[DirectReceiver] interface FrameSinksMetricsRecorder { // Starts frame counting per frame sink on the viz. StartFrameCounting(mojo_base.mojom.TimeTicks start_time,
diff --git a/services/viz/public/mojom/compositing/compositor_frame_sink.mojom b/services/viz/public/mojom/compositing/compositor_frame_sink.mojom index b0f7a70..e28e143 100644 --- a/services/viz/public/mojom/compositing/compositor_frame_sink.mojom +++ b/services/viz/public/mojom/compositing/compositor_frame_sink.mojom
@@ -39,6 +39,7 @@ // Each time a client has a graphical update, and receives an OnBeginFrame, it // is responsible for creating a CompositorFrame to update its portion of the // screen. +[DirectReceiver] interface CompositorFrameSink { // Sets parameters for the CompositorFrameSink. SetParams(CompositorFrameSinkParams params); @@ -96,6 +97,7 @@ SetThreads(array<Thread> threads); }; +[DirectReceiver] interface CompositorFrameSinkClient { // Notification that the previous CompositorFrame given to // SubmitCompositorFrame() has been processed and that another frame
diff --git a/services/viz/public/mojom/compositing/frame_sink_bundle.mojom b/services/viz/public/mojom/compositing/frame_sink_bundle.mojom index 3072de0a..dee2710 100644 --- a/services/viz/public/mojom/compositing/frame_sink_bundle.mojom +++ b/services/viz/public/mojom/compositing/frame_sink_bundle.mojom
@@ -46,6 +46,7 @@ // Controls an endpoint for aggregate communication regarding a collection of // related frame sinks. +[DirectReceiver] interface FrameSinkBundle { // Corresponds to a single message of the same name on CompositorFrameSink // for the identified sink.
diff --git a/services/viz/public/mojom/compositing/layer_context.mojom b/services/viz/public/mojom/compositing/layer_context.mojom index 9853d33..5650401b 100644 --- a/services/viz/public/mojom/compositing/layer_context.mojom +++ b/services/viz/public/mojom/compositing/layer_context.mojom
@@ -205,6 +205,7 @@ // Drives updates to a GPU-side LayerTreeHostImpl from its corresponding // client-side (e.g. renderer- or browser-side) LayerTreeHost. +[DirectReceiver] interface LayerContext { // Globally controls whether the tree contents are visible. SetVisible(bool visible);
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index c0688a0..c74a766e 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json
@@ -630,7 +630,20 @@ ], "experiments": [ { - "name": "Enabled", + "name": "Enabled_DefaultDevicePref_True", + "params": { + "show_bookmark_bar": "true" + }, + "enable_features": [ + "AndroidAppearanceSettings", + "AndroidBookmarkBar" + ] + }, + { + "name": "Enabled_DefaultDevicePref_False", + "params": { + "show_bookmark_bar": "false" + }, "enable_features": [ "AndroidAppearanceSettings", "AndroidBookmarkBar" @@ -11360,6 +11373,23 @@ ] } ], + "GlicAppendModelQualityClientId": [ + { + "platforms": [ + "mac", + "windows", + "linux" + ], + "experiments": [ + { + "name": "Enabled", + "enable_features": [ + "GlicAppendModelQualityClientId" + ] + } + ] + } + ], "GlicClientResponsivenessCheckExtension": [ { "platforms": [ @@ -11586,6 +11616,22 @@ ] } ], + "GlicWarming": [ + { + "platforms": [ + "mac", + "windows" + ], + "experiments": [ + { + "name": "Enabled", + "enable_features": [ + "GlicWarming" + ] + } + ] + } + ], "GlicZeroStateSuggestionsDogfood": [ { "platforms": [ @@ -16627,9 +16673,11 @@ "enable_features": [ "AiModeOmniboxEntryPoint", "AllowAiModeMatches", + "LoadingSuggestionsAnimation", "OmniboxHideAimEntrypointOnUserInput", "OmniboxToolbelt", - "OpenLensActionUITweaks" + "OpenLensActionUITweaks", + "SuggestionsFulfilledByLensSupported" ] } ]
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc index 6b3808b..06d1baf 100644 --- a/third_party/blink/common/features.cc +++ b/third_party/blink/common/features.cc
@@ -52,6 +52,11 @@ // Avoids copying ResourceRequest::TrustedParams when possible. BASE_FEATURE(AvoidTrustedParamsCopies, base::FEATURE_DISABLED_BY_DEFAULT); +// Async touchmoves after scroll. +BASE_FEATURE(kAsyncTouchMovesImmediatelyAfterScroll, + "AsyncTouchMovesImmediatelyAfterScroll", + base::FEATURE_DISABLED_BY_DEFAULT); + // Block all MIDI access with the MIDI_SYSEX permission BASE_FEATURE(BlockMidiByDefault, base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h index d2552a60..ca7cafb 100644 --- a/third_party/blink/public/common/features.h +++ b/third_party/blink/public/common/features.h
@@ -42,6 +42,12 @@ // Avoids copying ResourceRequest::TrustedParams when possible. BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kAvoidTrustedParamsCopies); +// Sends touch moves async once the scroll has already started. This means the +// generation of GestureScrollUpdate is not blocked on touch moves being handled +// by RendererMain. +BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE( + kAsyncTouchMovesImmediatelyAfterScroll); + BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kLowerHighResolutionTimerThreshold); // Allows running DevTools main thread debugger even when a renderer process
diff --git a/third_party/blink/public/mojom/content_extraction/ai_page_content.mojom b/third_party/blink/public/mojom/content_extraction/ai_page_content.mojom index 6d14d373..bda0e7aa 100644 --- a/third_party/blink/public/mojom/content_extraction/ai_page_content.mojom +++ b/third_party/blink/public/mojom/content_extraction/ai_page_content.mojom
@@ -309,6 +309,25 @@ array<ScriptTool> script_tools; }; +// Provides metadata for local redacted frames. +struct RedactedFrameMetadata { + enum Reason { + // This iframe was redacted because it is not on the same eTLD+1 site as + // the top-level frame. + kCrossSite, + // This iframe was redacted because it is not on the same origin as the + // top-level frame. + kCrossOrigin, + }; + // Reason this iframe was redacted. + Reason reason; +}; + +union AIPageContentIframeContent { + AIPageContentFrameData local_frame_data; + RedactedFrameMetadata redacted_frame_metadata; +}; + // Provides metadata for frames (local and remote) accessible from the // LocalFrameRoot of the corresponding AIPageContentAgent. struct AIPageContentIframeData { @@ -321,7 +340,7 @@ // Data common to all local frames (main and embedded). If this is a remote // frame, this metadata will be part of the AIPageContent pulled from the // local frame's renderer process. - AIPageContentFrameData? local_frame_data; + AIPageContentIframeContent? content; }; struct AIPageContentTableData { @@ -501,6 +520,10 @@ // Note: This option is not folded into AIPageContentMode since this // information is not added to the proto. int32 max_meta_elements = 0; + + // If enabled, only include frames that are same-site with the top level + // frame's origin. This implies same-site cross-origin frames are included. + bool include_same_site_only = false; }; // Used to obtain the AIPageContent representation for Documents. Lives in the
diff --git a/third_party/blink/public/mojom/input/input_handler.mojom b/third_party/blink/public/mojom/input/input_handler.mojom index fe01b2f..2b454e71 100644 --- a/third_party/blink/public/mojom/input/input_handler.mojom +++ b/third_party/blink/public/mojom/input/input_handler.mojom
@@ -296,6 +296,7 @@ // interface. If frame input actions are dispatched the WidgetInputHandler // should be fetched via the associated interface request so that input calls // remain in order. See https://goo.gl/x4ee8A for more details. +[DirectReceiver] interface FrameWidgetInputHandler { // Adds text decorations between a given valid start and end offsets in the // currently focused editable field. @@ -450,6 +451,7 @@ // an input interface for an associated Widget object. See // FrameWidgetInputHandler // for an interface at the frame level. +[DirectReceiver] interface WidgetInputHandler { // Tells widget focus has been changed. SetFocus(FocusState state);
diff --git a/third_party/blink/public/mojom/input/synchronous_compositor.mojom b/third_party/blink/public/mojom/input/synchronous_compositor.mojom index e2d0f94c..d55340d 100644 --- a/third_party/blink/public/mojom/input/synchronous_compositor.mojom +++ b/third_party/blink/public/mojom/input/synchronous_compositor.mojom
@@ -47,6 +47,7 @@ // java UI is drawn in lock step with content renderer by the webview. // The SynchronousCompositor is an associated interface with WidgetInputHandler // because input must be delivered in order with the compositing events. +[DirectReceiver] interface SynchronousCompositor { // Hardware draw asynchronously, ReturnFrame will return the result on // the associated SynchronousCompositorControlHost.
diff --git a/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h b/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h index 4453bf15..e51a369 100644 --- a/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h +++ b/third_party/blink/renderer/bindings/core/v8/to_v8_traits.h
@@ -494,7 +494,7 @@ struct ToV8Traits<IDLArray<T>> { [[nodiscard]] static v8::Local<v8::Value> ToV8(ScriptState* script_state, const FrozenArray<T>& value) { - return value.ToV8(script_state); + return const_cast<FrozenArray<T>&>(value).ToV8(script_state); } // TODO(yukishiino): Remove this overload as IDL FrozenArray should be
diff --git a/third_party/blink/renderer/core/input/gesture_manager.cc b/third_party/blink/renderer/core/input/gesture_manager.cc index ac42038..234ffed 100644 --- a/third_party/blink/renderer/core/input/gesture_manager.cc +++ b/third_party/blink/renderer/core/input/gesture_manager.cc
@@ -143,6 +143,13 @@ } } + // Long presses are the only gesture that could happen with an ongoing drag. + // We clear the flag on any other gesture in case the gesture manager didn't + // receive a drag end event for any reason. + if (gesture_event.GetType() != WebInputEvent::Type::kGestureLongPress) { + drag_in_progress_ = false; + } + switch (gesture_event.GetType()) { case WebInputEvent::Type::kGestureTapDown: return HandleGestureTapDown(targeted_event); @@ -408,7 +415,6 @@ WebInputEventResult GestureManager::HandleGestureShortPress( const GestureEventWithHitTestResults& targeted_event) { - drag_in_progress_ = false; if (frame_->GetSettings() && frame_->GetSettings()->GetTouchDragDropEnabled() && RuntimeEnabledFeatures::TouchDragOnShortPressEnabled() && @@ -435,8 +441,9 @@ gesture_context_menu_deferred_ = false; - if (RuntimeEnabledFeatures::TouchDragOnShortPressEnabled()) { - if (drag_in_progress_ && DragEndOpensContextMenu()) { + if (RuntimeEnabledFeatures::TouchDragOnShortPressEnabled() && + drag_in_progress_) { + if (DragEndOpensContextMenu()) { gesture_context_menu_deferred_ = true; return WebInputEventResult::kNotHandled; }
diff --git a/third_party/blink/renderer/core/input/mouse_event_manager.cc b/third_party/blink/renderer/core/input/mouse_event_manager.cc index eafaf122..64c3818 100644 --- a/third_party/blink/renderer/core/input/mouse_event_manager.cc +++ b/third_party/blink/renderer/core/input/mouse_event_manager.cc
@@ -733,6 +733,10 @@ ResetDragSource(); mouse_down_pos_ = frame_->View()->ConvertFromRootFrame( gfx::ToFlooredPoint(mouse_drag_event.PositionInRootFrame())); + // TODO(crbug.com/435174491): HandleDrag returns whether or not the + // application handled the drag attempt, not whether or not a drag started. + // `GestureManager` assumes that if this function returns `true` a drag has + // started. return HandleDrag(mev, gesture_event.primary_pointer_type == blink::WebPointerProperties::PointerType::kPen ? DragAndDropToolType::kStylusViaGesture @@ -846,7 +850,7 @@ return selection_controller_drag_result; } -// TODO(mustaq@chromium.org): The return value here is questionable. Why even a +// TODO(crbug.com/435174491): The return value here is questionable. Why even a // failing `TryStartDrag()` below returns a `true` here? bool MouseEventManager::HandleDrag(const MouseEventWithHitTestResults& event, DragAndDropToolType initiator) {
diff --git a/third_party/blink/renderer/core/loader/link_loader_test.cc b/third_party/blink/renderer/core/loader/link_loader_test.cc index f806c61..0150215 100644 --- a/third_party/blink/renderer/core/loader/link_loader_test.cc +++ b/third_party/blink/renderer/core/loader/link_loader_test.cc
@@ -510,7 +510,7 @@ EXPECT_EQ(kNotParserInserted, request.Options().ParserState()); EXPECT_EQ(params_->expected_credentials_mode, request.Options().CredentialsMode()); - EXPECT_EQ(Referrer::NoReferrer(), request.ReferrerString()); + EXPECT_EQ(Referrer::ClientReferrerString(), request.ReferrerString()); EXPECT_EQ(params_->referrer_policy, request.Options().GetReferrerPolicy()); EXPECT_EQ(params_->integrity, request.Options().GetIntegrityAttributeValue());
diff --git a/third_party/blink/renderer/core/loader/preload_helper.cc b/third_party/blink/renderer/core/loader/preload_helper.cc index 0f7f2568..f0c93d6 100644 --- a/third_party/blink/renderer/core/loader/preload_helper.cc +++ b/third_party/blink/renderer/core/loader/preload_helper.cc
@@ -62,6 +62,7 @@ #include "third_party/blink/renderer/platform/loader/link_header.h" #include "third_party/blink/renderer/platform/loader/subresource_integrity.h" #include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "third_party/blink/renderer/platform/weborigin/security_origin.h" namespace blink { @@ -699,8 +700,10 @@ params.referrer_policy, mojom::blink::FetchPriorityHint::kAuto, RenderBlockingBehavior::kNonBlocking), - Referrer::NoReferrer(), TextPosition::MinimumPosition(), - ModuleImportPhase::kEvaluation); + RuntimeEnabledFeatures::ModulePreloadReferrerEnabled() + ? Referrer::ClientReferrerString() + : Referrer::NoReferrer(), + TextPosition::MinimumPosition(), ModuleImportPhase::kEvaluation); // Step 13. "Fetch a modulepreload module script graph given url, destination, // settings object, and options. Wait until the algorithm asynchronously
diff --git a/third_party/blink/renderer/core/testing/data/core_test_bundle_data.filelist b/third_party/blink/renderer/core/testing/data/core_test_bundle_data.filelist index 392c534c..634697ce 100644 --- a/third_party/blink/renderer/core/testing/data/core_test_bundle_data.filelist +++ b/third_party/blink/renderer/core/testing/data/core_test_bundle_data.filelist
@@ -133,6 +133,7 @@ testing/data/fragment_middle_click.html testing/data/frame-a-b-c.html testing/data/frame.html +testing/data/frame_another.html testing/data/frame_owner_properties.html testing/data/frame_timing_1.html testing/data/frame_timing_2.html @@ -311,8 +312,8 @@ testing/data/no_scale_for_you.html testing/data/no_viewport_tag.html testing/data/nodeimage.html -testing/data/non_UTF8.wasm testing/data/non-scrollable.html +testing/data/non_UTF8.wasm testing/data/non_user_input_text_update.html testing/data/not_an_image.ico testing/data/notifications/100x100.png
diff --git a/third_party/blink/renderer/core/testing/data/frame_another.html b/third_party/blink/renderer/core/testing/data/frame_another.html new file mode 100644 index 0000000..ee59b611 --- /dev/null +++ b/third_party/blink/renderer/core/testing/data/frame_another.html
@@ -0,0 +1 @@ +I am another iframe
diff --git a/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent.cc b/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent.cc index 19761ea..a81fc168 100644 --- a/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent.cc +++ b/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent.cc
@@ -9,6 +9,7 @@ #include "base/trace_event/trace_id_helper.h" #include "services/metrics/public/cpp/ukm_builders.h" #include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom-blink.h" +#include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom-forward.h" #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h" #include "third_party/blink/renderer/core/css/properties/longhands.h" #include "third_party/blink/renderer/core/display_lock/display_lock_document_state.h" @@ -54,6 +55,7 @@ #include "third_party/blink/renderer/modules/content_extraction/ai_page_content_debug_utils.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" #include "ui/accessibility/ax_role_properties.h" #include "ui/gfx/geometry/point_conversions.h" #include "ui/gfx/geometry/rect_conversions.h" @@ -1047,18 +1049,37 @@ content_node.content_attributes->iframe_data = std::move(iframe_data); auto* local_frame = DynamicTo<LocalFrame>(frame); + if (!local_frame) { + return; + } + + if (options_->include_same_site_only && !frame.IsOutermostMainFrame()) { + const SecurityOrigin* frame_origin = + local_frame->GetSecurityContext()->GetSecurityOrigin(); + const SecurityOrigin* main_frame_origin = + local_frame->Top()->GetSecurityContext()->GetSecurityOrigin(); + CHECK(frame_origin); + CHECK(main_frame_origin); + if (!frame_origin->IsSameSiteWith(main_frame_origin)) { + content_node.content_attributes->iframe_data->content = + mojom::blink::AIPageContentIframeContent::NewRedactedFrameMetadata( + mojom::blink::RedactedFrameMetadata::New( + mojom::blink::RedactedFrameMetadata::Reason::kCrossSite)); + return; + } + } // Add interaction metadata before walking the tree to ensure we promote // interactive DOM nodes to ContentNodes. - if (local_frame && local_frame->GetDocument()) { + if (local_frame->GetDocument()) { auto frame_data = mojom::blink::AIPageContentFrameData::New(); AddFrameData(*local_frame, *frame_data); - content_node.content_attributes->iframe_data->local_frame_data = - std::move(frame_data); + content_node.content_attributes->iframe_data->content = + mojom::blink::AIPageContentIframeContent::NewLocalFrameData( + std::move(frame_data)); } - auto* child_layout_view = - local_frame ? local_frame->ContentLayoutObject() : nullptr; + auto* child_layout_view = local_frame->ContentLayoutObject(); if (child_layout_view) { RecursionData child_recursion_data(*child_layout_view->Style()); // The aria attribute values don't pierce frame boundaries. @@ -1071,9 +1092,9 @@ MaybeGenerateContentNode(*child_layout_view, child_recursion_data); CHECK(child_content_node); - // We could consider removing an iframe with no visible content. But this is - // likely not common and should be done in the browser so it's consistently - // done for local and remote frames. + // We could consider removing an iframe with no visible content. But this + // is likely not common and should be done in the browser so it's + // consistently done for local and remote frames. WalkChildren(*child_layout_view, *child_content_node, child_recursion_data); content_node.children_nodes.emplace_back(std::move(child_content_node)); }
diff --git a/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent_unittest.cc b/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent_unittest.cc index 4570e5f..be5beb7b 100644 --- a/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent_unittest.cc +++ b/third_party/blink/renderer/modules/content_extraction/ai_page_content_agent_unittest.cc
@@ -10,6 +10,7 @@ #include "mojo/public/cpp/test_support/test_utils.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/common/input/web_mouse_event.h" +#include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom-data-view.h" #include "third_party/blink/renderer/core/accessibility/ax_context.h" #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h" #include "third_party/blink/renderer/core/dom/document.h" @@ -62,6 +63,10 @@ ASSERT_TRUE(helper_.LocalMainFrame()); } + void TearDown() override { + url_test_helpers::UnregisterAllURLsAndClearMemoryCache(); + } + void CheckListItemWithText(const mojom::blink::AIPageContentNode& node, const String& expected_text) { const auto& attributes = *node.content_attributes; @@ -641,6 +646,128 @@ CheckTextNode(*iframe_root.children_nodes[0], "inside iframe"); } +TEST_F(AIPageContentAgentTest, CrossSiteIframeIncluded) { + KURL main_url = url_test_helpers::ToKURL("http://example.com/main.html"); + KURL cross_origin_url = + url_test_helpers::ToKURL("http://www.example.com/frame.html"); + KURL cross_site_url = + url_test_helpers::ToKURL("http://altostrat.com/frame_another.html"); + + // Mock the cross origin, same-site iframe's content. + url_test_helpers::RegisterMockedURLLoadFromBase( + WebString::FromUTF8("http://www.example.com/"), test::CoreTestDataPath(), + WebString::FromUTF8("frame.html")); + + // Mock the cross-site iframe's content. + url_test_helpers::RegisterMockedURLLoadFromBase( + WebString::FromUTF8("http://altostrat.com/"), test::CoreTestDataPath(), + WebString::FromUTF8("frame_another.html")); + + // Load the main page which contains the same-site iframe and the cross-origin + // iframe. + frame_test_helpers::LoadHTMLString( + helper_.LocalMainFrame(), + "<body>" + "<iframe src='http://www.example.com/frame.html'></iframe>" + "<iframe src='http://altostrat.com/frame_another.html'></iframe>" + "</body>", + main_url); + + // Let the iframe load. + test::RunPendingTasks(); + + auto options = GetAIPageContentOptionsForTest(); + + options.include_same_site_only = false; + GetAIPageContent(options); + + const auto& root = ContentRootNode(); + ASSERT_EQ(root.children_nodes.size(), 2u); + + // Both nodes should be present. + const auto& same_site_iframe_node = *root.children_nodes[0]; + const auto& cross_site_iframe_node = *root.children_nodes[1]; + CheckIframeNode(same_site_iframe_node); + CheckIframeNode(cross_site_iframe_node); + + // The contents of both nodes should be present as well. + ASSERT_EQ(same_site_iframe_node.children_nodes.size(), 1u); + ASSERT_EQ(cross_site_iframe_node.children_nodes.size(), 1u); + + const auto& same_site_iframe_root = *same_site_iframe_node.children_nodes[0]; + const auto& cross_site_iframe_root = + *cross_site_iframe_node.children_nodes[0]; + + CheckRootNode(same_site_iframe_root); + CheckRootNode(cross_site_iframe_root); + ASSERT_EQ(same_site_iframe_root.children_nodes.size(), 1u); + ASSERT_EQ(cross_site_iframe_root.children_nodes.size(), 1u); + + CheckTextNode(*same_site_iframe_root.children_nodes[0], "I am an iframe\n"); + CheckTextNode(*cross_site_iframe_root.children_nodes[0], + "I am another iframe\n"); +} + +TEST_F(AIPageContentAgentTest, CrossSiteIframeExcluded) { + KURL main_url = url_test_helpers::ToKURL("http://example.com/main.html"); + KURL cross_origin_url = + url_test_helpers::ToKURL("http://www.example.com/frame.html"); + KURL cross_site_url = + url_test_helpers::ToKURL("http://altostrat.com/frame_another.html"); + + // Mock the cross origin, same-site iframe's content. + url_test_helpers::RegisterMockedURLLoadFromBase( + WebString::FromUTF8("http://www.example.com/"), test::CoreTestDataPath(), + WebString::FromUTF8("frame.html")); + + // Mock the cross-site iframe's content. + url_test_helpers::RegisterMockedURLLoadFromBase( + WebString::FromUTF8("http://altostrat.com/"), test::CoreTestDataPath(), + WebString::FromUTF8("frame_another.html")); + + // Load the main page which contains the same-site iframe and the cross-origin + // iframe. + frame_test_helpers::LoadHTMLString( + helper_.LocalMainFrame(), + "<body>" + "<iframe src='http://www.example.com/frame.html'></iframe>" + "<iframe src='http://altostrat.com/frame_another.html'></iframe>" + "</body>", + main_url); + + // Let the iframe load. + test::RunPendingTasks(); + + auto options = GetAIPageContentOptionsForTest(); + + options.include_same_site_only = true; + GetAIPageContent(options); + + const auto& root = ContentRootNode(); + ASSERT_EQ(root.children_nodes.size(), 2u); + + // Both nodes should be present. + const auto& same_site_iframe_node = *root.children_nodes[0]; + const auto& cross_site_iframe_node = *root.children_nodes[1]; + CheckIframeNode(same_site_iframe_node); + CheckIframeNode(cross_site_iframe_node); + + // Only the contents of the same-site iframe should be present. + ASSERT_EQ(same_site_iframe_node.children_nodes.size(), 1u); + ASSERT_TRUE(cross_site_iframe_node.children_nodes.empty()); + ASSERT_EQ(cross_site_iframe_node.content_attributes->iframe_data->content + ->get_redacted_frame_metadata() + ->reason, + blink::mojom::RedactedFrameMetadata_Reason::kCrossSite); + + const auto& same_site_iframe_root = *same_site_iframe_node.children_nodes[0]; + + CheckRootNode(same_site_iframe_root); + ASSERT_EQ(same_site_iframe_root.children_nodes.size(), 1u); + + CheckTextNode(*same_site_iframe_root.children_nodes[0], "I am an iframe\n"); +} + TEST_F(AIPageContentAgentTest, NoLayoutElement) { frame_test_helpers::LoadHTMLString( helper_.LocalMainFrame(), @@ -2334,8 +2461,9 @@ Content()->frame_data->frame_interaction_info; ASSERT_FALSE(frame_interaction_info->selection); + EXPECT_TRUE(iframe.content_attributes->iframe_data->content); const auto& iframe_interaction_info = - iframe.content_attributes->iframe_data->local_frame_data + iframe.content_attributes->iframe_data->content->get_local_frame_data() ->frame_interaction_info; ASSERT_TRUE(iframe_interaction_info->selection); const auto& selection = *iframe_interaction_info->selection; @@ -2512,13 +2640,17 @@ mojom::blink::AIPageContentAttributeType::kIframe); const auto& iframe_data = *iframe.content_attributes->iframe_data; - EXPECT_EQ(iframe_data.local_frame_data->meta_data.size(), 2u); - EXPECT_EQ(iframe_data.local_frame_data->meta_data[0]->name, "author"); - EXPECT_EQ(iframe_data.local_frame_data->meta_data[0]->content, "Gary"); + EXPECT_TRUE(iframe.content_attributes->iframe_data->content); + EXPECT_EQ(iframe_data.content->get_local_frame_data()->meta_data.size(), 2u); + EXPECT_EQ(iframe_data.content->get_local_frame_data()->meta_data[0]->name, + "author"); + EXPECT_EQ(iframe_data.content->get_local_frame_data()->meta_data[0]->content, + "Gary"); - EXPECT_EQ(iframe_data.local_frame_data->meta_data[1]->name, "keywords"); - EXPECT_EQ(iframe_data.local_frame_data->meta_data[1]->content, + EXPECT_EQ(iframe_data.content->get_local_frame_data()->meta_data[1]->name, + "keywords"); + EXPECT_EQ(iframe_data.content->get_local_frame_data()->meta_data[1]->content, "HTML, CSS, JavaScript"); } @@ -2556,10 +2688,13 @@ mojom::blink::AIPageContentAttributeType::kIframe); const auto& iframe_data = *iframe.content_attributes->iframe_data; - EXPECT_EQ(iframe_data.local_frame_data->meta_data.size(), 1u); + EXPECT_TRUE(iframe_data.content); + EXPECT_EQ(iframe_data.content->get_local_frame_data()->meta_data.size(), 1u); - EXPECT_EQ(iframe_data.local_frame_data->meta_data[0]->name, "author"); - EXPECT_EQ(iframe_data.local_frame_data->meta_data[0]->content, "Gary"); + EXPECT_EQ(iframe_data.content->get_local_frame_data()->meta_data[0]->name, + "author"); + EXPECT_EQ(iframe_data.content->get_local_frame_data()->meta_data[0]->content, + "Gary"); EXPECT_EQ(iframe.children_nodes.size(), 1u); @@ -2572,10 +2707,15 @@ mojom::blink::AIPageContentAttributeType::kIframe); const auto& subiframe_data = *subiframe.content_attributes->iframe_data; - EXPECT_EQ(subiframe_data.local_frame_data->meta_data.size(), 1u); + EXPECT_TRUE(subiframe_data.content); + EXPECT_EQ(subiframe_data.content->get_local_frame_data()->meta_data.size(), + 1u); - EXPECT_EQ(subiframe_data.local_frame_data->meta_data[0]->name, "author"); - EXPECT_EQ(subiframe_data.local_frame_data->meta_data[0]->content, "Jordan"); + EXPECT_EQ(subiframe_data.content->get_local_frame_data()->meta_data[0]->name, + "author"); + EXPECT_EQ( + subiframe_data.content->get_local_frame_data()->meta_data[0]->content, + "Jordan"); } TEST_F(AIPageContentAgentTest, Title) { @@ -2949,8 +3089,10 @@ const auto& iframe = nodes[1]; EXPECT_EQ(iframe->content_attributes->attribute_type, mojom::blink::AIPageContentAttributeType::kIframe); - EXPECT_TRUE(iframe->content_attributes->iframe_data->local_frame_data - ->contains_paid_content); + EXPECT_TRUE(iframe->content_attributes->iframe_data->content); + EXPECT_TRUE( + iframe->content_attributes->iframe_data->content->get_local_frame_data() + ->contains_paid_content); auto& children = iframe->children_nodes[0]->children_nodes; EXPECT_FALSE( @@ -3034,8 +3176,10 @@ const auto& iframe1 = nodes[2]; EXPECT_EQ(iframe1->content_attributes->attribute_type, mojom::blink::AIPageContentAttributeType::kIframe); - EXPECT_TRUE(iframe1->content_attributes->iframe_data->local_frame_data - ->contains_paid_content); + EXPECT_TRUE(iframe1->content_attributes->iframe_data->content); + EXPECT_TRUE( + iframe1->content_attributes->iframe_data->content->get_local_frame_data() + ->contains_paid_content); const auto& children1 = iframe1->children_nodes[0]->children_nodes; EXPECT_FALSE( @@ -3048,8 +3192,10 @@ const auto& iframe2 = nodes[3]; EXPECT_EQ(iframe2->content_attributes->attribute_type, mojom::blink::AIPageContentAttributeType::kIframe); - EXPECT_FALSE(iframe2->content_attributes->iframe_data->local_frame_data - ->contains_paid_content); + EXPECT_TRUE(iframe2->content_attributes->iframe_data->content); + EXPECT_FALSE( + iframe2->content_attributes->iframe_data->content->get_local_frame_data() + ->contains_paid_content); const auto& children2 = iframe2->children_nodes[0]->children_nodes; EXPECT_FALSE( @@ -3062,8 +3208,10 @@ const auto& iframe3 = nodes[4]; EXPECT_EQ(iframe3->content_attributes->attribute_type, mojom::blink::AIPageContentAttributeType::kIframe); - EXPECT_TRUE(iframe3->content_attributes->iframe_data->local_frame_data - ->contains_paid_content); + EXPECT_TRUE(iframe3->content_attributes->iframe_data->content); + EXPECT_TRUE( + iframe3->content_attributes->iframe_data->content->get_local_frame_data() + ->contains_paid_content); const auto& children3 = iframe3->children_nodes[0]->children_nodes; EXPECT_FALSE(
diff --git a/third_party/blink/renderer/platform/bindings/frozen_array_base.cc b/third_party/blink/renderer/platform/bindings/frozen_array_base.cc index 79b3ff52..e273ba7a 100644 --- a/third_party/blink/renderer/platform/bindings/frozen_array_base.cc +++ b/third_party/blink/renderer/platform/bindings/frozen_array_base.cc
@@ -35,20 +35,6 @@ const WrapperTypeInfo& FrozenArrayBase::wrapper_type_info_ = frozen_array_wrapper_type_info_; -v8::Local<v8::Value> FrozenArrayBase::ToV8(ScriptState* script_state) const { - return const_cast<FrozenArrayBase*>(this)->ToV8(script_state); -} - -v8::Local<v8::Value> FrozenArrayBase::ToV8(ScriptState* script_state) { - v8::Local<v8::Object> wrapper; - if (DOMDataStore::GetWrapper(script_state, this).ToLocal(&wrapper)) - [[likely]] { - return wrapper; - } - - return Wrap(script_state); -} - v8::Local<v8::Value> FrozenArrayBase::Wrap(ScriptState* script_state) { DCHECK(!DOMDataStore::ContainsWrapper(script_state->GetIsolate(), this));
diff --git a/third_party/blink/renderer/platform/bindings/frozen_array_base.h b/third_party/blink/renderer/platform/bindings/frozen_array_base.h index 32e9dc3..9ee1a6b 100644 --- a/third_party/blink/renderer/platform/bindings/frozen_array_base.h +++ b/third_party/blink/renderer/platform/bindings/frozen_array_base.h
@@ -26,9 +26,6 @@ public: ~FrozenArrayBase() override = default; - v8::Local<v8::Value> ToV8(ScriptState* script_state) const; - v8::Local<v8::Value> ToV8(ScriptState* script_state); - // ScriptWrappable overrides: v8::Local<v8::Value> Wrap(ScriptState* script_state) override; [[nodiscard]] v8::Local<v8::Object> AssociateWithWrapper(
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 index 9c22dfd8..1fbd28a4 100644 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -3186,6 +3186,13 @@ name: "ModifyParagraphCrossEditingoundary", status: "stable", }, + // ModulePreloadReferrer enables modulepreload to send proper referrer + // headers as specified in the HTML spec. Behind a flag for safer rollout. + // https://crbug.com/441933555 + { + name: "ModulePreloadReferrer", + status: "experimental", + }, { name: "MojoJS", status: "test", @@ -5022,7 +5029,7 @@ // This feature enables drag and drop using touch input devices. Replaces // the old "--enable-touch-drag-drop" and "--disable-touch-drag-drop" // switches. - // Enabled by default in Android and ChromeOS. + // Enabled by default in Android, ChromeOS and Windows. { name: "TouchDragAndDrop", base_feature: "none", @@ -5033,6 +5040,9 @@ { name: "TouchDragOnShortPress", depends_on: ["TouchDragAndDrop"], + status: { + "Win": "stable", + }, }, // Many websites disable mouse support when touch APIs are available. We'd // like to enable this always but can't until more websites fix this bug.
diff --git a/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.cc b/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.cc index dba6c45..5d6b319 100644 --- a/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.cc +++ b/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.cc
@@ -381,6 +381,24 @@ return allowed; } +void MainThreadEventQueue::OnGestureScrollUpdateAck( + mojom::blink::InputEventResultState ack_state) { + base::AutoLock lock(shared_state_lock_); + if (shared_state_.gsu_acked_as_consumed_) { + return; + } + if (ack_state != mojom::blink::InputEventResultState::kConsumed) { + return; + } + shared_state_.gsu_acked_as_consumed_ = true; +} + +void MainThreadEventQueue::OnGestureScrollEndAck( + mojom::blink::InputEventResultState ack_state) { + base::AutoLock lock(shared_state_lock_); + shared_state_.gsu_acked_as_consumed_ = false; +} + void MainThreadEventQueue::HandleEvent( std::unique_ptr<WebCoalescedInputEvent> event, DispatchType original_dispatch_type, @@ -670,7 +688,9 @@ if (IsRafAlignedEvent(shared_state_.events_.front())) { // Throttle touchmoves that are async. - if (IsAsyncTouchMove(shared_state_.events_.front())) { + if (IsAsyncTouchMove(shared_state_.events_.front()) && + ShouldThrottleAsyncTouchMoves( + shared_state_.gsu_acked_as_consumed_)) { if (shared_state_.events_.size() == 1 && frame_time < shared_state_.last_async_touch_move_timestamp_ + kAsyncTouchMoveInterval) { @@ -957,4 +977,17 @@ return compositor_thread_only_; } +bool MainThreadEventQueue::ShouldThrottleAsyncTouchMoves( + bool gsu_acked_as_consumed) { + // TODO(441800312): Investigate updating touch moves throttling logic during + // scrolls. + if (base::FeatureList::IsEnabled( + blink::features::kAsyncTouchMovesImmediatelyAfterScroll)) { + // If a gsu is acked as consumed already, async touch moves should indeed be + // throttled. + return gsu_acked_as_consumed; + } + return true; +} + } // namespace blink
diff --git a/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.h b/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.h index 953bd7c..f7297bd 100644 --- a/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.h +++ b/third_party/blink/renderer/platform/widget/input/main_thread_event_queue.h
@@ -113,6 +113,9 @@ // Type of dispatching of the event. enum class DispatchType { kBlocking, kNonBlocking }; + void OnGestureScrollUpdateAck(mojom::blink::InputEventResultState ack_state); + void OnGestureScrollEndAck(mojom::blink::InputEventResultState ack_state); + // Called once the compositor has handled |event| and indicated that it is // a non-blocking event to be queued to the main thread. void HandleEvent(std::unique_ptr<WebCoalescedInputEvent> event, @@ -218,6 +221,7 @@ bool sent_main_frame_request_ = false; // A PostTask to the main thread has been sent but not executed yet. bool sent_post_task_ = false; + bool gsu_acked_as_consumed_ = false; base::TimeTicks last_async_touch_move_timestamp_; }; @@ -235,6 +239,7 @@ std::unique_ptr<InputEventPrediction> event_predictor_; private: + bool ShouldThrottleAsyncTouchMoves(bool gsu_acked_as_consumed); // Returns false if we are trying to send a gesture scroll event to the main // thread when we shouldn't be. Used for DCHECK in HandleEvent. bool Allowed(const WebInputEvent& event, bool force_allow);
diff --git a/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc b/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc index 3c80769..75ba894 100644 --- a/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc +++ b/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc
@@ -1210,6 +1210,15 @@ mojom::blink::InputEventResultState ack_state = InputEventDispositionToAck(event_disposition); + if (event->Event().GetType() == + blink::WebInputEvent::Type::kGestureScrollUpdate) { + input_event_queue_->OnGestureScrollUpdateAck(ack_state); + } + if (event->Event().GetType() == + blink::WebInputEvent::Type::kGestureScrollEnd) { + input_event_queue_->OnGestureScrollEndAck(ack_state); + } + if (ack_state == mojom::blink::InputEventResultState::kConsumed) { widget_scheduler_->DidHandleInputEventOnCompositorThread( event->Event(), scheduler::WidgetScheduler::InputEventState::
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations index 692e42bc..cf0f90e 100644 --- a/third_party/blink/web_tests/TestExpectations +++ b/third_party/blink/web_tests/TestExpectations
@@ -9604,9 +9604,6 @@ # Gardener 2025-08-05 crbug.com/436615917 inspector-protocol/timeline/long-animation-frame-trace-visual-update.js [ Failure Pass ] -# Gardener 2025-08-06 -crbug.com/436534724 external/wpt/resource-timing/tentative/initiator-url/document-initiated.html [ Pass Timeout ] - # Gardener 2025-08-07 crbug.com/435121429 external/wpt/web-animations/timing-model/animations/setting-the-start-time-of-an-animation.html [ Failure Pass ]
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-cross-origin-referrerpolicy.sub.html b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-cross-origin-referrerpolicy.sub.html new file mode 100644 index 0000000..2d914f3d --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-cross-origin-referrerpolicy.sub.html
@@ -0,0 +1,96 @@ +<!DOCTYPE html> +<html> +<head> +<title>Modulepreload Cross-Origin Referrer Policy Tests</title> +<meta name="author" title="Google" href="https://www.google.com/"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/links.html#link-type-modulepreload"> +<link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/"> +<meta name="assert" content="link rel=modulepreload respects the referrerpolicy attribute for cross-origin requests"> +<!-- + This test verifies that modulepreload correctly handles various referrer policies + for cross-origin requests. It tests all standard referrer policy values: + - no-referrer + - origin + - same-origin + - strict-origin + - strict-origin-when-cross-origin + - unsafe-url + + It also tests that modulepreload respects the document's default referrer policy. + + Each policy is tested by creating a modulepreload link with CORS enabled and the + specific policy, then verifying the referrer header that was sent when requesting + the resource from another origin. +--> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/security-features/resources/common.sub.js"></script> +<script> +// This test is more of a basic verification that the modulepreload element +// correctly handles cross-origin requests with referrer policies, rather than +// a comprehensive test of all referrer policy values. Those are tested more +// thoroughly in the standard WPT referrer-policy tests. + +setup({ + // Allow more time for cross-origin tests + explicit_timeout: true, + single_test: false +}); + +// Helper function to create a modulepreload element +function createModulePreload(url, referrerPolicy = null) { + const link = document.createElement('link'); + link.rel = 'modulepreload'; + link.href = url; + link.crossOrigin = 'anonymous'; // Enable CORS + + if (referrerPolicy !== null) { + link.referrerPolicy = referrerPolicy; + } + + return link; +} + +// Helper function to test a modulepreload element with a specific referrer policy +function testModulePreloadWithPolicy(policy, testName) { + promise_test(async t => { + // Set a timeout for this test + t.step_timeout(() => { + assert_unreached("Test timed out"); + }, 10000); + + return new Promise((resolve, reject) => { + const link = createModulePreload( + `https://{{domains[www1]}}:{{ports[https][0]}}/common/security-features/subresource/script.py`, + policy + ); + + link.onload = () => { + // If we got here, the load succeeded, which is what we want to verify + assert_true(true, "Cross-origin modulepreload with " + policy + " policy loaded successfully"); + resolve(); + }; + + link.onerror = () => { + reject(new Error("Failed to load cross-origin modulepreload with " + policy + " policy")); + }; + + document.head.appendChild(link); + }); + }, testName); +} + +// Test basic cross-origin cases with different referrer policies +testModulePreloadWithPolicy(null, "Cross-origin modulepreload with default referrer policy should load"); +testModulePreloadWithPolicy("no-referrer", "Cross-origin modulepreload with no-referrer policy should load"); +testModulePreloadWithPolicy("origin", "Cross-origin modulepreload with origin policy should load"); +testModulePreloadWithPolicy("same-origin", "Cross-origin modulepreload with same-origin policy should load"); +testModulePreloadWithPolicy("strict-origin", "Cross-origin modulepreload with strict-origin policy should load"); +testModulePreloadWithPolicy("strict-origin-when-cross-origin", "Cross-origin modulepreload with strict-origin-when-cross-origin policy should load"); +testModulePreloadWithPolicy("unsafe-url", "Cross-origin modulepreload with unsafe-url policy should load"); +</script> +</head> +<body> +<div id="log"></div> +</body> +</html> \ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-inline-referrerpolicy.html b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-inline-referrerpolicy.html new file mode 100644 index 0000000..6a743e5 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-inline-referrerpolicy.html
@@ -0,0 +1,88 @@ +<!DOCTYPE html> +<html> +<head> +<title>Modulepreload Inline Referrer Policy Tests</title> +<meta name="author" title="Google" href="https://www.google.com/"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/links.html#link-type-modulepreload"> +<link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/"> +<meta name="assert" content="link rel=modulepreload respects the referrerpolicy attribute on inline elements"> +<!-- + This test verifies that inline modulepreload elements (statically declared in HTML) + correctly handle referrer policies. It tests: + + 1. Default behavior (no referrerpolicy attribute) - should use origin or full URL depending on implementation + 2. origin referrerpolicy - should use only origin + 3. no-referrer referrerpolicy - should not send referrer + 4. Document-level referrer policy (via meta tag) - should be respected + + Unlike the other modulepreload referrer tests that create elements dynamically, + this test uses inline link elements in the HTML. +--> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +// Initialize the window.referrers object that will be used by echo-referrer.py +window.referrers = {}; + +// Create unique IDs for each test +const noReferrerPolicyId = Date.now(); +const originPolicyId = Date.now() + 1; +const noReferrerId = Date.now() + 2; +const originId = Date.now() + 3; +</script> + +<!-- Test with no referrerpolicy attribute (should use document default) --> +<link rel="modulepreload" href="/preload/resources/echo-referrer.py?uid=NOPOLICY_ID"> + +<!-- Test with origin referrerpolicy attribute --> +<link rel="modulepreload" href="/preload/resources/echo-referrer.py?uid=ORIGIN_ID" referrerpolicy="origin"> + +<!-- Test with no-referrer referrerpolicy attribute --> +<link rel="modulepreload" href="/preload/resources/echo-referrer.py?uid=NOREFERRER_ID" referrerpolicy="no-referrer"> + +<!-- For document policy test, add meta tag for origin referrer policy --> +<meta name="referrer" content="origin"> +<link rel="modulepreload" href="/preload/resources/echo-referrer.py?uid=DOC_POLICY_ID"> + +<script> +// Replace placeholder IDs with actual IDs in the HTML +document.documentElement.innerHTML = document.documentElement.innerHTML + .replace('NOPOLICY_ID', noReferrerPolicyId) + .replace('ORIGIN_ID', originPolicyId) + .replace('NOREFERRER_ID', noReferrerId) + .replace('DOC_POLICY_ID', originId); +</script> +</head> +<body> +<script> +// Ensure modules are loaded by importing them +promise_test(async t => { + await Promise.all([ + import(`/preload/resources/echo-referrer.py?uid=${noReferrerPolicyId}`), + import(`/preload/resources/echo-referrer.py?uid=${originPolicyId}`), + import(`/preload/resources/echo-referrer.py?uid=${noReferrerId}`), + import(`/preload/resources/echo-referrer.py?uid=${originId}`) + ]); + + // Test that module with no specified referrerpolicy + // Note: Some implementations may send only origin for inline modulepreload requests + const expectedDefault = location.origin + "/"; + assert_equals(window.referrers[noReferrerPolicyId], expectedDefault, + "Modulepreload with no referrerpolicy should use expected referrer"); + + // Test that module with origin referrerpolicy sends only origin + assert_equals(window.referrers[originPolicyId], location.origin + "/", + "Modulepreload with origin referrerpolicy should send origin only"); + + // Test that module with no-referrer policy sends no referrer + assert_equals(window.referrers[noReferrerId], "", + "Modulepreload with no-referrer referrerpolicy should not send referrer"); + + // Test that module with no policy but document policy uses document policy + assert_equals(window.referrers[originId], location.origin + "/", + "Modulepreload with no referrerpolicy should use document's policy"); + +}, "Inline modulepreload elements should respect the referrerpolicy attribute"); +</script> +</body> +</html> \ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-referrer-check.html b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-referrer-check.html new file mode 100644 index 0000000..4d422e79 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-referrer-check.html
@@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html> +<head> +<title>Modulepreload Referrer Header Check</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +// Initialize the window.referrers object that will be used by echo-referrer.py +window.referrers = {}; +</script> +</head> +<body> +<iframe id="test-frame"></iframe> + +<script> +// This test uses a simple approach to check if modulepreload sends a referrer header +promise_test(async t => { + const importId = Date.now(); + const preloadId = Date.now() + 1; + + // Import will set window.referrers[importId] to the referrer value + await import(`/preload/resources/echo-referrer.py?uid=${importId}`); + + const link = document.createElement('link'); + link.rel = 'modulepreload'; + link.href = `/preload/resources/echo-referrer.py?uid=${preloadId}`; + + // Wait for the preload to complete using onload event + const preloadComplete = new Promise(resolve => { + link.onload = resolve; + link.onerror = () => { + assert_unreached("Modulepreload failed to load"); + }; + }); + + document.head.appendChild(link); + await preloadComplete; + + // Second import ensures the module is loaded and referrer is recorded + await import(`/preload/resources/echo-referrer.py?uid=${preloadId}`); + + // Check if referrers were recorded in the global window.referrers object + assert_equals(window.referrers[importId], location.href, "Dynamic import should have a referrer header"); + assert_equals(window.referrers[preloadId], location.href, "Modulepreload should have a referrer header"); + +}, "Modulepreload should send a referrer header"); +</script> +</body> +</html> \ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-referrerpolicy.html b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-referrerpolicy.html new file mode 100644 index 0000000..061f076 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/module/modulepreload-referrerpolicy.html
@@ -0,0 +1,200 @@ +<!DOCTYPE html> +<html> +<head> +<title>Modulepreload Referrer Policy Tests</title> +<meta name="author" title="Google" href="https://www.google.com/"> +<link rel="help" href="https://html.spec.whatwg.org/multipage/links.html#link-type-modulepreload"> +<link rel="help" href="https://w3c.github.io/webappsec-referrer-policy/"> +<meta name="assert" content="link rel=modulepreload respects the referrerpolicy attribute"> +<!-- + This test verifies that modulepreload correctly handles various referrer policies + for same-origin requests. It tests all standard referrer policy values: + - no-referrer + - origin + - same-origin + - strict-origin + - strict-origin-when-cross-origin + - unsafe-url + Each policy is tested by creating a modulepreload link dynamically with the + specific policy and verifying the referrer header that was sent. +--> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +// Initialize the window.referrers object that will be used by echo-referrer.py +window.referrers = {}; +</script> +</head> +<body> +<script> +// Helper function to create a unique ID for each test +function generateUniqueId() { + return Date.now() + Math.floor(Math.random() * 1000); +} + +// Helper function to create a modulepreload element with a given referrer policy +function createModulePreload(url, referrerPolicy = null) { + const link = document.createElement('link'); + link.rel = 'modulepreload'; + link.href = url; + + if (referrerPolicy !== null) { + link.referrerPolicy = referrerPolicy; + } + + return link; +} + +// Helper function to wait for preload completion +function waitForPreload(link) { + return new Promise((resolve, reject) => { + link.onload = resolve; + link.onerror = () => reject(new Error("Modulepreload failed to load")); + }); +} + +// Test default referrer policy behavior +promise_test(async t => { + const uid = generateUniqueId(); + const url = `/preload/resources/echo-referrer.py?uid=${uid}`; + + // First import to establish baseline + await import(url); + + // Create modulepreload + const link = createModulePreload(url); + const preloadComplete = waitForPreload(link); + + document.head.appendChild(link); + await preloadComplete; + + // Import again to ensure the module is loaded + await import(url); + + // Check if referrer was sent + assert_equals(window.referrers[uid], location.href, "Modulepreload should use default referrer policy"); + +}, "Modulepreload should use default referrer policy"); + +// Test explicit no-referrer policy +promise_test(async t => { + const uid = generateUniqueId(); + const url = `/preload/resources/echo-referrer.py?uid=${uid}`; + + // Create modulepreload with no-referrer policy + const link = createModulePreload(url, 'no-referrer'); + const preloadComplete = waitForPreload(link); + + document.head.appendChild(link); + await preloadComplete; + + // Import again to ensure the module is loaded + await import(url); + + // Check if referrer was NOT sent + assert_equals(window.referrers[uid], "", "Modulepreload with no-referrer policy should not send referrer"); + +}, "Modulepreload with no-referrer policy should not send referrer"); + +// Test origin referrer policy +promise_test(async t => { + const uid = generateUniqueId(); + const url = `/preload/resources/echo-referrer.py?uid=${uid}`; + + // Create modulepreload with origin policy + const link = createModulePreload(url, 'origin'); + const preloadComplete = waitForPreload(link); + + document.head.appendChild(link); + await preloadComplete; + + // Import again to ensure the module is loaded + await import(url); + + // Check if origin-only referrer was sent + assert_equals(window.referrers[uid], location.origin + "/", "Modulepreload with origin policy should send origin-only referrer"); + +}, "Modulepreload with origin policy should send origin-only referrer"); + +// Test same-origin referrer policy +promise_test(async t => { + const uid = generateUniqueId(); + const url = `/preload/resources/echo-referrer.py?uid=${uid}`; + + // Create modulepreload with same-origin policy + const link = createModulePreload(url, 'same-origin'); + const preloadComplete = waitForPreload(link); + + document.head.appendChild(link); + await preloadComplete; + + // Import again to ensure the module is loaded + await import(url); + + // Check if full referrer was sent (for same-origin requests) + assert_equals(window.referrers[uid], location.href, "Modulepreload with same-origin policy should send full referrer for same-origin requests"); + +}, "Modulepreload with same-origin policy should send full referrer for same-origin requests"); + +// Test strict-origin referrer policy +promise_test(async t => { + const uid = generateUniqueId(); + const url = `/preload/resources/echo-referrer.py?uid=${uid}`; + + // Create modulepreload with strict-origin policy + const link = createModulePreload(url, 'strict-origin'); + const preloadComplete = waitForPreload(link); + + document.head.appendChild(link); + await preloadComplete; + + // Import again to ensure the module is loaded + await import(url); + + // Check if origin-only referrer was sent + assert_equals(window.referrers[uid], location.origin + "/", "Modulepreload with strict-origin policy should send origin-only referrer"); + +}, "Modulepreload with strict-origin policy should send origin-only referrer"); + +// Test strict-origin-when-cross-origin referrer policy +promise_test(async t => { + const uid = generateUniqueId(); + const url = `/preload/resources/echo-referrer.py?uid=${uid}`; + + // Create modulepreload with strict-origin-when-cross-origin policy + const link = createModulePreload(url, 'strict-origin-when-cross-origin'); + const preloadComplete = waitForPreload(link); + + document.head.appendChild(link); + await preloadComplete; + + // Import again to ensure the module is loaded + await import(url); + + // For same-origin requests, full URL should be sent + assert_equals(window.referrers[uid], location.href, "Modulepreload with strict-origin-when-cross-origin policy should send full referrer for same-origin requests"); + +}, "Modulepreload with strict-origin-when-cross-origin policy should send full referrer for same-origin requests"); + +// Test unsafe-url referrer policy +promise_test(async t => { + const uid = generateUniqueId(); + const url = `/preload/resources/echo-referrer.py?uid=${uid}`; + + // Create modulepreload with unsafe-url policy + const link = createModulePreload(url, 'unsafe-url'); + const preloadComplete = waitForPreload(link); + + document.head.appendChild(link); + await preloadComplete; + + // Import again to ensure the module is loaded + await import(url); + + // Check if full referrer was sent + assert_equals(window.referrers[uid], location.href, "Modulepreload with unsafe-url policy should send full referrer"); + +}, "Modulepreload with unsafe-url policy should send full referrer"); +</script> +</body> +</html> \ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/resources/test-initiator.js b/third_party/blink/web_tests/external/wpt/resource-timing/resources/test-initiator.js index c6ad50a..6e13a02f 100644 --- a/third_party/blink/web_tests/external/wpt/resource-timing/resources/test-initiator.js +++ b/third_party/blink/web_tests/external/wpt/resource-timing/resources/test-initiator.js
@@ -1,25 +1,38 @@ -if (observe_entry === undefined) { - throw new Error("You must include resource-timing/resources/observe-entry.js " - + "before including this script."); + +const with_timeout_message = async (promise, message, timeout = 1000) => { + return Promise.race([ + promise, + new Promise((resolve, reject) => { + step_timeout(() => { + reject(new Error(message)); + }, timeout); + }), + ]); } +const observe_entry_no_timeout = entry_name => { + const entry = new Promise(resolve => { + new PerformanceObserver((entry_list, observer) => { + for (const entry of entry_list.getEntries()) { + if (entry.name.endsWith(entry_name)) { + resolve(entry); + observer.disconnect(); + return; + } + } + }).observe({"type": "resource", "buffered": true}); + }); + return entry; +}; + // Asserts that, for the given name, there is/will-be a // PerformanceResourceTiming entry that has the given 'initiatorUrl'. The test // is labeled according to the given descriptor. -const initiator_url_test = (resourceName, expectedUrl, descriptor) => { +const initiator_url_test = (entry_name, expected_url, descriptor, timeout_msg) => { promise_test(async () => { - const entry = await observe_entry(resourceName); - assert_equals(entry.initiatorUrl, expectedUrl); - }, `The initiator Url for ${descriptor} must be '${expectedUrl}'`); + const promise = observe_entry_no_timeout(entry_name); + const entry = await with_timeout_message(promise, timeout_msg, /* timeout = */ 2000); + assert_equals(entry.initiatorUrl, expected_url); + }, `The initiator Url for ${descriptor} must be '${expected_url}'`); }; -const initiator_url_doc_write_test = (write_to_doc, - resourceName, expectedUrl, descriptor) => { - promise_test(async () => { - document.open(); - document.write(write_to_doc); - document.close(); - const entry = await observe_entry(resourceName); - assert_equals(entry.initiatorUrl, expectedUrl); - }, `The initiator Url for ${descriptor} must be '${expectedUrl}'`); -};
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/tentative/initiator-url/document-initiated.html b/third_party/blink/web_tests/external/wpt/resource-timing/tentative/initiator-url/document-initiated.html index 6b19227..09da305 100644 --- a/third_party/blink/web_tests/external/wpt/resource-timing/tentative/initiator-url/document-initiated.html +++ b/third_party/blink/web_tests/external/wpt/resource-timing/tentative/initiator-url/document-initiated.html
@@ -44,7 +44,14 @@ <script> display_paragraph(); - // Load arbitrary stylesheet with inline script + // TODO(crbug.com/40919714): The two elements below are not initiated by html. + // They are initiated by script. They should be moved out of this test. + // As of 08/2025 the two below are passing for chromium but because of wrong + // reasons. They are not recognized to be resources initaited by scripts, so they + // are thought to be initiated from main html, for which the url happens to be + // correct for inline scripts. + + // Load arbitrary stylesheet with inline script. const link = document.createElement("link"); link.rel = "stylesheet"; link.href = "../../resources/empty_style.css?inline-script"; @@ -69,27 +76,15 @@ "Ahem.ttf?initiator-html", "blue.png?using-Img-tag", /* "green.html", - TODO(guohuideng@microsoft.com): report initiator for iframe element. + TODO(crbug.com/40919714): report initiator for iframe element. */ "empty_style.css?inline-script", "blue.png?inline-script", ]; for (const resource of resources) { - initiator_url_test(resource, expectedInitiatorUrl, resource+" initiatorUrl from document-initiated"); + initiator_url_test(resource, expectedInitiatorUrl, resource+ + " initiatorUrl from document-initiated", resource+" timeout"); } - write_to_doc = '<img id="img_written" src="/images/blue.png?inline-script-doc-write">'; - resource = "blue.png?inline-script-doc-write"; - initiator_url_doc_write_test(write_to_doc, resource, expectedInitiatorUrl, - resource+" initiatorUrl from document-initiated"); - - write_to_doc = '<link id="css_written" rel="stylesheet" href="../../resources/empty_style.css?inline-script-doc-write">'; - resource = "empty_style.css?inline-script-doc-write"; - initiator_url_doc_write_test(write_to_doc, resource, expectedInitiatorUrl, resource+" initiatorUrl from document-initiated"); - - write_to_doc = '<script id="script_written" src="../../resources/empty.js?doc-write"><\/script>'; - resource = "empty.js?doc-write"; - initiator_url_doc_write_test(write_to_doc, resource, expectedInitiatorUrl, resource+" initiatorUrl from document-initiated"); - </script> </body>
diff --git a/third_party/blink/web_tests/external/wpt/resource-timing/tentative/initiator-url/inline-document-write.html b/third_party/blink/web_tests/external/wpt/resource-timing/tentative/initiator-url/inline-document-write.html new file mode 100644 index 0000000..ac8f204 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/resource-timing/tentative/initiator-url/inline-document-write.html
@@ -0,0 +1,35 @@ +<!DOCTYPE html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resource-timing/resources/observe-entry.js"></script> + <script src="../../resources/test-initiator.js"></script> + <script src="/common/get-host-info.sub.js"></script> + +</head> +<body> + <script> + // As of 08/2025, Chromium hasn't properly implemented |initiator_Url| for document.write(). + // These tests are passing because they are wrongly considered as html inline resources. + // document.write() is deprecated. The implementation of "initiator-url" for it is not + // planned. document.write() test cases should be removed. + document.write('<img id="img_written" src="/images/blue.png?inline-script-doc-write">'); + document.write('<link id="css_written" rel="stylesheet" href="../resources/empty_style.css?inline-script-doc-write">'); + document.write('<script id="script_written" src="../resources/empty.js?doc-write"><\/script>'); + + const host_info = get_host_info(); + const expectedInitiatorUrl = host_info["ORIGIN"] + + "/resource-timing/tentative/initiator-url/inline-document-write.html"; + + const resources = [ + "blue.png?inline-script-doc-write", + "empty_style.css?inline-script-doc-write", + "empty.js?doc-write", + ]; + + for (const resource of resources) { + initiator_url_test(resource, expectedInitiatorUrl, resource+ + " initiatorUrl from document-initiated", resource+" timeout"); + } + </script> +</body>
diff --git a/third_party/blink/web_tests/webaudio/Analyser/realtimeanalyser-downmix.html b/third_party/blink/web_tests/webaudio/Analyser/realtimeanalyser-downmix.html index 132046a..2e023a6 100644 --- a/third_party/blink/web_tests/webaudio/Analyser/realtimeanalyser-downmix.html +++ b/third_party/blink/web_tests/webaudio/Analyser/realtimeanalyser-downmix.html
@@ -7,18 +7,15 @@ <script src="../../resources/testharness.js"></script> <script src="../../resources/testharnessreport.js"></script> <script src="../resources/audit-util.js"></script> - <script src="../resources/audit.js"></script> <script src="../resources/fft.js"></script> <script src="../resources/realtimeanalyser-testing.js"></script> </head> <body> - <script id="layout-test-code"> - let sampleRate = 44100; - let renderFrames = 2048; + <script> + const sampleRate = 44100; + const renderFrames = 2048; - let audit = Audit.createTaskRunner(); - - let testConfigs = [ + const testConfigs = [ {channelCount: 1, message: 'mono', floatRelError: 6.3283e-8}, {channelCount: 2, message: 'stereo', floatRelError: 1.1681e-7}, {channelCount: 4, message: 'quad', floatRelError: 4.9793e-7}, @@ -26,42 +23,35 @@ {channelCount: 3, message: '3-channel', floatRelError: 6.3283e-8} ]; - // Create tasks for each entry in testConfigs - for (k in testConfigs) { - audit.define(testConfigs[k].message, (function(config) { - return function(task, should) { - runTest(config, should).then(() => task.done()); - }; - })(testConfigs[k])); + // Create tests for each entry in testConfigs + for (const config of testConfigs) { + promise_test(async (t) => { + await runTest(config); + }, `Analyser downmix ${config.message} to mono`); } - audit.run(); - // Test downmixing of the AnalyserNode time data. We use the downmixing // that automatically happens in the destination node to generate the // reference data which is compared to the data that the Analyser node has // captured. - function runTest(options, should) { + function runTest(options) { // Context MUST have exactly one channel so that we downmix the source // to mono to generate the reference. - let context = new OfflineAudioContext(1, renderFrames, sampleRate); + const context = new OfflineAudioContext(1, renderFrames, sampleRate); - let channels = options.channelCount || 1; - let source = context.createBufferSource(); + const channels = options.channelCount || 1; + const source = context.createBufferSource(); // The signals in each channel. Doesn't matter much what is in here, but // it's best if the values aren't linearly increasing so that the // average of the values isn't one of the values (in case the // implementation does something silly). Only need to support up to 6 // channels. - let bufferValues = [1, 2, 3, 4, 5, 6].map(function(x) { - return x * x - }); - ; + const bufferValues = [1, 2, 3, 4, 5, 6].map((x) => x * x); source.buffer = createConstantBuffer( context, renderFrames, bufferValues.slice(0, channels)); - let analyser = context.createAnalyser(); + const analyser = context.createAnalyser(); analyser.smoothingTimeConstant = 0; analyser.fftSize = 256; @@ -71,34 +61,34 @@ source.connect(analyser); source.connect(context.destination); - let timeData = new Float32Array(analyser.fftSize); - let freqData = new Float32Array(analyser.frequencyBinCount); + const timeData = new Float32Array(analyser.fftSize); + const freqData = new Float32Array(analyser.frequencyBinCount); - let suspendFrame = analyser.fftSize; + const suspendFrame = analyser.fftSize; context.suspend(suspendFrame / context.sampleRate) - .then(function() { + .then(() => { analyser.getFloatTimeDomainData(timeData); analyser.getFloatFrequencyData(freqData); }) .then(context.resume.bind(context)); source.start(); - return context.startRendering().then(function(renderedBuffer) { + return context.startRendering().then((renderedBuffer) => { // Verify the time domain data is correct. - let prefix = 'Analyser downmix ' + options.message + ' to mono' - should(timeData, prefix + ' time data') - .beEqualToArray(renderedBuffer.getChannelData(0).subarray( - 0, analyser.fftSize)); + const prefix = 'Analyser downmix ' + options.message + ' to mono'; + assert_array_equals( + timeData, + renderedBuffer.getChannelData(0).subarray(0, analyser.fftSize), + prefix + ' time data'); - let expectedTimeData = + const expectedTimeData = renderedBuffer.getChannelData(0).subarray(0, analyser.fftSize); - let fftOrder = Math.floor(Math.log2(analyser.fftSize)); - let expectedFreqData = + const fftOrder = Math.floor(Math.log2(analyser.fftSize)); + const expectedFreqData = computeFFTMagnitude(expectedTimeData, fftOrder).map(linearToDb); - compareFloatFreq( - prefix + ' freq data', freqData, expectedFreqData, should, { - precision: 6, + compareFloatFreq_W3CTH( + prefix + ' freq data', freqData, expectedFreqData, { floatRelError: options.floatRelError, }); });
diff --git a/third_party/blink/web_tests/webaudio/resources/realtimeanalyser-testing.js b/third_party/blink/web_tests/webaudio/resources/realtimeanalyser-testing.js index 17441d81..ea291005 100644 --- a/third_party/blink/web_tests/webaudio/resources/realtimeanalyser-testing.js +++ b/third_party/blink/web_tests/webaudio/resources/realtimeanalyser-testing.js
@@ -98,11 +98,30 @@ return {success: success, expected: expectedFreq}; } +// Compare the float frequency data in dB, |actualFreq|, against the expected +// value, |expectedFreq|. |options| is a dictionary with the property +// |floatRelError| for setting the comparison threshold and |precision| for +// setting the printed precision. Setting |precision| to |undefined| means +// printing all digits. If |options.precision} doesn't exist, use a default +// precision. +function compareFloatFreq_W3CTH(message, actualFreq, expectedFreq, options) { + // Any dB values below -100 is pretty much in the noise due to round-off in + // the (single-precisiion) FFT, so just clip those values to -100. + const lowerLimit = -100; + clipMagnitude(lowerLimit, expectedFreq); + clipMagnitude(lowerLimit, actualFreq); + const threshold = {relativeThreshold: options.floatRelError || 0}; + + assert_array_equal_within_eps(actualFreq, expectedFreq, threshold, message); + + return {success: true, expected: expectedFreq}; +} + // Apply FFT smoothing, accumulating the result in |oldFreqData| with the new // data in |newFreqData|. The smoothing time constant is |smoothingTime| function smoothFFT(oldFreqData, newFreqData, smoothingTime) { for (let k = 0; k < oldFreqData.length; ++k) { - let value = + const value = smoothingTime * oldFreqData[k] + (1 - smoothingTime) * newFreqData[k]; oldFreqData[k] = value; }
diff --git a/third_party/crabbyavif/BUILD.gn b/third_party/crabbyavif/BUILD.gn index c15d90ba7..d63ff65 100644 --- a/third_party/crabbyavif/BUILD.gn +++ b/third_party/crabbyavif/BUILD.gn
@@ -162,6 +162,7 @@ "src/src/codecs/mod.rs", "src/src/decoder/item.rs", "src/src/decoder/mod.rs", + "src/src/decoder/sampletransform.rs", "src/src/decoder/tile.rs", "src/src/decoder/track.rs", "src/src/gainmap.rs", @@ -182,6 +183,7 @@ "src/src/reformat/rgb_impl.rs", "src/src/reformat/scale.rs", "src/src/utils/clap.rs", + "src/src/utils/error.rs", "src/src/utils/mod.rs", "src/src/utils/pixels.rs", "src/src/utils/reader/mod.rs",
diff --git a/third_party/crabbyavif/README.chromium b/third_party/crabbyavif/README.chromium index bcaa1922..585f770 100644 --- a/third_party/crabbyavif/README.chromium +++ b/third_party/crabbyavif/README.chromium
@@ -2,7 +2,7 @@ Short Name: crabbyavif URL: https://github.com/webmproject/CrabbyAvif Version: N/A -Revision: 4c70b98d1ebc8a210f2919be7ccabbcf23061cb5 +Revision: f96d484aa93776f076a2949805fd29bc4888b1c4 License: Apache-2.0 License File: LICENSE Security Critical: yes
diff --git a/third_party/crabbyavif/src b/third_party/crabbyavif/src index 4c70b98..f96d484 160000 --- a/third_party/crabbyavif/src +++ b/third_party/crabbyavif/src
@@ -1 +1 @@ -Subproject commit 4c70b98d1ebc8a210f2919be7ccabbcf23061cb5 +Subproject commit f96d484aa93776f076a2949805fd29bc4888b1c4
diff --git a/third_party/depot_tools b/third_party/depot_tools index 83ad192..0e9c00f 160000 --- a/third_party/depot_tools +++ b/third_party/depot_tools
@@ -1 +1 @@ -Subproject commit 83ad19235f633ab5ac843b3e977d53f4b7b042a5 +Subproject commit 0e9c00fe9f9783104836bd68870b5253c8c76121
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src index 5c0782b..6f55537 160000 --- a/third_party/devtools-frontend/src +++ b/third_party/devtools-frontend/src
@@ -1 +1 @@ -Subproject commit 5c0782b1ec316a185616cd76223d66c8b2b4dbbb +Subproject commit 6f55537aa2a782d9e8093896c857add3a88d01f3
diff --git a/third_party/federated_compute/BUILD.gn b/third_party/federated_compute/BUILD.gn index e2ef7b0..5ef8094 100644 --- a/third_party/federated_compute/BUILD.gn +++ b/third_party/federated_compute/BUILD.gn
@@ -2,10 +2,14 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//testing/test.gni") import("//third_party/protobuf/proto_library.gni") config("confidential_compute_include") { - include_dirs = [ "src" ] + include_dirs = [ + "chromium", + "src", + ] } proto_library("confidential_compute_proto") { @@ -34,10 +38,10 @@ ] sources = [ + "chromium/fcp/confidentialcompute/cose.cc", "src/fcp/base/base_name.cc", "src/fcp/base/digest.cc", "src/fcp/base/monitoring.cc", - "src/fcp/confidentialcompute/cose.cc", "src/fcp/confidentialcompute/crypto.cc", ] @@ -52,7 +56,33 @@ ] deps = [ + "//base", + "//components/cbor", "//third_party/abseil-cpp:absl", "//third_party/boringssl", ] } + +if (!is_win && !is_android && !is_ios && !is_fuchsia) { + test("federated_compute_tests") { + testonly = true + public = [ "chromium/fcp/testing/testing.h" ] + + sources = [ + "chromium/fcp/confidentialcompute/cose_test.cc", + "chromium/fcp/testing/testing.cc", + ] + + deps = [ + ":confidential_compute", + "//base", + "//components/cbor", + "//testing/gmock", + "//testing/gtest", + "//testing/gtest:gtest_main", + "//third_party/abseil-cpp:absl", + "//third_party/protobuf:protobuf_full", + "//third_party/protobuf:struct_proto", + ] + } +}
diff --git a/third_party/federated_compute/chromium/DEPS b/third_party/federated_compute/chromium/DEPS new file mode 100644 index 0000000..2670c2b --- /dev/null +++ b/third_party/federated_compute/chromium/DEPS
@@ -0,0 +1,10 @@ +noparent = True + +include_rules = [ + "+absl", + "+fcp", + "+gmock", + "+google", + "+gtest", + "+components/cbor", +] \ No newline at end of file
diff --git a/third_party/federated_compute/chromium/fcp/confidentialcompute/cose.cc b/third_party/federated_compute/chromium/fcp/confidentialcompute/cose.cc new file mode 100644 index 0000000..35e28615 --- /dev/null +++ b/third_party/federated_compute/chromium/fcp/confidentialcompute/cose.cc
@@ -0,0 +1,810 @@ +// Copyright 2024 Google LLC +// +// 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. + +#include "fcp/confidentialcompute/cose.h" + +#include <cstdint> +#include <memory> +#include <optional> +#include <string> +#include <tuple> +#include <utility> +#include <vector> + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "components/cbor/reader.h" +#include "components/cbor/values.h" +#include "components/cbor/writer.h" +#include "fcp/base/monitoring.h" + +namespace fcp::confidential_compute { + +namespace { + +// CWT Claims; see https://www.iana.org/assignments/cwt/cwt.xhtml. +enum CwtClaim { + kExp = 4, // Expiration time. + kIat = 6, // Issued at. + + // Claims in the private space (-65537 and below). + // See ../protos/confidentialcompute/cbor_ids.md for claims originating from + // this project and + // https://github.com/project-oak/oak/blob/main/oak_dice/src/cert.rs for Oak + // claims. + kPublicKey = -65537, // Claim containing serialized public key. + kConfigProperties = -65538, // Claim containing configuration properties. + kAccessPolicySha256 = -65543, // Claim containing access policy hash. + kOakPublicKey = -4670552, // Oak claim containing serialized public key. +}; + +// COSE Header parameters; see https://www.iana.org/assignments/cose/cose.xhtml. +enum CoseHeaderParameter { + kHdrAlg = 1, + kHdrKid = 4, + + // Parameters in the private space (-65537 and below). + // See ../protos/confidentialcompute/cbor_ids.md. + kEncapsulatedKey = -65537, + kSrcState = -65538, + kDstState = -65539, +}; + +// COSE Key parameters; see https://www.iana.org/assignments/cose/cose.xhtml. +enum CoseKeyParameter { + // Common parameters. + kKty = 1, + kKid = 2, + kAlg = 3, + kKeyOps = 4, + + // OKP parameters. + kOkpCrv = -1, + kOkpX = -2, + kOkpD = -4, + + // Symmetric parameters. + kSymmetricK = -1, +}; + +// COSE Key types; see https://www.iana.org/assignments/cose/cose.xhtml. +enum CoseKeyType { + kOkp = 1, + kSymmetric = 4, +}; + +// Builds the protected header for a COSE structure, which is a map encoded as a +// bstr. +std::vector<uint8_t> BuildProtectedHeader( + std::optional<int64_t> algorithm, + const std::optional<std::optional<std::string>>& src_state, + const std::optional<std::string>& dst_state) { + cbor::Value::MapValue map; + if (algorithm) { + map.emplace(cbor::Value(CoseHeaderParameter::kHdrAlg), + cbor::Value(*algorithm)); + } + if (src_state) { + map.emplace(cbor::Value(CoseHeaderParameter::kSrcState), + *src_state + ? cbor::Value(**src_state, cbor::Value::Type::BYTE_STRING) + : cbor::Value(cbor::Value::SimpleValue::NULL_VALUE)); + } + if (dst_state) { + map.emplace(cbor::Value(CoseHeaderParameter::kDstState), + cbor::Value(*dst_state, cbor::Value::Type::BYTE_STRING)); + } + std::optional<std::vector<uint8_t>> encoded_protected_header = + cbor::Writer::Write(cbor::Value(std::move(map))); + if (!encoded_protected_header) { + return {}; + } + return *encoded_protected_header; +} + +// Builds the payload for a CWT, which is a map of CWT claims encoded as a bstr. +// See RFC 8392 section 7.1. +absl::StatusOr<std::vector<uint8_t>> BuildCwtPayload(const OkpCwt& cwt) { + cbor::Value::MapValue map; + if (cwt.expiration_time) { + map.emplace(cbor::Value(CwtClaim::kExp), + cbor::Value(absl::ToUnixSeconds(*cwt.expiration_time))); + } + if (cwt.issued_at) { + map.emplace(cbor::Value(CwtClaim::kIat), + cbor::Value(absl::ToUnixSeconds(*cwt.issued_at))); + } + if (cwt.public_key) { + FCP_ASSIGN_OR_RETURN(std::string encoded_public_key, + cwt.public_key->Encode()); + map.emplace( + cbor::Value(CwtClaim::kPublicKey), + cbor::Value(encoded_public_key, cbor::Value::Type::BYTE_STRING)); + } + if (!cwt.config_properties.empty()) { + map.emplace( + cbor::Value(CwtClaim::kConfigProperties), + cbor::Value(cwt.config_properties, cbor::Value::Type::BYTE_STRING)); + } + if (!cwt.access_policy_sha256.empty()) { + map.emplace( + cbor::Value(CwtClaim::kAccessPolicySha256), + cbor::Value(cwt.access_policy_sha256, cbor::Value::Type::BYTE_STRING)); + } + + std::optional<std::vector<uint8_t>> encoded_cwt_payload = + cbor::Writer::Write(cbor::Value(std::move(map))); + if (!encoded_cwt_payload) { + return absl::InternalError("failed to build cwt payload"); + ; + } + return *encoded_cwt_payload; +} + +// Parses a serialized protected header from a COSE structure and sets the +// corresponding output variables (if non-null). +absl::Status ParseProtectedHeader( + const std::vector<uint8_t>& serialized_header, + std::optional<int64_t>* algorithm, + std::optional<std::optional<std::string>>* src_state, + std::optional<std::string>* dst_state) { + std::optional<cbor::Value> header = cbor::Reader::Read(serialized_header); + if (!header || !header->is_map()) { + return absl::InvalidArgumentError("protected header is invalid"); + } + + // Process the parameters map. + const cbor::Value::MapValue& map = header->GetMap(); + for (const auto& [key, value] : map) { + if (!key.is_integer()) continue; + switch (key.GetInteger()) { + case CoseHeaderParameter::kHdrAlg: + if (algorithm) { + if (!value.is_integer()) { + return absl::InvalidArgumentError(absl::StrCat( + "unsupported algorithm parameter type ", value.type())); + } + *algorithm = value.GetInteger(); + } + break; + + case CoseHeaderParameter::kSrcState: + if (src_state) { + if (value.is_simple() && + value.GetSimpleValue() == cbor::Value::SimpleValue::NULL_VALUE) { + *src_state = std::optional<std::string>(std::nullopt); + } else if (value.is_bytestring()) { + *src_state = value.GetBytestringAsString(); + } else { + return absl::InvalidArgumentError( + absl::StrCat("unsupported src_state type ", value.type())); + } + } + break; + + case CoseHeaderParameter::kDstState: + if (dst_state) { + if (!value.is_bytestring()) { + return absl::InvalidArgumentError( + absl::StrCat("unsupported dst_state type ", value.type())); + } + *dst_state = value.GetBytestringAsString(); + } + break; + + default: + break; + } + } + return absl::OkStatus(); +} + +// Parses a serialized CWT payload and updates the OkpCwt. +absl::Status ParseCwtPayload(const std::vector<uint8_t>& serialized_payload, + OkpCwt& cwt) { + std::optional<cbor::Value> payload = cbor::Reader::Read(serialized_payload); + if (!payload || !payload->is_map()) { + return absl::InvalidArgumentError("cwt payload is invalid"); + } + + // Process the claims map. + const cbor::Value::MapValue& map = payload->GetMap(); + for (const auto& [key, value] : map) { + if (!key.is_integer()) continue; + switch (key.GetInteger()) { + case CwtClaim::kExp: + if (!value.is_integer()) { + return absl::InvalidArgumentError( + absl::StrCat("unsupported exp type ", value.type())); + } + cwt.expiration_time = absl::FromUnixSeconds(value.GetInteger()); + break; + + case CwtClaim::kIat: + if (!value.is_integer()) { + return absl::InvalidArgumentError( + absl::StrCat("unsupported iat type ", value.type())); + } + cwt.issued_at = absl::FromUnixSeconds(value.GetInteger()); + break; + + case CwtClaim::kOakPublicKey: + case CwtClaim::kPublicKey: { + if (!value.is_bytestring()) { + return absl::InvalidArgumentError( + absl::StrCat("unsupported public_key type ", value.type())); + } + FCP_ASSIGN_OR_RETURN(cwt.public_key, + OkpKey::Decode(value.GetBytestringAsString())); + break; + } + + case CwtClaim::kConfigProperties: + if (!value.is_bytestring()) { + return absl::InvalidArgumentError( + absl::StrCat("unsupported configuration type ", value.type())); + } + cwt.config_properties = value.GetBytestringAsString(); + break; + + case CwtClaim::kAccessPolicySha256: + if (!value.is_bytestring()) { + return absl::InvalidArgumentError(absl::StrCat( + "unsupported access_policy_sha256 type ", value.type())); + } + cwt.access_policy_sha256 = value.GetBytestringAsString(); + break; + + default: + break; + } + } + return absl::OkStatus(); +} + +// Builds a Sig_structure object for a COSE_Sign or COSE_Sign1 structure. +// See RFC 9052 section 4.4 for the contents of the Sig_structure. +std::string BuildSigStructure( + std::vector<uint8_t> body_protected, + std::optional<std::vector<uint8_t>> sign_protected, absl::string_view aad, + std::vector<uint8_t> payload) { + cbor::Value::ArrayValue sig_structure; + sig_structure.emplace_back(sign_protected ? "Signature" : "Signature1"); + sig_structure.emplace_back(std::move(body_protected)); + if (sign_protected) { + sig_structure.emplace_back(std::move(*sign_protected)); + } + sig_structure.emplace_back(std::string(aad), cbor::Value::Type::BYTE_STRING); + sig_structure.emplace_back(std::move(payload)); + + std::optional<std::vector<uint8_t>> encoded_sig_structure = + cbor::Writer::Write(cbor::Value(std::move(sig_structure))); + if (!encoded_sig_structure) { + return {}; + } + return std::string(encoded_sig_structure->begin(), + encoded_sig_structure->end()); +} + +// Parses a serialized COSE_Sign or COSE_Sign1 structure and returns the +// protected header, signer protected header (COSE_Sign only), payload, and +// signature. +absl::StatusOr< + std::tuple<std::vector<uint8_t>, std::optional<std::vector<uint8_t>>, + std::vector<uint8_t>, std::vector<uint8_t>>> +ParseCoseSign(absl::string_view encoded) { + std::optional<cbor::Value> value = + cbor::Reader::Read(std::vector<uint8_t>(encoded.begin(), encoded.end())); + if (!value || !value->is_array()) { + return absl::InvalidArgumentError("COSE_Sign is invalid"); + } + + const cbor::Value::ArrayValue& array = value->GetArray(); + if (array.size() != 4 || !array[0].is_bytestring() || !array[1].is_map() || + !array[2].is_bytestring()) { + return absl::InvalidArgumentError("COSE_Sign is invalid"); + } + + // Extract the signature and signer protected header (COSE_Sign only). + std::optional<std::vector<uint8_t>> sign_protected; + std::optional<std::vector<uint8_t>> signature; + const cbor::Value& signature_component = array[3]; + if (signature_component.is_bytestring()) { + // If the 4th element is a bstr, we're decoding a COSE_Sign1 structure. + signature = std::move(signature_component.GetBytestring()); + } else if (signature_component.is_array() && + signature_component.GetArray().size() > 0) { + // If the 4th element is an array, we're decoding a COSE_Sign structure. + // Use the signature and protected header from the first COSE_Signature, + // which is a (protected header, unprotected header, signature) tuple. + const cbor::Value::ArrayValue& sigs = signature_component.GetArray(); + if (sigs[0].is_array() && sigs[0].GetArray().size() == 3 && + sigs[0].GetArray()[0].is_bytestring() && + sigs[0].GetArray()[2].is_bytestring()) { + sign_protected = std::move(sigs[0].GetArray()[0].GetBytestring()); + signature = std::move(sigs[0].GetArray()[2].GetBytestring()); + } + } + + if (!signature) { + return absl::InvalidArgumentError("COSE_Sign is invalid"); + } + + return std::make_tuple( + std::move(array[0].GetBytestring()), std::move(sign_protected), + std::move(array[2].GetBytestring()), std::move(*signature)); +} + +// Builds the payload for a ReleaseToken, which is a COSE_Encrypt0 object +// encoded as a bstr. See also RFC 9052 section 5.2. +std::vector<uint8_t> BuildReleaseTokenPayload(const ReleaseToken& token) { + cbor::Value::ArrayValue array_value; + array_value.emplace_back(BuildProtectedHeader( + token.encryption_algorithm, token.src_state, token.dst_state)); + + cbor::Value::MapValue unprotected_header; + if (token.encryption_key_id) { + unprotected_header.emplace( + CoseHeaderParameter::kHdrKid, + cbor::Value(*token.encryption_key_id, cbor::Value::Type::BYTE_STRING)); + } + if (token.encapped_key) { + unprotected_header.emplace( + CoseHeaderParameter::kEncapsulatedKey, + cbor::Value(*token.encapped_key, cbor::Value::Type::BYTE_STRING)); + } + array_value.emplace_back(std::move(unprotected_header)); + array_value.emplace_back(token.encrypted_payload, + cbor::Value::Type::BYTE_STRING); + std::optional<std::vector<uint8_t>> encoded_array = + cbor::Writer::Write(cbor::Value(std::move(array_value))); + if (!encoded_array) { + return {}; + } + return *encoded_array; +} + +// Builds a Enc_structure object for a COSE_Encrypt0 structure. +// See RFC 9052 section 5.3 for the contents of the Enc_structure. +std::string BuildEncStructure(std::vector<uint8_t> protected_header, + absl::string_view aad) { + cbor::Value::ArrayValue enc_structure; + enc_structure.emplace_back("Encrypt0"); + enc_structure.emplace_back(std::move(protected_header)); + enc_structure.emplace_back(aad, cbor::Value::Type::BYTE_STRING); + auto result = cbor::Writer::Write(cbor::Value(std::move(enc_structure))); + if (!result) { + return {}; + } + return std::string(result->begin(), result->end()); +} + +// Parses a serialized ReleaseToken payload and updated the ReleaseToken. +absl::StatusOr< + std::tuple<std::vector<uint8_t>, cbor::Value, std::vector<uint8_t>>> +ParseCoseEncrypt0(const std::vector<uint8_t>& encoded) { + std::optional<cbor::Value> value = + cbor::Reader::Read(std::vector<uint8_t>(encoded.begin(), encoded.end())); + if (!value || !value->is_array()) { + return absl::InvalidArgumentError("COSE_Sign is invalid"); + } + + const cbor::Value::ArrayValue& array = value->GetArray(); + if (array.size() != 3) { + return absl::InvalidArgumentError( + "COSE_Encrypt0 is invalid: not an array of size 3."); + } + + if (!array[0].is_bytestring() || !array[1].is_map() || + !array[2].is_bytestring()) { + return absl::InvalidArgumentError( + "COSE_Encrypt0 is invalid: wrong element types."); + } + + return std::make_tuple(array[0].GetBytestring(), array[1].Clone(), + array[2].GetBytestring()); +} + +} // namespace + +absl::StatusOr<OkpKey> OkpKey::Decode(absl::string_view encoded) { + std::optional<cbor::Value> encoded_value = + cbor::Reader::Read(std::vector<uint8_t>(encoded.begin(), encoded.end())); + if (!encoded_value || !encoded_value->is_map()) { + return absl::InvalidArgumentError("okpkey is invalid"); + } + + std::optional<int64_t> kty; + OkpKey okp_key; + const cbor::Value::MapValue& map = encoded_value->GetMap(); + for (const auto& [key, value] : map) { + if (!key.is_integer()) continue; + switch (key.GetInteger()) { + case CoseKeyParameter::kKty: + if (!value.is_integer()) { + return absl::InvalidArgumentError( + absl::StrCat("unsupported kty type ", value.type())); + } + kty = value.GetInteger(); + break; + + case CoseKeyParameter::kKid: + if (!value.is_bytestring()) { + return absl::InvalidArgumentError( + absl::StrCat("unsupported kid type ", value.type())); + } + okp_key.key_id = value.GetBytestringAsString(); + break; + + case CoseKeyParameter::kAlg: + if (!value.is_integer()) { + return absl::InvalidArgumentError( + absl::StrCat("unsupported alg type ", value.type())); + } + okp_key.algorithm = value.GetInteger(); + break; + + case CoseKeyParameter::kKeyOps: + if (!value.is_array()) { + return absl::InvalidArgumentError( + absl::StrCat("unsupported key_ops type ", value.type())); + } + for (const cbor::Value& op : value.GetArray()) { + if (!op.is_integer()) { + return absl::InvalidArgumentError( + absl::StrCat("unsupported key_ops entry type", op.type())); + } + okp_key.key_ops.push_back(op.GetInteger()); + } + break; + + case CoseKeyParameter::kOkpCrv: + if (!value.is_integer()) { + return absl::InvalidArgumentError( + absl::StrCat("unsupported curve type ", value.type())); + } + okp_key.curve = value.GetInteger(); + break; + + case CoseKeyParameter::kOkpX: + if (!value.is_bytestring()) { + return absl::InvalidArgumentError( + absl::StrCat("unsupported x type ", value.type())); + } + okp_key.x = value.GetBytestringAsString(); + break; + + case CoseKeyParameter::kOkpD: + if (!value.is_bytestring()) { + return absl::InvalidArgumentError( + absl::StrCat("unsupported d type ", value.type())); + } + okp_key.d = value.GetBytestringAsString(); + break; + + default: + break; + } + } + + if (!kty.has_value() || *kty != CoseKeyType::kOkp) { + return absl::InvalidArgumentError("missing or wrong Cose_Key type"); + } + return okp_key; +} + +absl::StatusOr<std::string> OkpKey::Encode() const { + // Generate a map containing the parameters that are set. + cbor::Value::MapValue map; + map.emplace(cbor::Value(CoseKeyParameter::kKty), + cbor::Value(CoseKeyType::kOkp)); + if (!key_id.empty()) { + map.emplace(cbor::Value(CoseKeyParameter::kKid), + cbor::Value(key_id, cbor::Value::Type::BYTE_STRING)); + } + if (algorithm) { + map.emplace(cbor::Value(CoseKeyParameter::kAlg), cbor::Value(*algorithm)); + } + if (!key_ops.empty()) { + cbor::Value::ArrayValue array; + for (int64_t key_op : key_ops) { + array.emplace_back(key_op); + } + map.emplace(cbor::Value(CoseKeyParameter::kKeyOps), + cbor::Value(std::move(array))); + } + if (curve) { + map.emplace(cbor::Value(CoseKeyParameter::kOkpCrv), cbor::Value(*curve)); + } + if (!x.empty()) { + map.emplace(cbor::Value(CoseKeyParameter::kOkpX), + cbor::Value(x, cbor::Value::Type::BYTE_STRING)); + } + if (!d.empty()) { + map.emplace(cbor::Value(CoseKeyParameter::kOkpD), + cbor::Value(d, cbor::Value::Type::BYTE_STRING)); + } + std::optional<std::vector<uint8_t>> encoded_key = + cbor::Writer::Write(cbor::Value(std::move(map))); + if (!encoded_key) { + return absl::InternalError("failed to encode OkpKey"); + } + return std::string(encoded_key->begin(), encoded_key->end()); +} + +absl::StatusOr<SymmetricKey> SymmetricKey::Decode(absl::string_view encoded) { + std::optional<cbor::Value> symmetric_key_value = + cbor::Reader::Read(std::vector<uint8_t>(encoded.begin(), encoded.end())); + if (!symmetric_key_value || !symmetric_key_value->is_map()) { + return absl::InvalidArgumentError("symmetric key is invalid"); + } + + std::optional<int64_t> kty; + SymmetricKey symmetric_key; + const cbor::Value::MapValue& map = symmetric_key_value->GetMap(); + for (const auto& [key, value] : map) { + if (!key.is_integer()) continue; + switch (key.GetInteger()) { + case CoseKeyParameter::kKty: + if (!value.is_integer()) { + return absl::InvalidArgumentError( + absl::StrCat("unsupported kty type ", value.type())); + } + kty = value.GetInteger(); + break; + + case CoseKeyParameter::kAlg: + if (!value.is_integer()) { + return absl::InvalidArgumentError( + absl::StrCat("unsupported alg type ", value.type())); + } + symmetric_key.algorithm = value.GetInteger(); + break; + + case CoseKeyParameter::kKeyOps: + if (!value.is_array()) { + return absl::InvalidArgumentError( + absl::StrCat("unsupported key_ops type ", value.type())); + } + for (const cbor::Value& op : value.GetArray()) { + if (!op.is_integer()) { + return absl::InvalidArgumentError( + absl::StrCat("unsupported key_ops entry type", op.type())); + } + symmetric_key.key_ops.push_back(op.GetInteger()); + } + break; + + case CoseKeyParameter::kSymmetricK: + if (!value.is_bytestring()) { + return absl::InvalidArgumentError( + absl::StrCat("unsupported k type ", value.type())); + } + symmetric_key.k = value.GetBytestringAsString(); + break; + + default: + break; + } + } + + if (!kty.has_value() || *kty != CoseKeyType::kSymmetric) { + return absl::InvalidArgumentError("missing or wrong Cose_Key type"); + } + return symmetric_key; +} + +absl::StatusOr<std::string> SymmetricKey::Encode() const { + cbor::Value::MapValue map; + map.emplace(cbor::Value(CoseKeyParameter::kKty), + cbor::Value(CoseKeyType::kSymmetric)); + if (algorithm) { + map.emplace(cbor::Value(CoseKeyParameter::kAlg), cbor::Value(*algorithm)); + } + if (!key_ops.empty()) { + cbor::Value::ArrayValue array; + for (int64_t key_op : key_ops) { + array.emplace_back(key_op); + } + map.emplace(cbor::Value(CoseKeyParameter::kKeyOps), + cbor::Value(std::move(array))); + } + if (!k.empty()) { + map.emplace(cbor::Value(CoseKeyParameter::kSymmetricK), + cbor::Value(k, cbor::Value::Type::BYTE_STRING)); + } + std::optional<std::vector<uint8_t>> encoded_key = + cbor::Writer::Write(cbor::Value(std::move(map))); + if (!encoded_key) { + return absl::InternalError("failed to encode SymmetricKey"); + } + return std::string(encoded_key->begin(), encoded_key->end()); +} + +absl::StatusOr<std::string> OkpCwt::BuildSigStructureForSigning( + absl::string_view aad) const { + std::vector<uint8_t> protected_header = + BuildProtectedHeader(algorithm, /*src_state=*/std::nullopt, + /*dst_state=*/std::nullopt); + FCP_ASSIGN_OR_RETURN(std::vector<uint8_t> payload, BuildCwtPayload(*this)); + return BuildSigStructure(std::move(protected_header), std::nullopt, aad, + std::move(payload)); +} + +absl::StatusOr<std::string> OkpCwt::GetSigStructureForVerifying( + absl::string_view encoded, absl::string_view aad) { + std::vector<uint8_t> body_protected, payload; + std::optional<std::vector<uint8_t>> sign_protected; + FCP_ASSIGN_OR_RETURN( + std::tie(body_protected, sign_protected, payload, std::ignore), + ParseCoseSign(encoded)); + return BuildSigStructure(std::move(body_protected), std::move(sign_protected), + aad, std::move(payload)); +} + +absl::StatusOr<OkpCwt> OkpCwt::Decode(absl::string_view encoded) { + std::vector<uint8_t> body_protected, payload, signature; + std::optional<std::vector<uint8_t>> sign_protected; + FCP_ASSIGN_OR_RETURN( + std::tie(body_protected, sign_protected, payload, signature), + ParseCoseSign(encoded)); + OkpCwt cwt; + // When decoding a COSE_Sign structure, information will be in the signer + // protected header instead of the body protected header. + FCP_RETURN_IF_ERROR(ParseProtectedHeader( + sign_protected ? *sign_protected : body_protected, &cwt.algorithm, + /*src_state=*/nullptr, + /*dst_state=*/nullptr)); + FCP_RETURN_IF_ERROR(ParseCwtPayload(payload, cwt)); + cwt.signature = std::string(signature.begin(), signature.end()); + return cwt; +} + +absl::StatusOr<std::string> OkpCwt::Encode() const { + // See RFC 9052 section 4.2 for the contents of the COSE_Sign1 structure. + cbor::Value::ArrayValue array; + array.emplace_back(BuildProtectedHeader(algorithm, /*src_state=*/std::nullopt, + /*dst_state=*/std::nullopt)); + array.emplace_back(cbor::Value::MapValue()); // unprotected header + FCP_ASSIGN_OR_RETURN(std::vector<uint8_t> payload, BuildCwtPayload(*this)); + array.emplace_back(std::move(payload)); + array.emplace_back(signature, cbor::Value::Type::BYTE_STRING); + std::optional<std::vector<uint8_t>> encoded_array = + cbor::Writer::Write(cbor::Value(std::move(array))); + if (!encoded_array) { + return absl::InternalError("failed to encode OkpCwt"); + } + return std::string(encoded_array->begin(), encoded_array->end()); +} + +absl::StatusOr<std::string> ReleaseToken::BuildEncStructureForEncrypting( + absl::string_view aad) const { + std::vector<uint8_t> protected_header = + BuildProtectedHeader(encryption_algorithm, src_state, dst_state); + return BuildEncStructure(std::move(protected_header), aad); +} + +absl::StatusOr<std::string> ReleaseToken::GetEncStructureForDecrypting( + absl::string_view encoded, absl::string_view aad) { + std::vector<uint8_t> payload; + FCP_ASSIGN_OR_RETURN(std::tie(std::ignore, std::ignore, payload, std::ignore), + ParseCoseSign(encoded)); + std::vector<uint8_t> protected_header; + FCP_ASSIGN_OR_RETURN(std::tie(protected_header, std::ignore, std::ignore), + ParseCoseEncrypt0(payload)); + return BuildEncStructure(std::move(protected_header), aad); +} + +absl::StatusOr<std::string> ReleaseToken::BuildSigStructureForSigning( + absl::string_view aad) const { + std::vector<uint8_t> protected_header = + BuildProtectedHeader(signing_algorithm, /*src_state=*/std::nullopt, + /*dst_state=*/std::nullopt); + std::vector<uint8_t> payload = BuildReleaseTokenPayload(*this); + return BuildSigStructure(std::move(protected_header), std::nullopt, aad, + std::move(payload)); +} + +absl::StatusOr<std::string> ReleaseToken::GetSigStructureForVerifying( + absl::string_view encoded, absl::string_view aad) { + // Like a CWT, a ReleaseToken is also a COSE_Sign1 object, so the + // Sig_structure is the same. + return OkpCwt::GetSigStructureForVerifying(encoded, aad); +} + +absl::StatusOr<ReleaseToken> ReleaseToken::Decode(absl::string_view encoded) { + ReleaseToken token; + + // Parse the outer COSE_Sign1 structure. + std::vector<uint8_t> protected_header_sign, signature; + std::optional<std::vector<uint8_t>> payload_sign; + FCP_ASSIGN_OR_RETURN( + std::tie(protected_header_sign, std::ignore, payload_sign, signature), + ParseCoseSign(encoded)); + + if (!payload_sign) { + return absl::InternalError( + "empty payload sign when decoding release token"); + } + + FCP_RETURN_IF_ERROR( + ParseProtectedHeader(protected_header_sign, &token.signing_algorithm, + /*src_state=*/nullptr, /*dst_state=*/nullptr)); + token.signature = std::string(signature.begin(), signature.end()); + + // Parse the inner COSE_Encrypt0 structure. + std::vector<uint8_t> protected_header_encrypt, encrypted_payload; + cbor::Value unprotected_header; + FCP_ASSIGN_OR_RETURN( + std::tie(protected_header_encrypt, unprotected_header, encrypted_payload), + ParseCoseEncrypt0(*payload_sign)); + + FCP_RETURN_IF_ERROR(ParseProtectedHeader(protected_header_encrypt, + &token.encryption_algorithm, + &token.src_state, &token.dst_state)); + token.encrypted_payload = + std::string(encrypted_payload.begin(), encrypted_payload.end()); + + // Process the COSE_Encrypt0 unprotected header. + if (unprotected_header.is_map()) { + for (const auto& [key, value] : unprotected_header.GetMap()) { + if (!key.is_integer()) { + continue; // Ignore other key types. + } + switch (key.GetInteger()) { + case CoseHeaderParameter::kHdrKid: + if (!value.is_bytestring()) { + return absl::InvalidArgumentError( + absl::StrCat("unsupported kid type ", value.type())); + } + token.encryption_key_id = value.GetBytestringAsString(); + break; + + case CoseHeaderParameter::kEncapsulatedKey: + if (!value.is_bytestring()) { + return absl::InvalidArgumentError(absl::StrCat( + "unsupported encapsulated key type ", value.type())); + } + token.encapped_key = value.GetBytestringAsString(); + break; + + default: + break; + } + } + } + return token; +} + +absl::StatusOr<std::string> ReleaseToken::Encode() const { + // See RFC 9052 section 4.2 for the contents of the COSE_Sign1 structure. + cbor::Value::ArrayValue array; + array.emplace_back(BuildProtectedHeader(signing_algorithm, + /*src_state=*/std::nullopt, + /*dst_state=*/std::nullopt)); + array.emplace_back(cbor::Value::MapValue()); // unprotected header + array.emplace_back(BuildReleaseTokenPayload(*this)); + array.emplace_back(signature, cbor::Value::Type::BYTE_STRING); + std::optional<std::vector<uint8_t>> encoded_array = + cbor::Writer::Write(cbor::Value(std::move(array))); + if (!encoded_array) { + return absl::InternalError("failed to build release token"); + } + return std::string(encoded_array->begin(), encoded_array->end()); +} + +} // namespace fcp::confidential_compute \ No newline at end of file
diff --git a/third_party/federated_compute/chromium/fcp/confidentialcompute/cose_test.cc b/third_party/federated_compute/chromium/fcp/confidentialcompute/cose_test.cc new file mode 100644 index 0000000..c4a796b --- /dev/null +++ b/third_party/federated_compute/chromium/fcp/confidentialcompute/cose_test.cc
@@ -0,0 +1,724 @@ +// Copyright 2024 Google LLC +// +// 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. + +#include "fcp/confidentialcompute/cose.h" + +#include <optional> +#include <string> + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "fcp/testing/testing.h" +#include "gmock/gmock.h" +#include "google/protobuf/struct.pb.h" +#include "gtest/gtest.h" + +namespace fcp::confidential_compute { +namespace { + +using ::testing::ElementsAre; +using ::testing::IsEmpty; + +MATCHER_P(IsOkAndHolds, matcher, "") { + return arg.ok() && + testing::ExplainMatchResult(matcher, arg.value(), result_listener); +} + +TEST(OkpKeyTest, EncodeEmpty) { + EXPECT_THAT(OkpKey().Encode(), + IsOkAndHolds("\xa1" // map with 1 item: + "\x01\x01" // key type (1): OKP (1) + )); +} + +TEST(OkpKeyTest, EncodeFull) { + EXPECT_THAT((OkpKey{ + .key_id = "key-id", + .algorithm = 7, + .key_ops = {1, 2}, + .curve = 45, + .x = "x-value", + .d = "d-value", + }) + .Encode(), + IsOkAndHolds("\xa7" // map with 7 items: + "\x01\x01" // key type (1): OKP (1) + "\x02\x46key-id" // key id (2): b"key-id" + "\x03\x07" // algorithm (3): 7 + "\x04\x82\x01\x02" // key_ops (4): [1, 2] + "\x20\x18\x2d" // curve (-1): 45 + "\x21\x47x-value" // x (-2): b"x-value" + "\x23\x47\x64-value" // d (-4): b"d-value" + )); +} + +TEST(OkpKeyTest, DecodeEmpty) { + absl::StatusOr<OkpKey> key = OkpKey::Decode("\xa1\x01\x01"); + ASSERT_OK(key); + EXPECT_EQ(key->key_id, ""); + EXPECT_EQ(key->algorithm, std::nullopt); + EXPECT_THAT(key->key_ops, IsEmpty()); + EXPECT_EQ(key->curve, std::nullopt); + EXPECT_EQ(key->x, ""); + EXPECT_EQ(key->d, ""); +} + +TEST(OkpKeyTest, DecodeFull) { + absl::StatusOr<OkpKey> key = OkpKey::Decode( + "\xa7\x01\x01\x02\x46key-id\x03\x07\x04\x82\x01\x02\x20\x18\x2d\x21\x47x-" + "value\x23\x47\x64-value"); + ASSERT_OK(key); + EXPECT_EQ(key->key_id, "key-id"); + EXPECT_EQ(key->algorithm, 7); + EXPECT_THAT(key->key_ops, ElementsAre(1, 2)); + EXPECT_EQ(key->curve, 45); + EXPECT_EQ(key->x, "x-value"); + EXPECT_EQ(key->d, "d-value"); +} + +TEST(OkpKeyTest, DecodeInvalid) { + EXPECT_THAT(OkpKey::Decode(""), IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(OkpKey::Decode("\xa5"), // map with 5 items + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(OkpKey::Decode("\xa0 extra"), // map with 0 items + " extra" + IsCode(absl::StatusCode::kInvalidArgument)); +} + +TEST(SymmetricKeyTest, EncodeEmpty) { + EXPECT_THAT(SymmetricKey().Encode(), + IsOkAndHolds("\xa1" // map with 1 item: + "\x01\x04" // key type (1): Symmetric (4) + )); +} + +TEST(SymmetricKeyTest, EncodeFull) { + EXPECT_THAT((SymmetricKey{ + .algorithm = 7, + .key_ops = {1, 2}, + .k = "secret", + }) + .Encode(), + IsOkAndHolds("\xa4" // map with 4 items: + "\x01\x04" // key type (1): Symmetric (4) + "\x03\x07" // algorithm (3): 7 + "\x04\x82\x01\x02" // key_ops (4): [1, 2] + "\x20\x46secret" // k (-1): b"secret" + )); +} + +TEST(SymmetricKeyTest, DecodeEmpty) { + absl::StatusOr<SymmetricKey> key = SymmetricKey::Decode("\xa1\x01\x04"); + ASSERT_OK(key); + EXPECT_EQ(key->algorithm, std::nullopt); + EXPECT_THAT(key->key_ops, IsEmpty()); + EXPECT_EQ(key->k, ""); +} + +TEST(SymmetricKeyTest, DecodeFull) { + absl::StatusOr<SymmetricKey> key = SymmetricKey::Decode( + "\xa4\x01\x04\x03\x07\x04\x82\x01\x02\x20\x46secret"); + ASSERT_OK(key); + EXPECT_EQ(key->algorithm, 7); + EXPECT_THAT(key->key_ops, ElementsAre(1, 2)); + EXPECT_EQ(key->k, "secret"); +} + +TEST(SymmetricKeyTest, DecodeInvalid) { + EXPECT_THAT(SymmetricKey::Decode(""), + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(SymmetricKey::Decode("\xa3"), // map with 5 items + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(SymmetricKey::Decode("\xa0 xtra"), // map with 0 items + " xtra" + IsCode(absl::StatusCode::kInvalidArgument)); +} + +TEST(OkpCwtTest, BuildSigStructureForSigningEmpty) { + EXPECT_THAT(OkpCwt().BuildSigStructureForSigning(""), + IsOkAndHolds("\x84" // array with 4 items: + "\x6aSignature1" // "Signature1" + "\x41\xa0" // protected headers + "\x40" // aad: b"" + "\x41\xa0" // payload (empty claims map) + )); +} + +TEST(OkpCwtTest, BuildSigStructureForSigningFull) { + EXPECT_THAT( + (OkpCwt{ + .algorithm = 7, + .issued_at = absl::FromUnixSeconds(1000), + .expiration_time = absl::FromUnixSeconds(2000), + .public_key = OkpKey(), + .signature = "signature", + }) + .BuildSigStructureForSigning("aad"), + IsOkAndHolds(absl::string_view( + "\x84" // array with 4 items: + "\x6aSignature1" // "Signature1" + "\x43\xa1" // protected headers: bstr map w/ 1 item + "\x01\x07" // alg: 7 + "\x43" // associated data: 3-byte string + "aad" + "\x52\xa3" // payload: bstr map w/ 3 items (claims) + "\x04\x19\x07\xd0" // expiration time (4) = 2000 + "\x06\x19\x03\xe8" // issued at (6) = 1000 + "\x3a\x00\x01\x00\x00\x43\xa1\x01\x01", // public key (-65537) + // = empty OkpKey + 39))); +} + +TEST(OkpCwtTest, GetSigStructureForVerifyingMatchesStructureForSigning) { + OkpCwt cwt{ + .algorithm = 7, + .issued_at = absl::FromUnixSeconds(1000), + .expiration_time = absl::FromUnixSeconds(2000), + .public_key = OkpKey(), + .signature = "signature", + }; + absl::StatusOr<std::string> expected = cwt.BuildSigStructureForSigning("aad"); + ASSERT_OK(expected); + absl::StatusOr<std::string> encoded = cwt.Encode(); + ASSERT_OK(encoded); + + absl::StatusOr<std::string> sig_structure = + OkpCwt::GetSigStructureForVerifying(*encoded, "aad"); + ASSERT_OK(sig_structure); + EXPECT_EQ(*sig_structure, *expected); +} + +TEST(OkpCwtTest, GetSigStructureForVerifyingInvalid) { + EXPECT_THAT(OkpCwt::GetSigStructureForVerifying("", ""), + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(OkpCwt::GetSigStructureForVerifying("\xa3", // map with 3 items + ""), + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(OkpCwt::GetSigStructureForVerifying( + "\xa0 extra", // map with 0 items + " extra" + ""), + IsCode(absl::StatusCode::kInvalidArgument)); + + // Even if the CBOR is valid, the top-level structure must be a 4-element + // array. + EXPECT_THAT( + OkpCwt::GetSigStructureForVerifying("\xa0", ""), // map with 0 items + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT( + OkpCwt::GetSigStructureForVerifying("\x80", ""), // array with 0 items + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(OkpCwt::GetSigStructureForVerifying( + "\x83\x41\xa0\xa0\x41\xa0", // array with 3 items + ""), + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(OkpCwt::GetSigStructureForVerifying( + "\x85\x41\xa0\xa0\x41\xa0\x40\x40", // array with 5 items + ""), + IsCode(absl::StatusCode::kInvalidArgument)); + + // The map entry types must be bstr, *, bstr, *. + // "\x84\x41\xa0\xa0\x41\xa0\x40" is valid. + EXPECT_THAT(OkpCwt::GetSigStructureForVerifying( + "\x84\xa0\xa0\x41\xa0\x40", // 1st not bstr + ""), + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(OkpCwt::GetSigStructureForVerifying( + "\x84\x41\xa0\xa0\xa0\x40", // 3rd not bstr + ""), + IsCode(absl::StatusCode::kInvalidArgument)); +} + +TEST(OkpCwtTest, GetSigStructureForVerifyingReferenceExample) { + // From RFC 8392 Section A.3, without the leading COSE_Sign1 tag "d2": + std::string encoded = ""; + EXPECT_TRUE(absl::HexStringToBytes( + "8443a10126a104524173796d6d657472696345434453413235365850a701756" + "36f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f" + "61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d" + "9f0061a5610d9f007420b7158405427c1ff28d23fbad1f29c4c7c6a555e601d6f" + "a29f9179bc3d7438bacaca5acd08c8d4d4f96131680c429a01f85951ecee743a5" + "2b9b63632c57209120e1c9e30", + &encoded)); + absl::StatusOr<std::string> sig_structure = + OkpCwt::GetSigStructureForVerifying(encoded, "aad"); + ASSERT_OK(sig_structure); + EXPECT_EQ( + absl::BytesToHexString(*sig_structure), + "846a5369676e61747572653143a10126436161645850a70175636f61703a2f2f61732e65" + "78616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e657861" + "6d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71"); +} + +TEST(OkpCwtTest, EncodeEmpty) { + EXPECT_THAT( + OkpCwt().Encode(), + IsOkAndHolds("\x84" // array with 4 items: + "\x41\xa0" // bstr containing empty map (protected headers) + "\xa0" // empty map (unprotected headers) + "\x41\xa0" // bstr containing empty map (claims) + "\x40" // b"" (signature) + )); +} + +TEST(OkpCwtTest, EncodeFull) { + google::protobuf::Struct config_properties; + (*config_properties.mutable_fields())["x"].set_bool_value(true); + + EXPECT_THAT( + (OkpCwt{ + .algorithm = 7, + .issued_at = absl::FromUnixSeconds(1000), + .expiration_time = absl::FromUnixSeconds(2000), + .public_key = OkpKey(), + .config_properties = config_properties.SerializeAsString(), + .access_policy_sha256 = "hash", + .signature = "signature", + }) + .Encode(), + IsOkAndHolds(absl::string_view( + "\x84" // array with 4 items: + "\x43\xa1\x01\x07" // bstr containing { alg: 7 } (protected headers) + "\xa0" // empty map (unprotected headers) + "\x58\x2b\xa5" // bstr containing a map with 5 items: (claims) + "\x04\x19\x07\xd0" // expiration time (4) = 2000 + "\x06\x19\x03\xe8" // issued at (6) = 1000 + "\x3a\x00\x01\x00\x00\x43\xa1\x01\x01" // public key (-65537) + // = empty OkpKey + "\x3a\x00\x01\x00\x01\x49" // config (-65538) = { + "\x0a\x07" // fields (1) { + "\x0a\x01x" // key (1): "x" + "\x12\x02" // value (2): { + "\x20\x01" // bool_value (4): true + // } + // } + "\x3a\x00\x01\x00\x06\x44hash" // access_policy_sha256 (-65543) = + // b"hash" + "\x49signature", + 61))); +} + +TEST(OkpCwtTest, DecodeEmpty) { + absl::StatusOr<OkpCwt> key = OkpCwt::Decode("\x84\x41\xa0\xa0\x41\xa0\x40"); + ASSERT_OK(key); + EXPECT_EQ(key->issued_at, std::nullopt); + EXPECT_EQ(key->expiration_time, std::nullopt); + EXPECT_EQ(key->public_key, std::nullopt); + EXPECT_EQ(key->config_properties, ""); + EXPECT_EQ(key->access_policy_sha256, ""); + EXPECT_EQ(key->signature, ""); +} + +TEST(OkpCwtTest, DecodeFull) { + absl::StatusOr<OkpCwt> cwt = OkpCwt::Decode(absl::string_view( + "\x84\x43\xa1\x01\x07\xa0\x58\x2b\xa5\x04\x19\x07\xd0\x06\x19\x03\xe8\x3a" + "\x00\x01\x00\x00\x43\xa1\x01\x01\x3a\x00\x01\x00\x01\x49\x0a\x07\x0a\x01" + "x\x12\x02\x20\x01\x3a\x00\x01\x00\x06\x44hash\x49signature", + 61)); + ASSERT_OK(cwt); + EXPECT_EQ(cwt->issued_at, absl::FromUnixSeconds(1000)); + EXPECT_EQ(cwt->expiration_time, absl::FromUnixSeconds(2000)); + EXPECT_TRUE(cwt->public_key.has_value()); + EXPECT_EQ(cwt->access_policy_sha256, "hash"); + EXPECT_EQ(cwt->signature, "signature"); + + google::protobuf::Struct config_properties; + ASSERT_TRUE(config_properties.ParseFromString(cwt->config_properties)); + + // Because `google/protobuf/util/message_differencer.h` cannot be used with + // PROTOBUF_EXPORT in Chromium, we cannot use EqualsProto. As such, we + // manually match the expected fields. + + // EXPECT_THAT(config_properties, EqualsProto(R"pb( + // fields { + // key: "x" + // value { bool_value: true } + // } + // )pb")); + + const auto& fields = config_properties.fields(); + ASSERT_EQ(fields.size(), 1u); + + const auto it = fields.find("x"); + ASSERT_NE(it, fields.end()); + + const auto& value = it->second; + ASSERT_EQ(value.kind_case(), google::protobuf::Value::kBoolValue); + ASSERT_TRUE(value.bool_value()); +} + +TEST(OkpCwtTest, DecodeInvalid) { + EXPECT_THAT(OkpCwt::Decode(""), IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(OkpCwt::Decode("\xa3"), // map with 5 items + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(OkpCwt::Decode("\xa0 extra"), // map with 0 items + " extra" + IsCode(absl::StatusCode::kInvalidArgument)); + + // Even if the CBOR is valid, the top-level structure must be a 4-element + // array. + EXPECT_THAT(OkpCwt::Decode("\xa0"), // map with 0 items + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(OkpCwt::Decode("\x80"), // array with 0 items + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(OkpCwt::Decode("\x83\x41\xa0\xa0\x41\xa0"), // array with 3 items + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT( + OkpCwt::Decode("\x85\x41\xa0\xa0\x41\xa0\x40\x40"), // array with 5 items + IsCode(absl::StatusCode::kInvalidArgument)); + + // The map entry types must be bstr, map, bstr, bstr. + // "\x84\x41\xa0\xa0\x41\xa0\x40" is valid. + EXPECT_THAT(OkpCwt::Decode("\x84\xa0\xa0\x41\xa0\x40"), // 1st not bstr + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(OkpCwt::Decode("\x84\x41\xa0\x40\x41\xa0\x40"), // 2nd not map + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(OkpCwt::Decode("\x84\x41\xa0\xa0\xa0\x40"), // 3rd not bstr + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(OkpCwt::Decode("\x84\x41\xa0\xa0\x41\xa0\xa0"), // 4th not bstr + IsCode(absl::StatusCode::kInvalidArgument)); +} + +TEST(OkpCwtTest, DecodeCoseSign1ReferenceExample) { + // From RFC 8392 Section A.3, without the leading COSE_Sign1 tag "d2": + std::string encoded = ""; + EXPECT_TRUE(absl::HexStringToBytes( + "8443a10126a104524173796d6d657472696345434453413235365850a701756" + "36f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f" + "61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d" + "9f0061a5610d9f007420b7158405427c1ff28d23fbad1f29c4c7c6a555e601d6f" + "a29f9179bc3d7438bacaca5acd08c8d4d4f96131680c429a01f85951ecee743a5" + "2b9b63632c57209120e1c9e30", + &encoded)); + absl::StatusOr<OkpCwt> cwt = OkpCwt::Decode(encoded); + ASSERT_OK(cwt); + EXPECT_EQ(cwt->issued_at, absl::FromUnixSeconds(1443944944)); + EXPECT_EQ(cwt->expiration_time, absl::FromUnixSeconds(1444064944)); + EXPECT_FALSE(cwt->public_key.has_value()); + std::string expected_signature = ""; + EXPECT_TRUE(absl::HexStringToBytes( + "5427c1ff28d23fbad1f29c4c7c6a555e601d6fa29f9179bc3d7438bacaca5acd08c8" + "d4d4f96131680c429a01f85951ecee743a52b9b63632c57209120e1c9e30", + &expected_signature)); + EXPECT_EQ(cwt->signature, expected_signature); +} + +TEST(OkpCwtTest, VerifyAndDecodeCoseSign) { + // The encoded CWT and signature structure were generated using the Rust coset + // crate. + std::string encoded = ""; + EXPECT_TRUE(absl::HexStringToBytes( + "8440a05846a30419162e061904d23a000100005836a5010102466b65792d6964033a0001" + "000020042158204850e21e94eb470337fd46a401f4c8b46150195732fb47d53fa0533f59" + "4cb342828343a10126a058208dfb544c010408b5c24eeaf67e2ff89b98dcab365d50b244" + "7d569c1561540bc28344a101382ea058206b33008400add41a3b82ef4bf4bb85fcf8d2d5" + "7d2f453bcddb277cf3fa3880d6", + &encoded)); + + absl::StatusOr<std::string> sig_structure = + OkpCwt::GetSigStructureForVerifying(encoded, ""); + ASSERT_OK(sig_structure); + EXPECT_EQ( + absl::BytesToHexString(*sig_structure), + "85695369676e61747572654043a10126405846a30419162e061904d23a000100005836a5" + "010102466b65792d6964033a0001000020042158204850e21e94eb470337fd46a401f4c8" + "b46150195732fb47d53fa0533f594cb342"); + + absl::StatusOr<OkpCwt> cwt = OkpCwt::Decode(encoded); + ASSERT_OK(cwt); + EXPECT_EQ(cwt->algorithm, -7); + EXPECT_EQ(cwt->issued_at, absl::FromUnixSeconds(1234)); + EXPECT_EQ(cwt->expiration_time, absl::FromUnixSeconds(5678)); + ASSERT_TRUE(cwt->public_key.has_value()); + EXPECT_EQ(cwt->public_key->key_id, "key-id"); + EXPECT_EQ(cwt->public_key->algorithm, -65537); + EXPECT_EQ(cwt->public_key->curve, 4); + EXPECT_NE(cwt->public_key->x, ""); +} +TEST(ReleaseTokenTest, EncodeEmpty) { + EXPECT_THAT( + ReleaseToken().Encode(), + IsOkAndHolds( + "\x84" // array with 4 items: + "\x41\xa0" // bstr containing empty map (protected headers) + "\xa0" // empty map (unprotected headers) + "\x45\x83" // bstr containing an array with 3 items: (COSE_Encrypt0) + "\x41\xa0" // bstr containing empty map (protected headers) + "\xa0" // empty map (unprotected headers) + "\x40" // b"" (encrypted payload) + "\x40" // b"" (signature) + )); +} + +TEST(ReleaseTokenTest, EncodeFull) { + EXPECT_THAT( + (ReleaseToken{ + .signing_algorithm = 1, + .encryption_algorithm = 2, + .encryption_key_id = "key-id", + .src_state = "old-state", + .dst_state = "new-state", + .encrypted_payload = "payload", + .encapped_key = "key", + .signature = "signature", + }) + .Encode(), + IsOkAndHolds(absl::string_view( + "\x84" // array with 4 items: + "\x43\xa1\x01\x01" // bstr containing { alg: 1 } (protected headers) + "\xa0" // empty map (unprotected headers) + "\x58\x3e\x83" // bstr containing an array with 3 items: + // (COSE_Encrypt0) + "\x58\x21\xa3" // bstr containing a map with 3 items: + // (protected headers) + "\x01\x02" // alg: 2 + "\x3a\x00\x01\x00\x01\x49old-state" // -65538: b"old-state" + "\x3a\x00\x01\x00\x02\x49new-state" // -65539: b"new-state" + "\xa2" // map with 2 items: (unprotected headers) + "\x04\x46key-id" // key_id: b"key-id" + "\x3a\x00\x01\x00\x00\x43key" // encapped_key: b"key" + "\x47payload" // b"encrypted-payload" (payload) + "\x49signature", // b"signature" (signature) + 80))); +} + +TEST(ReleaseTokenTest, EncodeNullSrcState) { + EXPECT_THAT( + (ReleaseToken{ + .src_state = std::optional<std::string>(std::nullopt), + }) + .Encode(), + IsOkAndHolds(absl::string_view( + "\x84" // array with 4 items: + "\x41\xa0" // bstr containing empty map (protected headers) + "\xa0" // empty map (unprotected headers) + "\x4b\x83" // bstr containing an array with 3 items: + // (COSE_Encrypt0) + "\x47\xa1\x3a\x00\x01\x00\x01\xf6" // bstr containing { src_state: + // null } (protected headers) + "\xa0" // empty map (unprotected headers) + "\x40" // b"" (payload) + "\x40", // b"" (signature) + 17))); +} + +TEST(ReleaseTokenTest, DecodeEmpty) { + absl::StatusOr<ReleaseToken> release_token = + ReleaseToken::Decode("\x84\x41\xa0\xa0\x45\x83\x41\xa0\xa0\x40\x40"); + ASSERT_OK(release_token); + EXPECT_EQ(release_token->signing_algorithm, std::nullopt); + EXPECT_EQ(release_token->encryption_algorithm, std::nullopt); + EXPECT_EQ(release_token->encryption_key_id, std::nullopt); + EXPECT_EQ(release_token->src_state, std::nullopt); + EXPECT_EQ(release_token->dst_state, std::nullopt); + EXPECT_EQ(release_token->encrypted_payload, ""); + EXPECT_EQ(release_token->encapped_key, std::nullopt); + EXPECT_EQ(release_token->signature, ""); +} + +TEST(ReleaseTokenTest, DecodeFull) { + absl::StatusOr<ReleaseToken> release_token = + ReleaseToken::Decode(absl::string_view( + "\x84\x43\xa1\x01\x01\xa0\x58\x3e\x83\x58\x21\xa3\x01\x02\x3a\x00\x01" + "\x00\x01\x49old-state\x3a\x00\x01\x00\x02\x49new-state\xa2\x04\x46ke" + "y-id\x3a\x00\x01\x00\x00\x43key\x47payload\x49signature", + 80)); + ASSERT_OK(release_token); + EXPECT_EQ(release_token->signing_algorithm, 1); + EXPECT_EQ(release_token->encryption_algorithm, 2); + EXPECT_EQ(release_token->encryption_key_id, "key-id"); + EXPECT_EQ(release_token->src_state, "old-state"); + EXPECT_EQ(release_token->dst_state, "new-state"); + EXPECT_EQ(release_token->encrypted_payload, "payload"); + EXPECT_EQ(release_token->encapped_key, "key"); + EXPECT_EQ(release_token->signature, "signature"); +} + +TEST(ReleaseTokenTest, DecodeNullSrcState) { + absl::StatusOr<ReleaseToken> release_token = ReleaseToken::Decode( + absl::string_view("\x84\x41\xa0\xa0\x4b\x83\x47\xa1\x3a\x00\x01\x00\x01" + "\xf6\xa0\x40\x40", + 17)); + ASSERT_OK(release_token); + ASSERT_TRUE(release_token->src_state.has_value()); + EXPECT_EQ(*release_token->src_state, std::nullopt); +} + +TEST(ReleaseTokenTest, DecodeInvalidCoseSign1) { + EXPECT_THAT(ReleaseToken::Decode(""), + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(ReleaseToken::Decode("\xa3"), // map with 3 items + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT( + ReleaseToken::Decode("\xa0 extra"), // map with 0 items + " extra" + IsCode(absl::StatusCode::kInvalidArgument)); + + // Even if the CBOR is valid, the top-level structure must be a 4-element + // array. + EXPECT_THAT(ReleaseToken::Decode("\xa0"), // map with 0 items + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(ReleaseToken::Decode("\x80"), // array with 0 items + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT( + ReleaseToken::Decode("\x83\x41\xa0\xa0\x41\xa0"), // array with 3 items + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(ReleaseToken::Decode( // array with 5 items + "\x85\x41\xa0\xa0\x41\xa0\x40\x40"), + IsCode(absl::StatusCode::kInvalidArgument)); + + // The COSE_Sign1 array entry types must be bstr, map, bstr, bstr. + // "\x84\x41\xa0\xa0\x45\x83\x41\xa0\xa0\x40\x40" is valid. + EXPECT_THAT(ReleaseToken::Decode( // 1st not bstr + "\x84\xa0\xa0\x45\x83\x41\xa0\xa0\x40\x40"), + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(ReleaseToken::Decode( // 2nd not map + "\x84\x41\xa0\x40\x45\x83\x41\xa0\xa0\x40\x40"), + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(ReleaseToken::Decode("\x84\x41\xa0\xa0\xa0\x40"), // 3rd not bstr + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(ReleaseToken::Decode( // 4th not bstr + "\x84\x41\xa0\xa0\x45\x83\x41\xa0\xa0\x40\xa0"), + IsCode(absl::StatusCode::kInvalidArgument)); +} + +TEST(ReleaseTokenTest, DecodeInvalidCoseEncrypt0) { + // The serialized COSE_Encrypt0 is wrapped in a COSE_Sign1 structure: + // "\x84\x41\xa0\xa0\x4N<COSE_Encrypt0>\x40" is valid. + + EXPECT_THAT(ReleaseToken::Decode("\x84\x41\xa0\xa0\x40\x40"), // empty string + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT( + ReleaseToken::Decode("\x84\x41\xa0\xa0\x41\xa3\x40"), // map with 3 items + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(ReleaseToken::Decode( // map with 0 items + " extra" + "\x84\x41\xa0\xa0\x47\xa0 extra\x40"), + IsCode(absl::StatusCode::kInvalidArgument)); + + // Even if the CBOR is valid, the top-level structure must be a 4-element + // array. + EXPECT_THAT( + ReleaseToken::Decode("\x84\x41\xa0\xa0\x41\xa0\x40"), // map with 0 items + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(ReleaseToken::Decode( // array with 0 items + "\x84\x41\xa0\xa0\x41\x80\x40"), + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(ReleaseToken::Decode( // array with 2 items + "\x84\x41\xa0\xa0\x44\x82\x41\xa0\xa0\x40"), + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(ReleaseToken::Decode( // array with 4 items + "\x84\x41\xa0\xa0\x46\x84\x41\xa0\xa0\x40\x40\x40"), + IsCode(absl::StatusCode::kInvalidArgument)); + + // The COSE_Encrypt0 array entry types must be bstr, map, bstr. + // "\x84\x41\xa0\xa0\x45\x83\x41\xa0\xa0\x40\x40" is valid. + EXPECT_THAT(ReleaseToken::Decode( // 1st not bstr + "\x84\x41\xa0\xa0\x44\x83\xa0\xa0\x40\x40"), + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(ReleaseToken::Decode( // 2nd not map + "\x84\x41\xa0\xa0\x45\x83\x41\xa0\x40\x40\x40"), + IsCode(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT(ReleaseToken::Decode( // 3rd not bstr + "\x84\x41\xa0\xa0\x45\x83\x41\xa0\xa0\xa0\x40"), + IsCode(absl::StatusCode::kInvalidArgument)); +} + +TEST(ReleaseTokenTest, BuildEncStructureForEncrypting) { + EXPECT_THAT((ReleaseToken{ + .signing_algorithm = 1, + .encryption_algorithm = 2, + .encryption_key_id = "key-id", + .src_state = "old-state", + .dst_state = "new-state", + .encrypted_payload = "payload", + .encapped_key = "key", + .signature = "signature", + }) + .BuildEncStructureForEncrypting("aad"), + IsOkAndHolds(absl::string_view( + "\x83" // array with 3 items: + "\x68\x45ncrypt0" // "Encrypt0" + "\x58\x21\xa3" // bstr containing a map with 3 items: + "\x01\x02" // alg: 2 + "\x3a\x00\x01\x00\x01\x49old-state" // -65538: b"old-state" + "\x3a\x00\x01\x00\x02\x49new-state" // -65539: b"new-state" + "\x43\x61\x61\x64", // b"aad" + 49))); +} + +TEST(ReleaseTokenTest, GetEncStructureForDecrypting) { + EXPECT_THAT(ReleaseToken::GetEncStructureForDecrypting( + absl::string_view( + "\x84\x43\xa1\x01\x01\xa0\x58\x3e\x83\x58\x21\xa3\x01\x02" + "\x3a\x00\x01\x00\x01\x49old-state\x3a\x00\x01\x00\x02" + "\x49new-state\xa2\x04\x46key-id\x3a\x00\x01\x00\x00\x43" + "key\x47payload\x49signature", + 80), + "aad"), + IsOkAndHolds(absl::string_view( + "\x83" // array with 3 items: + "\x68\x45ncrypt0" // "Encrypt0" + "\x58\x21\xa3" // bstr containing a map with 3 items: + "\x01\x02" // alg: 2 + "\x3a\x00\x01\x00\x01\x49old-state" // -65538: b"old-state" + "\x3a\x00\x01\x00\x02\x49new-state" // -65539: b"new-state" + "\x43\x61\x61\x64", // b"aad" + 49))); +} + +TEST(ReleaseTokenTest, BuildSigStructureForSigning) { + EXPECT_THAT((ReleaseToken{ + .signing_algorithm = 1, + .encryption_algorithm = 2, + .encryption_key_id = "key-id", + .src_state = "old-state", + .dst_state = "new-state", + .encrypted_payload = "payload", + .encapped_key = "key", + .signature = "signature", + }) + .BuildSigStructureForSigning("aad"), + IsOkAndHolds(absl::string_view( + "\x84" // array with 4 items: + "\x6aSignature1" // "Signature1" + "\x43\xa1\x01\x01" // bstr containing { alg: 1 } + "\x43\x61\x61\x64" // b"aad" + // bstr containing COSE_Encrypt0 + "\x58\x3e\x83\x58\x21\xa3\x01\x02\x3a\x00\x01\x00\x01\x49old-" + "state\x3a\x00\x01\x00\x02\x49new-state\xa2\x04\x46key-" + "id\x3a\x00\x01\x00\x00\x43key\x47payload", + 84))); +} + +TEST(ReleaseTokenTest, GetSigStructureForVerifying) { + EXPECT_THAT(ReleaseToken::GetSigStructureForVerifying( + absl::string_view( + "\x84\x43\xa1\x01\x01\xa0\x58\x3e\x83\x58\x21\xa3\x01\x02" + "\x3a\x00\x01\x00\x01\x49old-state\x3a\x00\x01\x00\x02" + "\x49new-state\xa2\x04\x46key-id\x3a\x00\x01\x00\x00\x43" + "key\x47payload\x49signature", + 80), + "aad"), + IsOkAndHolds(absl::string_view( + "\x84" // array with 4 items: + "\x6aSignature1" // "Signature1" + "\x43\xa1\x01\x01" // bstr containing { alg: 1 } + "\x43\x61\x61\x64" // b"aad" + // bstr containing COSE_Encrypt0 + "\x58\x3e\x83\x58\x21\xa3\x01\x02\x3a\x00\x01\x00\x01\x49old-" + "state\x3a\x00\x01\x00\x02\x49new-state\xa2\x04\x46key-" + "id\x3a\x00\x01\x00\x00\x43key\x47payload", + 84))); +} + +} // namespace +} // namespace fcp::confidential_compute
diff --git a/third_party/federated_compute/chromium/fcp/testing/testing.cc b/third_party/federated_compute/chromium/fcp/testing/testing.cc new file mode 100644 index 0000000..86f13f0 --- /dev/null +++ b/third_party/federated_compute/chromium/fcp/testing/testing.cc
@@ -0,0 +1,26 @@ +/* + * Copyright 2017 Google LLC + * + * 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. + */ + +#include "fcp/testing/testing.h" + +#include "fcp/base/monitoring.h" + +namespace fcp { + +StatusMatcher IsCode(StatusCode code) { return StatusMatcher(code); } +StatusMatcher IsOk() { return IsCode(OK); } + +} // namespace fcp
diff --git a/third_party/federated_compute/chromium/fcp/testing/testing.h b/third_party/federated_compute/chromium/fcp/testing/testing.h new file mode 100644 index 0000000..9f016a1 --- /dev/null +++ b/third_party/federated_compute/chromium/fcp/testing/testing.h
@@ -0,0 +1,93 @@ +/* + * Copyright 2017 Google LLC + * + * 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. + */ + +#ifndef FCP_TESTING_TESTING_H_ +#define FCP_TESTING_TESTING_H_ + +#include <iostream> +#include <memory> +#include <string> +#include <type_traits> + +#include "absl/status/status.h" +#include "absl/strings/string_view.h" +#include "fcp/base/monitoring.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +// This file defines platform dependent utilities for testing, +// based on the public version of googletest. + +namespace fcp { + +// Convenience macros for `EXPECT_THAT(s, IsOk())`, where `s` is either +// a `Status` or a `StatusOr<T>`. +// Old versions of the protobuf library define EXPECT_OK as well, so we only +// conditionally define our version. +#if !defined(EXPECT_OK) +#define EXPECT_OK(result) EXPECT_THAT(result, fcp::IsOk()) +#endif +#define ASSERT_OK(result) ASSERT_THAT(result, fcp::IsOk()) + +/** + * Polymorphic matchers for Status or StatusOr on status code. + */ +template <typename T> +bool IsCode(StatusOr<T> const& x, StatusCode code) { + return x.status().code() == code; +} +inline bool IsCode(Status const& x, StatusCode code) { + return x.code() == code; +} + +template <typename T> +class StatusMatcherImpl : public ::testing::MatcherInterface<T> { + public: + explicit StatusMatcherImpl(StatusCode code) : code_(code) {} + void DescribeTo(::std::ostream* os) const override { + *os << "is " << absl::StatusCodeToString(code_); + } + void DescribeNegationTo(::std::ostream* os) const override { + *os << "is not " << absl::StatusCodeToString(code_); + } + bool MatchAndExplain( + T x, ::testing::MatchResultListener* listener) const override { + return IsCode(x, code_); + } + + private: + StatusCode code_; +}; + +class StatusMatcher { + public: + explicit StatusMatcher(StatusCode code) : code_(code) {} + + template <typename T> + operator testing::Matcher<T>() const { // NOLINT + return ::testing::MakeMatcher(new StatusMatcherImpl<T>(code_)); + } + + private: + StatusCode code_; +}; + +StatusMatcher IsCode(StatusCode code); +StatusMatcher IsOk(); + +} // namespace fcp + +#endif // FCP_TESTING_TESTING_H_
diff --git a/third_party/perfetto b/third_party/perfetto index d17b40b..49af9ec 160000 --- a/third_party/perfetto +++ b/third_party/perfetto
@@ -1 +1 @@ -Subproject commit d17b40b3b5e36f3744f1d010fe3ba2d3c55559c0 +Subproject commit 49af9ecad055e597f1cc7d57677454b72606e96d
diff --git a/third_party/protobuf/BUILD.gn b/third_party/protobuf/BUILD.gn index b2efa8253..891430b 100644 --- a/third_party/protobuf/BUILD.gn +++ b/third_party/protobuf/BUILD.gn
@@ -34,6 +34,10 @@ } } +proto_library("struct_proto") { + sources = [ "src/google/protobuf/struct.proto" ] +} + if (is_component_build) { config("protobuf_use_dlls") { defines = [ "PROTOBUF_USE_DLLS" ] @@ -161,6 +165,9 @@ # Sync requires descriptors for testing. "//components/sync:sync_full_proto_unittests", "//components/sync/protocol:full_proto", + + # third_party/federated_compute requires it for testing. + "//third_party/federated_compute:federated_compute_tests", ] }
diff --git a/third_party/skia b/third_party/skia index 359f3d7..7c2f502 160000 --- a/third_party/skia +++ b/third_party/skia
@@ -1 +1 @@ -Subproject commit 359f3d7cc7edfcb93e99ab5ed7e9f2f5fdd8ef85 +Subproject commit 7c2f502e3304f4c76413696a9035470c32ac6dde
diff --git a/third_party/sqlite/BUILD.gn b/third_party/sqlite/BUILD.gn index 4826a3c..46b11e7 100644 --- a/third_party/sqlite/BUILD.gn +++ b/third_party/sqlite/BUILD.gn
@@ -456,7 +456,10 @@ "fuzz/sql_query_proto_to_string.cc", "fuzz/sql_query_proto_to_string.h", ] - deps = [ ":sqlite3_lpm_fuzzer_input" ] + deps = [ + ":sqlite3_lpm_fuzzer_input", + "//base", + ] # TODO(crbug.com/40031409): Fix code that adds exit-time destructors and # enable the diagnostic by removing this line. @@ -472,6 +475,7 @@ ] deps = [ ":sqlite3_lpm_fuzzer_core", + "//base", "//third_party/libprotobuf-mutator", ] additional_configs = [ ":sqlite_warnings" ] @@ -491,6 +495,7 @@ ] deps = [ ":sqlite3_lpm_fuzzer_core", + "//base", "//third_party/libprotobuf-mutator", ] additional_configs = [ @@ -517,6 +522,7 @@ ] deps = [ ":sqlite3_lpm_fuzzer_core", + "//base", "//third_party/libprotobuf-mutator", ] libfuzzer_options = [ "max_len=111000" ] @@ -530,6 +536,7 @@ ] deps = [ ":sqlite3_lpm_fuzzer_core", + "//base", "//third_party/libprotobuf-mutator", ] libfuzzer_options = [ "max_len=111000" ] @@ -543,6 +550,7 @@ ] deps = [ ":sqlite3_lpm_fuzzer_core", + "//base", "//third_party/libprotobuf-mutator", ] libfuzzer_options = [
diff --git a/third_party/sqlite/fuzz/sql_query_proto_to_string.cc b/third_party/sqlite/fuzz/sql_query_proto_to_string.cc index 0ec82934..f74b6ac9 100644 --- a/third_party/sqlite/fuzz/sql_query_proto_to_string.cc +++ b/third_party/sqlite/fuzz/sql_query_proto_to_string.cc
@@ -12,6 +12,7 @@ #include <string> #include <vector> +#include "base/no_destructor.h" #include "third_party/sqlite/fuzz/icu_codes.pb.h" #include "third_party/sqlite/fuzz/sql_queries.pb.h" #include "third_party/sqlite/fuzz/sql_query_grammar.pb.h" @@ -22,7 +23,7 @@ #define CONV_FN(TYPE, VAR_NAME) std::string TYPE##ToString(const TYPE& VAR_NAME) #define RETURN_IF_DISABLED_QUERY(TYPE) \ - if (disabled_queries_.count(#TYPE) != 0) \ + if (disabled_queries_->count(#TYPE) != 0) \ return ""; namespace sql_fuzzer { @@ -54,7 +55,7 @@ constexpr uint32_t kMaxViewNumber = 5; constexpr uint32_t kMaxTriggerNumber = 10; -std::set<std::string> disabled_queries_; +static base::NoDestructor<std::set<std::string>> disabled_queries_; } // namespace CONV_FN(Expr, expr); @@ -2726,7 +2727,7 @@ } void SetDisabledQueries(std::set<std::string> disabled_queries) { - disabled_queries_ = disabled_queries; + *disabled_queries_ = disabled_queries; } } // namespace sql_fuzzer
diff --git a/third_party/webrtc b/third_party/webrtc index db0e64c..35d9a8f 160000 --- a/third_party/webrtc +++ b/third_party/webrtc
@@ -1 +1 @@ -Subproject commit db0e64c5e80168839d74ae0cc6173ccd537ae211 +Subproject commit 35d9a8f86a90ddfebd9c8b5f6ddce14b68a1a879
diff --git a/tools/android/logcat_filtering/styles.css b/tools/android/logcat_filtering/styles.css index 866a3ca3..a790d3a 100644 --- a/tools/android/logcat_filtering/styles.css +++ b/tools/android/logcat_filtering/styles.css
@@ -258,6 +258,10 @@ align-items: flex-start; } +#checkbox-controls .checkbox-column:last-of-type { + margin-right: 40px; +} + #checkbox-controls label { display: flex; align-items: center;
diff --git a/tools/autotest.py b/tools/autotest.py index 056fa7e..796629c8 100755 --- a/tools/autotest.py +++ b/tools/autotest.py
@@ -126,6 +126,7 @@ '//third_party/crc32c:crc32c_benchmark', '//third_party/crc32c:crc32c_tests', '//third_party/dawn/src/dawn/tests/benchmarks:dawn_benchmarks', + '//third_party/federated_compute:federated_compute_tests', '//third_party/highway:highway_tests', '//third_party/ipcz/src:ipcz_tests', '//third_party/libaom:av1_encoder_fuzz_test',
diff --git a/tools/clang/scripts/analyze_includes.py b/tools/clang/scripts/analyze_includes.py index 8bdb8e56..d07ce5b5 100755 --- a/tools/clang/scripts/analyze_includes.py +++ b/tools/clang/scripts/analyze_includes.py
@@ -17,7 +17,9 @@ Windows due to crbug.com/1223741#c9) The script takes roughly half an hour on a fast machine for the chrome build -target, which is considered fast enough for batch job purposes for now. +target, which is considered fast enough for batch job purposes for now. It can +be sped up significantly by using multiple processes with the --proccesses option, +but it will also use significantly more memory as a result (OOM is a risk). If --json-out is not provided, the script exits after printing some statistics to stdout. This is significantly faster than generating the full JSON data. For @@ -28,7 +30,10 @@ """ import argparse +import concurrent.futures +import functools import json +import math import os import pathlib import re @@ -36,6 +41,7 @@ import unittest from collections import defaultdict from datetime import datetime, timezone +from itertools import batched def parse_build(build_log, root_filter=None): @@ -52,19 +58,24 @@ # invocations depending on -D flags. For such cases, includes[file] will be # the union of those includes. - # Normalize paths. - normalized = {} - + @functools.cache def norm(fn): - if fn not in normalized: - x = fn.replace('\\\\', '\\') - # Use Path.resolve() rather than path.realpath() to get the canonical - # upper/lower-case version of the path on Windows. - p = pathlib.Path(os.path.join(build_dir, x)).resolve() - x = os.path.relpath(p) - x = x.replace(os.path.sep, '/') - normalized[fn] = x - return normalized[fn] + x = fn.replace('\\\\', '\\') + # Use Path.resolve() rather than path.realpath() to get the canonical + # upper/lower-case version of the path on Windows. + p = pathlib.Path(os.path.join(build_dir, x)).resolve() + x = os.path.relpath(p) + return x.replace(os.path.sep, '/') + + @functools.cache + def parse_include_line(line): + m = INCLUDE_RE.match(line) + + if m: + depth = len(m.group(1)) + filename = norm(m.group(2)) + + return filename, depth # ninja: Entering directory `out/foo' ENTER_DIR_RE = re.compile(r'ninja: Entering directory `(.*?)\'$') @@ -81,16 +92,15 @@ skipping_root = False for line in build_log: - m = INCLUDE_RE.match(line) # TODO(https://crbug.com/435303792): Ignore precompiled modules (.pcm) until # an appropriate way to calculate their size for include analysis is found. - if m and not m.group(2).endswith('.pcm'): + if (parsed := parse_include_line(line)) and not parsed[0].endswith('.pcm'): if skipping_root: continue prev_depth = len(file_stack) - 1 - depth = len(m.group(1)) - filename = norm(m.group(2)) - includes.setdefault(filename, set()) + filename, depth = parsed + if filename not in includes: + includes[filename] = set() if depth > prev_depth: if sys.platform != 'win32': @@ -101,16 +111,14 @@ print('missing include under', file_stack[0]) continue else: - for _ in range(prev_depth - depth + 1): - file_stack.pop() + del file_stack[-(prev_depth - depth + 1):] includes[file_stack[-1]].add(filename) file_stack.append(filename) continue - m = COMPILE_RE.match(line) # Clang module compile uses -x after -c, so skip that from include analysis. - if m and m.group(2) != '-x': + if (m := COMPILE_RE.match(line)) and m.group(2) != '-x': skipping_root = False filename = norm(m.group(2)) if root_filter and not root_filter.match(filename): @@ -121,8 +129,7 @@ includes.setdefault(filename, set()) continue - m = ENTER_DIR_RE.match(line) - if m: + if (m := ENTER_DIR_RE.match(line)): build_dir = m.group(1) continue @@ -340,6 +347,25 @@ return {n: dom_set(n) for n in vertex} +def compute_added_sizes(args): + """Helper to compute added sizes from the given root.""" + + roots, includes, sizes = args + added_sizes = {node: 0 for node in includes} + + for root in roots: + doms = compute_doms(root, includes) + + for node in doms: + if node not in sizes: + # Skip the (src,dst) pseudo nodes. + continue + for dom in doms[node]: + added_sizes[dom] += sizes[node] + + return added_sizes + + class TestComputeDoms(unittest.TestCase): def test_basic(self): includes = {} @@ -394,28 +420,35 @@ self.assertEqual(doms['r'], set(['r'])) -def trans_size(root, includes, sizes): - """Compute the transitive size of a file, i.e. the size of the file itself and - all its transitive includes.""" - return sum([sizes[n] for n in post_order_nodes(root, includes)]) - - def log(*args, **kwargs): """Log output to stderr.""" print(*args, file=sys.stderr, **kwargs) -def analyze(target, revision, build_log_file, json_file, root_filter): +def analyze(target, revision, build_log_file, json_file, root_filter, processes=1): log('Parsing build log...') (roots, includes) = parse_build(build_log_file, root_filter) log('Getting file sizes...') sizes = {name: os.path.getsize(name) for name in includes} - log('Computing transitive sizes...') - trans_sizes = {n: trans_size(n, includes, sizes) for n in includes} + log('Computing transitive sizes and prevalence...') + build_size = 0 + trans_sizes = {name: 0 for name in includes} + prevalence = {name: 0 for name in includes} - build_size = sum([trans_sizes[n] for n in roots]) + for n in includes: + for node in post_order_nodes(n, includes): + # Compute the transitive size of a file, i.e. the size of the + # file itself and all its transitive includes. + trans_sizes[n] += sizes[node] + + if n in roots: + prevalence[node] += 1 + + # Total build size is the sum of the transitive size of all roots. + if n in roots: + build_size += trans_sizes[n] print('build_size', build_size) @@ -423,12 +456,6 @@ log('--json-out not set; exiting.') return 0 - log('Counting prevalence...') - prevalence = {name: 0 for name in includes} - for r in roots: - for n in post_order_nodes(r, includes): - prevalence[n] += 1 - # Map from file to files that include it. log('Building reverse include map...') included_by = {k: set() for k in includes} @@ -448,15 +475,24 @@ augmented_includes[src].add((src, dst)) augmented_includes[(src, dst)] = {dst} - added_sizes = {node: 0 for node in augmented_includes} - for r in roots: - doms = compute_doms(r, augmented_includes) - for node in doms: - if node not in sizes: - # Skip the (src,dst) pseudo nodes. - continue - for dom in doms[node]: - added_sizes[dom] += sizes[node] + if processes > 1: + added_sizes = {node: 0 for node in augmented_includes} + + # Break up the list of roots into chunks based on the number of processes and pass them + # to each worker. Giving each worker a complete chunk of roots to work on rather than + # having workers pull as they go minimizes contention and gives better parallelization. + chunk_size = math.ceil(float(len(roots)) / processes) + chunked = list(batched(roots, chunk_size)) + + with concurrent.futures.ProcessPoolExecutor(max_workers=processes) as pool: + for computed_added_sizes in pool.map( + compute_added_sizes, + ((chunk, augmented_includes, sizes) for chunk in chunked), + ): + for dom, size in computed_added_sizes.items(): + added_sizes[dom] += size + else: + added_sizes = compute_added_sizes((roots, augmented_includes, sizes)) # Assign a number to each filename for tighter JSON representation. names = [] @@ -482,7 +518,7 @@ 'files': names, 'roots': [nr(x) for x in sorted(roots)], 'includes': [[nr(x) for x in sorted(includes[n])] for n in names], - 'included_by': [[nr(x) for x in included_by[n]] for n in names], + 'included_by': [[nr(x) for x in sorted(included_by[n])] for n in names], 'sizes': [sizes[n] for n in names], 'tsizes': [trans_sizes[n] for n in names], 'asizes': [added_sizes[n] for n in names], @@ -513,6 +549,12 @@ help='Write full analysis data to a JSON file (- for stdout).') parser.add_argument('--root-filter', help='Regex to filter which root files are analyzed.') + parser.add_argument('--processes', + action="store", + type=int, + default=1, + help="Use multiple processes to speed up the analysis - " + "note that this scales memory usage significantly") args = parser.parse_args() if args.json_out and not (args.target and args.revision): @@ -526,7 +568,7 @@ return 1 analyze(args.target, args.revision, args.build_log, args.json_out, - root_filter) + root_filter, processes=args.processes) if __name__ == '__main__':
diff --git a/tools/gritsettings/translation_expectations.pyl b/tools/gritsettings/translation_expectations.pyl index 3dc6085..55b02ea 100644 --- a/tools/gritsettings/translation_expectations.pyl +++ b/tools/gritsettings/translation_expectations.pyl
@@ -140,9 +140,9 @@ }, # Internal grds that contain parts not available publicly and thus should # not be checked by translation script. + # NOTE: This list does NOT itemize src-internal translations. "internal_grds": [ # Test grd that contains a part file that doesn't exist. "tools/translation/testdata/internal.grd", - "third_party/search_engines_data/resources_internal/search_engine_descriptions_strings.grd", ], }
diff --git a/tools/metrics/histograms/metadata/attribution_reporting/histograms.xml b/tools/metrics/histograms/metadata/attribution_reporting/histograms.xml index d9af2832..0957362 100644 --- a/tools/metrics/histograms/metadata/attribution_reporting/histograms.xml +++ b/tools/metrics/histograms/metadata/attribution_reporting/histograms.xml
@@ -651,21 +651,6 @@ </summary> </histogram> -<histogram name="Conversions.FirstBatch.HttpResponseOrNetErrorCode" - enum="CombinedHttpResponseAndNetErrorCode" expires_after="2026-02-22"> - <owner>apaseltiner@chromium.org</owner> - <owner>measurement-api-dev+metrics@google.com</owner> - <summary> - Error info for sending an attribution report, recorded for each report - within the first report delivery batch for a new user session i.e. upon - initialization of AttributionManager. The HTTP response code is recorded if - there is no net error code for the request, or the net error code indicates - there was a response code failure. Note: The first batch of reports may not - send exactly at manager startup due to purposeful delays added as to not - allow temporal joins between them. - </summary> -</histogram> - <histogram name="Conversions.GetAllDataKeysTime" units="ms" expires_after="2026-02-22"> <owner>linnan@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/autofill/enums.xml b/tools/metrics/histograms/metadata/autofill/enums.xml index 3dad0eb..be723cf 100644 --- a/tools/metrics/histograms/metadata/autofill/enums.xml +++ b/tools/metrics/histograms/metadata/autofill/enums.xml
@@ -4157,6 +4157,8 @@ <enum name="SaveAndFillFormEvent"> <int value="0" label="The Save and Fill suggestion was shown"/> <int value="1" label="The Save and Fill suggestion was accepted"/> + <int value="2" label="The form was filled after Save and Fill finished"/> + <int value="3" label="The form was submitted after Save and Fill finished"/> </enum> <!-- LINT.ThenChange(/components/autofill/core/browser/metrics/payments/save_and_fill_metrics.h:SaveAndFillFormEvent) -->
diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml index a7e23eb..c7030e8 100644 --- a/tools/metrics/histograms/metadata/autofill/histograms.xml +++ b/tools/metrics/histograms/metadata/autofill/histograms.xml
@@ -4123,7 +4123,7 @@ </histogram> <histogram name="Autofill.iOS.TriggeredFormExtractionFromDriver.{Range}" - units="count" expires_after="2025-10-12"> + units="count" expires_after="2026-10-12"> <owner>vincb@google.com</owner> <owner>tmartino@chromium.org</owner> <owner>bling-transactions@google.com</owner> @@ -5977,6 +5977,26 @@ </token> </histogram> +<histogram name="Autofill.SaveAndFill.Funnel.{SaveType}.{Result}" + enum="SaveAndFillFormEvent" expires_after="2026-06-01"> + <owner>siyua@chromium.org</owner> + <owner>averina@google.com</owner> + <owner>payments-autofill-team@google.com</owner> + <summary> + Records form events to calculate the Save and Fill conversion rate. Logged + when the form is filled and when form is submitted. It has two breakdowns to + indicate that the save is a {SaveType} and {Result}. + </summary> + <token key="SaveType"> + <variant name="Local" summary="local save"/> + <variant name="Upload" summary="server save"/> + </token> + <token key="Result"> + <variant name="Failure" summary="save failed"/> + <variant name="Success" summary="save succeeded"/> + </token> +</histogram> + <histogram name="Autofill.SaveAndFill.GetDetailsForCreateCard.Latency{RequestResult}" units="ms" expires_after="2026-06-01">
diff --git a/tools/metrics/histograms/metadata/cookie/histograms.xml b/tools/metrics/histograms/metadata/cookie/histograms.xml index c821f71..3ecc6dd 100644 --- a/tools/metrics/histograms/metadata/cookie/histograms.xml +++ b/tools/metrics/histograms/metadata/cookie/histograms.xml
@@ -1122,16 +1122,6 @@ </summary> </histogram> -<histogram name="Cookie.TimeDatabaseMigrationToV19" units="ms" - expires_after="2025-09-22"> - <owner>arichiv@chromium.org</owner> - <owner>src/net/cookies/OWNERS</owner> - <summary> - The amount of time (ms) to migrate a v18 cookie database to v19. Migration - occurs upon first startup of a browser version with v18 database code. - </summary> -</histogram> - <histogram name="Cookie.TimeDatabaseMigrationToV20" units="ms" expires_after="2025-11-14"> <owner>cfredric@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/extensions/enums.xml b/tools/metrics/histograms/metadata/extensions/enums.xml index fbd0e019..f1c0c9a 100644 --- a/tools/metrics/histograms/metadata/extensions/enums.xml +++ b/tools/metrics/histograms/metadata/extensions/enums.xml
@@ -2820,8 +2820,8 @@ <int value="1931" label="AUTOFILLPRIVATE_GETPAYOVERTIMEISSUERLIST"/> <int value="1932" label="AUTOFILLPRIVATE_SETAUTOFILLAIOPTINSTATUS"/> <int value="1933" label="AUTOFILLPRIVATE_GETAUTOFILLAIOPTINSTATUS"/> - <int value="1934" label="EXPERIMENTALACTOR_STARTTASK"/> - <int value="1935" label="EXPERIMENTALACTOR_EXECUTEACTION"/> + <int value="1934" label="DELETED_EXPERIMENTALACTOR_STARTTASK"/> + <int value="1935" label="DELETED_EXPERIMENTALACTOR_EXECUTEACTION"/> <int value="1936" label="EXPERIMENTALACTOR_STOPTASK"/> <int value="1937" label="BOOKMARKMANAGERPRIVATE_OPENINNEWTABGROUP"/> <int value="1938" label="CONTROLLEDFRAMEINTERNAL_CONTEXTMENUSUPDATE"/>
diff --git a/tools/metrics/histograms/metadata/ios/histograms.xml b/tools/metrics/histograms/metadata/ios/histograms.xml index 7183270a..ce725bf8 100644 --- a/tools/metrics/histograms/metadata/ios/histograms.xml +++ b/tools/metrics/histograms/metadata/ios/histograms.xml
@@ -2867,7 +2867,7 @@ </histogram> <histogram name="IOS.MagicStack.Module.Click.AppBundlePromo" units="index" - expires_after="2026-01-18"> + expires_after="2025-12-02"> <owner>ericekey@google.com</owner> <owner>bling-pandamonium@google.com</owner> <summary>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml index 89f051b..eb89f48 100644 --- a/tools/metrics/histograms/metadata/others/histograms.xml +++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -4009,7 +4009,7 @@ </histogram> <histogram name="DirectSockets.TCPServerNetworkFailures" enum="NetErrorCodes" - expires_after="2025-09-13"> + expires_after="2026-09-13"> <owner>greengrape@google.com</owner> <owner>iwa-team@google.com</owner> <summary> @@ -4018,21 +4018,21 @@ </histogram> <histogram name="DirectSockets.TCPServerReadableStreamError" - enum="NetErrorCodes" expires_after="2025-09-13"> + enum="NetErrorCodes" expires_after="2026-09-13"> <owner>greengrape@google.com</owner> <owner>iwa-team@google.com</owner> <summary>How often tcp server socket readable stream is errored.</summary> </histogram> <histogram name="DirectSockets.TCPWritableStreamError" enum="NetErrorCodes" - expires_after="2025-09-13"> + expires_after="2026-09-13"> <owner>greengrape@google.com</owner> <owner>iwa-team@google.com</owner> <summary>How often tcp socket writable stream is errored.</summary> </histogram> <histogram name="DirectSockets.UDPNetworkFailures" enum="NetErrorCodes" - expires_after="2025-09-13"> + expires_after="2026-09-13"> <owner>greengrape@google.com</owner> <owner>iwa-team@google.com</owner> <summary> @@ -4041,14 +4041,14 @@ </histogram> <histogram name="DirectSockets.UDPReadableStreamError" enum="NetErrorCodes" - expires_after="2025-11-23"> + expires_after="2026-11-23"> <owner>greengrape@google.com</owner> <owner>iwa-team@google.com</owner> <summary>How often udp socket readable stream is errored.</summary> </histogram> <histogram name="DirectSockets.UDPWritableStreamError" enum="NetErrorCodes" - expires_after="2025-09-13"> + expires_after="2026-09-13"> <owner>greengrape@google.com</owner> <owner>iwa-team@google.com</owner> <summary>How often udp socket writable stream is errored.</summary>
diff --git a/tools/metrics/histograms/metadata/password/enums.xml b/tools/metrics/histograms/metadata/password/enums.xml index d63e8b14..f3d1051d 100644 --- a/tools/metrics/histograms/metadata/password/enums.xml +++ b/tools/metrics/histograms/metadata/password/enums.xml
@@ -608,27 +608,6 @@ policy"/> </enum> -<!-- LINT.IfChange(LoginDbDeprecationExportProgress) --> - -<enum name="LoginDbDeprecationExportProgress"> - <int value="0" label="Scheduled"/> - <int value="1" label="Started"/> - <int value="2" label="Finished"/> -</enum> - -<!-- LINT.ThenChange(/components/password_manager/core/browser/export/login_db_deprecation_runner.h:LoginDbDeprecationExportProgress) --> - -<!-- LINT.IfChange(LoginDbDeprecationExportResult) --> - -<enum name="LoginDbDeprecationExportResult"> - <int value="0" label="Success"/> - <int value="1" label="No passwords"/> - <int value="2" label="Error fetching paswords"/> - <int value="3" label="File write error"/> -</enum> - -<!-- LINT.ThenChange(/components/password_manager/core/browser/export/login_db_deprecation_password_exporter.h:LoginDbDeprecationExportResult) --> - <enum name="LoginsChangedTrigger"> <summary> Triggers that may indirectly cause PasswordStore observers notifications @@ -1456,7 +1435,7 @@ <int value="0" label="Internal backend missing"/> <int value="1" label="No GMS, no Play Store"/> <int value="2" label="Outdated GMS"/> - <int value="3" label="Auto-export pending"/> + <int value="3" label="(obsolete) Auto-export pending"/> </enum> <!-- LINT.ThenChange(/chrome/browser/password_manager/android/password_manager_android_util.h:PasswordManagerNotAvailableReason) -->
diff --git a/tools/metrics/histograms/metadata/password/histograms.xml b/tools/metrics/histograms/metadata/password/histograms.xml index 7ee612a..02e8f79d2c 100644 --- a/tools/metrics/histograms/metadata/password/histograms.xml +++ b/tools/metrics/histograms/metadata/password/histograms.xml
@@ -3297,17 +3297,6 @@ </summary> </histogram> -<histogram - name="PasswordManager.PasswordSharingRecipients.FetchAccessTokenResult" - enum="GoogleServiceAuthError" expires_after="2025-10-12"> - <owner>rushans@google.com</owner> - <owner>mamir@chromium.org</owner> - <summary> - Records the result of access token fetch for the password sharing recipients - request. Recorded when a token fetch completes (including retries). - </summary> -</histogram> - <histogram name="PasswordManager.PasswordSharingRecipients.ResponseOrErrorCode" enum="CombinedHttpResponseAndNetErrorCode" expires_after="2026-02-22"> <owner>rushans@google.com</owner> @@ -4363,42 +4352,6 @@ </summary> </histogram> -<histogram name="PasswordManager.UPM.LoginDbDeprecationExport.Latency" - units="ms" expires_after="2026-02-22"> - <owner>ioanap@chromium.org</owner> - <owner>vasilii@chromium.org</owner> - <summary> - Records the latency of the auto-export preceeding the login database - deprecation on Android. The export is performed on start-up, for clients who - didn't migrate to UPM. Recorded only when the export completes successfully. - It includes the time required to fetch passwords from the database. - </summary> -</histogram> - -<histogram name="PasswordManager.UPM.LoginDbDeprecationExport.Progress" - enum="LoginDbDeprecationExportProgress" expires_after="2025-10-12"> - <owner>ioanap@chromium.org</owner> - <owner>vasilii@chromium.org</owner> - <summary> - Records when the pre-deprecation export flow is scheduled, started and - finished, irrespective of success status. The success status breakdown is - recorded in "PasswordManager.UPM.LoginDbDeprecationExport.Result". - The export might be started with a delay. Android only. - </summary> -</histogram> - -<histogram name="PasswordManager.UPM.LoginDbDeprecationExport.Result" - enum="LoginDbDeprecationExportResult" expires_after="2026-02-22"> - <owner>ioanap@chromium.org</owner> - <owner>vasilii@chromium.org</owner> - <summary> - Records the result of the auto-export preceeding the login database - deprecation on Android. The export is performed on start-up, for clients who - didn't migrate to UPM. Recorded when the export completes, or before it - starts if there has been an issue fetching passwords. Android only. - </summary> -</histogram> - <histogram name="PasswordManager.UPM.NoGmsNoPasswordsDialogShown" enum="BooleanShown" expires_after="2026-02-22"> <owner>ioanap@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/sql/histograms.xml b/tools/metrics/histograms/metadata/sql/histograms.xml index af3cfebb..7e19265d 100644 --- a/tools/metrics/histograms/metadata/sql/histograms.xml +++ b/tools/metrics/histograms/metadata/sql/histograms.xml
@@ -183,8 +183,8 @@ </histogram> <histogram name="Sql.Recovery.Result" enum="SqlRecoveryResult" - expires_after="2025-09-15"> - <owner>asully@chromium.org</owner> + expires_after="2026-02-01"> + <owner>etienneb@chromium.org</owner> <owner>chrome-catan@google.com</owner> <summary> Outcome of attempting to recover a database with sql::Recovery. @@ -192,8 +192,8 @@ </histogram> <histogram name="Sql.Recovery.Result.{DatabaseTag}" enum="SqlRecoveryResult" - expires_after="2026-01-18"> - <owner>asully@chromium.org</owner> + expires_after="2026-02-01"> + <owner>etienneb@chromium.org</owner> <owner>chrome-catan@google.com</owner> <summary> Outcome of attempting to recover the {DatabaseTag} database with @@ -204,7 +204,7 @@ <histogram name="Sql.Recovery.ResultCode" enum="SqliteLoggedResultCode" expires_after="2026-02-01"> - <owner>asully@chromium.org</owner> + <owner>etienneb@chromium.org</owner> <owner>chrome-catan@google.com</owner> <summary> SQLite result code from attempting to recover a database with sql::Recovery. @@ -216,8 +216,8 @@ </histogram> <histogram name="Sql.Recovery.ResultCode.{DatabaseTag}" - enum="SqliteLoggedResultCode" expires_after="2025-09-15"> - <owner>asully@chromium.org</owner> + enum="SqliteLoggedResultCode" expires_after="2026-02-01"> + <owner>etienneb@chromium.org</owner> <owner>chrome-catan@google.com</owner> <summary> SQLite result code from attempting to recover the {DatabaseTag} database
diff --git a/tools/metrics/histograms/metadata/webapps/histograms.xml b/tools/metrics/histograms/metadata/webapps/histograms.xml index 6c7eb96a..6081162 100644 --- a/tools/metrics/histograms/metadata/webapps/histograms.xml +++ b/tools/metrics/histograms/metadata/webapps/histograms.xml
@@ -1590,7 +1590,7 @@ </histogram> <histogram name="WebApp.Isolated.OrphanedBundlesCleanupJobError" - enum="IsolatedWebAppCleanupOrphanedIWAsError" expires_after="2025-10-05"> + enum="IsolatedWebAppCleanupOrphanedIWAsError" expires_after="2026-02-09"> <owner>giovax@chromium.org</owner> <owner>pwa-commercial@google.com</owner> <owner>src/chrome/browser/web_applications/isolated_web_apps/OWNERS</owner> @@ -1736,7 +1736,7 @@ </histogram> <histogram name="WebApp.Isolated.SwbnFileUsabilityError" - enum="IsolatedWebAppSwbnFileUsabilityError" expires_after="2025-10-05"> + enum="IsolatedWebAppSwbnFileUsabilityError" expires_after="2026-02-09"> <owner>giovax@chromium.org</owner> <owner>pwa-commercial@google.com</owner> <owner>src/chrome/browser/web_applications/isolated_web_apps/OWNERS</owner>
diff --git a/tools/metrics/histograms/metadata/webauthn/histograms.xml b/tools/metrics/histograms/metadata/webauthn/histograms.xml index 052dbf3..a31a10a 100644 --- a/tools/metrics/histograms/metadata/webauthn/histograms.xml +++ b/tools/metrics/histograms/metadata/webauthn/histograms.xml
@@ -560,7 +560,7 @@ </histogram> <histogram name="WebAuthentication.Windows.FindHelloDialogIterationCount" - units="iterations" expires_after="2025-09-07"> + units="iterations" expires_after="2026-06-30"> <owner>kenrb@chromium.org</owner> <owner>chrome-webauthn@google.com</owner> <summary> @@ -571,7 +571,7 @@ </histogram> <histogram name="WebAuthentication.Windows.ForegroundedWindowsHelloDialog" - enum="WindowsForegroundedHelloDialog" expires_after="2025-09-07"> + enum="WindowsForegroundedHelloDialog" expires_after="2026-06-30"> <owner>kenrb@chromium.org</owner> <owner>chrome-webauthn@google.com</owner> <summary>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml index d4feee69..548f45e3 100644 --- a/tools/metrics/ukm/ukm.xml +++ b/tools/metrics/ukm/ukm.xml
@@ -3739,6 +3739,50 @@ </metric> </event> +<event name="AutofillAi.UserPromptMetrics"> + <owner>jihadghanna@google.com</owner> + <owner>jkeitel@google.com</owner> + <owner>brunobraga@google.com</owner> + <summary> + Records when an AutofillAi prompt is shown to the user, such as saving or + updating entities. + </summary> + <metric name="EntityType"> + <summary> + The type of entity for which the current metric record is being logged + (E.g. Passport, Driver's License, etc.). + </summary> + </metric> + <metric name="FormSessionIdentifier"> + <summary> + A random variable assigned to a specific instance of a HTML form in a + specific renderer. This should be globally unique and suitable for + counting distinct forms. Two different users would have different values. + A single user loading the same form in two different tabs would have two + different values. This is used to link the fields to a form in + AutofillAi.KeyMetrics. + </summary> + </metric> + <metric name="PromptType"> + <summary> + Describes the type of prompt shown to the user. This could be for example + a save or update entity prompt. + </summary> + </metric> + <metric name="Result"> + <summary> + The decision made by the user after being shown a prompt. This could be + for example, accepted, declined or ignored. + </summary> + </metric> + <metric name="StorageType"> + <summary> + Describes to which storage the data will be saved if the user accepts the + prompt, such locally or in the wallet server. + </summary> + </metric> +</event> + <event name="BackForwardCacheDisabledForRenderFrameHostReason"> <owner>hajimehoshi@chromium.org</owner> <summary>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json index 8f1ccb0..f3f76b2 100644 --- a/tools/perf/core/perfetto_binary_roller/binary_deps.json +++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -1,16 +1,16 @@ { "trace_processor_shell": { "linux_arm64": { - "hash": "4a4432738c52fb7b071d0d3bd8478a3026ca3d55", - "full_remote_path": "perfetto-luci-artifacts/3b37d4c14b693cb89309578baa5ad77751307250/linux-arm64/trace_processor_shell" + "hash": "8df907de30d065b73667c8c4adfe6c75e9936e0d", + "full_remote_path": "perfetto-luci-artifacts/d17b40b3b5e36f3744f1d010fe3ba2d3c55559c0/linux-arm64/trace_processor_shell" }, "win": { - "hash": "bb20d180f4376b4e101b81662a24b21879733b6e", - "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/43afaf571d990c0f3275c6800cf3ed42138bdc26/trace_processor_shell.exe" + "hash": "597b5d7d406a89b4237451ec94545ad23cc77980", + "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/d17b40b3b5e36f3744f1d010fe3ba2d3c55559c0/trace_processor_shell.exe" }, "linux_arm": { - "hash": "8f61747472bfaf68561d72d515a1ca4643b8f166", - "full_remote_path": "perfetto-luci-artifacts/3b37d4c14b693cb89309578baa5ad77751307250/linux-arm/trace_processor_shell" + "hash": "d2819bf77c3920780f2b33cc43f328d24cc1e427", + "full_remote_path": "perfetto-luci-artifacts/d17b40b3b5e36f3744f1d010fe3ba2d3c55559c0/linux-arm/trace_processor_shell" }, "mac": { "hash": "f5d83eca972747f7c3db9f35c07ed57902418e8c", @@ -21,8 +21,8 @@ "full_remote_path": "perfetto-luci-artifacts/d17b40b3b5e36f3744f1d010fe3ba2d3c55559c0/mac-arm64/trace_processor_shell" }, "linux": { - "hash": "bcb0bf8bc8af7c097b0389189bbb3c6cd7d5fc3e", - "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/9e99848ef108842c9c6f1fefe5bf8d4fd44c8496/trace_processor_shell" + "hash": "b1296477ed3d348b1e367a5569467c6815146fb2", + "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/d17b40b3b5e36f3744f1d010fe3ba2d3c55559c0/trace_processor_shell" } }, "power_profile.sql": {
diff --git a/ui/base/ui_base_features.cc b/ui/base/ui_base_features.cc index de6fd4f5..f2d4b3a 100644 --- a/ui/base/ui_base_features.cc +++ b/ui/base/ui_base_features.cc
@@ -265,7 +265,7 @@ // This feature enables drag and drop using touch input devices. BASE_FEATURE(kTouchDragAndDrop, "TouchDragAndDrop", -#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) +#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN) base::FEATURE_ENABLED_BY_DEFAULT #else base::FEATURE_DISABLED_BY_DEFAULT
diff --git a/ui/compositor/compositor.cc b/ui/compositor/compositor.cc index 57cac20..d51815a 100644 --- a/ui/compositor/compositor.cc +++ b/ui/compositor/compositor.cc
@@ -901,7 +901,7 @@ "cc,benchmark", "FramePresented", frame_timing_details.presentation_feedback.timestamp, "environment", "browser"); - observer_list_.Notify(&CompositorObserver::OnDidPresentCompositorFrame, + observer_list_.Notify(&CompositorObserver::OnDidPresentCompositorFrame, this, frame_token, frame_timing_details.presentation_feedback); }
diff --git a/ui/compositor/compositor_observer.h b/ui/compositor/compositor_observer.h index a2f67f9d..82e37a1 100644 --- a/ui/compositor/compositor_observer.h +++ b/ui/compositor/compositor_observer.h
@@ -71,6 +71,7 @@ // Called when the presentation feedback was received from the viz. virtual void OnDidPresentCompositorFrame( + Compositor* compositor, uint32_t frame_token, const gfx::PresentationFeedback& feedback) {}
diff --git a/ui/compositor/layer_unittest.cc b/ui/compositor/layer_unittest.cc index 15b5002f..0de15f8 100644 --- a/ui/compositor/layer_unittest.cc +++ b/ui/compositor/layer_unittest.cc
@@ -431,6 +431,7 @@ } void OnDidPresentCompositorFrame( + ui::Compositor* compositor, uint32_t frame_token, const gfx::PresentationFeedback& feedback) override { ended_ = true;
diff --git a/ui/compositor/test/draw_waiter_for_test.cc b/ui/compositor/test/draw_waiter_for_test.cc index 5e4fe22a..8a9e36d 100644 --- a/ui/compositor/test/draw_waiter_for_test.cc +++ b/ui/compositor/test/draw_waiter_for_test.cc
@@ -51,6 +51,7 @@ } void DrawWaiterForTest::OnDidPresentCompositorFrame( + ui::Compositor* compositor, uint32_t frame_token, const gfx::PresentationFeedback& feedback) { if (wait_event_ == WAIT_FOR_COMPOSITING_ENDED) {
diff --git a/ui/compositor/test/draw_waiter_for_test.h b/ui/compositor/test/draw_waiter_for_test.h index aa26d0a..f4206ff3 100644 --- a/ui/compositor/test/draw_waiter_for_test.h +++ b/ui/compositor/test/draw_waiter_for_test.h
@@ -49,6 +49,7 @@ void OnCompositingStarted(Compositor* compositor, base::TimeTicks start_time) override; void OnDidPresentCompositorFrame( + Compositor* compositor, uint32_t frame_token, const gfx::PresentationFeedback& feedback) override;
diff --git a/ui/compositor/total_animation_throughput_reporter_unittest.cc b/ui/compositor/total_animation_throughput_reporter_unittest.cc index 5e1e05ab..914c25bf 100644 --- a/ui/compositor/total_animation_throughput_reporter_unittest.cc +++ b/ui/compositor/total_animation_throughput_reporter_unittest.cc
@@ -70,6 +70,7 @@ } void OnDidPresentCompositorFrame( + ui::Compositor* compositor, uint32_t frame_token, const gfx::PresentationFeedback& feedback) override { if (waiting_for_did_present_compositor_frame_) {
diff --git a/ui/gfx/geometry/insets_f_unittest.cc b/ui/gfx/geometry/insets_f_unittest.cc index 3443aab3..0ca32ad 100644 --- a/ui/gfx/geometry/insets_f_unittest.cc +++ b/ui/gfx/geometry/insets_f_unittest.cc
@@ -98,6 +98,16 @@ EXPECT_FALSE(insets.IsEmpty()); } +TEST(InsetsFTest, Transpose) { + InsetsF insets = InsetsF::TLBR(1.25, 2.5, 3.75, 4.875); + insets.Transpose(); + EXPECT_EQ(insets, InsetsF::TLBR(2.5, 1.25, 4.875, 3.75)); + + insets = InsetsF(); + insets.Transpose(); + EXPECT_EQ(insets, InsetsF()); +} + TEST(InsetsFTest, Operators) { InsetsF insets = InsetsF().set_left(2.5f).set_right(4.1f).set_top(1.f).set_bottom(3.3f);
diff --git a/ui/gfx/geometry/insets_outsets_base.h b/ui/gfx/geometry/insets_outsets_base.h index 4ca1286..85752c1 100644 --- a/ui/gfx/geometry/insets_outsets_base.h +++ b/ui/gfx/geometry/insets_outsets_base.h
@@ -6,6 +6,7 @@ #define UI_GFX_GEOMETRY_INSETS_OUTSETS_BASE_H_ #include <string> +#include <utility> #include "base/component_export.h" #include "base/numerics/clamped_math.h" @@ -47,6 +48,13 @@ // Returns true if the insets/outsets are empty. bool IsEmpty() const { return width() == 0 && height() == 0; } + // Flips x- and y-axes. + void Transpose() { + using std::swap; + swap(top_, left_); + swap(bottom_, right_); + } + // These setters can be used together with the default constructor and the // single-parameter constructor to construct Insets instances, for example: // // T, L, B, R
diff --git a/ui/gfx/geometry/insets_outsets_f_base.h b/ui/gfx/geometry/insets_outsets_f_base.h index 40fee5e5..0c2d0e5 100644 --- a/ui/gfx/geometry/insets_outsets_f_base.h +++ b/ui/gfx/geometry/insets_outsets_f_base.h
@@ -6,6 +6,7 @@ #define UI_GFX_GEOMETRY_INSETS_OUTSETS_F_BASE_H_ #include <string> +#include <utility> #include "base/component_export.h" @@ -35,6 +36,13 @@ // Returns true if the insets/outsets are empty. bool IsEmpty() const { return width() == 0.f && height() == 0.f; } + // Flips x- and y-axes. + void Transpose() { + using std::swap; + swap(top_, left_); + swap(bottom_, right_); + } + // These setters can be used together with the default constructor and the // single-parameter constructor to construct InsetsF instances, for example: // // T, L, B, R
diff --git a/ui/gfx/geometry/insets_unittest.cc b/ui/gfx/geometry/insets_unittest.cc index 0972ae5..08b44ba7 100644 --- a/ui/gfx/geometry/insets_unittest.cc +++ b/ui/gfx/geometry/insets_unittest.cc
@@ -121,6 +121,16 @@ EXPECT_FALSE(insets.IsEmpty()); } +TEST(InsetsTest, Transpose) { + Insets insets = Insets::TLBR(1, 2, 3, 4); + insets.Transpose(); + EXPECT_EQ(insets, Insets::TLBR(2, 1, 4, 3)); + + insets = Insets(); + insets.Transpose(); + EXPECT_EQ(insets, Insets()); +} + TEST(InsetsTest, Operators) { Insets insets = Insets().set_left_right(2, 4).set_top_bottom(1, 3); insets += Insets().set_left_right(6, 8).set_top_bottom(5, 7);
diff --git a/ui/gfx/mojom/delegated_ink_point_renderer.mojom b/ui/gfx/mojom/delegated_ink_point_renderer.mojom index 7434bef..1b06a7d 100644 --- a/ui/gfx/mojom/delegated_ink_point_renderer.mojom +++ b/ui/gfx/mojom/delegated_ink_point_renderer.mojom
@@ -13,6 +13,7 @@ // ink trail. When the browser detects the end of the trail, it will call // ResetPrediction() so that viz does not predict any points further than what // the user is expecting. +[DirectReceiver] interface DelegatedInkPointRenderer { // Used to send the DelegatedInkPoint that was created in the browser process // to viz in order to be drawn as part of the delegated ink trail. @@ -22,4 +23,4 @@ // by previously received points. Used by the browser process when a delegated // ink trail should end. ResetPrediction(); -}; \ No newline at end of file +};
diff --git a/ui/ozone/platform/wayland/host/wayland_tablet_tool.cc b/ui/ozone/platform/wayland/host/wayland_tablet_tool.cc index 9caab86..472f8807 100644 --- a/ui/ozone/platform/wayland/host/wayland_tablet_tool.cc +++ b/ui/ozone/platform/wayland/host/wayland_tablet_tool.cc
@@ -64,9 +64,7 @@ } // namespace -WaylandTabletTool::FrameData::FrameData() { - pointer_details.pointer_type = EventPointerType::kPen; -} +WaylandTabletTool::FrameData::FrameData() = default; WaylandTabletTool::FrameData::~FrameData() = default; @@ -144,6 +142,7 @@ void WaylandTabletTool::ResetFrameData() { frame_data_ = FrameData(); + frame_data_.pointer_details.pointer_type = pointer_type_; } // static @@ -151,7 +150,8 @@ zwp_tablet_tool_v2* tool, uint32_t tool_type) { auto* self = static_cast<WaylandTabletTool*>(data); - self->frame_data_.pointer_details.pointer_type = ToolToPointerType(tool_type); + self->pointer_type_ = ToolToPointerType(tool_type); + self->frame_data_.pointer_details.pointer_type = self->pointer_type_; } // static @@ -338,6 +338,7 @@ zwp_tablet_tool_v2* tool, uint32_t time) { auto* self = static_cast<WaylandTabletTool*>(data); + self->frame_data_.pointer_details.pointer_type = self->pointer_type_; self->frame_data_.timestamp = wl::EventMillisecondsToTimeTicks(time); self->DispatchBufferedEvents(); self->ResetFrameData();
diff --git a/ui/ozone/platform/wayland/host/wayland_tablet_tool.h b/ui/ozone/platform/wayland/host/wayland_tablet_tool.h index 7d47e081..21be13175 100644 --- a/ui/ozone/platform/wayland/host/wayland_tablet_tool.h +++ b/ui/ozone/platform/wayland/host/wayland_tablet_tool.h
@@ -116,6 +116,7 @@ }; FrameData frame_data_; + EventPointerType pointer_type_ = EventPointerType::kPen; }; class WaylandTabletTool::Delegate {
diff --git a/ui/views/controls/tree/tree_view.cc b/ui/views/controls/tree/tree_view.cc index 47d0717..8f91aad 100644 --- a/ui/views/controls/tree/tree_view.cc +++ b/ui/views/controls/tree/tree_view.cc
@@ -879,14 +879,6 @@ active_node_ = node; } - if (selection_changed) { - SchedulePaintForNode(selected_node_); - SetAccessibleSelectionForNode(selected_node_, false); - selected_node_ = node; - SetAccessibleSelectionForNode(selected_node_, true); - SchedulePaintForNode(selected_node_); - } - if (active_changed && node) { // GetForegroundBoundsForNode() returns RTL-flipped coordinates for paint. // Un-flip before passing to ScrollRectToVisible(), which uses layout @@ -910,11 +902,14 @@ } if (selection_changed) { + SchedulePaintForNode(selected_node_); + SetAccessibleSelectionForNode(selected_node_, false); + selected_node_ = node; + SetAccessibleSelectionForNode(selected_node_, true); + SchedulePaintForNode(selected_node_); AXVirtualView* ax_selected_view = node ? node->accessibility_view() : nullptr; - if (ax_selected_view) { - ax_selected_view->NotifyEvent(ax::mojom::Event::kSelection, true); - } else { + if (!ax_selected_view) { NotifyAccessibilityEventDeprecated(ax::mojom::Event::kSelection, true); } }
diff --git a/ui/views/controls/tree/tree_view_unittest.cc b/ui/views/controls/tree/tree_view_unittest.cc index a82628af..b4f1e072 100644 --- a/ui/views/controls/tree/tree_view_unittest.cc +++ b/ui/views/controls/tree/tree_view_unittest.cc
@@ -1287,6 +1287,27 @@ GetAccessibilityViewByName("a"), ax::mojom::Event::kFocus))); EXPECT_TRUE(FiredAccessibilityEvent(std::make_pair( GetAccessibilityViewByName("a"), ax::mojom::Event::kSelection))); + + // On node selection, ensure the focus event is fired before the selection + // event. + ClearAccessibilityEvents(); + tree()->SetModel(&model_); + tree()->SetSelectedNode(GetNodeByTitle("b")); + std::vector<ax::mojom::Event> events; + for (const auto& event : accessibility_events()) { + if (event.first == GetAccessibilityViewByName("b")) { + events.push_back(event.second); + } + } + + auto focus_it = + std::find(events.begin(), events.end(), ax::mojom::Event::kFocus); + auto selection_it = + std::find(events.begin(), events.end(), ax::mojom::Event::kSelection); + ASSERT_NE(focus_it, events.end()); + ASSERT_NE(selection_it, events.end()); + EXPECT_LT(std::distance(events.begin(), focus_it), + std::distance(events.begin(), selection_it)); } } // namespace views