diff --git a/DEPS b/DEPS index 6549d77..1b60a04e 100644 --- a/DEPS +++ b/DEPS
@@ -312,11 +312,11 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Skia # and whatever else without interference from each other. - 'skia_revision': '8296b1df7e1fa34e3378d597f9d285f9c4f6f9d8', + 'skia_revision': '8810cff96d09cd1aa4386527561fc21d0036b091', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. - 'v8_revision': '8d2f5169115a949eea2d3a1a8675885655b1114f', + 'v8_revision': '419b5ea8df9fb8dc551075135473c24adb82937c', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling ANGLE # and whatever else without interference from each other. @@ -336,7 +336,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Fuchsia sdk # and whatever else without interference from each other. - 'fuchsia_version': 'version:30.20251218.4.1', + 'fuchsia_version': 'version:31.20260204.7.1', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling google-toolbox-for-mac # and whatever else without interference from each other. @@ -400,7 +400,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling devtools-frontend # and whatever else without interference from each other. - 'devtools_frontend_revision': '16d736bf9361bd7a4e26bfec357a2f106468c467', + 'devtools_frontend_revision': 'dea9116ea444a915d659b78f160d909db789319b', # 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. @@ -1207,7 +1207,7 @@ 'packages': [ { 'package': 'chromium/chrome/android/orderfiles/arm64', - 'version': 'F2a6zIgtv41WqdJImFCUoc-MI04EuTKGp3khPjeHlRgC', + 'version': 'M_DsYusUj4vlnGESISKYlIyOAUdf464eriMJ7ubKVesC', }, ], 'condition': 'checkout_android and non_git_source', @@ -1229,7 +1229,7 @@ 'packages': [ { 'package': 'chromium/android_webview/tools/orderfiles/arm64', - 'version': '9alqR-ethfT8JfMrQVg3UWUMfvSoOq2f4OmcfP7htyMC', + 'version': 'EoS4Z_W-Qxi-nue0au9D9dmxLSd0q-T0MxxSzjUc-RQC', }, ], 'condition': 'checkout_android and non_git_source', @@ -1626,7 +1626,7 @@ 'src/clank': { 'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' + - 'c4cbefe19894a5496065cd0b48d182056b062684', + 'b00a5740a69683220ef77346e7b220345f3a8ed0', 'condition': 'checkout_android and checkout_src_internal', }, @@ -2937,7 +2937,7 @@ 'src/third_party/tflite/src': Var('chromium_git') + '/external/github.com/tensorflow/tensorflow.git' + '@' + '01e030d23d3b904d98cbf908da74d63b3c186949', 'src/third_party/litert/src': - Var('chromium_git') + '/external/github.com/google-ai-edge/LiteRT.git' + '@' + 'ba80d53cf2e97763b48ebbd03120871b57820f99', + Var('chromium_git') + '/external/github.com/google-ai-edge/LiteRT.git' + '@' + '320c13c17b995e7ccd7b8d6560db255d2f994199', 'src/third_party/turbine/cipd': { 'packages': [ { @@ -3001,7 +3001,7 @@ Var('chromium_git') + '/webpagereplay.git' + '@' + Var('webpagereplay_revision'), 'src/third_party/webrtc': - Var('webrtc_git') + '/src.git' + '@' + 'a4ea3c2b9c7708d08b20efb4167a0c20ecf3e3a7', + Var('webrtc_git') + '/src.git' + '@' + '33387a2045e49f8b458ce9631fb74613461f8c3d', # 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. @@ -3784,7 +3784,7 @@ 'src/ios_internal': { 'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' + - 'fd8562181f848f91602e15903c2087549ebfc693', + '85213da2ba5de03bda5a52539b276c288b872b46', 'condition': 'checkout_ios and checkout_src_internal', },
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn index a13fda5..8069ff0 100644 --- a/android_webview/BUILD.gn +++ b/android_webview/BUILD.gn
@@ -1144,6 +1144,7 @@ deps = [ ":concatenate_resources_allowlists", + "//components/dom_distiller/core:dom_distiller_viewer_js", "//components/neterror/resources:bundle_js", "//components/resources:about_credits", "//components/resources/ssl/ssl_error_assistant:make_ssl_error_assistant_protobuf",
diff --git a/android_webview/browser/BUILD.gn b/android_webview/browser/BUILD.gn index 26734fc..8417e46 100644 --- a/android_webview/browser/BUILD.gn +++ b/android_webview/browser/BUILD.gn
@@ -259,6 +259,7 @@ "//components/content_capture/browser", "//components/content_relationship_verification", "//components/input", + "//components/stylus_handwriting/android", # Called via JNI in CrashpadMain "//components/crash/android:crashpad_main",
diff --git a/android_webview/browser/DEPS b/android_webview/browser/DEPS index 3d7ac8a..b3c2eca3 100644 --- a/android_webview/browser/DEPS +++ b/android_webview/browser/DEPS
@@ -55,6 +55,7 @@ "+components/spellcheck/browser", "+components/spellcheck/common", "+components/strings", + "+components/stylus_handwriting/android/stylus_handwriting_feature_map.h", "+components/tracing/common", "+components/url_formatter", "+components/url_matcher",
diff --git a/android_webview/browser/aw_field_trials.cc b/android_webview/browser/aw_field_trials.cc index 50ef970..ee925a4 100644 --- a/android_webview/browser/aw_field_trials.cc +++ b/android_webview/browser/aw_field_trials.cc
@@ -18,6 +18,7 @@ #include "components/payments/content/android/payment_feature_map.h" #include "components/permissions/features.h" #include "components/safe_browsing/core/common/features.h" +#include "components/stylus_handwriting/android/stylus_handwriting_feature_map.h" #include "components/variations/feature_overrides.h" #include "components/viz/common/features.h" #include "content/public/common/content_features.h" @@ -312,4 +313,8 @@ // Don't pass the data about browser window position on screen to WebView. aw_feature_overrides.DisableFeature(ui::kAndroidUseCorrectWindowBounds); + + // Launched for WebView. Experimentation needed for Chrome on Android. + aw_feature_overrides.EnableFeature( + stylus_handwriting::android::kProbeStylusWritingInBackground); }
diff --git a/android_webview/browser/cookie_manager.cc b/android_webview/browser/cookie_manager.cc index f7edba0..f7cd5be 100644 --- a/android_webview/browser/cookie_manager.cc +++ b/android_webview/browser/cookie_manager.cc
@@ -456,12 +456,8 @@ void CookieManager::SetCookie(JNIEnv* env, const JavaRef<jstring>& url, std::string& cookie_value, - const JavaRef<jobject>& java_callback) { - DCHECK(java_callback) << "Unexpected null Java callback"; + base::OnceCallback<void(bool)> callback) { GURL host(ConvertJavaStringToUTF16(env, url)); - base::OnceCallback<void(bool)> callback = - base::BindOnce(&base::android::RunBooleanCallbackAndroid, - ScopedJavaGlobalRef<jobject>(java_callback)); ExecCookieTask(base::BindOnce(&CookieManager::SetCookieHelper, base::Unretained(this), host, cookie_value, @@ -590,12 +586,7 @@ void CookieManager::RemoveSessionCookies( JNIEnv* env, - const JavaRef<jobject>& java_callback) { - DCHECK(java_callback) << "Unexpected null Java callback"; - base::OnceCallback<void(bool)> callback = - base::BindOnce(&base::android::RunBooleanCallbackAndroid, - ScopedJavaGlobalRef<jobject>(java_callback)); - + base::OnceCallback<void(bool)> callback) { ExecCookieTask(base::BindOnce(&CookieManager::RemoveSessionCookiesHelper, base::Unretained(this), std::move(callback))); } @@ -629,13 +620,7 @@ } void CookieManager::RemoveAllCookies(JNIEnv* env, - const JavaRef<jobject>& java_callback) { - DCHECK(java_callback) << "Unexpected null Java callback"; - - base::OnceCallback<void(bool)> callback = - base::BindOnce(&base::android::RunBooleanCallbackAndroid, - ScopedJavaGlobalRef<jobject>(java_callback)); - + base::OnceCallback<void(bool)> callback) { ExecCookieTask(base::BindOnce(&CookieManager::RemoveAllCookiesHelper, base::Unretained(this), std::move(callback))); }
diff --git a/android_webview/browser/cookie_manager.h b/android_webview/browser/cookie_manager.h index e91e7ac..c312f897 100644 --- a/android_webview/browser/cookie_manager.h +++ b/android_webview/browser/cookie_manager.h
@@ -119,7 +119,7 @@ void SetCookie(JNIEnv* env, const base::android::JavaRef<jstring>& url, std::string& value, - const base::android::JavaRef<jobject>& java_callback); + base::OnceCallback<void(bool)> callback); void SetCookieSync(JNIEnv* env, const base::android::JavaRef<jstring>& url, std::string& value); @@ -131,11 +131,9 @@ JNIEnv* env, const base::android::JavaRef<jstring>& url); - void RemoveAllCookies(JNIEnv* env, - const base::android::JavaRef<jobject>& java_callback); - void RemoveSessionCookies( - JNIEnv* env, - const base::android::JavaRef<jobject>& java_callback); + void RemoveAllCookies(JNIEnv* env, base::OnceCallback<void(bool)> callback); + void RemoveSessionCookies(JNIEnv* env, + base::OnceCallback<void(bool)> callback); void RemoveAllCookiesSync(JNIEnv* env); void RemoveSessionCookiesSync(JNIEnv* env); void RemoveExpiredCookies(JNIEnv* env);
diff --git a/android_webview/java/src/org/chromium/android_webview/AwCookieManager.java b/android_webview/java/src/org/chromium/android_webview/AwCookieManager.java index 7ddb4ea0..f2667d19 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwCookieManager.java +++ b/android_webview/java/src/org/chromium/android_webview/AwCookieManager.java
@@ -304,7 +304,7 @@ long nativeCookieManager, String url, @JniType("std::string") String value, - CookieCallback callback); + @JniType("base::OnceCallback<void(bool)>") CookieCallback callback); void setCookieSync( long nativeCookieManager, String url, @JniType("std::string") String value); @@ -314,11 +314,15 @@ String[] getCookieInfo(long nativeCookieManager, String url); - void removeSessionCookies(long nativeCookieManager, CookieCallback callback); + void removeSessionCookies( + long nativeCookieManager, + @JniType("base::OnceCallback<void(bool)>") CookieCallback callback); void removeSessionCookiesSync(long nativeCookieManager); - void removeAllCookies(long nativeCookieManager, CookieCallback callback); + void removeAllCookies( + long nativeCookieManager, + @JniType("base::OnceCallback<void(bool)>") CookieCallback callback); void removeAllCookiesSync(long nativeCookieManager);
diff --git a/ash/accessibility/accessibility_controller.cc b/ash/accessibility/accessibility_controller.cc index 15a5518a..bdcaaa8 100644 --- a/ash/accessibility/accessibility_controller.cc +++ b/ash/accessibility/accessibility_controller.cc
@@ -3818,7 +3818,6 @@ Shell::Get()->UpdateCursorCompositingEnabled(); break; case FeatureType::kLiveCaption: - live_caption().SetEnabled(enabled); break; case FeatureType::kMonoAudio: CrasAudioHandler::Get()->SetOutputMonoEnabled(enabled);
diff --git a/ash/capture_mode/capture_mode_education_controller.cc b/ash/capture_mode/capture_mode_education_controller.cc index 3fa00656..4a4c4d61 100644 --- a/ash/capture_mode/capture_mode_education_controller.cc +++ b/ash/capture_mode/capture_mode_education_controller.cc
@@ -201,8 +201,7 @@ auto* pref_service = GetPrefService(); CHECK(pref_service); - if (!(features::IsCaptureModeEducationBypassLimitsEnabled() || - skip_prefs_for_test_)) { + if (!skip_prefs_for_test_) { const int shown_count = pref_service->GetInteger(prefs::kCaptureModeEducationShownCount); const base::Time last_shown_time =
diff --git a/ash/capture_mode/capture_mode_education_controller_unittest.cc b/ash/capture_mode/capture_mode_education_controller_unittest.cc index f2e7858b..63b6770 100644 --- a/ash/capture_mode/capture_mode_education_controller_unittest.cc +++ b/ash/capture_mode/capture_mode_education_controller_unittest.cc
@@ -545,34 +545,4 @@ NudgeCatalogName::kCaptureModeEducationShortcutTutorial, 0); } -class CaptureModeEducationControllerBypassLimitsFlagTest - : public CaptureModeEducationControllerTest { - public: - CaptureModeEducationControllerBypassLimitsFlagTest() { - scoped_feature_list_.InitWithFeaturesAndParameters( - /*enabled_features=*/ - {{features::kCaptureModeEducation, - {{"CaptureModeEducationParam", "ShortcutNudge"}}}, - {features::kCaptureModeEducationBypassLimits, {}}}, - /*disabled_features=*/{}); - } - CaptureModeEducationControllerBypassLimitsFlagTest( - const CaptureModeEducationControllerBypassLimitsFlagTest&) = delete; - CaptureModeEducationControllerBypassLimitsFlagTest& operator=( - const CaptureModeEducationControllerBypassLimitsFlagTest&) = delete; - ~CaptureModeEducationControllerBypassLimitsFlagTest() override = default; - - protected: - base::test::ScopedFeatureList scoped_feature_list_; -}; - -TEST_F(CaptureModeEducationControllerBypassLimitsFlagTest, NoShowLimits) { - // Show the nudge more than three times without advancing the clock, - // it should be visible each time. - for (int i = 0; i < 4; i++) { - ActivateNudgeAndCheckVisibility(); - CancelNudge(kCaptureModeNudgeId); - } -} - } // namespace ash
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc index cf268a4..ded6fd7e 100644 --- a/ash/constants/ash_features.cc +++ b/ash/constants/ash_features.cc
@@ -366,11 +366,6 @@ CaptureModeEducationParam::kShortcutNudge, &capture_mode_education_type_options}; -// Enables bypassing the 3 times / 24 hours show limits for the Capture Mode -// education nudges and tutorials. -BASE_FEATURE(kCaptureModeEducationBypassLimits, - base::FEATURE_DISABLED_BY_DEFAULT); - // Enables on-device OCR functionality in capture mode, used as part of the // Scanner and Sunfish features. BASE_FEATURE(kCaptureModeOnDeviceOcr, base::FEATURE_ENABLED_BY_DEFAULT); @@ -2393,10 +2388,6 @@ return base::FeatureList::IsEnabled(kCaptureModeEducation); } -bool IsCaptureModeEducationBypassLimitsEnabled() { - return base::FeatureList::IsEnabled(kCaptureModeEducationBypassLimits); -} - bool IsCaptureModeOnDeviceOcrEnabled() { return (IsScannerEnabled() || IsSunfishFeatureEnabled()) && base::FeatureList::IsEnabled(kCaptureModeOnDeviceOcr);
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h index 5ab4952..13dc28c 100644 --- a/ash/constants/ash_features.h +++ b/ash/constants/ash_features.h
@@ -161,8 +161,6 @@ COMPONENT_EXPORT(ASH_CONSTANTS) extern const base::FeatureParam<CaptureModeEducationParam> kCaptureModeEducationParam; -COMPONENT_EXPORT(ASH_CONSTANTS) -BASE_DECLARE_FEATURE(kCaptureModeEducationBypassLimits); COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kCaptureModeOnDeviceOcr); COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kCellularBypassESimInstallationConnectivityCheck); @@ -1022,8 +1020,6 @@ COMPONENT_EXPORT(ASH_CONSTANTS) bool IsBocaReceiverCustomPollingEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsBrightnessControlInSettingsEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsCaptureModeEducationEnabled(); -COMPONENT_EXPORT(ASH_CONSTANTS) -bool IsCaptureModeEducationBypassLimitsEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsCaptureModeOnDeviceOcrEnabled(); COMPONENT_EXPORT(ASH_CONSTANTS) bool IsContinuousOverviewScrollAnimationEnabled();
diff --git a/base/android/callback_android.h b/base/android/callback_android.h index 85d6acd..0ef2667f 100644 --- a/base/android/callback_android.h +++ b/base/android/callback_android.h
@@ -5,9 +5,8 @@ #ifndef BASE_ANDROID_CALLBACK_ANDROID_H_ #define BASE_ANDROID_CALLBACK_ANDROID_H_ -#include <jni.h> - #include <string> +#include <type_traits> #include <vector> #include "base/android/scoped_java_ref.h" @@ -15,11 +14,12 @@ #include "base/functional/callback.h" #include "base/time/time.h" #include "base/types/optional_ref.h" +#include "third_party/jni_zero/default_conversions.h" +#include "third_party/jni_zero/jni_zero.h" // Provides helper utility methods that run the given callback with the // specified argument. -namespace base { -namespace android { +namespace base::android { void BASE_EXPORT RunObjectCallbackAndroid(const JavaRef<jobject>& callback, const JavaRef<jobject>& arg); @@ -45,11 +45,46 @@ void BASE_EXPORT RunByteArrayCallbackAndroid(const JavaRef<jobject>& callback, const std::vector<uint8_t>& arg); -} // namespace android -} // namespace base +} // namespace base::android + +namespace base::android::internal { +template <typename T> +struct IsOnceCallback : std::false_type {}; + +template <typename T> +struct IsOnceCallback<base::OnceCallback<void(T)>> : std::true_type { + using ArgType = T; +}; + +template <typename T> +struct IsRepeatingCallback : std::false_type {}; + +template <typename T> +struct IsRepeatingCallback<base::RepeatingCallback<void(T)>> : std::true_type { + using ArgType = T; +}; + +template <typename T> +void RunJavaCallback(const jni_zero::ScopedJavaGlobalRef<jobject>& callback, + T arg) { + if constexpr (requires { jni_zero::ToJniType(nullptr, std::move(arg)); }) { + JNIEnv* env = jni_zero::AttachCurrentThread(); + base::android::RunObjectCallbackAndroid( + callback, jni_zero::ToJniType(env, std::move(arg))); + } else { + static_assert( + sizeof(T) == 0, + "Could not find ToJniType<> specialization for the callback's " + "parameter. Make sure the header declaring it is #included before " + "base/callback_android.h"); + } +} + +} // namespace base::android::internal namespace jni_zero { +// @JniType("base::OnceClosure") Runnable template <> inline base::OnceClosure FromJniType<base::OnceClosure>( JNIEnv* env, @@ -58,6 +93,7 @@ jni_zero::ScopedJavaGlobalRef<>(env, obj)); } +// @JniType("base::RepeatingClosure") Runnable template <> inline base::RepeatingClosure FromJniType<base::RepeatingClosure>( JNIEnv* env, @@ -66,6 +102,26 @@ jni_zero::ScopedJavaGlobalRef<>(env, obj)); } +// @JniType("base::OnceCallback<void(NativeFoo)>") Callback<JavaFoo> +template <typename T> + requires(base::android::internal::IsOnceCallback<T>::value) +inline T FromJniType(JNIEnv* env, const JavaRef<jobject>& obj) { + namespace internal = base::android::internal; + using ArgType = typename internal::IsOnceCallback<T>::ArgType; + return base::BindOnce(&internal::RunJavaCallback<ArgType>, + ScopedJavaGlobalRef<jobject>(env, obj)); +} + +// @JniType("base::RepeatingCallback<void(NativeFoo)>") Callback<JavaFoo> +template <typename T> + requires(base::android::internal::IsRepeatingCallback<T>::value) +inline T FromJniType(JNIEnv* env, const JavaRef<jobject>& obj) { + namespace internal = base::android::internal; + using ArgType = typename internal::IsRepeatingCallback<T>::ArgType; + return base::BindRepeating(&internal::RunJavaCallback<ArgType>, + ScopedJavaGlobalRef<jobject>(env, obj)); +} + } // namespace jni_zero #endif // BASE_ANDROID_CALLBACK_ANDROID_H_
diff --git a/build/android/pylib/local/device/local_device_environment.py b/build/android/pylib/local/device/local_device_environment.py index ef3d556..dde8381 100644 --- a/build/android/pylib/local/device/local_device_environment.py +++ b/build/android/pylib/local/device/local_device_environment.py
@@ -123,7 +123,8 @@ self._skia_gold_consider_unsupported = False if hasattr(args, 'skia_gold_consider_unsupported'): self._skia_gold_consider_unsupported = args.skia_gold_consider_unsupported - self._use_persistent_shell = args.use_persistent_shell + + self._use_persistent_shell = not args.disable_persistent_shell use_local_devil_tools = False if hasattr(args, 'use_local_devil_tools'):
diff --git a/build/android/test_runner.py b/build/android/test_runner.py index 0eb4b6f..50c280c 100755 --- a/build/android/test_runner.py +++ b/build/android/test_runner.py
@@ -221,16 +221,14 @@ namespace.local_output = True namespace.num_retries = 0 namespace.skip_clear_data = True - namespace.use_persistent_shell = True - parser.add_argument( - '--fast-local-dev', - type=bool, - nargs=0, - action=FastLocalDevAction, - help='Alias for: --num-retries=0 --enable-device-cache ' - '--enable-concurrent-adb --skip-clear-data ' - '--extract-test-list-from-filter --use-persistent-shell --local-output') + parser.add_argument('--fast-local-dev', + type=bool, + nargs=0, + action=FastLocalDevAction, + help='Alias for: --num-retries=0 --enable-device-cache ' + '--enable-concurrent-adb --skip-clear-data ' + '--extract-test-list-from-filter --local-output') # TODO(jbudorick): Remove this once downstream bots have switched to # api.test_results. @@ -259,11 +257,12 @@ dest='repeat', type=int, default=0, help='Number of times to repeat the specified set of tests.') - # Not useful for junit tests. + # There may be steps that involve setting up a new emulator or device + # resets that may not interact well with a persistent shell connection. parser.add_argument( - '--use-persistent-shell', + '--disable-persistent-shell', action='store_true', - help='Uses a persistent shell connection for the adb connection.') + help='Use a non-persistent shell connection for the adb connection.') # This is currently only implemented for gtests and instrumentation tests. parser.add_argument(
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1 index 3239444..48b7853f 100644 --- a/build/fuchsia/linux_internal.sdk.sha1 +++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@ -30.20251217.103.1 +31.20260204.103.1
diff --git a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/FeedV2NewTabPageTest.java b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/FeedV2NewTabPageTest.java index bb1af45..a21e825d 100644 --- a/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/FeedV2NewTabPageTest.java +++ b/chrome/android/feed/core/javatests/src/org/chromium/chrome/browser/feed/FeedV2NewTabPageTest.java
@@ -30,6 +30,7 @@ import android.animation.AnimatorListenerAdapter; import android.content.pm.ActivityInfo; import android.os.Build; +import android.text.format.DateUtils; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; @@ -58,6 +59,7 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import org.chromium.base.FakeTimeTestRule; import org.chromium.base.ThreadUtils; import org.chromium.base.test.params.ParameterAnnotations; import org.chromium.base.test.params.ParameterProvider; @@ -72,6 +74,7 @@ import org.chromium.base.test.util.DoNotBatch; import org.chromium.base.test.util.Feature; import org.chromium.base.test.util.Features.DisableFeatures; +import org.chromium.base.test.util.Features.EnableFeatures; import org.chromium.base.test.util.Restriction; import org.chromium.chrome.browser.ChromeTabbedActivity; import org.chromium.chrome.browser.feed.sections.SectionHeaderListProperties; @@ -88,6 +91,7 @@ import org.chromium.chrome.browser.suggestions.tile.TilesLinearLayout; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.toolbar.top.ToolbarPhone; +import org.chromium.chrome.browser.ui.signin.signin_promo.NtpSigninPromoDelegate; import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate; import org.chromium.chrome.test.R; import org.chromium.chrome.test.transit.ChromeTransitTestRules; @@ -154,6 +158,8 @@ public final SigninTestRule mSigninTestRule = new SigninTestRule(); + @Rule public FakeTimeTestRule mFakeTimeTestRule = new FakeTimeTestRule(); + // Mock sign-in environment needs to be destroyed after ChromeActivity in case there are // observers registered in the AccountManagerFacade mock. @Rule @@ -227,7 +233,7 @@ } private void openNewTabPage() { - mActivityTestRule.loadUrl(getOriginalNativeNtpUrl()); + mActivityTestRule.loadUrlInNewTab(getOriginalNativeNtpUrl()); mTab = mActivityTestRule.getActivityTab(); NewTabPageTestUtils.waitForNtpLoaded(mTab); @@ -445,6 +451,51 @@ onView(withId(R.id.signin_promo_view_container)).check(matches(isDisplayed())); } + @Test + @MediumTest + @Feature({"FeedNewTabPage"}) + @EnableFeatures({ + "EnableSeamlessSignin" + + ":seamless-signin-promo-type/compact" + + "/seamless-signin-string-type/continueButton" + }) + public void testSignInPromo_shownIfTimeElapsedSinceFirstShownIsLessThanFirstShownLimit() { + // Show the promo for the first time. + openNewTabPage(); + onView(withId(R.id.signin_promo_view_container)).check(matches(isDisplayed())); + + // Advance time, but not beyond the first time shown limit. + mFakeTimeTestRule.advanceMillis( + (NtpSigninPromoDelegate.NTP_SYNC_PROMO_NTP_SINCE_FIRST_TIME_SHOWN_LIMIT_HOURS - 1) + * DateUtils.HOUR_IN_MILLIS); + + // Open a new tab, the promo should still be shown. + openNewTabPage(); + onView(withId(R.id.signin_promo_view_container)).check(matches(isDisplayed())); + } + + @Test + @MediumTest + @Feature({"FeedNewTabPage"}) + @EnableFeatures({ + "EnableSeamlessSignin" + + ":seamless-signin-promo-type/compact" + + "/seamless-signin-string-type/continueButton" + }) + public void + testSignInPromo_shownIfTimeElapsedSinceFirstShownExceedsFirstShownLimitAndResetThreshold() { + // Show the promo for the first time. + openNewTabPage(); + onView(withId(R.id.signin_promo_view_container)).check(matches(isDisplayed())); + + // Advance time beyond the the first time shown limit and the last time shown reset period. + mFakeTimeTestRule.advanceMillis( + (NtpSigninPromoDelegate.NTP_SYNC_PROMO_RESET_AFTER_DAYS * DateUtils.DAY_IN_MILLIS)); + // Open a new tab, the promo should be shown. + openNewTabPage(); + onView(withId(R.id.signin_promo_view_container)).check(matches(isDisplayed())); + } + // The three-dot button in the feed's header is removed by the `NewTabPageCustomization` // feature. Disabling this feature flag ensures the current test continues to validate the // three-dot button's functionality.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/ModelTrackingOrchestrator.java b/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/ModelTrackingOrchestrator.java index 61736cf..e8b5f27a 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/ModelTrackingOrchestrator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/ModelTrackingOrchestrator.java
@@ -102,7 +102,7 @@ private final String mWindowTag; private final TabModelSelector mTabModelSelector; - private final Map<Token, CollectionSaveForwarder> mGroupForwarderMap = new HashMap<>(); + private final Map<Token, Boolean> mGroupIncognitoStatus = new HashMap<>(); private final IncognitoTabModelObserver mIncognitoTabModelObserver = new IncognitoTabModelObserver() { @Override @@ -137,22 +137,14 @@ public void didCreateNewGroup(Tab destinationTab, TabGroupModelFilter filter) { Token groupId = destinationTab.getTabGroupId(); assert groupId != null; - - TabStripCollection collection = filter.getTabModel().getTabStripCollection(); - if (collection == null) return; - - CollectionSaveForwarder forwarder = - CollectionSaveForwarder.createForTabGroup( - destinationTab.getProfile(), groupId, collection); - mGroupForwarderMap.put(groupId, forwarder); + mGroupIncognitoStatus.put(groupId, filter.getTabModel().isOffTheRecord()); } @Override public void didRemoveTabGroup( int oldRootId, @Nullable Token oldTabGroupId, int removalReason) { if (oldTabGroupId == null) return; - CollectionSaveForwarder forwarder = mGroupForwarderMap.remove(oldTabGroupId); - if (forwarder != null) forwarder.destroy(); + mGroupIncognitoStatus.remove(oldTabGroupId); } @Override @@ -258,10 +250,7 @@ itm.removeIncognitoObserver(mIncognitoTabModelObserver); } - for (CollectionSaveForwarder forwarder : mGroupForwarderMap.values()) { - forwarder.destroy(); - } - mGroupForwarderMap.clear(); + mGroupIncognitoStatus.clear(); for (boolean incognito : new boolean[] {false, true}) { TabGroupModelFilter filter = getFilter(incognito); @@ -274,6 +263,14 @@ if (mIncognitoSynchronizerManager != null) mIncognitoSynchronizerManager.reset(); } + /** Saves the tab through an associated synchronizer. */ + public void saveTab(Tab tab) { + StorageCollectionSynchronizer synchronizer = + tab.isOffTheRecord() ? mIncognitoSynchronizer : mRegularSynchronizer; + if (synchronizer == null) return; + synchronizer.saveTab(tab); + } + private StorageCollectionSynchronizer getSynchronizer( ProfileAndCollection profileAndCollection, boolean incognito) { if (incognito) { @@ -332,9 +329,15 @@ } private void saveTabGroupPayload(Token tabGroupId) { - CollectionSaveForwarder forwarder = mGroupForwarderMap.get(tabGroupId); - if (forwarder == null) return; - forwarder.savePayload(); + Boolean isIncognito = mGroupIncognitoStatus.get(tabGroupId); + if (isIncognito == null) return; + + StorageCollectionSynchronizer synchronizer = + isIncognito ? mIncognitoSynchronizer : mRegularSynchronizer; + + if (synchronizer != null) { + synchronizer.saveTabGroupPayload(tabGroupId); + } } private void initActiveTabTracking(boolean incognito) { @@ -354,16 +357,12 @@ } private void initVisualDataTracking(boolean incognito) { - var profileAndCollection = getProfileAndCollection(mTabModelSelector, incognito); TabGroupModelFilter filter = getFilter(incognito); assert filter != null; // Add forwarders for untracked groups. for (Token groupId : filter.getAllTabGroupIds()) { - CollectionSaveForwarder forwarder = - CollectionSaveForwarder.createForTabGroup( - profileAndCollection.profile, groupId, profileAndCollection.collection); - mGroupForwarderMap.put(groupId, forwarder); + mGroupIncognitoStatus.put(groupId, incognito); } filter.addTabGroupObserver(mVisualDataUpdateObserver);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/TabStateStore.java b/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/TabStateStore.java index 68ab97d..7d5ceea0 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/TabStateStore.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/tabmodel/TabStateStore.java
@@ -413,7 +413,7 @@ // not attached to a parent collection will not be restored at startup and shouldn't be // saved. If the tab becomes attached to a collection later it will be saved then. if (tab.isDestroyed() || tab.isClosing() || !tab.hasParentCollection()) return; - mTabStateStorageService.saveTabData(tab); + mModelTrackingManager.saveTab(tab); } private void assertOtrOperationSafe(boolean isOtrOperation) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/OWNERS index 68b9da4..2330c824 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/OWNERS +++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/OWNERS
@@ -2,5 +2,6 @@ peconn@chromium.org sinansahin@google.com adelm@google.com +lizeb@chromium.org per-file *MismatchNotificationController*=samarchehade@google.com
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/DomDistillerTabUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/DomDistillerTabUtils.java index 10ee25c..135f76862 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/DomDistillerTabUtils.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/DomDistillerTabUtils.java
@@ -171,7 +171,8 @@ @NativeMethods public interface Natives { void distillCurrentPageAndViewIfSuccessful( - WebContents webContents, Callback<Boolean> callback); + WebContents webContents, + @JniType("base::OnceCallback<void(bool)>") Callback<Boolean> callback); void distillCurrentPage(WebContents webContents); @@ -186,6 +187,7 @@ InterceptNavigationDelegate delegate, WebContents webContents); void runReadabilityHeuristicsOnWebContents( - @Nullable WebContents webContents, Callback<Boolean> callback); + @Nullable WebContents webContents, + @JniType("base::OnceCallback<void(bool)>") Callback<Boolean> callback); } }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationManager.java b/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationManager.java index 51e99cbd..6afd31f 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationManager.java
@@ -49,7 +49,7 @@ .setContentText(actionMessage) .setOngoing(true) .setVisibility(Notification.VISIBILITY_SECRET) - .setSmallIcon(R.drawable.ic_incognito_fill_24dp) + .setSmallIcon(R.drawable.ic_incognito) .setShowWhen(false) .setLocalOnly(true) .setGroup(NotificationConstants.GROUP_INCOGNITO);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java index 7ef3d2e..99603ee6 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java
@@ -85,12 +85,14 @@ import org.chromium.chrome.browser.util.AndroidTaskUtils; import org.chromium.components.browser_ui.desktop_windowing.DesktopWindowStateManager; import org.chromium.components.browser_ui.widget.MenuOrKeyboardActionController; +import org.chromium.components.embedder_support.util.UrlUtilities; import org.chromium.components.favicon.LargeIconBridge; import org.chromium.components.feature_engagement.EventConstants; import org.chromium.components.feature_engagement.Tracker; import org.chromium.components.messages.MessageDispatcher; import org.chromium.components.messages.MessageDispatcherProvider; import org.chromium.components.tab_group_sync.TabGroupSyncService; +import org.chromium.components.url_formatter.UrlFormatter; import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.ui.modaldialog.ModalDialogManager; @@ -1506,7 +1508,7 @@ List<InstanceInfo> instanceInfoList = new ArrayList(); for (int instanceId : instanceIds) { // Do not update the Recent Tabs page if the closed window has no regular tabs. - if (!hasRegularTabs(instanceId)) { + if (!hasRestorableRegularTabs(instanceId)) { continue; } InstanceInfo instanceInfo = @@ -1528,8 +1530,10 @@ instanceInfoList.add(instanceInfo); } - RecentlyClosedEntriesManagerTrackerFactory.getInstance() - .onInstancesClosed(instanceInfoList, isPermanentDeletion); + if (instanceInfoList.size() > 0) { + RecentlyClosedEntriesManagerTrackerFactory.getInstance() + .onInstancesClosed(instanceInfoList, isPermanentDeletion); + } } /** @@ -1543,19 +1547,25 @@ * * @param source The window closure source, from {@link CloseWindowAppSource}. */ - private boolean isPermanentClosureSource(@CloseWindowAppSource int source) { + private static boolean isPermanentClosureSource(@CloseWindowAppSource int source) { if (!UiUtils.isRecentlyClosedTabsAndWindowsEnabled()) return true; return source != CloseWindowAppSource.WINDOW_MANAGER; } - private boolean hasRegularTabs(int instanceId) { - return MultiInstancePersistentStore.readNormalTabCount(instanceId) > 0; + private static boolean hasRestorableRegularTabs(int instanceId) { + int normalTabCount = MultiInstancePersistentStore.readNormalTabCount(instanceId); + + if (normalTabCount > 1) return true; + if (normalTabCount == 0) return false; + + String activeUrl = MultiInstancePersistentStore.readActiveTabUrl(instanceId); + return !UrlUtilities.isNtpUrl(UrlFormatter.fixupUrl(activeUrl)); } - private boolean shouldPermanentlyDeleteWindow( + private static boolean shouldPermanentlyDeleteWindow( int instanceId, @CloseWindowAppSource int source) { - return isPermanentClosureSource(source) || !hasRegularTabs(instanceId); + return isPermanentClosureSource(source) || !hasRestorableRegularTabs(instanceId); } @VisibleForTesting @@ -1632,7 +1642,7 @@ // subsequent task kill will also not be reflected as an instance closure until the Recent // Tabs page is reopened. if (UiUtils.isRecentlyClosedTabsAndWindowsEnabled()) { - boolean isPermanentDeletion = !hasRegularTabs(mInstanceId); + boolean isPermanentDeletion = !hasRestorableRegularTabs(mInstanceId); if (!isPermanentDeletion) { MultiInstancePersistentStore.writeClosureTime(mInstanceId);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java index e87675ee..708566f 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/native_page/NativePageFactory.java
@@ -5,11 +5,11 @@ package org.chromium.chrome.browser.native_page; import static org.chromium.build.NullUtil.assumeNonNull; -import static org.chromium.chrome.browser.url_constants.ExtensionsUrlOverrideRegistry.getBookmarksPageOverrideEnabled; -import static org.chromium.chrome.browser.url_constants.ExtensionsUrlOverrideRegistry.getHistoryPageOverrideEnabled; -import static org.chromium.chrome.browser.url_constants.ExtensionsUrlOverrideRegistry.getIncognitoBookmarksPageOverrideEnabled; -import static org.chromium.chrome.browser.url_constants.ExtensionsUrlOverrideRegistry.getIncognitoNtpOverrideEnabled; -import static org.chromium.chrome.browser.url_constants.ExtensionsUrlOverrideRegistry.getNtpOverrideEnabled; +import static org.chromium.chrome.browser.url_constants.UrlOverrideUtils.isBookmarksPageOverrideEnabled; +import static org.chromium.chrome.browser.url_constants.UrlOverrideUtils.isHistoryPageOverrideEnabled; +import static org.chromium.chrome.browser.url_constants.UrlOverrideUtils.isIncognitoBookmarksPageOverrideEnabled; +import static org.chromium.chrome.browser.url_constants.UrlOverrideUtils.isIncognitoNtpOverrideEnabled; +import static org.chromium.chrome.browser.url_constants.UrlOverrideUtils.isNtpOverrideEnabled; import android.app.Activity; import android.content.Context; @@ -457,7 +457,7 @@ if (url == null) return null; GURL gurl = new GURL(url); - if (isChromePageUrlOverriddenByExtension(gurl, isIncognito)) { + if (isChromePageUrlOverridden(gurl, isIncognito)) { RecordUserAction.record("ChromeSchemePage.OverrideTriggered"); return null; } @@ -501,31 +501,29 @@ } /** - * Returns whether the given url is for a chrome:// scheme page that is being overridden by an - * extension. + * Returns whether the given url is for a chrome:// scheme page that is being overridden. * * <p>chrome-native:// scheme pages are not affected by this. * * @param url The url to be checked. * @param isIncognito Whether the page is to be displayed in incognito mode. */ - private static boolean isChromePageUrlOverriddenByExtension(GURL url, boolean isIncognito) { - if (!ChromeFeatureList.sChromeNativeUrlOverriding.isEnabled() - || !UrlConstants.CHROME_SCHEME.equals(url.getScheme())) { + private static boolean isChromePageUrlOverridden(GURL url, boolean isIncognito) { + if (!UrlConstants.CHROME_SCHEME.equals(url.getScheme())) { return false; } String host = url.getHost(); - if (UrlConstants.NTP_HOST.equals(host)) { - return isIncognito ? getIncognitoNtpOverrideEnabled() : getNtpOverrideEnabled(); - } else if (UrlConstants.BOOKMARKS_HOST.equals(host)) { - return isIncognito - ? getIncognitoBookmarksPageOverrideEnabled() - : getBookmarksPageOverrideEnabled(); - } else if (UrlConstants.HISTORY_HOST.equals(host)) { - return !isIncognito && getHistoryPageOverrideEnabled(); - } - return false; + return switch (host) { + case UrlConstants.NTP_HOST -> + isIncognito ? isIncognitoNtpOverrideEnabled() : isNtpOverrideEnabled(); + case UrlConstants.BOOKMARKS_HOST -> + isIncognito + ? isIncognitoBookmarksPageOverrideEnabled() + : isBookmarksPageOverrideEnabled(); + case UrlConstants.HISTORY_HOST -> !isIncognito && isHistoryPageOverrideEnabled(); + default -> false; + }; } void setNativePageBuilderForTesting(NativePageBuilder builder) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java index 77e4e3b..2c49bb6d 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java
@@ -930,6 +930,7 @@ ChromeFeatureList.FULLSCREEN_INSETS_API_MIGRATION, ChromeFeatureList.FULLSCREEN_INSETS_API_MIGRATION_ON_AUTOMOTIVE }) + @DisabledTest(message = "crbug.com/481488944") public void testEnterPendingPersistentFullscreen() { FullscreenManagerTestUtils.disableBrowserOverrides(); WebPageStation page = mActivityTestRule.startOnUrl(LONG_FULLSCREEN_API_HTML_TEST_PAGE); @@ -1307,6 +1308,7 @@ ChromeFeatureList.FULLSCREEN_INSETS_API_MIGRATION, ChromeFeatureList.FULLSCREEN_INSETS_API_MIGRATION_ON_AUTOMOTIVE }) + @DisabledTest(message = "crbug.com/481488944") public void testFullscreenPageHeight() throws Throwable { launchOnFullscreenMode(LONG_HTML_TEST_PAGE, true); Assert.assertTrue(getPersistentFullscreenMode());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31Test.java b/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31Test.java index 0922c90..df22ba7 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31Test.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31Test.java
@@ -359,6 +359,46 @@ @Test @MediumTest + public void + closeWindowFromWindowManager_RecentlyClosedEntriesNotUpdated_WindowContainsOnlyOneNtp() { + // Set initial instance limit. + MultiWindowUtils.setMaxInstancesForTesting(5); + + ChromeTabbedActivity firstActivity = mActivityTestRule.getActivity(); + ChromeTabbedActivity otherActivity = + createNewWindow( + firstActivity, + /* instanceId= */ 1, + /* addIncognitoExtras= */ false, + /* loadCustomUrl= */ false); + + // Check initial state of instances. + verifyInstanceState(/* expectedActiveInstances= */ 2, /* expectedTotalInstances= */ 2); + + // Verify there is 0 entry in the RecentlyClosedEntriesManager. + RecentlyClosedEntriesManager recentlyClosedEntriesManager = + firstActivity.getRecentlyClosedEntriesManagerForTesting(); + assertEquals(0, recentlyClosedEntriesManager.getRecentlyClosedEntries().size()); + + // Close the window that contains only 1 NTP. + ThreadUtils.runOnUiThreadBlocking( + () -> + mMultiInstanceManager.closeWindows( + Collections.singletonList(otherActivity.getWindowIdForTesting()), + CloseWindowAppSource.WINDOW_MANAGER)); + + // Check state of instances after one instance is closed - the closed window should be + // permanently deleted. + verifyInstanceState(/* expectedActiveInstances= */ 1, /* expectedTotalInstances= */ 1); + + // Verify there is 0 window entry in the RecentlyClosedEntriesManager after the window + // closure. + List<RecentlyClosedEntry> entries = recentlyClosedEntriesManager.getRecentlyClosedEntries(); + assertEquals("There should be 0 recently closed entry", 0, entries.size()); + } + + @Test + @MediumTest @EnableFeatures(ChromeFeatureList.RECENTLY_CLOSED_TABS_AND_WINDOWS) public void restoreWindow_RecentlyClosedEntriesUpdated() { // Set initial instance limit. @@ -819,7 +859,7 @@ } private ChromeTabbedActivity createNewWindow( - Context context, int instanceId, boolean addIncognitoExtras) { + Context context, int instanceId, boolean addIncognitoExtras, boolean loadCustomUrl) { Intent intent = MultiWindowUtils.createNewWindowIntent( context, @@ -844,11 +884,23 @@ activity.getActivityTab(), notNullValue())); Tab tab = ThreadUtils.runOnUiThreadBlocking(() -> activity.getActivityTab()); - ChromeTabUtils.loadUrlOnUiThread(tab, UrlConstants.GOOGLE_URL); + if (loadCustomUrl) { + ChromeTabUtils.waitForTabPageLoaded( + tab, + UrlConstants.GOOGLE_URL, + () -> { + ChromeTabUtils.loadUrlOnUiThread(tab, UrlConstants.GOOGLE_URL); + }); + } mExtraActivities.add(activity); return activity; } + private ChromeTabbedActivity createNewWindow( + Context context, int instanceId, boolean addIncognitoExtras) { + return createNewWindow(context, instanceId, addIncognitoExtras, /* loadCustomUrl= */ true); + } + private void verifyInstanceState(int expectedActiveInstances, int expectedTotalInstances) { CriteriaHelper.pollUiThread( () -> {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/UrlBarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/UrlBarTest.java index 45ec6b640..7da49feb 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/UrlBarTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/UrlBarTest.java
@@ -470,6 +470,7 @@ @Test @SmallTest + @DisabledTest(message = "Disabled because of crbug.com/477262537") public void testAutocorrectionChangesTriggerCorrectSuggestions() { mOmnibox.setComposingText("test", 0, 4); mOmnibox.setAutocompleteText("ing is fun", null);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherActionMenuRenderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherActionMenuRenderTest.java index 3d91afc..98a1434 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherActionMenuRenderTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherActionMenuRenderTest.java
@@ -72,7 +72,7 @@ @Rule public ChromeRenderTestRule mRenderTestRule = ChromeRenderTestRule.Builder.withPublicCorpus() - .setRevision(4) + .setRevision(5) .setBugComponent(ChromeRenderTestRule.Component.UI_BROWSER_MOBILE_TAB_SWITCHER) .build();
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java index e6ed1f6..f28c66f9 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java
@@ -947,6 +947,30 @@ } @Test + public void testCloseWindows_OnInstancesClosedNotInvoked_WindowContainsOnlyOneNtp() { + TabGroupSyncServiceFactory.setForTesting(mTabGroupSyncService); + when(mTabGroupSyncService.getAllGroupIds()).thenReturn(new String[] {}); + + assertEquals(0, allocInstanceIndex(PASSED_ID_INVALID, mActivityTask56)); + assertEquals(1, allocInstanceIndex(PASSED_ID_INVALID, mActivityTask57)); + MultiInstancePersistentStore.writeTabCount( + 1, /* normalTabCount= */ 1, /* incognitoTabCount= */ 0); + MultiInstancePersistentStore.writeActiveTabUrl(1, "chrome-native://newtab/"); + + assertEquals(2, mMultiInstanceManager.getInstanceInfo(PersistedInstanceType.ANY).size()); + + // Close the window that contains only 1 NTP. + mMultiInstanceManager.closeWindows( + Collections.singletonList(1), CloseWindowAppSource.WINDOW_MANAGER); + + // Verify that #onInstanceClosed is never invoked. + verify(mRecentlyClosedTracker, never()).onInstancesClosed(any(), anyBoolean()); + + // Verify the window that contains only 1 NTP is permanently closed. + assertEquals(1, mMultiInstanceManager.getInstanceInfo(PersistedInstanceType.ANY).size()); + } + + @Test public void testCloseAllWindows_markedForDeletion() { MultiWindowUtils.setMaxInstancesForTesting(5); @@ -3148,9 +3172,10 @@ long closureTime1 = MultiInstancePersistentStore.readClosureTime(/* instanceId= */ 1); assertEquals("Closure time should be updated.", 0, closureTime1); - InOrder inOrderVerifier = inOrder(mRecentlyClosedTracker); - inOrderVerifier.verify(mRecentlyClosedTracker).onInstancesClosed(any(), eq(false)); - inOrderVerifier.verify(mRecentlyClosedTracker).onInstancesClosed(any(), eq(true)); + // Verify #onInstancesClosed is only invoked for the window that contains restorable regular + // tabs. + verify(mRecentlyClosedTracker, times(1)).onInstancesClosed(any(), eq(false)); + verify(mRecentlyClosedTracker, never()).onInstancesClosed(any(), eq(true)); } @Test @@ -3208,9 +3233,10 @@ /* instanceId= */ 1, /* normalTabCount= */ 0, /* incognitoTabCount= */ 3); manager2.onDestroy(); - InOrder inOrderVerifier = inOrder(mRecentlyClosedTracker); - inOrderVerifier.verify(mRecentlyClosedTracker).onInstancesClosed(any(), eq(false)); - inOrderVerifier.verify(mRecentlyClosedTracker).onInstancesClosed(any(), eq(true)); + // Verify #onInstancesClosed is only invoked for the window that contains restorable regular + // tabs. + verify(mRecentlyClosedTracker, times(1)).onInstancesClosed(any(), eq(false)); + verify(mRecentlyClosedTracker, never()).onInstancesClosed(any(), eq(true)); } private TabGroupMetadata getTabGroupMetadata(boolean isIncognito) {
diff --git a/chrome/app/OWNERS b/chrome/app/OWNERS index ad9852ab..7d7c0f7 100644 --- a/chrome/app/OWNERS +++ b/chrome/app/OWNERS
@@ -18,10 +18,12 @@ per-file access_code_cast_strings.grdp=file://chrome/browser/ui/webui/access_code_cast/OWNERS per-file actor_strings.grdp=file://chrome/browser/actor/OWNERS +per-file actor_strings.grdp=file://chrome/browser/glic/android/OWNERS per-file app_management_strings.grdp=file://chrome/browser/ui/webui/app_management/OWNERS per-file glic_strings.grdp=file://chrome/browser/glic/OWNERS +per-file glic_strings.grdp=file://chrome/browser/glic/android/OWNERS per-file gmc_strings.grdp=file://chrome/browser/ui/global_media_controls/OWNERS
diff --git a/chrome/app/actor_strings_grdp/OWNERS b/chrome/app/actor_strings_grdp/OWNERS index 5145dbe..5c89629 100644 --- a/chrome/app/actor_strings_grdp/OWNERS +++ b/chrome/app/actor_strings_grdp/OWNERS
@@ -1 +1,2 @@ file://chrome/browser/actor/OWNERS +file://chrome/browser/glic/android/OWNERS
diff --git a/chrome/app/glic_strings.grdp b/chrome/app/glic_strings.grdp index 963a4c8..783c8176 100644 --- a/chrome/app/glic_strings.grdp +++ b/chrome/app/glic_strings.grdp
@@ -380,6 +380,9 @@ <message name="IDS_GLIC_BUTTON_ENTRYPOINT_BROWSE_LABEL" desc="Label on the button used to open Gemini in Chrome."> Browse with AI </message> + <message name="IDS_GLIC_BUTTON_ENTRYPOINT_ASK_ABOUT_THIS_PAGE_LABEL" desc="Label on the button used to open Gemini in Chrome."> + Ask about this page? + </message> <!-- Content area context menu. --> <if expr="use_titlecase">
diff --git a/chrome/app/glic_strings_grdp/IDS_GLIC_BUTTON_ENTRYPOINT_ASK_ABOUT_THIS_PAGE_LABEL.png.sha1 b/chrome/app/glic_strings_grdp/IDS_GLIC_BUTTON_ENTRYPOINT_ASK_ABOUT_THIS_PAGE_LABEL.png.sha1 new file mode 100644 index 0000000..c470bf7b --- /dev/null +++ b/chrome/app/glic_strings_grdp/IDS_GLIC_BUTTON_ENTRYPOINT_ASK_ABOUT_THIS_PAGE_LABEL.png.sha1
@@ -0,0 +1 @@ +a85fc3a669b9a2576258cfd967a3941dad8f6e40 \ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index ff59afa2..9389f44 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc
@@ -5460,10 +5460,6 @@ FEATURE_WITH_PARAMS_VALUE_TYPE(ash::features::kCaptureModeEducation, kCaptureModeEducationVariations, "CaptureModeEducation")}, - {"ash-capture-mode-education-bypass-limits", - flag_descriptions::kCaptureModeEducationBypassLimitsName, - flag_descriptions::kCaptureModeEducationBypassLimitsDescription, kOsCrOS, - FEATURE_VALUE_TYPE(ash::features::kCaptureModeEducationBypassLimits)}, {"ash-limit-shelf-items-to-active-desk", flag_descriptions::kLimitShelfItemsToActiveDeskName, flag_descriptions::kLimitShelfItemsToActiveDeskDescription, kOsCrOS, @@ -7659,11 +7655,6 @@ kNtpRealboxCr23ThemingVariations, "NtpRealboxCr23Theming")}, - {"ntp-realbox-match-searchbox-theme", - flag_descriptions::kNtpRealboxMatchSearchboxThemeName, - flag_descriptions::kNtpRealboxMatchSearchboxThemeDescription, kOsDesktop, - FEATURE_VALUE_TYPE(ntp_features::kRealboxMatchSearchboxTheme)}, - {"ntp-realbox-use-google-g-icon", flag_descriptions::kNtpRealboxUseGoogleGIconName, flag_descriptions::kNtpRealboxUseGoogleGIconDescription, kOsDesktop, @@ -8616,10 +8607,6 @@ kOsCrOS, FEATURE_VALUE_TYPE(features::kAccessibilityCaptionsOnBrailleDisplay)}, - {"event-based-log-upload", flag_descriptions::kEventBasedLogUpload, - flag_descriptions::kEventBasedLogUploadDescription, kOsCrOS, - FEATURE_VALUE_TYPE(features::kEventBasedLogUpload)}, - #endif // BUILDFLAG(IS_CHROMEOS) {"enable-fenced-frames-developer-mode",
diff --git a/chrome/browser/actor/actor_task.cc b/chrome/browser/actor/actor_task.cc index cf0494f..e41f3a31 100644 --- a/chrome/browser/actor/actor_task.cc +++ b/chrome/browser/actor/actor_task.cc
@@ -250,12 +250,9 @@ ++total_number_of_interruptions_; } - // In the new implementation, stopped tasks are tracked separately as they - // need to store additional information before they're cleared. - bool should_dispatch = !base::FeatureList::IsEnabled( - features::kGlicActorUiGlobalTaskIndicator) || - !stopped_reason_; - if (should_dispatch) { + // Stopped tasks are tracked separately as they need to store additional + // information before they're cleared. + if (!stopped_reason_) { ui_event_dispatcher_->OnActorTaskSyncChange( ui::UiEventDispatcher::ChangeTaskState{ .task_id = id_, .old_state = old_state, .new_state = new_state}); @@ -417,13 +414,11 @@ SetState(final_state); - if (base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator)) { ui_event_dispatcher_->OnActorTaskSyncChange(ui::UiEventDispatcher::StopTask{ .task_id = id_, .final_state = final_state, .title = title_, .last_acted_on_tab_handle = last_tab_handle}); - } } void ActorTask::Pause(bool from_actor) {
diff --git a/chrome/browser/actor/actor_test_util.cc b/chrome/browser/actor/actor_test_util.cc index ec999fb9..bc07f91 100644 --- a/chrome/browser/actor/actor_test_util.cc +++ b/chrome/browser/actor/actor_test_util.cc
@@ -585,6 +585,14 @@ : WindowOpenDisposition::NEW_BACKGROUND_TAB); } +std::unique_ptr<ToolRequest> MakeActivateTabRequest(TabHandle tab) { + return std::make_unique<ActivateTabToolRequest>(tab); +} + +std::unique_ptr<ToolRequest> MakeCloseTabRequest(TabHandle tab) { + return std::make_unique<CloseTabToolRequest>(tab); +} + std::unique_ptr<ToolRequest> MakeAttemptLoginRequest(TabInterface& tab) { return std::make_unique<AttemptLoginToolRequest>(tab.GetHandle()); }
diff --git a/chrome/browser/actor/actor_test_util.h b/chrome/browser/actor/actor_test_util.h index 7627a01a..f4e10550f 100644 --- a/chrome/browser/actor/actor_test_util.h +++ b/chrome/browser/actor/actor_test_util.h
@@ -178,6 +178,8 @@ tabs::TabInterface* observe_tab = nullptr); std::unique_ptr<ToolRequest> MakeCreateTabRequest(SessionID window_id, bool foreground); +std::unique_ptr<ToolRequest> MakeActivateTabRequest(tabs::TabHandle tab); +std::unique_ptr<ToolRequest> MakeCloseTabRequest(tabs::TabHandle tab); std::unique_ptr<ToolRequest> MakeAttemptLoginRequest(tabs::TabInterface& tab); std::unique_ptr<ToolRequest> MakeScriptToolRequest( content::RenderFrameHost& rfh,
diff --git a/chrome/browser/actor/execution_engine_origin_gating_browsertest.cc b/chrome/browser/actor/execution_engine_origin_gating_browsertest.cc index a6d4173..451439a 100644 --- a/chrome/browser/actor/execution_engine_origin_gating_browsertest.cc +++ b/chrome/browser/actor/execution_engine_origin_gating_browsertest.cc
@@ -25,6 +25,7 @@ #include "content/public/test/browser_test_utils.h" #include "content/public/test/content_browser_test_utils.h" #include "net/dns/mock_host_resolver.h" +#include "url/origin.h" namespace actor { @@ -133,19 +134,23 @@ return browser()->tab_strip_model()->GetActiveWebContents(); } - InteractiveTestApi::MultiStep CreateMockWebClientRequest( - const std::string_view handle_dialog_js) { + [[nodiscard]] InteractiveTestApi::MultiStep CreateMockWebClientRequest( + const std::string_view handle_dialog_js, + const base::Location& location = FROM_HERE) { return InAnyContext(WithElement( glic::test::kGlicContentsElementId, - [handle_dialog_js](::ui::TrackedElement* el) mutable { + [handle_dialog_js, location](::ui::TrackedElement* el) mutable { content::WebContents* glic_contents = AsInstrumentedWebContents(el)->web_contents(); - ASSERT_TRUE(content::ExecJs(glic_contents, handle_dialog_js)); + ASSERT_TRUE(content::ExecJs(glic_contents, handle_dialog_js)) + << ", expected at " << location.ToString(); })); } - InteractiveTestApi::MultiStep VerifyUserConfirmationDialogRequest( - const base::DictValue& expected_request) { + [[nodiscard]] InteractiveTestApi::MultiStep + VerifyUserConfirmationDialogRequest( + const base::DictValue& expected_request, + const base::Location& location = FROM_HERE) { static constexpr char kGetUserConfirmationDialogRequest[] = R"js( (() => { @@ -153,11 +158,13 @@ })(); )js"; return VerifyWebClientRequest(kGetUserConfirmationDialogRequest, - expected_request); + expected_request, location); } - InteractiveTestApi::MultiStep VerifyNavigationConfirmationRequest( - const base::DictValue& expected_request) { + [[nodiscard]] InteractiveTestApi::MultiStep + VerifyNavigationConfirmationRequest( + const base::DictValue& expected_request, + const base::Location& location = FROM_HERE) { static constexpr char kGetNavigationConfirmationRequestData[] = R"js( (() => { @@ -165,7 +172,7 @@ })(); )js"; return VerifyWebClientRequest(kGetNavigationConfirmationRequestData, - expected_request); + expected_request, location); } content::RenderFrameHost* main_frame() { @@ -198,7 +205,8 @@ InteractiveTestApi::MultiStep VerifyWebClientRequest( const std::string_view get_request_js, - const base::DictValue& expected_request) { + const base::DictValue& expected_request, + const base::Location& location) { return InAnyContext(WithElement( glic::test::kGlicContentsElementId, [&, get_request_js](::ui::TrackedElement* el) { @@ -206,7 +214,8 @@ AsInstrumentedWebContents(el)->web_contents(); auto eval_result = content::EvalJs(glic_contents, get_request_js); const auto& actual_request = eval_result.ExtractDict(); - ASSERT_EQ(expected_request, actual_request); + ASSERT_EQ(expected_request, actual_request) + << ", expected at " << location.ToString(); })); } @@ -287,12 +296,10 @@ content::JsReplace("setLink($1);", second_url))); ClickTarget("#link", mojom::ActionResultCode::kOk); - auto expected_request = - base::DictValue() - .Set("navigationOrigin", - url::Origin::Create(second_url).GetDebugString()) - .Set("taskId", actor_task().id().value()); - RunTestSequence(VerifyNavigationConfirmationRequest(expected_request)); + RunTestSequence(VerifyNavigationConfirmationRequest( + base::test::ParseJsonDict(content::JsReplace( + R"({"navigationOrigin": $1, "taskId": $2})", + url::Origin::Create(second_url), actor_task().id().value())))); // The first navigation should log that gating was not applied. The second // should log that gating was applied. @@ -332,12 +339,10 @@ content::JsReplace("setLink($1);", second_url))); ClickTarget("#link", mojom::ActionResultCode::kTriggeredNavigationBlocked); - auto expected_request = - base::DictValue() - .Set("navigationOrigin", - url::Origin::Create(second_url).GetDebugString()) - .Set("taskId", actor_task().id().value()); - RunTestSequence(VerifyNavigationConfirmationRequest(expected_request)); + RunTestSequence(VerifyNavigationConfirmationRequest( + base::test::ParseJsonDict(content::JsReplace( + R"({"navigationOrigin": $1, "taskId": $2})", + url::Origin::Create(second_url), actor_task().id().value())))); // Should log that permission was *denied* once. histogram_tester_for_init_.ExpectBucketCount( @@ -365,12 +370,10 @@ content::JsReplace("setLink($1);", blocked_url))); ClickTarget("#link", mojom::ActionResultCode::kOk); - auto expected_request = - base::DictValue() - .Set("navigationOrigin", - url::Origin::Create(blocked_url).GetDebugString()) - .Set("forBlocklistedOrigin", true); - RunTestSequence(VerifyUserConfirmationDialogRequest(expected_request)); + RunTestSequence(VerifyUserConfirmationDialogRequest( + base::test::ParseJsonDict(content::JsReplace( + R"({"navigationOrigin": $1, "forBlocklistedOrigin": true})", + url::Origin::Create(blocked_url))))); // The first navigation should log that gating was not applied. The second // should log that gating was applied. @@ -439,7 +442,7 @@ "navigationOrigin": $1, "forBlocklistedOrigin": false })", - url::Origin::Create(other_url).GetDebugString())))); + url::Origin::Create(other_url))))); // Start back at `start_url`, and try another x-origin navigation to // `other_url`. @@ -451,7 +454,7 @@ "navigationOrigin": $1, "forBlocklistedOrigin": false })", - url::Origin::Create(start_url).GetDebugString())))); + url::Origin::Create(start_url))))); // Now this should proceed without a user confirmation or a server // confirmation, since the user has already confirmed it. @@ -486,7 +489,7 @@ "navigationOrigin": $1, "forBlocklistedOrigin": false })", - url::Origin::Create(eventually_sensitive).GetDebugString())))); + url::Origin::Create(eventually_sensitive))))); base::FilePath proto_path = temp_dir_.GetPath().Append(FILE_PATH_LITERAL("base_proto_v2.pb")); @@ -509,7 +512,7 @@ "navigationOrigin": $1, "forBlocklistedOrigin": false })", - url::Origin::Create(start_url).GetDebugString())))); + url::Origin::Create(start_url))))); // Now this should proceed without a user confirmation or a server // confirmation, since the user has already confirmed it. @@ -537,12 +540,10 @@ content::JsReplace("setLink($1);", blocked_url))); ClickTarget("#link", mojom::ActionResultCode::kTriggeredNavigationBlocked); - auto expected_request = - base::DictValue() - .Set("navigationOrigin", - url::Origin::Create(blocked_url).GetDebugString()) - .Set("forBlocklistedOrigin", true); - RunTestSequence(VerifyUserConfirmationDialogRequest(expected_request)); + RunTestSequence(VerifyUserConfirmationDialogRequest( + base::test::ParseJsonDict(content::JsReplace( + R"({"navigationOrigin": $1, "forBlocklistedOrigin": true})", + url::Origin::Create(blocked_url))))); // Should log that permission was *denied* once. histogram_tester_for_init_.ExpectBucketCount( @@ -674,12 +675,10 @@ result.GetCallback()); ExpectOkResult(result); - auto expected_request = - base::DictValue() - .Set("navigationOrigin", - url::Origin::Create(blocked_origin_url).GetDebugString()) - .Set("forBlocklistedOrigin", true); - VerifyUserConfirmationDialogRequest(expected_request); + RunTestSequence(VerifyUserConfirmationDialogRequest( + base::test::ParseJsonDict(content::JsReplace( + R"({"navigationOrigin": $1, "forBlocklistedOrigin": true})", + url::Origin::Create(blocked_origin_url))))); // Trigger ExecutionEngine destructor for metrics. actor_keyed_service().ResetForTesting(); @@ -734,12 +733,10 @@ result.GetCallback()); ExpectOkResult(result); - auto expected_request = - base::DictValue() - .Set("navigationOrigin", - url::Origin::Create(normal_page_with_link).GetDebugString()) - .Set("forBlocklistedOrigin", true); - VerifyUserConfirmationDialogRequest(expected_request); + RunTestSequence(VerifyUserConfirmationDialogRequest( + base::test::ParseJsonDict(content::JsReplace( + R"({"navigationOrigin": $1, "forBlocklistedOrigin": true})", + url::Origin::Create(blocked_page))))); // Trigger ExecutionEngine destructor for metrics. actor_keyed_service().ResetForTesting(); @@ -1252,12 +1249,10 @@ content::JsReplace("setLink($1);", second_url))); ClickTarget("#link", mojom::ActionResultCode::kOk); - auto expected_request = - base::DictValue() - .Set("navigationOrigin", - url::Origin::Create(second_url).GetDebugString()) - .Set("forBlocklistedOrigin", false); - VerifyUserConfirmationDialogRequest(expected_request); + RunTestSequence(VerifyUserConfirmationDialogRequest( + base::test::ParseJsonDict(content::JsReplace( + R"({"navigationOrigin": $1, "forBlocklistedOrigin": false})", + url::Origin::Create(second_url))))); // Trigger ExecutionEngine destructor for metrics. actor_keyed_service().ResetForTesting(); @@ -1447,12 +1442,10 @@ ASSERT_TRUE(content::ExecJs( web_contents(), content::JsReplace("setLink($1);", confirmlist_url))); ClickTarget("#link", mojom::ActionResultCode::kTriggeredNavigationBlocked); - auto expected_request = - base::DictValue() - .Set("navigationOrigin", - url::Origin::Create(confirmlist_url).GetDebugString()) - .Set("forBlocklistedOrigin", true); - VerifyUserConfirmationDialogRequest(expected_request); + RunTestSequence(VerifyUserConfirmationDialogRequest( + base::test::ParseJsonDict(content::JsReplace( + R"({"navigationOrigin": $1, "forBlocklistedOrigin": true})", + url::Origin::Create(confirmlist_url))))); // Should log that permission was *denied* once. histogram_tester_for_init_.ExpectBucketCount(
diff --git a/chrome/browser/actor/execution_engine_unittest.cc b/chrome/browser/actor/execution_engine_unittest.cc index 383a392..ebff26e 100644 --- a/chrome/browser/actor/execution_engine_unittest.cc +++ b/chrome/browser/actor/execution_engine_unittest.cc
@@ -552,10 +552,6 @@ #define MAYBE_ActorTaskCompletedHistogram ActorTaskCompletedHistogram #endif TEST_F(ExecutionEngineTest, MAYBE_ActorTaskCompletedHistogram) { - base::test::ScopedFeatureList scoped_features; - scoped_features.InitAndEnableFeatureWithParameters( - features::kGlicActorUiGlobalTaskIndicator, {}); - content::NavigationSimulator::NavigateAndCommitFromBrowser( web_contents(), GURL("http://localhost/"));
diff --git a/chrome/browser/actor/tools/tab_management_tool.cc b/chrome/browser/actor/tools/tab_management_tool.cc index b45d87b2..56c7b27 100644 --- a/chrome/browser/actor/tools/tab_management_tool.cc +++ b/chrome/browser/actor/tools/tab_management_tool.cc
@@ -48,43 +48,79 @@ void TabManagementTool::Invoke(ToolCallback callback) { callback_ = std::move(callback); + CHECK(window_id_.has_value() || target_tab_.has_value()); - // TODO(crbug.com/445993857): Only the create action is hooked up and - // implemented. - switch (action_) { - case kCreate: { - CHECK(window_id_.has_value()); - CHECK(create_disposition_.has_value()); - BrowserWindowInterface* browser_window_interface = - BrowserWindowInterface::FromSessionID( - SessionID::FromSerializedValue(window_id_.value())); - if (!browser_window_interface) { - PostResponseTask(std::move(callback_), - MakeResult(mojom::ActionResultCode::kWindowWentAway)); - return; - } - - // The observer is removed in the TabStripModelObserver's destructor. - browser_window_interface->GetTabStripModel()->AddObserver(this); - - // Watch for the window going away as well so we don't wait indefinitely. - browser_did_close_subscription_ = - browser_window_interface->RegisterBrowserDidClose(base::BindRepeating( - &TabManagementTool::OnBrowserDidClose, base::Unretained(this))); - - // Open a blank tab. - browser_window_interface->OpenGURL(GURL(url::kAboutBlankURL), - create_disposition_.value()); - break; - } - case kActivate: - case kClose: - CHECK(target_tab_.has_value()); - NOTIMPLEMENTED() << "ActivateTab and CloseTab not yet implemented"; - PostResponseTask(std::move(callback_), - MakeResult(mojom::ActionResultCode::kNotImplemented)); - return; + if (target_tab_.has_value() && !target_tab_->Get()) { + PostResponseTask(std::move(callback_), + MakeResult(mojom::ActionResultCode::kTabWentAway)); + return; } + + BrowserWindowInterface* browser_window_interface = + window_id_.has_value() + ? BrowserWindowInterface::FromSessionID( + SessionID::FromSerializedValue(window_id_.value())) + : target_tab_.value().Get()->GetBrowserWindowInterface(); + + if (!browser_window_interface) { + PostResponseTask(std::move(callback_), + MakeResult(mojom::ActionResultCode::kWindowWentAway)); + return; + } + + // The observer is removed in the TabStripModelObserver's destructor. + browser_window_interface->GetTabStripModel()->AddObserver(this); + + // Watch for the window going away as well so we don't wait indefinitely. + browser_did_close_subscription_ = + browser_window_interface->RegisterBrowserDidClose(base::BindRepeating( + &TabManagementTool::OnBrowserDidClose, base::Unretained(this))); + + // Each of these functions result in a call to `TabStripModelChanged` if they + // complete successfully. Otherwise, they will fire `callback_` early and + // return. + switch (action_) { + case kCreate: + CreateTab(browser_window_interface); + break; + case kActivate: + ActivateTab(browser_window_interface); + break; + case kClose: + CloseTab(); + break; + } +} + +void TabManagementTool::CreateTab( + BrowserWindowInterface* browser_window_interface) { + CHECK(window_id_.has_value()); + CHECK(create_disposition_.has_value()); + // Open a blank tab. + browser_window_interface->OpenGURL(GURL(url::kAboutBlankURL), + create_disposition_.value()); +} + +void TabManagementTool::ActivateTab( + BrowserWindowInterface* browser_window_interface) { + CHECK(target_tab_.has_value()); + // The tab might already be activated. In which case, treat that as a + // successful tool invocation. + if (target_tab_->Get()->IsActivated()) { + PostResponseTask(std::move(callback_), MakeOkResult()); + return; + } + + int target_tab_index = + browser_window_interface->GetTabStripModel()->GetIndexOfTab( + target_tab_->Get()); + CHECK(target_tab_index != TabStripModel::kNoTab); + browser_window_interface->GetTabStripModel()->ActivateTabAt(target_tab_index); +} + +void TabManagementTool::CloseTab() { + CHECK(target_tab_.has_value()); + target_tab_->Get()->Close(); } std::string TabManagementTool::DebugString() const { @@ -112,6 +148,15 @@ return std::make_unique<ObservationDelayController>(task_id(), journal()); } +void TabManagementTool::UpdateTaskBeforeInvoke(ActorTask& task, + ToolCallback callback) const { + if (action_ == kClose) { + CHECK(target_tab_.has_value()); + task.RemoveTab(*target_tab_); + } + std::move(callback).Run(MakeOkResult()); +} + void TabManagementTool::UpdateTaskAfterInvoke(ActorTask& task, mojom::ActionResultPtr result, ToolCallback callback) const { @@ -130,25 +175,70 @@ TabStripModel* tab_strip_model, const TabStripModelChange& change, const TabStripSelectionChange& selection) { - if (action_ != kCreate) { + if (!callback_) { return; } - if (change.type() == TabStripModelChange::kInserted) { - if (callback_) { - CHECK_GT(change.GetInsert()->contents.size(), 0ul); - target_tab_ = change.GetInsert()->contents[0].tab->GetHandle(); + // Determine if a tab we are waiting for has closed unexpectedly. If so, + // return early. + if (action_ == kActivate && change.type() == TabStripModelChange::kRemoved) { + auto result_it = std::find_if( + change.GetRemove()->contents.begin(), + change.GetRemove()->contents.end(), + [&](const TabStripModelChange::RemovedTab& removed_tab) { + return removed_tab.tab->GetHandle() == this->target_tab_.value(); + }); + if (result_it != change.GetRemove()->contents.end()) { + PostResponseTask(std::move(callback_), + MakeResult(mojom::ActionResultCode::kTabWentAway)); + return; + } + } + + // Handle successful tab creation. + if (action_ == kCreate && change.type() == TabStripModelChange::kInserted) { + CHECK_GT(change.GetInsert()->contents.size(), 0ul); + target_tab_ = change.GetInsert()->contents[0].tab->GetHandle(); + PostResponseTask(std::move(callback_), MakeOkResult()); + return; + } + + // Handle successful tab activation. + if (action_ == kActivate && + change.type() == TabStripModelChange::kSelectionOnly) { + if (selection.active_tab_changed() && + *target_tab_ == selection.new_tab->GetHandle()) { PostResponseTask(std::move(callback_), MakeOkResult()); } + return; + } + + // Handle successful tab removal. + if (action_ == kClose && change.type() == TabStripModelChange::kRemoved) { + CHECK_GT(change.GetRemove()->contents.size(), 0ul); + TabHandle removed_tab = change.GetRemove()->contents[0].tab->GetHandle(); + if (*target_tab_ != removed_tab) { + // Observing a different tab close, so do nothing. + return; + } + // Our single tab should have been deleted, rather than moved elsewhere. + if (change.GetRemove()->contents[0].remove_reason != + TabRemovedReason::kDeleted) { + PostResponseTask(std::move(callback_), + MakeResult(mojom::ActionResultCode::kTabWentAway)); + return; + } + + PostResponseTask(std::move(callback_), MakeOkResult()); + return; } } void TabManagementTool::OnBrowserDidClose(BrowserWindowInterface* browser) { - // If the window is destroyed in the interval after a create tab has been - // invoked but before the tab's been added, this ensures we don't hang waiting - // for the new tab. - CHECK(window_id_); - if (action_ == kCreate && callback_) { + // If the window is destroyed in the interval after a tab action has been + // invoked but before the action is completed, this ensures we don't hang + // waiting for the action. + if (callback_) { PostResponseTask(std::move(callback_), MakeResult(mojom::ActionResultCode::kWindowWentAway)); }
diff --git a/chrome/browser/actor/tools/tab_management_tool.h b/chrome/browser/actor/tools/tab_management_tool.h index 911308c..18a2060d 100644 --- a/chrome/browser/actor/tools/tab_management_tool.h +++ b/chrome/browser/actor/tools/tab_management_tool.h
@@ -20,7 +20,6 @@ // A tool to manage the tabs in a browser window, e.g. create, close, // activate, etc. -// TODO(crbug.com/445993857): Implement actions other than create. class TabManagementTool : public Tool, public TabStripModelObserver { public: enum Action { kCreate, kActivate, kClose }; @@ -46,6 +45,8 @@ std::unique_ptr<ObservationDelayController> GetObservationDelayer( ObservationDelayController::PageStabilityConfig page_stability_config) override; + void UpdateTaskBeforeInvoke(ActorTask& task, + ToolCallback callback) const override; void UpdateTaskAfterInvoke(ActorTask& task, mojom::ActionResultPtr result, ToolCallback callback) const override; @@ -57,6 +58,10 @@ const TabStripSelectionChange& selection) final; private: + void CreateTab(BrowserWindowInterface* browser_window_interface); + void ActivateTab(BrowserWindowInterface* browser_window_interface); + void CloseTab(); + // Called when the browser with `window_id_` has closed. void OnBrowserDidClose(BrowserWindowInterface* browser);
diff --git a/chrome/browser/actor/tools/tab_management_tool_browsertest.cc b/chrome/browser/actor/tools/tab_management_tool_browsertest.cc index 290a533..1765aa2 100644 --- a/chrome/browser/actor/tools/tab_management_tool_browsertest.cc +++ b/chrome/browser/actor/tools/tab_management_tool_browsertest.cc
@@ -13,6 +13,7 @@ #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/common/actor.mojom.h" +#include "chrome/test/base/ui_test_utils.h" #include "components/tabs/public/tab_interface.h" #include "content/public/browser/web_contents.h" #include "content/public/test/browser_test.h" @@ -142,5 +143,108 @@ ASSERT_TRUE(observation_result.value()->screenshot_result.has_value()); } +IN_PROC_BROWSER_TEST_F(ActorTabManagementToolBrowserTest, ActivateTab) { + // Navigate the first tab. + const GURL start_tab_url = + embedded_test_server()->GetURL("/actor/blank.html"); + ASSERT_TRUE(content::NavigateToURL(web_contents(), start_tab_url)); + + // Create a second tab in the foreground. + ui_test_utils::NavigateToURLWithDisposition( + browser(), start_tab_url, WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); + TabStripModel* tsm = browser()->tab_strip_model(); + ASSERT_EQ(tsm->count(), 2); + ASSERT_EQ(tsm->GetTabAtIndex(1), tsm->GetActiveTab()); + + // Use a TabManagementTool to activate the first tab again, which should + // bring it to the foreground. + std::unique_ptr<ToolRequest> action = + MakeActivateTabRequest(tsm->GetTabAtIndex(0)->GetHandle()); + ActResultFuture act_result; + actor_task().Act(ToRequestList(action), act_result.GetCallback()); + ExpectOkResult(act_result); + + ASSERT_EQ(tsm->count(), 2); + ASSERT_EQ(tsm->GetTabAtIndex(0), tsm->GetActiveTab()); +} + +IN_PROC_BROWSER_TEST_F(ActorTabManagementToolBrowserTest, CloseTab) { + // Navigate the first tab. + const GURL start_tab_url = + embedded_test_server()->GetURL("/actor/blank.html"); + ASSERT_TRUE(content::NavigateToURL(web_contents(), start_tab_url)); + + // Create a second tab in the foreground. + ui_test_utils::NavigateToURLWithDisposition( + browser(), start_tab_url, WindowOpenDisposition::NEW_FOREGROUND_TAB, + ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); + TabStripModel* tsm = browser()->tab_strip_model(); + ASSERT_EQ(tsm->count(), 2); + ASSERT_EQ(tsm->GetTabAtIndex(1), tsm->GetActiveTab()); + + // Use a TabManagementTool to close the inactive tab. + std::unique_ptr<ToolRequest> action = + MakeCloseTabRequest(tsm->GetTabAtIndex(0)->GetHandle()); + ActResultFuture act_result; + actor_task().Act(ToRequestList(action), act_result.GetCallback()); + ExpectOkResult(act_result); + + ASSERT_EQ(tsm->count(), 1); + ASSERT_EQ(tsm->GetTabAtIndex(0), tsm->GetActiveTab()); +} + +// Ensures that if a tab is closed, it's properly removed from the list of tabs +// managed by the ActorTask. +IN_PROC_BROWSER_TEST_F(ActorTabManagementToolBrowserTest, + CloseTabRemovesFromActorTask) { + // Create a new tab, ensure it's added to the set of acted on tabs. + { + std::unique_ptr<ToolRequest> action = + MakeCreateTabRequest(browser()->session_id(), /*foreground=*/false); + ActResultFuture result; + actor_task().Act(ToRequestList(action), result.GetCallback()); + ExpectOkResult(result); + + EXPECT_EQ(actor_task().GetTabs().size(), 1ul); + + // Since the tab was added in the background, the current tab should not + // have been added. + EXPECT_FALSE(actor_task().GetTabs().contains(active_tab()->GetHandle())); + } + + // Create a second tab, ensure it too is added to the set of acted on tabs. + { + std::unique_ptr<ToolRequest> action = + MakeCreateTabRequest(browser()->session_id(), /*foreground=*/true); + ActResultFuture result; + actor_task().Act(ToRequestList(action), result.GetCallback()); + ExpectOkResult(result); + + EXPECT_EQ(actor_task().GetTabs().size(), 2ul); + + // This time the tab was created in the foreground so the active tab must be + // in the set. + EXPECT_TRUE(actor_task().GetTabs().contains(active_tab()->GetHandle())); + } + + { + tabs::TabHandle closed_tab_handle = active_tab()->GetHandle(); + // Use a TabManagementTool to close the active tab. + std::unique_ptr<ToolRequest> action = + MakeCloseTabRequest(closed_tab_handle); + ActResultFuture result; + actor_task().Act(ToRequestList(action), result.GetCallback()); + ExpectOkResult(result); + + // The closed tab should no longer be managed by the actor. + EXPECT_EQ(actor_task().GetTabs().size(), 1ul); + + // Because the active tab was closed, the new active tab should be + // different. + EXPECT_NE(closed_tab_handle, active_tab()->GetHandle()); + } +} + } // namespace } // namespace actor
diff --git a/chrome/browser/actor/ui/actor_ui_state_manager.cc b/chrome/browser/actor/ui/actor_ui_state_manager.cc index a849156..ad881f9 100644 --- a/chrome/browser/actor/ui/actor_ui_state_manager.cc +++ b/chrome/browser/actor/ui/actor_ui_state_manager.cc
@@ -164,13 +164,7 @@ case ActorTask::State::kFailed: case ActorTask::State::kCancelled: case ActorTask::State::kFinished: - if (base::FeatureList::IsEnabled( - features::kGlicActorUiGlobalTaskIndicator)) { - LOG(FATAL) << "Stopped states should be processed via StopTask event."; - } else { - NotifyActorTaskStopped(task_id); - } - break; + LOG(FATAL) << "Stopped states should be processed via StopTask event."; } #if !BUILDFLAG(SKIP_ANDROID_UNMIGRATED_ACTOR_FILES) @@ -253,8 +247,6 @@ this->OnActorTaskStateChange(e.task_id, e.state); }, [this](const StopTask& e) { - if (base::FeatureList::IsEnabled( - features::kGlicActorUiGlobalTaskIndicator)) { // Cancelled tasks are intentionally not stored. if (e.final_state == ActorTask::State::kCancelled) { NotifyActorTaskStopped(e.task_id); @@ -277,7 +269,6 @@ base::Seconds( features::kGlicActorUiCompletedTaskExpiryDelaySeconds .Get())); - } }, [](const StoppedActingOnTab& e) { #if !BUILDFLAG(SKIP_ANDROID_UNMIGRATED_ACTOR_FILES)
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 89ac292..ac4640fe 100644 --- a/chrome/browser/actor/ui/actor_ui_state_manager_unittest.cc +++ b/chrome/browser/actor/ui/actor_ui_state_manager_unittest.cc
@@ -196,10 +196,6 @@ } TEST_F(ActorUiStateManagerTest, OnActorTaskState_FinalStateCrashes) { - base::test::ScopedFeatureList scoped_features; - scoped_features.InitAndEnableFeatureWithParameters( - features::kGlicActorUiGlobalTaskIndicator, {}); - EXPECT_DEATH(actor_ui_state_manager()->OnUiEvent( TaskStateChanged(TaskId(123), ActorTask::State::kCancelled)), ""); @@ -354,9 +350,6 @@ TEST_F(ActorUiStateManagerUiEventUiTabScopedTest, GetsInactiveTaskInfoBeforeExpiry) { - base::test::ScopedFeatureList scoped_features; - scoped_features.InitAndEnableFeatureWithParameters( - features::kGlicActorUiGlobalTaskIndicator, {}); TaskId task_id = actor_keyed_service()->CreateTaskForTesting(); StartTask start_task_event(task_id); actor_ui_state_manager()->OnUiEvent(start_task_event); @@ -371,9 +364,6 @@ TEST_F(ActorUiStateManagerUiEventUiTabScopedTest, DoesNotGetInactiveTaskInfoAfterExpiry) { - base::test::ScopedFeatureList scoped_features; - scoped_features.InitAndEnableFeatureWithParameters( - features::kGlicActorUiGlobalTaskIndicator, {}); TaskId task_id = actor_keyed_service()->CreateTaskForTesting(); StartTask start_task_event(task_id); actor_ui_state_manager()->OnUiEvent(start_task_event); @@ -388,9 +378,6 @@ } TEST_F(ActorUiStateManagerUiEventUiTabScopedTest, GetsActiveTaskInfo) { - base::test::ScopedFeatureList scoped_features; - scoped_features.InitAndEnableFeatureWithParameters( - features::kGlicActorUiGlobalTaskIndicator, {}); TaskId task_id = actor_keyed_service()->CreateTaskForTesting(); base::RunLoop loop;
diff --git a/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_controller.cc b/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_controller.cc index 08dbb9e..2bffdcd3 100644 --- a/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_controller.cc +++ b/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_controller.cc
@@ -58,8 +58,7 @@ browser_->GetProfile()) ->actor_task_list_bubble_rows(); // Do not show bubble if there are no rows to show. - if (base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator) && - task_id_to_state.empty()) { + if (task_id_to_state.empty()) { return; } bubble_widget_ = ActorTaskListBubble::ShowBubble(
diff --git a/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_controller_unittest.cc b/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_controller_unittest.cc index ee36e511..dc80380 100644 --- a/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_controller_unittest.cc +++ b/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_controller_unittest.cc
@@ -33,18 +33,14 @@ #include "chrome/browser/ui/tabs/glic_actor_task_icon_manager_factory.h" #endif -class ActorTaskListBubbleControllerTest - : public ChromeViewsTestBase, - public testing::WithParamInterface<bool> { +class ActorTaskListBubbleControllerTest : public ChromeViewsTestBase { public: ActorTaskListBubbleControllerTest() { - if (GetParam()) { - feature_list_.InitAndEnableFeature( - features::kGlicActorUiGlobalTaskIndicator); - } else { - feature_list_.InitAndDisableFeature( - features::kGlicActorUiGlobalTaskIndicator); - } + std::vector<base::test::FeatureRefAndParams> enabled_features = { + {features::kGlicActor, + {{features::kGlicActorPolicyControlExemption.name, "true"}}}}; + feature_list_.InitWithFeaturesAndParameters(std::move(enabled_features), + {}); } void SetUp() override { @@ -147,7 +143,7 @@ base::test::ScopedFeatureList feature_list_; }; -TEST_P(ActorTaskListBubbleControllerTest, ShowBubbleRecordsHistogram) { +TEST_F(ActorTaskListBubbleControllerTest, ShowBubbleRecordsHistogram) { #if BUILDFLAG(ENABLE_GLIC) actor::ActorKeyedService* actor_service = actor::ActorKeyedService::Get(profile_.get()); @@ -182,23 +178,10 @@ anchor_widget_->GetContentsView()); histogram_tester.ExpectBucketCount("Actor.Ui.TaskListBubble.Rows", 1, 1); - if (ActorTaskListBubbleControllerTest::GetParam()) { - histogram_tester.ExpectBucketCount("Actor.Ui.TaskListBubble.Rows", 4, 1); - } else { - // Row will be removed on stop if GlicActorUiGlobalTaskIndicator is - // disabled. - histogram_tester.ExpectBucketCount("Actor.Ui.TaskListBubble.Rows", 3, 1); - } + histogram_tester.ExpectBucketCount("Actor.Ui.TaskListBubble.Rows", 4, 1); + EXPECT_EQ( 2u, histogram_tester.GetAllSamples("Actor.Ui.TaskListBubble.Rows").size()); #endif } - -INSTANTIATE_TEST_SUITE_P(All, - ActorTaskListBubbleControllerTest, - testing::Bool(), - [](const testing::TestParamInfo<bool>& info) { - return info.param ? "GlobalIndicatorEnabled" - : "GlobalIndicatorDisabled"; - });
diff --git a/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_interactive_uitest.cc b/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_interactive_uitest.cc index 4822813b..7c4aa4f 100644 --- a/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_interactive_uitest.cc +++ b/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_interactive_uitest.cc
@@ -47,7 +47,6 @@ {{features::kGlicActorPolicyControlExemption.name, "true"}}}, {features::kGlicActorUi, {{features::kGlicActorUiTaskIconName, "true"}}}, - {features::kGlicActorUiGlobalTaskIndicator, {}}, #endif // BUILDFLAG(ENABLE_GLIC) }, {});
diff --git a/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_unittest.cc b/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_unittest.cc index ee8be64..70c5b250a 100644 --- a/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_unittest.cc +++ b/chrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_unittest.cc
@@ -29,27 +29,18 @@ #include "ui/views/widget/unique_widget_ptr.h" using ::tabs::MockTabInterface; -class ActorTaskListBubbleTest : public ChromeViewsTestBase, - public testing::WithParamInterface<bool> { +class ActorTaskListBubbleTest : public ChromeViewsTestBase { public: ActorTaskListBubbleTest() = default; void SetUp() override { ChromeViewsTestBase::SetUp(); - base::test::FeatureRefAndParams enable_glic_policy = { - features::kGlicActor, - {{features::kGlicActorPolicyControlExemption.name, "true"}}}; - if (GetParam()) { - feature_list_.InitWithFeaturesAndParameters( - /*enabled_features=*/{enable_glic_policy, - {features::kGlicActorUiGlobalTaskIndicator, - {}}}, - /*disabled_features=*/{}); - } else { - feature_list_.InitWithFeaturesAndParameters( - /*enabled_features=*/{enable_glic_policy}, - /*disabled_features=*/{features::kGlicActorUiGlobalTaskIndicator}); - } + + std::vector<base::test::FeatureRefAndParams> enabled_features = { + {features::kGlicActor, + {{features::kGlicActorPolicyControlExemption.name, "true"}}}}; + feature_list_.InitWithFeaturesAndParameters(std::move(enabled_features), + {}); TestingProfile::Builder builder; builder.AddTestingFactory( @@ -121,13 +112,13 @@ MockTabInterface& mock_tab() { return mock_tab_; } private: - base::test::ScopedFeatureList feature_list_; std::unique_ptr<TestingProfile> profile_; MockTabInterface mock_tab_; views::UniqueWidgetPtr anchor_widget_; + base::test::ScopedFeatureList feature_list_; }; -TEST_P(ActorTaskListBubbleTest, CreateAndShowBubbleWithTasks) { +TEST_F(ActorTaskListBubbleTest, CreateAndShowBubbleWithTasks) { absl::flat_hash_map<actor::TaskId, bool> task_list; task_list[CreatePausedTask()] = true; task_list[CreatePausedTask()] = false; @@ -150,7 +141,7 @@ // TODO(crbug.com/469817191): Handle non-existent task_ids alongside completed // task ids. -TEST_P(ActorTaskListBubbleTest, CreateShowBubbleWithInvalidTask) { +TEST_F(ActorTaskListBubbleTest, CreateShowBubbleWithInvalidTask) { base::HistogramTester histogram_tester; absl::flat_hash_map<actor::TaskId, bool> task_list; task_list[actor::TaskId(1)] = true; @@ -163,7 +154,7 @@ actor::ui::ActorUiTaskIconError::kBubbleTaskDoesntExist, 1); } -TEST_P(ActorTaskListBubbleTest, CreateAndShowBubbleWithClosedTabTask) { +TEST_F(ActorTaskListBubbleTest, CreateAndShowBubbleWithClosedTabTask) { actor::TaskId id = actor_service_->CreateTaskForTesting(); actor_service_->GetTask(id)->Pause(/*from_actor=*/true); absl::flat_hash_map<actor::TaskId, bool> task_list; @@ -187,11 +178,3 @@ EXPECT_FALSE(static_cast<RichHoverButton*>(content_view->children().front()) ->GetEnabled()); } - -INSTANTIATE_TEST_SUITE_P(All, - ActorTaskListBubbleTest, - testing::Bool(), - [](const testing::TestParamInfo<bool>& info) { - return info.param ? "GlobalIndicatorEnabled" - : "GlobalIndicatorDisabled"; - });
diff --git a/chrome/browser/android/customtabs/OWNERS b/chrome/browser/android/customtabs/OWNERS index 764c3ba0..8c01572 100644 --- a/chrome/browser/android/customtabs/OWNERS +++ b/chrome/browser/android/customtabs/OWNERS
@@ -1,2 +1 @@ -lizeb@chromium.org -peconn@chromium.org +file://chrome/android/java/src/org/chromium/chrome/browser/customtabs/OWNERS
diff --git a/chrome/browser/android/storage_collection_synchronizer_android.cc b/chrome/browser/android/storage_collection_synchronizer_android.cc index 634a5d6..6042c17b 100644 --- a/chrome/browser/android/storage_collection_synchronizer_android.cc +++ b/chrome/browser/android/storage_collection_synchronizer_android.cc
@@ -8,6 +8,7 @@ #include "chrome/browser/android/collection_storage_observer_factory_android.h" #include "chrome/browser/android/storage_restore_orchestrator_factory_android.h" #include "chrome/browser/android/tab_state_storage_service_factory.h" +#include "components/tab_groups/tab_group_id.h" #include "components/tabs/public/android/jni_conversion.h" #include "third_party/jni_zero/jni_zero.h" @@ -30,6 +31,18 @@ synchronizer_.FullSave(); } +void StorageCollectionSynchronizerAndroid::SaveTab(JNIEnv* env, + TabAndroid* tab) { + synchronizer_.SaveTab(tab); +} + +void StorageCollectionSynchronizerAndroid::SaveTabGroupPayload( + JNIEnv* env, + base::Token group_id) { + synchronizer_.SaveTabGroupPayload( + tab_groups::TabGroupId::FromRawToken(group_id)); +} + void StorageCollectionSynchronizerAndroid::ConsumeRestoreOrchestratorFactory( JNIEnv* env, const jni_zero::JavaRef<jobject>& j_object) {
diff --git a/chrome/browser/android/storage_collection_synchronizer_android.h b/chrome/browser/android/storage_collection_synchronizer_android.h index ba56812..9c610bd 100644 --- a/chrome/browser/android/storage_collection_synchronizer_android.h +++ b/chrome/browser/android/storage_collection_synchronizer_android.h
@@ -7,6 +7,7 @@ #include <jni.h> +#include "chrome/browser/android/tab_android.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/tab/storage_collection_synchronizer.h" #include "chrome/browser/tab/tab_state_storage_service.h" @@ -27,6 +28,8 @@ const StorageCollectionSynchronizerAndroid&) = delete; void FullSave(JNIEnv* env); + void SaveTab(JNIEnv* env, TabAndroid* tab); + void SaveTabGroupPayload(JNIEnv* env, base::Token group_id); void ConsumeRestoreOrchestratorFactory( JNIEnv* env,
diff --git a/chrome/browser/ash/file_system_provider/service_worker_lifetime_manager.cc b/chrome/browser/ash/file_system_provider/service_worker_lifetime_manager.cc index d0d7ba0..59419acd 100644 --- a/chrome/browser/ash/file_system_provider/service_worker_lifetime_manager.cc +++ b/chrome/browser/ash/file_system_provider/service_worker_lifetime_manager.cc
@@ -8,6 +8,7 @@ #include <utility> #include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "content/public/common/child_process_id.h" #include "extensions/browser/process_manager.h" #include "extensions/browser/process_manager_factory.h" #include "third_party/blink/public/mojom/service_worker/service_worker_database.mojom.h" @@ -64,7 +65,7 @@ std::set<KeepaliveKey>& keepalive_keys = it->second; extensions::WorkerId worker_id{ target.extension_id, - target.render_process_id, + content::ChildProcessId::FromUnsafeValue(target.render_process_id), target.service_worker_version_id, target.worker_thread_id, };
diff --git a/chrome/browser/ash/file_system_provider/service_worker_lifetime_manager_unittest.cc b/chrome/browser/ash/file_system_provider/service_worker_lifetime_manager_unittest.cc index 772d9da..d44ca36 100644 --- a/chrome/browser/ash/file_system_provider/service_worker_lifetime_manager_unittest.cc +++ b/chrome/browser/ash/file_system_provider/service_worker_lifetime_manager_unittest.cc
@@ -9,6 +9,7 @@ #include "base/memory/raw_ref.h" #include "base/strings/stringprintf.h" +#include "content/public/common/child_process_id.h" #include "extensions/browser/event_router.h" #include "extensions/browser/service_worker/worker_id.h" #include "testing/gtest/include/gtest/gtest.h" @@ -90,10 +91,11 @@ .service_worker_version_id = 2, .worker_thread_id = 3, }; - const extensions::WorkerId kWorkerId(/*extension_id=*/"ext1", - /*render_process_id=*/1, - /*version_id=*/2, - /*thread_id=*/3); + const extensions::WorkerId kWorkerId( + /*extension_id=*/"ext1", + /*render_process_id=*/content::ChildProcessId::FromUnsafeValue(1), + /*version_id=*/2, + /*thread_id=*/3); // Simple case: a single request is dispatched and completed. @@ -125,7 +127,8 @@ auto worker = [](int version_id) { return extensions::WorkerId( /*extension_id=*/"ext1", - /*render_process_id=*/1000, version_id, + /*render_process_id=*/content::ChildProcessId::FromUnsafeValue(1000), + version_id, /*thread_id=*/3); }; @@ -188,12 +191,12 @@ }; const extensions::WorkerId kWorkerId1( /*extension_id=*/"ext1", - /*render_process_id=*/1000, + /*render_process_id=*/content::ChildProcessId::FromUnsafeValue(1000), /*version_id=*/2, /*thread_id=*/3); const extensions::WorkerId kWorkerId2( /*extension_id=*/"ext2", - /*render_process_id=*/1001, + /*render_process_id=*/content::ChildProcessId::FromUnsafeValue(1001), /*version_id=*/4, /*thread_id=*/5);
diff --git a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc index 5012587..628cbe2 100644 --- a/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc +++ b/chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.cc
@@ -44,7 +44,6 @@ #include "chrome/browser/ash/policy/uploading/status_uploader.h" #include "chrome/browser/ash/policy/uploading/system_log_uploader.h" #include "chrome/browser/browser_process.h" -#include "chrome/common/chrome_features.h" #include "chrome/common/pref_names.h" #include "chromeos/ash/components/install_attributes/install_attributes.h" #include "chromeos/ash/components/system/statistics_provider.h" @@ -268,9 +267,7 @@ metric_reporting_manager_ = reporting::MetricReportingManager::Create( managed_session_service_.get()); os_updates_reporter_ = reporting::OsUpdatesReporter::Create(); - if (base::FeatureList::IsEnabled(features::kEventBasedLogUpload)) { - event_based_log_manager_ = std::make_unique<EventBasedLogManager>(); - } + event_based_log_manager_ = std::make_unique<EventBasedLogManager>(); } NotifyConnected();
diff --git a/chrome/browser/browser_features.cc b/chrome/browser/browser_features.cc index 875b4d0..b0e531a 100644 --- a/chrome/browser/browser_features.cc +++ b/chrome/browser/browser_features.cc
@@ -147,6 +147,9 @@ base::FEATURE_ENABLED_BY_DEFAULT); #endif // BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING) +// Enables app-menu item for reporting an unsafe site to Google. +BASE_FEATURE(kReportUnsafeSite, base::FEATURE_DISABLED_BY_DEFAULT); + // When this feature is enabled, the network service will restart unsandboxed if // a previous attempt to launch it sandboxed failed. BASE_FEATURE(kRestartNetworkServiceUnsandboxedForFailedLaunch,
diff --git a/chrome/browser/browser_features.h b/chrome/browser/browser_features.h index a9cbacc..b9a910cf 100644 --- a/chrome/browser/browser_features.h +++ b/chrome/browser/browser_features.h
@@ -80,6 +80,8 @@ BASE_DECLARE_FEATURE(kInstallPlatformExperienceHelperWin); #endif // BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING) +BASE_DECLARE_FEATURE(kReportUnsafeSite); + BASE_DECLARE_FEATURE(kRestartNetworkServiceUnsandboxedForFailedLaunch); BASE_DECLARE_FEATURE(kSandboxExternalProtocolBlocked); BASE_DECLARE_FEATURE(kSandboxExternalProtocolBlockedWarning);
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index 5c31e4ea..9898ac1b 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc
@@ -7052,16 +7052,14 @@ #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \ BUILDFLAG(IS_CHROMEOS) - // Rewrite chrome://settings/autofill and chrome://settings/enhancedAutofill - // to chrome://settings/yourSavedInfo. + // Rewrite chrome://settings/enhancedAutofill to chrome://settings/autofill. if (url->SchemeIs(content::kChromeUIScheme) && url->GetHost() == chrome::kChromeUISettingsHost && - (url->GetPath() == chrome::kChromeUIAutofillPath || - url->GetPath() == chrome::kChromeUIAutofillAiPath) && + url->GetPath() == chrome::kChromeUIAutofillAiPath && base::FeatureList::IsEnabled( autofill::features::kYourSavedInfoSettingsPage)) { GURL::Replacements replacements; - replacements.SetPathStr(chrome::kChromeUIYourSavedInfoPath); + replacements.SetPathStr(chrome::kChromeUIAutofillPath); *url = url->ReplaceComponents(replacements); }
diff --git a/chrome/browser/chrome_content_browser_client_unittest.cc b/chrome/browser/chrome_content_browser_client_unittest.cc index 86c0166..f29e7fd9 100644 --- a/chrome/browser/chrome_content_browser_client_unittest.cc +++ b/chrome/browser/chrome_content_browser_client_unittest.cc
@@ -1140,18 +1140,6 @@ #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \ BUILDFLAG(IS_CHROMEOS) -TEST_F(ChromeContentSettingsRedirectTest, RedirectAutofillURL) { - base::test::ScopedFeatureList scoped_feature_list; - scoped_feature_list.InitAndEnableFeature( - autofill::features::kYourSavedInfoSettingsPage); - - TestChromeContentBrowserClient test_content_browser_client; - const GURL help_url("chrome://settings/autofill"); - GURL dest_url = help_url; - test_content_browser_client.HandleWebUI(&dest_url, &profile_); - EXPECT_EQ(GURL("chrome://settings/yourSavedInfo"), dest_url); -} - TEST_F(ChromeContentSettingsRedirectTest, RedirectEnhancedAutofillURL) { base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitAndEnableFeature( @@ -1161,7 +1149,7 @@ const GURL enhanced_autofill_url("chrome://settings/enhancedAutofill"); GURL dest_url = enhanced_autofill_url; test_content_browser_client.HandleWebUI(&dest_url, &profile_); - EXPECT_EQ(GURL("chrome://settings/yourSavedInfo"), dest_url); + EXPECT_EQ(GURL("chrome://settings/autofill"), dest_url); } TEST_F(ChromeContentSettingsRedirectTest, RedirectAddressesURL) {
diff --git a/chrome/browser/commerce/android/java/src/org/chromium/chrome/browser/commerce/PriceTrackingUtils.java b/chrome/browser/commerce/android/java/src/org/chromium/chrome/browser/commerce/PriceTrackingUtils.java index aff8857b..e679b28a 100644 --- a/chrome/browser/commerce/android/java/src/org/chromium/chrome/browser/commerce/PriceTrackingUtils.java +++ b/chrome/browser/commerce/android/java/src/org/chromium/chrome/browser/commerce/PriceTrackingUtils.java
@@ -55,10 +55,12 @@ @JniType("Profile*") Profile profile, long bookmarkId, boolean enabled, - Callback<Boolean> callback, + @JniType("base::OnceCallback<void(bool)>") Callback<Boolean> callback, boolean bookmarkCreatedForPriceTracking); void isBookmarkPriceTracked( - @JniType("Profile*") Profile profile, long bookmarkId, Callback<Boolean> callback); + @JniType("Profile*") Profile profile, + long bookmarkId, + @JniType("base::OnceCallback<void(bool)>") Callback<Boolean> callback); } }
diff --git a/chrome/browser/commerce/android/price_tracking_utils_android.cc b/chrome/browser/commerce/android/price_tracking_utils_android.cc index 90be8b7c..1df6cf7c 100644 --- a/chrome/browser/commerce/android/price_tracking_utils_android.cc +++ b/chrome/browser/commerce/android/price_tracking_utils_android.cc
@@ -29,7 +29,7 @@ Profile* profile, int64_t bookmark_id, bool enabled, - const JavaRef<jobject>& j_callback, + base::OnceCallback<void(bool)> callback, bool bookmark_created_by_price_tracking) { CHECK(profile); @@ -44,21 +44,16 @@ const bookmarks::BookmarkNode* node = bookmarks::GetBookmarkNodeByID(model, bookmark_id); - SetPriceTrackingStateForBookmark( - service, model, node, enabled, - base::BindOnce( - [](const ScopedJavaGlobalRef<jobject>& callback, bool success) { - base::android::RunBooleanCallbackAndroid(callback, success); - }, - ScopedJavaGlobalRef<jobject>(j_callback)), - bookmark_created_by_price_tracking); + SetPriceTrackingStateForBookmark(service, model, node, enabled, + std::move(callback), + bookmark_created_by_price_tracking); } static void JNI_PriceTrackingUtils_IsBookmarkPriceTracked( JNIEnv* env, Profile* profile, int64_t bookmark_id, - const JavaRef<jobject>& j_callback) { + base::OnceCallback<void(bool)> callback) { CHECK(profile); ShoppingService* service = @@ -72,13 +67,7 @@ const bookmarks::BookmarkNode* node = bookmarks::GetBookmarkNodeByID(model, bookmark_id); - IsBookmarkPriceTracked( - service, model, node, - base::BindOnce( - [](const ScopedJavaGlobalRef<jobject>& callback, bool is_tracked) { - base::android::RunBooleanCallbackAndroid(callback, is_tracked); - }, - ScopedJavaGlobalRef<jobject>(j_callback))); + IsBookmarkPriceTracked(service, model, node, std::move(callback)); } } // namespace commerce
diff --git a/chrome/browser/contextual_cueing/contextual_cueing_helper_interactive_uitest.cc b/chrome/browser/contextual_cueing/contextual_cueing_helper_interactive_uitest.cc index 20d49cee..7592a0c 100644 --- a/chrome/browser/contextual_cueing/contextual_cueing_helper_interactive_uitest.cc +++ b/chrome/browser/contextual_cueing/contextual_cueing_helper_interactive_uitest.cc
@@ -29,6 +29,7 @@ #include "chrome/browser/ui/views/tabs/tab_strip_action_container.h" #include "chrome/common/chrome_features.h" #include "chrome/common/webui_url_constants.h" +#include "chrome/grit/generated_resources.h" #include "chrome/test/base/interactive_test_utils.h" #include "chrome/test/base/ui_test_utils.h" #include "components/contextual_tasks/public/features.h" @@ -43,6 +44,7 @@ #include "content/public/test/browser_test_utils.h" #include "net/dns/mock_host_resolver.h" #include "services/metrics/public/cpp/ukm_builders.h" +#include "ui/base/l10n/l10n_util.h" #if BUILDFLAG(ENABLE_GLIC) @@ -146,7 +148,9 @@ https_server_.GetURL("enabled.com", "/optimization_guide/hello.html"), WindowOpenDisposition::NEW_FOREGROUND_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP)); - EXPECT_EQ("test label", nudge_delegate.last_nudge_label_); + EXPECT_EQ(l10n_util::GetStringUTF8( + IDS_GLIC_BUTTON_ENTRYPOINT_ASK_ABOUT_THIS_PAGE_LABEL), + nudge_delegate.last_nudge_label_); histogram_tester.ExpectUniqueSample( "ContextualCueing.NudgeDecision.GlicContextualCueing", @@ -166,7 +170,9 @@ // Simulate reload. chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB); - EXPECT_EQ("test label", nudge_delegate.last_nudge_label_); + EXPECT_EQ(l10n_util::GetStringUTF8( + IDS_GLIC_BUTTON_ENTRYPOINT_ASK_ABOUT_THIS_PAGE_LABEL), + nudge_delegate.last_nudge_label_); // Simulate new navigation. Should clear nudge. ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), @@ -405,7 +411,9 @@ https_server_.GetURL("enabled.com", "/optimization_guide/hello.html"), WindowOpenDisposition::NEW_FOREGROUND_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP)); - EXPECT_EQ("test label", nudge_delegate.last_nudge_label_); + EXPECT_EQ(l10n_util::GetStringUTF8( + IDS_GLIC_BUTTON_ENTRYPOINT_ASK_ABOUT_THIS_PAGE_LABEL), + nudge_delegate.last_nudge_label_); EXPECT_TRUE(nudge_delegate.GetIsShowingGlicNudge()); // Make sure it's cleared on error page. @@ -426,7 +434,9 @@ https_server_.GetURL("enabled.com", "/optimization_guide/hello.html"), WindowOpenDisposition::NEW_FOREGROUND_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP)); - EXPECT_EQ("test label", nudge_delegate.last_nudge_label_); + EXPECT_EQ(l10n_util::GetStringUTF8( + IDS_GLIC_BUTTON_ENTRYPOINT_ASK_ABOUT_THIS_PAGE_LABEL), + nudge_delegate.last_nudge_label_); EXPECT_TRUE(nudge_delegate.GetIsShowingGlicNudge()); ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition( @@ -560,7 +570,9 @@ WindowOpenDisposition::NEW_FOREGROUND_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP)); nudge_delegate.WaitUntilValidNudge(); - EXPECT_EQ("cue label", nudge_delegate.last_nudge_label_); + EXPECT_EQ(l10n_util::GetStringUTF8( + IDS_GLIC_BUTTON_ENTRYPOINT_ASK_ABOUT_THIS_PAGE_LABEL), + nudge_delegate.last_nudge_label_); EXPECT_TRUE(nudge_delegate.GetIsShowingGlicNudge()); histogram_tester.ExpectUniqueSample(
diff --git a/chrome/browser/contextual_cueing/contextual_cueing_page_data.cc b/chrome/browser/contextual_cueing/contextual_cueing_page_data.cc index e2aa25c..81306ac 100644 --- a/chrome/browser/contextual_cueing/contextual_cueing_page_data.cc +++ b/chrome/browser/contextual_cueing/contextual_cueing_page_data.cc
@@ -8,8 +8,10 @@ #include "base/strings/string_util.h" #include "base/task/single_thread_task_runner.h" #include "chrome/browser/contextual_cueing/contextual_cueing_features.h" +#include "chrome/grit/generated_resources.h" #include "content/public/browser/web_contents.h" #include "pdf/buildflags.h" +#include "ui/base/l10n/l10n_util.h" #if BUILDFLAG(ENABLE_PDF) #include "components/pdf/browser/pdf_document_helper.h" @@ -100,9 +102,11 @@ return; } else if (config.has_cue_label()) { std::move(cueing_decision_callback_) - .Run(base::ok(CueingResult{config.cue_label(), - /*prompt_suggestion=*/"", - /*is_dynamic=*/false})); + .Run(base::ok(CueingResult{ + l10n_util::GetStringUTF8( + IDS_GLIC_BUTTON_ENTRYPOINT_ASK_ABOUT_THIS_PAGE_LABEL), + /*prompt_suggestion=*/"", + /*is_dynamic=*/false})); return; } } else if (decision == kNeedsPdfPageCount) {
diff --git a/chrome/browser/contextual_cueing/contextual_cueing_page_data_unittest.cc b/chrome/browser/contextual_cueing/contextual_cueing_page_data_unittest.cc index afa5f1d1..f7e8585 100644 --- a/chrome/browser/contextual_cueing/contextual_cueing_page_data_unittest.cc +++ b/chrome/browser/contextual_cueing/contextual_cueing_page_data_unittest.cc
@@ -8,12 +8,14 @@ #include "base/test/test_future.h" #include "chrome/browser/contextual_cueing/contextual_cueing_enums.h" #include "chrome/browser/contextual_cueing/contextual_cueing_features.h" +#include "chrome/grit/generated_resources.h" #include "chrome/test/base/chrome_render_view_host_test_harness.h" #include "chrome/test/base/testing_profile.h" #include "content/public/browser/web_contents.h" #include "content/public/test/test_renderer_host.h" #include "content/public/test/test_utils.h" #include "content/public/test/web_contents_tester.h" +#include "ui/base/l10n/l10n_util.h" namespace contextual_cueing { @@ -55,7 +57,9 @@ std::move(metadata), future.GetCallback()); ASSERT_TRUE(future.Wait()); - EXPECT_EQ("basic label", future.Get().value().cue_label); + EXPECT_EQ(l10n_util::GetStringUTF8( + IDS_GLIC_BUTTON_ENTRYPOINT_ASK_ABOUT_THIS_PAGE_LABEL), + future.Get().value().cue_label); EXPECT_FALSE(future.Get().value().is_dynamic); } @@ -159,7 +163,9 @@ InvokePdfPageCountReceived(4); ASSERT_TRUE(future.Wait()); - EXPECT_EQ("pdf label", future.Get().value().cue_label); + EXPECT_EQ(l10n_util::GetStringUTF8( + IDS_GLIC_BUTTON_ENTRYPOINT_ASK_ABOUT_THIS_PAGE_LABEL), + future.Get().value().cue_label); EXPECT_FALSE(future.Get().value().is_dynamic); } @@ -189,7 +195,9 @@ std::move(metadata), future.GetCallback()); ASSERT_TRUE(future.Wait()); - EXPECT_EQ("basic label", future.Get().value().cue_label); + EXPECT_EQ(l10n_util::GetStringUTF8( + IDS_GLIC_BUTTON_ENTRYPOINT_ASK_ABOUT_THIS_PAGE_LABEL), + future.Get().value().cue_label); EXPECT_FALSE(future.Get().value().is_dynamic); } #endif @@ -236,7 +244,9 @@ std::move(metadata), future.GetCallback()); ASSERT_TRUE(future.Wait()); - EXPECT_EQ("basic label", future.Get().value().cue_label); + EXPECT_EQ(l10n_util::GetStringUTF8( + IDS_GLIC_BUTTON_ENTRYPOINT_ASK_ABOUT_THIS_PAGE_LABEL), + future.Get().value().cue_label); EXPECT_FALSE(future.Get().value().is_dynamic); }
diff --git a/chrome/browser/contextual_tasks/BUILD.gn b/chrome/browser/contextual_tasks/BUILD.gn index 96fb6c8..75e9a9e 100644 --- a/chrome/browser/contextual_tasks/BUILD.gn +++ b/chrome/browser/contextual_tasks/BUILD.gn
@@ -119,6 +119,7 @@ if (!is_android) { sources += [ "contextual_tasks_composebox_handler.h", + "contextual_tasks_cookie_synchronizer.h", "contextual_tasks_internals_page_handler.h", "contextual_tasks_navigation_throttle.h", "contextual_tasks_side_panel_coordinator.h", @@ -168,6 +169,7 @@ "contextual_search_session_finder.cc", "contextual_search_session_finder.h", "contextual_tasks_composebox_handler.cc", + "contextual_tasks_cookie_synchronizer.cc", "contextual_tasks_internals_page_handler.cc", "contextual_tasks_navigation_throttle.cc", "contextual_tasks_page_handler.cc", @@ -296,6 +298,7 @@ "contextual_tasks_context_scoring_utils_unittest.cc", "contextual_tasks_context_service_factory_unittest.cc", "contextual_tasks_context_signal_utils_unittest.cc", + "contextual_tasks_cookie_synchronizer_unittest.cc", "contextual_tasks_page_handler_unittest.cc", "contextual_tasks_side_panel_coordinator_unittest.cc", "contextual_tasks_tab_visit_tracker_unittest.cc",
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_cookie_synchronizer.cc b/chrome/browser/contextual_tasks/contextual_tasks_cookie_synchronizer.cc new file mode 100644 index 0000000..a5a861a --- /dev/null +++ b/chrome/browser/contextual_tasks/contextual_tasks_cookie_synchronizer.cc
@@ -0,0 +1,154 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/contextual_tasks/contextual_tasks_cookie_synchronizer.h" + +#include <memory> + +#include "base/check.h" +#include "base/functional/bind.h" +#include "base/time/time.h" +#include "build/buildflag.h" +#include "components/signin/public/base/consent_level.h" +#include "components/signin/public/base/multilogin_parameters.h" +#include "components/signin/public/base/signin_buildflags.h" +#include "components/signin/public/identity_manager/identity_manager.h" +#include "components/signin/public/identity_manager/set_accounts_in_cookie_result.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/storage_partition.h" +#include "content/public/browser/storage_partition_config.h" +#include "google_apis/gaia/core_account_id.h" +#include "google_apis/gaia/gaia_auth_fetcher.h" +#include "services/network/public/mojom/cookie_manager.mojom.h" + +namespace contextual_tasks { + +namespace { + +content::StoragePartitionConfig GetContextualTasksStoragePartitionConfig( + content::BrowserContext* browser_context) { + // This storage partition must match the partition attribute in + // chrome/browser/resources/contextual_tasks/app.html.ts: + // "persist:contextual-tasks". + return content::StoragePartitionConfig::Create( + browser_context, "contextual-tasks", + /*partition_name=*/"contextual-tasks", + /*in_memory=*/false); +} + +} // namespace + +ContextualTasksCookieSynchronizer::ContextualTasksCookieSynchronizer( + content::BrowserContext* context, + signin::IdentityManager* identity_manager) + : context_(context), identity_manager_(identity_manager) { + CHECK(context_); + observation_.Observe(identity_manager); +} + +ContextualTasksCookieSynchronizer::~ContextualTasksCookieSynchronizer() = + default; + +std::unique_ptr<GaiaAuthFetcher> +ContextualTasksCookieSynchronizer::CreateGaiaAuthFetcherForPartition( + GaiaAuthConsumer* consumer, + const gaia::GaiaSource& source) { + return std::make_unique<GaiaAuthFetcher>( + consumer, source, + GetStoragePartition()->GetURLLoaderFactoryForBrowserProcess()); +} + +network::mojom::CookieManager* +ContextualTasksCookieSynchronizer::GetCookieManagerForPartition() { + return GetStoragePartition()->GetCookieManagerForBrowserProcess(); +} + +#if BUILDFLAG(ENABLE_DICE_SUPPORT) +network::mojom::DeviceBoundSessionManager* +ContextualTasksCookieSynchronizer::GetDeviceBoundSessionManagerForPartition() { + return nullptr; +} +#endif // BUILDFLAG(ENABLE_DICE_SUPPORT) + +void ContextualTasksCookieSynchronizer::CopyCookiesToWebviewStoragePartition() { + if (cookie_loader_) { + // A request is in progress already. + return; + } + + // Set a timeout to avoid hanging if multilogin hangs. + timeout_.Start(FROM_HERE, kCookieSyncDefaultTimeout, + base::BindOnce(&ContextualTasksCookieSynchronizer::OnTimeout, + base::Unretained(this))); + + if (!GetStoragePartition()) { + CompleteAuth(false); + return; + } + + BeginCookieSync(); +} + +void ContextualTasksCookieSynchronizer::OnIdentityManagerShutdown( + signin::IdentityManager* identity_manager) { + observation_.Reset(); + cookie_loader_.reset(); +} + +void ContextualTasksCookieSynchronizer::BeginCookieSync() { + // We only need primary account authentication in the webview. + CoreAccountId primary_account_id = + identity_manager_->GetPrimaryAccountId(signin::ConsentLevel::kSignin); + if (primary_account_id.empty()) { + CompleteAuth(false); + return; + } + signin::MultiloginParameters parameters = { + gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER, + {primary_account_id}}; + + // Use kChrome source with a suffix. + gaia::GaiaSource source(gaia::GaiaSource::kChrome, "contextual-tasks"); + + cookie_loader_ = + identity_manager_->GetAccountsCookieMutator() + ->SetAccountsInCookieForPartition( + this, parameters, source, + base::BindOnce(&ContextualTasksCookieSynchronizer::OnAuthFinished, + weak_ptr_factory_.GetWeakPtr())); +} + +void ContextualTasksCookieSynchronizer::OnAuthFinished( + signin::SetAccountsInCookieResult cookie_result) { + switch (cookie_result) { + case signin::SetAccountsInCookieResult::kSuccess: + CompleteAuth(/*is_success=*/true); + break; + case signin::SetAccountsInCookieResult::kTransientError: + CompleteAuth(/*is_success=*/false); + break; + case signin::SetAccountsInCookieResult::kPersistentError: + CompleteAuth(/*is_success=*/false); + break; + } +} + +void ContextualTasksCookieSynchronizer::OnTimeout() { + // TODO(crbug.com/40284489): Add UMA metrics if needed. + CompleteAuth(/*is_success=*/false); +} + +void ContextualTasksCookieSynchronizer::CompleteAuth(bool is_success) { + timeout_.Stop(); + cookie_loader_.reset(); +} + +content::StoragePartition* +ContextualTasksCookieSynchronizer::GetStoragePartition() { + content::StoragePartition* partition = context_->GetStoragePartition( + GetContextualTasksStoragePartitionConfig(context_)); + return partition; +} + +} // namespace contextual_tasks
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_cookie_synchronizer.h b/chrome/browser/contextual_tasks/contextual_tasks_cookie_synchronizer.h new file mode 100644 index 0000000..134356f5 --- /dev/null +++ b/chrome/browser/contextual_tasks/contextual_tasks_cookie_synchronizer.h
@@ -0,0 +1,96 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CONTEXTUAL_TASKS_CONTEXTUAL_TASKS_COOKIE_SYNCHRONIZER_H_ +#define CHROME_BROWSER_CONTEXTUAL_TASKS_CONTEXTUAL_TASKS_COOKIE_SYNCHRONIZER_H_ + +#include <memory> +#include <vector> + +#include "base/functional/callback_forward.h" +#include "base/gtest_prod_util.h" +#include "base/memory/raw_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/scoped_observation.h" +#include "base/timer/timer.h" +#include "build/build_config.h" +#include "build/buildflag.h" +#include "components/signin/public/base/signin_buildflags.h" +#include "components/signin/public/identity_manager/accounts_cookie_mutator.h" +#include "components/signin/public/identity_manager/identity_manager.h" +#include "content/public/browser/browser_context.h" + +namespace content { +class BrowserContext; +class StoragePartition; +} // namespace content + +namespace signin { +enum class SetAccountsInCookieResult; +} // namespace signin + +namespace contextual_tasks { + +// Helper to sync cookies to the webview storage partition. +class ContextualTasksCookieSynchronizer + : public signin::AccountsCookieMutator::PartitionDelegate, + public signin::IdentityManager::Observer { + public: + static constexpr base::TimeDelta kCookieSyncDefaultTimeout = base::Seconds(7); + + ContextualTasksCookieSynchronizer(content::BrowserContext* context, + signin::IdentityManager* identity_manager); + ContextualTasksCookieSynchronizer(const ContextualTasksCookieSynchronizer&) = + delete; + ContextualTasksCookieSynchronizer& operator=( + const ContextualTasksCookieSynchronizer&) = delete; + ~ContextualTasksCookieSynchronizer() override; + + // Virtual for overriding in tests. + virtual void CopyCookiesToWebviewStoragePartition(); + + // signin::IdentityManager::Observer + void OnIdentityManagerShutdown( + signin::IdentityManager* identity_manager) override; + + protected: + // Returns storage partition for this authentication request. + // visible for testing. + virtual content::StoragePartition* GetStoragePartition(); + + virtual void CompleteAuth(bool is_success); + + private: + void BeginCookieSync(); + + // signin::AccountsCookieMutator::PartitionDelegate: + std::unique_ptr<GaiaAuthFetcher> CreateGaiaAuthFetcherForPartition( + GaiaAuthConsumer* consumer, + const gaia::GaiaSource& source) override; + network::mojom::CookieManager* GetCookieManagerForPartition() override; +#if BUILDFLAG(ENABLE_DICE_SUPPORT) + network::mojom::DeviceBoundSessionManager* + GetDeviceBoundSessionManagerForPartition() override; +#endif // BUILDFLAG(ENABLE_DICE_SUPPORT) + + // Handles the webview authentication result. + void OnAuthFinished(signin::SetAccountsInCookieResult cookie_result); + void OnTimeout(); + + const raw_ptr<content::BrowserContext> context_; + const raw_ptr<signin::IdentityManager> identity_manager_; + base::ScopedObservation<signin::IdentityManager, + signin::IdentityManager::Observer> + observation_{this}; + + base::OneShotTimer timeout_; + std::unique_ptr<signin::AccountsCookieMutator::SetAccountsInCookieTask> + cookie_loader_; + base::WeakPtrFactory<ContextualTasksCookieSynchronizer> weak_ptr_factory_{ + this}; +}; + +} // namespace contextual_tasks + +#endif // CHROME_BROWSER_CONTEXTUAL_TASKS_CONTEXTUAL_TASKS_COOKIE_SYNCHRONIZER_H_
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_cookie_synchronizer_unittest.cc b/chrome/browser/contextual_tasks/contextual_tasks_cookie_synchronizer_unittest.cc new file mode 100644 index 0000000..cbe20cb --- /dev/null +++ b/chrome/browser/contextual_tasks/contextual_tasks_cookie_synchronizer_unittest.cc
@@ -0,0 +1,246 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/contextual_tasks/contextual_tasks_cookie_synchronizer.h" + +#include <string> + +#include "base/memory/raw_ptr.h" +#include "base/run_loop.h" +#include "base/strings/stringprintf.h" +#include "base/test/bind.h" +#include "base/test/task_environment.h" +#include "base/test/test_future.h" +#include "base/time/time.h" +#include "chrome/test/base/testing_profile.h" +#include "components/signin/public/base/consent_level.h" +#include "components/signin/public/base/test_signin_client.h" +#include "components/signin/public/identity_manager/identity_manager.h" +#include "components/signin/public/identity_manager/identity_test_environment.h" +#include "components/signin/public/identity_manager/set_accounts_in_cookie_result.h" +#include "components/sync_preferences/testing_pref_service_syncable.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/storage_partition.h" +#include "content/public/browser/storage_partition_config.h" +#include "content/public/test/browser_task_environment.h" +#include "content/public/test/test_storage_partition.h" +#include "google_apis/gaia/gaia_constants.h" +#include "google_apis/gaia/gaia_urls.h" +#include "services/network/public/mojom/cookie_manager.mojom.h" +#include "services/network/test/test_cookie_manager.h" +#include "services/network/test/test_url_loader_factory.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace contextual_tasks { + +namespace { + +constexpr char kTestAccountEmail[] = "user@test.com"; + +constexpr char kCookieResponseSuccess[] = R"( + { "status": "OK", + "cookies":[ + { + "name":"CookieName", + "value":"CookieValue", + "domain":".google.com", + "path":"/" + } + ] + })"; +constexpr char kCookieResponseRetry[] = R"( + { "status": "RETRY", + "cookies":[] + })"; +constexpr char kCookieResponseError[] = R"( + { "status": "ERROR", + "cookies":[] + })"; + +// Returns HTTP request response that will lead to the given `result`. +std::string GetResponseFromResult(signin::SetAccountsInCookieResult result) { + switch (result) { + case signin::SetAccountsInCookieResult::kSuccess: + return kCookieResponseSuccess; + case signin::SetAccountsInCookieResult::kTransientError: + return kCookieResponseRetry; + case signin::SetAccountsInCookieResult::kPersistentError: + return kCookieResponseError; + } +} + +class ContextualTasksCookieSynchronizerForTest + : public ContextualTasksCookieSynchronizer { + public: + ContextualTasksCookieSynchronizerForTest( + content::BrowserContext* context, + signin::IdentityManager* identity_manager, + content::TestStoragePartition* test_storage_partition) + : ContextualTasksCookieSynchronizer(context, identity_manager), + test_storage_partition_(test_storage_partition) {} + + content::StoragePartition* GetStoragePartition() override { + return test_storage_partition_; + } + + void SetCallback(base::OnceCallback<void(bool)> callback) { + callback_ = std::move(callback); + } + + protected: + void CompleteAuth(bool is_success) override { + ContextualTasksCookieSynchronizer::CompleteAuth(is_success); + if (callback_) { + std::move(callback_).Run(is_success); + } + } + + private: + raw_ptr<content::TestStoragePartition> test_storage_partition_; + base::OnceCallback<void(bool)> callback_; +}; + +} // namespace + +class ContextualTasksCookieSynchronizerTest : public testing::Test { + public: + ContextualTasksCookieSynchronizerTest() = default; + ~ContextualTasksCookieSynchronizerTest() override = default; + + protected: + ContextualTasksCookieSynchronizerForTest& cookie_synchronizer() { + return cookie_synchronizer_; + } + + // Sets the network response to the given result. Applies to all subsequent + // network requests. + void SetResponseForResult(signin::SetAccountsInCookieResult result) { + test_signin_client_.GetTestURLLoaderFactory()->AddResponse( + RequestURL().spec(), GetResponseFromResult(result)); + } + + GURL RequestURL() const { + return GaiaUrls::GetInstance()->oauth_multilogin_url().Resolve( + base::StringPrintf("?source=%s&reuseCookies=0", + "ChromiumBrowsercontextual-tasks")); + } + + void SetUp() override { + testing::Test::SetUp(); + + test_storage_partition_.set_cookie_manager_for_browser_process( + &test_cookie_manager_); + test_storage_partition_.set_url_loader_factory_for_browser_process( + test_signin_client_.GetTestURLLoaderFactory()); + + identity_test_env_.MakePrimaryAccountAvailable( + kTestAccountEmail, signin::ConsentLevel::kSignin); + identity_test_env_.SetAutomaticIssueOfAccessTokens(true); + + test_signin_client_.GetTestURLLoaderFactory()->SetInterceptor( + base::BindLambdaForTesting( + [&](const network::ResourceRequest& request) { + LOG(ERROR) << "Requested URL: " << request.url.spec(); + })); + } + + base::TimeDelta kCookieSyncDefaultTimeout = + ContextualTasksCookieSynchronizer::kCookieSyncDefaultTimeout; + content::BrowserTaskEnvironment task_environment_{ + base::test::TaskEnvironment::TimeSource::MOCK_TIME}; + + TestingProfile test_profile_; + + sync_preferences::TestingPrefServiceSyncable prefs_; + TestSigninClient test_signin_client_{&prefs_}; + signin::IdentityTestEnvironment identity_test_env_{ + /*test_url_loader_factory=*/nullptr, &prefs_, &test_signin_client_}; + + content::TestStoragePartition test_storage_partition_; + network::TestCookieManager test_cookie_manager_; + + ContextualTasksCookieSynchronizerForTest cookie_synchronizer_{ + &test_profile_, identity_test_env_.identity_manager(), + &test_storage_partition_}; +}; + +TEST_F(ContextualTasksCookieSynchronizerTest, AuthSuccess) { + base::test::TestFuture<bool> result; + SetResponseForResult(signin::SetAccountsInCookieResult::kSuccess); + + cookie_synchronizer().SetCallback(result.GetCallback()); + cookie_synchronizer().CopyCookiesToWebviewStoragePartition(); + EXPECT_TRUE(result.Get()); +} + +TEST_F(ContextualTasksCookieSynchronizerTest, AuthPersistentFailure) { + base::test::TestFuture<bool> result; + SetResponseForResult(signin::SetAccountsInCookieResult::kPersistentError); + + cookie_synchronizer().SetCallback(result.GetCallback()); + cookie_synchronizer().CopyCookiesToWebviewStoragePartition(); + EXPECT_FALSE(result.Get()); +} + +TEST_F(ContextualTasksCookieSynchronizerTest, AuthTransientSuccessOnRetry) { + // This test verifies that OAuthMultiloginHelper performs retries for us. + base::test::TestFuture<bool> result; + + int request_count = 0; + test_signin_client_.GetTestURLLoaderFactory()->SetInterceptor( + base::BindLambdaForTesting([&](const network::ResourceRequest& request) { + if (request.url != RequestURL()) { + return; + } + SetResponseForResult( + request_count++ == 0 + ? signin::SetAccountsInCookieResult::kTransientError + : signin::SetAccountsInCookieResult::kSuccess); + })); + + cookie_synchronizer().SetCallback(result.GetCallback()); + cookie_synchronizer().CopyCookiesToWebviewStoragePartition(); + EXPECT_TRUE(result.Get()); +} + +TEST_F(ContextualTasksCookieSynchronizerTest, AuthTransientFailure_MaxRetry) { + base::test::TestFuture<bool> result; + SetResponseForResult(signin::SetAccountsInCookieResult::kTransientError); + + cookie_synchronizer().SetCallback(result.GetCallback()); + cookie_synchronizer().CopyCookiesToWebviewStoragePartition(); + EXPECT_FALSE(result.Get()); +} + +TEST_F(ContextualTasksCookieSynchronizerTest, FailsOnTimeOut) { + base::test::TestFuture<bool> result; + cookie_synchronizer().SetCallback(result.GetCallback()); + cookie_synchronizer().CopyCookiesToWebviewStoragePartition(); + + task_environment_.FastForwardBy(kCookieSyncDefaultTimeout - + base::Milliseconds(10)); + EXPECT_FALSE(result.IsReady()); + task_environment_.FastForwardBy(base::Milliseconds(10)); + EXPECT_FALSE(result.Get()); +} + +TEST_F(ContextualTasksCookieSynchronizerTest, WorksAfterTimeout) { + base::test::TestFuture<bool> result; + cookie_synchronizer().SetCallback(result.GetCallback()); + cookie_synchronizer().CopyCookiesToWebviewStoragePartition(); + + task_environment_.FastForwardBy(kCookieSyncDefaultTimeout); + EXPECT_FALSE(result.Get()); + + result.Clear(); + SetResponseForResult(signin::SetAccountsInCookieResult::kSuccess); + + cookie_synchronizer().SetCallback(result.GetCallback()); + cookie_synchronizer().CopyCookiesToWebviewStoragePartition(); + + EXPECT_TRUE(result.Get()); +} + +} // namespace contextual_tasks
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_ui.cc b/chrome/browser/contextual_tasks/contextual_tasks_ui.cc index 97d0fd3..11af851 100644 --- a/chrome/browser/contextual_tasks/contextual_tasks_ui.cc +++ b/chrome/browser/contextual_tasks/contextual_tasks_ui.cc
@@ -7,6 +7,7 @@ #include "base/base64.h" #include "base/check_deref.h" #include "base/feature_list.h" +#include "base/functional/callback_helpers.h" #include "base/memory/raw_ref.h" #include "base/metrics/histogram_functions.h" #include "base/strings/string_split.h" @@ -176,6 +177,12 @@ contextual_tasks::ContextualTasksServiceFactory::GetForProfile( Profile::FromBrowserContext( web_ui->GetWebContents()->GetBrowserContext()))) { + if (contextual_tasks::ShouldEnableCookieSync()) { + cookie_synchronizer_ = + std::make_unique<contextual_tasks::ContextualTasksCookieSynchronizer>( + web_ui->GetWebContents()->GetBrowserContext(), + IdentityManagerFactory::GetForProfile(Profile::FromWebUI(web_ui))); + } inner_web_contents_creation_observer_ = std::make_unique<InnerFrameCreationObvserver>( web_ui->GetWebContents(), @@ -610,6 +617,12 @@ } } +void ContextualTasksUI::SetCookieSynchronizerForTesting( + std::unique_ptr<contextual_tasks::ContextualTasksCookieSynchronizer> + cookie_synchronizer) { + cookie_synchronizer_ = std::move(cookie_synchronizer); +} + void ContextualTasksUI::OnInnerWebContentsCreated( content::WebContents* inner_contents) { // This is assumed to only be called once per WebUI lifetime. Can be called @@ -622,6 +635,14 @@ nav_observer_ = std::make_unique<FrameNavObserver>( inner_contents, ui_service_, contextual_tasks_service_, this); embedded_web_contents_ = inner_contents->GetWeakPtr(); + + // If the cookie sync is enabled, trigger the cookie sync now that the + // embedded page is created. This is a fire and forget call, assuming the + // cookie sync will succeed eventually, and relying on OAuth tokens until + // then. + if (cookie_synchronizer_) { + cookie_synchronizer_->CopyCookiesToWebviewStoragePartition(); + } } void ContextualTasksUI::OnContextRetrievedForActiveTab(
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_ui.h b/chrome/browser/contextual_tasks/contextual_tasks_ui.h index 6569916..7b77404 100644 --- a/chrome/browser/contextual_tasks/contextual_tasks_ui.h +++ b/chrome/browser/contextual_tasks/contextual_tasks_ui.h
@@ -15,6 +15,7 @@ #include "base/timer/timer.h" #include "base/uuid.h" #include "chrome/browser/contextual_tasks/contextual_tasks_composebox_handler.h" +#include "chrome/browser/contextual_tasks/contextual_tasks_cookie_synchronizer.h" #include "chrome/browser/contextual_tasks/contextual_tasks_internals.mojom.h" #include "chrome/browser/contextual_tasks/contextual_tasks_page_handler.h" #include "chrome/browser/contextual_tasks/contextual_tasks_side_panel_coordinator.h" @@ -205,6 +206,10 @@ // Shows an OAuth error dialog. void ShowOauthErrorDialog(); + void SetCookieSynchronizerForTesting( + std::unique_ptr<contextual_tasks::ContextualTasksCookieSynchronizer> + cookie_synchronizer); + private: // An observer specifically to watch for the creation of the hosted remote // page. This is attached to the WebContents for the WebUI and notifies the @@ -256,6 +261,8 @@ GetSidePanelCoordinator(); std::unique_ptr<ContextualTasksComposeboxHandler> composebox_handler_; + std::unique_ptr<contextual_tasks::ContextualTasksCookieSynchronizer> + cookie_synchronizer_; raw_ptr<contextual_tasks::ContextualTasksUiService> ui_service_; raw_ptr<contextual_tasks::ContextualTasksService> contextual_tasks_service_;
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_ui_browsertest.cc b/chrome/browser/contextual_tasks/contextual_tasks_ui_browsertest.cc index bfd15d8..0c10b211 100644 --- a/chrome/browser/contextual_tasks/contextual_tasks_ui_browsertest.cc +++ b/chrome/browser/contextual_tasks/contextual_tasks_ui_browsertest.cc
@@ -9,6 +9,7 @@ #include "base/test/bind.h" #include "base/test/scoped_feature_list.h" #include "chrome/browser/contextual_tasks/contextual_tasks.mojom.h" +#include "chrome/browser/contextual_tasks/contextual_tasks_cookie_synchronizer.h" #include "chrome/browser/contextual_tasks/contextual_tasks_service_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/signin/identity_manager_factory.h" @@ -102,6 +103,18 @@ (override)); }; +class MockContextualTasksCookieSynchronizer + : public contextual_tasks::ContextualTasksCookieSynchronizer { + public: + MockContextualTasksCookieSynchronizer( + content::BrowserContext* context, + signin::IdentityManager* identity_manager) + : ContextualTasksCookieSynchronizer(context, identity_manager) {} + ~MockContextualTasksCookieSynchronizer() override = default; + + MOCK_METHOD(void, CopyCookiesToWebviewStoragePartition, (), (override)); +}; + } // namespace class ContextualTasksUIBrowserTest : public InProcessBrowserTest { @@ -297,6 +310,25 @@ } IN_PROC_BROWSER_TEST_F(ContextualTasksUIBrowserTest, + OnInnerWebContentsCreated_TriggersCookieSync) { + auto mock_synchronizer = std::make_unique< + testing::StrictMock<MockContextualTasksCookieSynchronizer>>( + browser()->profile(), identity_test_env_->identity_manager()); + + EXPECT_CALL(*mock_synchronizer, CopyCookiesToWebviewStoragePartition()) + .Times(1); + + controller_->SetCookieSynchronizerForTesting(std::move(mock_synchronizer)); + + // Create inner contents to trigger the observer. + std::unique_ptr<content::WebContents> inner_contents = + content::WebContents::Create( + content::WebContents::CreateParams(browser()->profile())); + + TriggerOnInnerWebContentsCreated(inner_contents.get()); +} + +IN_PROC_BROWSER_TEST_F(ContextualTasksUIBrowserTest, OnLensOverlayStateChanged) { testing::NiceMock<MockContextualTasksPage> mock_page;
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_ui_service.cc b/chrome/browser/contextual_tasks/contextual_tasks_ui_service.cc index 7ab583f..8507d9e 100644 --- a/chrome/browser/contextual_tasks/contextual_tasks_ui_service.cc +++ b/chrome/browser/contextual_tasks/contextual_tasks_ui_service.cc
@@ -574,7 +574,7 @@ text_directives); } -void ContextualTasksUiService::OnSearchResultsNavigationInTab( +void ContextualTasksUiService::OnNonThreadNavigationInTab( const GURL& url, base::WeakPtr<tabs::TabInterface> tab) { if (!tab || !tab->GetContents()) { @@ -701,15 +701,16 @@ // If the navigation is to a search results page or AI page, it is allowed // if being viewed in the side panel, but only if it is intercepted without // the side panel-specific params. If the params have already been added, do - // nothing, otherwise this logic causes an infinite "intercept" loop. - if (IsSearchResultsUrl(url_params.url) || is_nav_to_ai) { + // nothing, otherwise this logic causes an infinite "intercept" loop. Any + // "allowed domain" (e.g. Google) should not be treated as a thread link. + if (IsAllowedHost(url_params.url) || is_nav_to_ai) { if (tab) { if (!is_nav_to_ai) { // The SRP should never be embedded in the WebUI when viewed in a tab. base::SequencedTaskRunner::GetCurrentDefault()->PostTask( FROM_HERE, base::BindOnce( - &ContextualTasksUiService::OnSearchResultsNavigationInTab, + &ContextualTasksUiService::OnNonThreadNavigationInTab, weak_ptr_factory_.GetWeakPtr(), url_params.url, tab->GetWeakPtr())); return true;
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_ui_service.h b/chrome/browser/contextual_tasks/contextual_tasks_ui_service.h index 95bc0e3c..6b58f91 100644 --- a/chrome/browser/contextual_tasks/contextual_tasks_ui_service.h +++ b/chrome/browser/contextual_tasks/contextual_tasks_ui_service.h
@@ -82,10 +82,10 @@ base::WeakPtr<tabs::TabInterface> tab, base::WeakPtr<BrowserWindowInterface> browser); - // A notification that a navigation to the search results page occurred in the - // contextual tasks WebUI while being viewed in a tab (as opposed to side - // panel). - virtual void OnSearchResultsNavigationInTab( + // A notification that a navigation to a link that is not related to the ai + // thread occurred in the contextual tasks WebUI while being viewed in a tab + // (as opposed to side panel). + virtual void OnNonThreadNavigationInTab( const GURL& url, base::WeakPtr<tabs::TabInterface> tab);
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_ui_service_unittest.cc b/chrome/browser/contextual_tasks/contextual_tasks_ui_service_unittest.cc index 5d82dd0..5c9e9e5 100644 --- a/chrome/browser/contextual_tasks/contextual_tasks_ui_service_unittest.cc +++ b/chrome/browser/contextual_tasks/contextual_tasks_ui_service_unittest.cc
@@ -89,7 +89,7 @@ base::WeakPtr<BrowserWindowInterface> browser), (override)); MOCK_METHOD(void, - OnSearchResultsNavigationInTab, + OnNonThreadNavigationInTab, (const GURL& url, base::WeakPtr<tabs::TabInterface> tab), (override)); MOCK_METHOD(void, @@ -546,8 +546,7 @@ base::RunLoop run_loop; EXPECT_CALL(*service_for_nav_, OnThreadLinkClicked(_, _, _, _)).Times(0); - EXPECT_CALL(*service_for_nav_, - OnSearchResultsNavigationInTab(navigated_url, _)) + EXPECT_CALL(*service_for_nav_, OnNonThreadNavigationInTab(navigated_url, _)) .WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); EXPECT_CALL(*service_for_nav_, OnNavigationToAiPageIntercepted(_, _, _)) .Times(0); @@ -577,8 +576,7 @@ ON_CALL(tab, GetContents).WillByDefault(Return(web_contents.get())); EXPECT_CALL(*service_for_nav_, OnThreadLinkClicked(_, _, _, _)).Times(0); - EXPECT_CALL(*service_for_nav_, - OnSearchResultsNavigationInTab(navigated_url, _)) + EXPECT_CALL(*service_for_nav_, OnNonThreadNavigationInTab(navigated_url, _)) .Times(1); EXPECT_CALL(*service_for_nav_, OnNavigationToAiPageIntercepted(_, _, _)) .Times(0); @@ -593,6 +591,69 @@ task_environment()->RunUntilIdle(); } +// Any non-AI page navigation when viewed in a tab should navigate the tab. +TEST_F(ContextualTasksUiServiceTest, AllowedHostNavigation_ViewedInTab) { + GURL navigated_url("https://google.com"); + GURL host_web_content_url(chrome::kChromeUIContextualTasksURL); + + ON_CALL(*aim_eligibility_service_, HasAimUrlParams(_)) + .WillByDefault(Return(false)); + + auto web_contents = content::WebContentsTester::CreateTestWebContents( + profile_.get(), content::SiteInstance::Create(profile_.get())); + content::WebContentsTester::For(web_contents.get()) + ->SetLastCommittedURL(host_web_content_url); + tabs::MockTabInterface tab; + ON_CALL(tab, GetContents).WillByDefault(Return(web_contents.get())); + + EXPECT_CALL(*service_for_nav_, OnThreadLinkClicked(_, _, _, _)).Times(0); + EXPECT_CALL(*service_for_nav_, OnNonThreadNavigationInTab(navigated_url, _)) + .Times(1); + EXPECT_CALL(*service_for_nav_, OnNavigationToAiPageIntercepted(_, _, _)) + .Times(0); + EXPECT_TRUE(service_for_nav_->HandleNavigationImpl( + CreateOpenUrlParams(navigated_url, true), web_contents.get(), &tab, + /*is_from_embedded_page=*/true, + /*is_to_new_tab=*/false)); + // TODO(crbug.com/470448689): RunUntilIdle is needed to ensure the EXPECT_CALL + // above that is sent to a posted task never gets called. Using RunUntilIdle + // is bad practice and these tests should be updated to avoid the need for + // RunUntilIdle. + task_environment()->RunUntilIdle(); +} + +// Any other link that isn't AI or an allowed host should be treated as a thread +// link when viewed in a tab. +TEST_F(ContextualTasksUiServiceTest, Navigation_ViewedInTab) { + GURL navigated_url("https://example.com"); + GURL host_web_content_url(chrome::kChromeUIContextualTasksURL); + + ON_CALL(*aim_eligibility_service_, HasAimUrlParams(_)) + .WillByDefault(Return(false)); + + auto web_contents = content::WebContentsTester::CreateTestWebContents( + profile_.get(), content::SiteInstance::Create(profile_.get())); + content::WebContentsTester::For(web_contents.get()) + ->SetLastCommittedURL(host_web_content_url); + tabs::MockTabInterface tab; + ON_CALL(tab, GetContents).WillByDefault(Return(web_contents.get())); + + EXPECT_CALL(*service_for_nav_, OnThreadLinkClicked(navigated_url, _, _, _)) + .Times(1); + EXPECT_CALL(*service_for_nav_, OnNonThreadNavigationInTab(_, _)).Times(0); + EXPECT_CALL(*service_for_nav_, OnNavigationToAiPageIntercepted(_, _, _)) + .Times(0); + EXPECT_TRUE(service_for_nav_->HandleNavigationImpl( + CreateOpenUrlParams(navigated_url, true), web_contents.get(), &tab, + /*is_from_embedded_page=*/true, + /*is_to_new_tab=*/false)); + // TODO(crbug.com/470448689): RunUntilIdle is needed to ensure the EXPECT_CALL + // above that is sent to a posted task never gets called. Using RunUntilIdle + // is bad practice and these tests should be updated to avoid the need for + // RunUntilIdle. + task_environment()->RunUntilIdle(); +} + // If the search results page is navigated to while viewing the UI in the side // panel (e.g. no tab tied to the WebContents), ensure the correct event is // fired.
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_url_loader_factory_interceptor.cc b/chrome/browser/contextual_tasks/contextual_tasks_url_loader_factory_interceptor.cc index a64d7f6..9bc1570 100644 --- a/chrome/browser/contextual_tasks/contextual_tasks_url_loader_factory_interceptor.cc +++ b/chrome/browser/contextual_tasks/contextual_tasks_url_loader_factory_interceptor.cc
@@ -42,8 +42,15 @@ const char kAuthorizationHeader[] = "Authorization"; const char kBearerPrefix[] = "Bearer "; +const char kOneGoogleIdentifier[] = "onegoogle"; bool ShouldAddAuthHeader(const GURL& url) { + // Don't add the Authorization header to OGB URLs, since they don't support + // OAuth. Attaching an OAuth token can result in 403 errors. + if (url.spec().contains(kOneGoogleIdentifier)) { + return false; + } + // Only add the Authorization header to domains in the allow list. for (const char* domain : kAuthTokenAllowList) { if (url.DomainIs(domain)) {
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_url_loader_factory_interceptor_browsertest.cc b/chrome/browser/contextual_tasks/contextual_tasks_url_loader_factory_interceptor_browsertest.cc index af0da6c..5e8d5b62 100644 --- a/chrome/browser/contextual_tasks/contextual_tasks_url_loader_factory_interceptor_browsertest.cc +++ b/chrome/browser/contextual_tasks/contextual_tasks_url_loader_factory_interceptor_browsertest.cc
@@ -140,13 +140,18 @@ if (request.relative_url.find("/echoheader?Authorization") != std::string::npos) { auto it = request.headers.find("Authorization"); + std::string header_value; if (it != request.headers.end()) { + header_value = it->second; + } + if (!header_value.empty() || + request.relative_url.find("onegoogle") != std::string::npos) { content::GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce( &ContextualTasksUrlLoaderFactoryInterceptorBrowserTest:: OnAuthHeaderCaptured, - base::Unretained(this), it->second)); + base::Unretained(this), header_value)); } return std::make_unique<net::test_server::BasicHttpResponse>(); } @@ -333,4 +338,52 @@ run_loop.Run(); } +IN_PROC_BROWSER_TEST_F(ContextualTasksUrlLoaderFactoryInterceptorBrowserTest, + OneGoogleUrlDoesNotHaveAuthToken) { + base::RunLoop run_loop; + auth_capture_quit_closure_ = run_loop.QuitClosure(); + + // Navigate to the Contextual Tasks WebUI. + ASSERT_TRUE(ui_test_utils::NavigateToURL( + browser(), GURL(chrome::kChromeUIContextualTasksURL))); + + // Wait for the WebUI to load and create the webview. + content::WebContents* web_ui_contents = + TabListInterface::From(browser())->GetActiveTab()->GetContents(); + + // Script to find the webview and navigate it. + // Note: We access the shadowRoot of the app. + std::string script = content::JsReplace( + R"( + (async () => { + let app = document.querySelector('contextual-tasks-app'); + while (!app) { + await new Promise(r => setTimeout(r, 100)); + app = document.querySelector('contextual-tasks-app'); + } + // Wait for shadow root + while (!app.shadowRoot) { + await new Promise(r => setTimeout(r, 100)); + } + // Wait for threadFrame + let webview = app.shadowRoot.querySelector('#threadFrame'); + while (!webview) { + await new Promise(r => setTimeout(r, 100)); + webview = app.shadowRoot.querySelector('#threadFrame'); + } + webview.src = $1; + })(); + )", + https_server_.GetURL(kTestHost, "/onegoogle/echoheader?Authorization") + .spec()); + + EXPECT_TRUE(content::ExecJs(web_ui_contents, script)); + + // Wait for the request to reach the server. + run_loop.Run(); + + // Verify the header. + EXPECT_TRUE(captured_auth_header_.empty()); +} + } // namespace contextual_tasks
diff --git a/chrome/browser/dom_distiller/tab_utils_android.cc b/chrome/browser/dom_distiller/tab_utils_android.cc index 37be78258..74bab57 100644 --- a/chrome/browser/dom_distiller/tab_utils_android.cc +++ b/chrome/browser/dom_distiller/tab_utils_android.cc
@@ -31,19 +31,10 @@ static void JNI_DomDistillerTabUtils_DistillCurrentPageAndViewIfSuccessful( JNIEnv* env, const JavaRef<jobject>& j_web_contents, - const JavaRef<jobject>& j_callback) { + base::OnceCallback<void(bool)> callback) { content::WebContents* web_contents = content::WebContents::FromJavaWebContents(j_web_contents); - ::DistillCurrentPageAndViewIfSuccessful( - web_contents, - base::BindOnce( - [](const jni_zero::ScopedJavaGlobalRef<jobject>& callback, - bool success) { - if (callback) { - base::android::RunBooleanCallbackAndroid(callback, success); - } - }, - jni_zero::ScopedJavaGlobalRef<jobject>(j_callback))); + ::DistillCurrentPageAndViewIfSuccessful(web_contents, std::move(callback)); } static void JNI_DomDistillerTabUtils_DistillCurrentPage( @@ -103,10 +94,7 @@ static void JNI_DomDistillerTabUtils_RunReadabilityHeuristicsOnWebContents( JNIEnv* env, const JavaRef<jobject>& j_web_contents, - const JavaRef<jobject>& j_callback) { - base::OnceCallback<void(bool)> callback = - base::BindOnce(&base::android::RunBooleanCallbackAndroid, - base::android::ScopedJavaGlobalRef<jobject>(j_callback)); + base::OnceCallback<void(bool)> callback) { content::WebContents* web_contents = content::WebContents::FromJavaWebContents(j_web_contents); ::RunReadabilityHeuristicsOnWebContents(web_contents, std::move(callback));
diff --git a/chrome/browser/enterprise/util/android/java/src/org/chromium/chrome/browser/enterprise/util/DataProtectionBridge.java b/chrome/browser/enterprise/util/android/java/src/org/chromium/chrome/browser/enterprise/util/DataProtectionBridge.java index 151de50..758106e9 100644 --- a/chrome/browser/enterprise/util/android/java/src/org/chromium/chrome/browser/enterprise/util/DataProtectionBridge.java +++ b/chrome/browser/enterprise/util/android/java/src/org/chromium/chrome/browser/enterprise/util/DataProtectionBridge.java
@@ -8,6 +8,7 @@ import androidx.annotation.VisibleForTesting; +import org.jni_zero.JniType; import org.jni_zero.NativeMethods; import org.chromium.base.Callback; @@ -175,24 +176,38 @@ @VisibleForTesting public interface Natives { void verifyCopyTextIsAllowedByPolicy( - String text, RenderFrameHost renderFrameHost, Callback<Boolean> callback); + String text, + RenderFrameHost renderFrameHost, + @JniType("base::OnceCallback<void(bool)>") Callback<Boolean> callback); void verifyCopyUrlIsAllowedByPolicy( - String url, RenderFrameHost renderFrameHost, Callback<Boolean> callback); + String url, + RenderFrameHost renderFrameHost, + @JniType("base::OnceCallback<void(bool)>") Callback<Boolean> callback); void verifyCopyImageIsAllowedByPolicy( - String imageUri, RenderFrameHost renderFrameHost, Callback<Boolean> callback); + String imageUri, + RenderFrameHost renderFrameHost, + @JniType("base::OnceCallback<void(bool)>") Callback<Boolean> callback); void verifyShareTextIsAllowedByPolicy( - String text, RenderFrameHost renderFrameHost, Callback<Boolean> callback); + String text, + RenderFrameHost renderFrameHost, + @JniType("base::OnceCallback<void(bool)>") Callback<Boolean> callback); void verifyShareUrlIsAllowedByPolicy( - String url, RenderFrameHost renderFrameHost, Callback<Boolean> callback); + String url, + RenderFrameHost renderFrameHost, + @JniType("base::OnceCallback<void(bool)>") Callback<Boolean> callback); void verifyShareImageIsAllowedByPolicy( - String imageUri, RenderFrameHost renderFrameHost, Callback<Boolean> callback); + String imageUri, + RenderFrameHost renderFrameHost, + @JniType("base::OnceCallback<void(bool)>") Callback<Boolean> callback); void verifyGenericCopyImageActionIsAllowedByPolicy( - String imageUri, RenderFrameHost renderFrameHost, Callback<Boolean> callback); + String imageUri, + RenderFrameHost renderFrameHost, + @JniType("base::OnceCallback<void(bool)>") Callback<Boolean> callback); } }
diff --git a/chrome/browser/enterprise/util/data_protection_bridge.cc b/chrome/browser/enterprise/util/data_protection_bridge.cc index 93a6edef..eb29755d 100644 --- a/chrome/browser/enterprise/util/data_protection_bridge.cc +++ b/chrome/browser/enterprise/util/data_protection_bridge.cc
@@ -67,16 +67,12 @@ void VerifyCopyIsAllowedByPolicy( const base::android::JavaRef<jobject>& jrender_frame_host, - const JavaRef<jobject>& j_callback, + base::OnceCallback<void(bool)> callback, const ui::ClipboardMetadata& metadata, const content::ClipboardPasteData& data) { RenderFrameHost* render_frame_host = RenderFrameHost::FromJavaRenderFrameHost(jrender_frame_host); - base::OnceCallback<void(bool)> boolean_java_callback = - base::BindOnce(&base::android::RunBooleanCallbackAndroid, - ScopedJavaGlobalRef<jobject>(j_callback)); - enterprise_data_protection::IsClipboardCopyAllowedByPolicy( CreateClipboardEndpoint(render_frame_host), metadata, data, base::BindOnce( @@ -86,21 +82,17 @@ std::optional<std::u16string> replacement_data) { std::move(callback).Run(!data.empty()); }, - std::move(boolean_java_callback))); + std::move(callback))); } void VerifyShareIsAllowedByPolicy( const base::android::JavaRef<jobject>& jrender_frame_host, - const JavaRef<jobject>& j_callback, + base::OnceCallback<void(bool)> callback, const ui::ClipboardMetadata& metadata, const content::ClipboardPasteData& data) { RenderFrameHost* render_frame_host = RenderFrameHost::FromJavaRenderFrameHost(jrender_frame_host); - base::OnceCallback<void(bool)> boolean_java_callback = - base::BindOnce(&base::android::RunBooleanCallbackAndroid, - ScopedJavaGlobalRef<jobject>(j_callback)); - enterprise_data_protection::IsClipboardShareAllowedByPolicy( CreateClipboardEndpoint(render_frame_host), metadata, data, base::BindOnce( @@ -110,21 +102,17 @@ std::optional<std::u16string> replacement_data) { std::move(callback).Run(!data.empty()); }, - std::move(boolean_java_callback))); + std::move(callback))); } void VerifyGenericCopyActionIsAllowedByPolicy( const base::android::JavaRef<jobject>& jrender_frame_host, - const JavaRef<jobject>& j_callback, + base::OnceCallback<void(bool)> callback, const ui::ClipboardMetadata& metadata, const content::ClipboardPasteData& data) { RenderFrameHost* render_frame_host = RenderFrameHost::FromJavaRenderFrameHost(jrender_frame_host); - base::OnceCallback<void(bool)> boolean_java_callback = - base::BindOnce(&base::android::RunBooleanCallbackAndroid, - ScopedJavaGlobalRef<jobject>(j_callback)); - enterprise_data_protection::IsClipboardGenericCopyActionAllowedByPolicy( CreateClipboardEndpoint(render_frame_host), metadata, data, base::BindOnce( @@ -134,7 +122,7 @@ std::optional<std::u16string> replacement_data) { std::move(callback).Run(!data.empty()); }, - std::move(boolean_java_callback))); + std::move(callback))); } } // namespace @@ -144,14 +132,14 @@ JNIEnv* env, const JavaRef<jstring>& j_text, const base::android::JavaRef<jobject>& jrender_frame_host, - const JavaRef<jobject>& j_callback) { + base::OnceCallback<void(bool)> callback) { std::u16string text = base::android::ConvertJavaStringToUTF16(env, j_text); ClipboardPasteData data; data.text = text; VerifyCopyIsAllowedByPolicy( - jrender_frame_host, j_callback, + jrender_frame_host, std::move(callback), { .size = text.size() * sizeof(std::u16string::value_type), .format_type = ui::ClipboardFormatType::PlainTextType(), @@ -164,14 +152,14 @@ JNIEnv* env, const JavaRef<jstring>& j_url, const base::android::JavaRef<jobject>& jrender_frame_host, - const JavaRef<jobject>& j_callback) { + base::OnceCallback<void(bool)> callback) { std::u16string url = base::android::ConvertJavaStringToUTF16(env, j_url); ClipboardPasteData data; data.text = url; VerifyCopyIsAllowedByPolicy( - jrender_frame_host, j_callback, + jrender_frame_host, std::move(callback), { .size = url.size() * sizeof(std::u16string::value_type), .format_type = ui::ClipboardFormatType::UrlType(), @@ -184,7 +172,7 @@ JNIEnv* env, const JavaRef<jstring>& j_image_uri, const base::android::JavaRef<jobject>& jrender_frame_host, - const JavaRef<jobject>& j_callback) { + base::OnceCallback<void(bool)> callback) { std::u16string image_uri = base::android::ConvertJavaStringToUTF16(env, j_image_uri); @@ -192,7 +180,7 @@ data.text = image_uri; VerifyCopyIsAllowedByPolicy( - jrender_frame_host, j_callback, + jrender_frame_host, std::move(callback), { // TODO(crbug.com/344593255): Retrieve the bitmap size when it's // needed by the data controls logic. @@ -206,14 +194,14 @@ JNIEnv* env, const JavaRef<jstring>& j_text, const base::android::JavaRef<jobject>& jrender_frame_host, - const JavaRef<jobject>& j_callback) { + base::OnceCallback<void(bool)> callback) { std::u16string text = base::android::ConvertJavaStringToUTF16(env, j_text); ClipboardPasteData data; data.text = text; VerifyShareIsAllowedByPolicy( - jrender_frame_host, j_callback, + jrender_frame_host, std::move(callback), { .size = text.size() * sizeof(std::u16string::value_type), .format_type = ui::ClipboardFormatType::PlainTextType(), @@ -226,14 +214,14 @@ JNIEnv* env, const JavaRef<jstring>& j_url, const base::android::JavaRef<jobject>& jrender_frame_host, - const JavaRef<jobject>& j_callback) { + base::OnceCallback<void(bool)> callback) { std::u16string url = base::android::ConvertJavaStringToUTF16(env, j_url); ClipboardPasteData data; data.text = url; VerifyShareIsAllowedByPolicy( - jrender_frame_host, j_callback, + jrender_frame_host, std::move(callback), { .size = url.size() * sizeof(std::u16string::value_type), .format_type = ui::ClipboardFormatType::UrlType(), @@ -246,7 +234,7 @@ JNIEnv* env, const JavaRef<jstring>& j_image_uri, const base::android::JavaRef<jobject>& jrender_frame_host, - const JavaRef<jobject>& j_callback) { + base::OnceCallback<void(bool)> callback) { std::u16string image_uri = base::android::ConvertJavaStringToUTF16(env, j_image_uri); @@ -254,7 +242,7 @@ data.text = image_uri; VerifyShareIsAllowedByPolicy( - jrender_frame_host, j_callback, + jrender_frame_host, std::move(callback), { // TODO(crbug.com/344593255): Retrieve the bitmap size when it's // needed by the data controls logic. @@ -269,7 +257,7 @@ JNIEnv* env, const JavaRef<jstring>& j_image_uri, const base::android::JavaRef<jobject>& jrender_frame_host, - const JavaRef<jobject>& j_callback) { + base::OnceCallback<void(bool)> callback) { std::u16string image_uri = base::android::ConvertJavaStringToUTF16(env, j_image_uri); @@ -277,7 +265,7 @@ data.text = image_uri; VerifyGenericCopyActionIsAllowedByPolicy( - jrender_frame_host, j_callback, + jrender_frame_host, std::move(callback), { // TODO(crbug.com/344593255): Retrieve the bitmap size when it's // needed by the data controls logic.
diff --git a/chrome/browser/extensions/api/developer_private/inspectable_views_finder.cc b/chrome/browser/extensions/api/developer_private/inspectable_views_finder.cc index 1ffc8294..3500a88 100644 --- a/chrome/browser/extensions/api/developer_private/inspectable_views_finder.cc +++ b/chrome/browser/extensions/api/developer_private/inspectable_views_finder.cc
@@ -91,7 +91,7 @@ InspectableViewsFinder::View InspectableViewsFinder::ConstructView( const GURL& url, int render_process_id, - int render_frame_id, + int render_view_id, bool incognito, bool is_iframe, api::developer_private::ViewType type) { @@ -100,7 +100,7 @@ view.render_process_id = render_process_id; // NOTE(devlin): This is called "render_view_id" in the api for legacy // reasons, but it's not a high priority to change. - view.render_view_id = render_frame_id; + view.render_view_id = render_view_id; view.incognito = incognito; view.is_iframe = is_iframe; view.type = type; @@ -206,7 +206,9 @@ for (const WorkerId& service_worker_id : service_worker_ids) { result->push_back(ConstructView( BackgroundInfo::GetBackgroundServiceWorkerScriptURL(&extension), - service_worker_id.render_process_id, -1, is_incognito, false, + service_worker_id.render_process_id.GetUnsafeValue(), + /*render_view_id=*/-1, is_incognito, + /*is_iframe=*/false, api::developer_private::ViewType::kExtensionServiceWorkerBackground)); } }
diff --git a/chrome/browser/extensions/api/web_request/web_request_apitest.cc b/chrome/browser/extensions/api/web_request/web_request_apitest.cc index 128134e9..fd2bd023 100644 --- a/chrome/browser/extensions/api/web_request/web_request_apitest.cc +++ b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
@@ -80,6 +80,7 @@ #include "content/public/browser/storage_partition.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/webui_config_map.h" +#include "content/public/common/child_process_id.h" #include "content/public/common/content_features.h" #include "content/public/common/page_type.h" #include "content/public/test/browser_test.h" @@ -7630,7 +7631,8 @@ // it's active listeners should be removed. EventRouterInterceptorForStopListenerRemoval event_listener_removal_on_stop_interceptor( - profile(), previous_service_worker_id->render_process_id); + profile(), + previous_service_worker_id->render_process_id.GetUnsafeValue()); // Stop the extension's service worker. The worker listener, due to the // interceptor, will stay registered as an active listener. However,
diff --git a/chrome/browser/feed/android/feed_surface_renderer_bridge.cc b/chrome/browser/feed/android/feed_surface_renderer_bridge.cc index 5aaa297..42167ab2 100644 --- a/chrome/browser/feed/android/feed_surface_renderer_bridge.cc +++ b/chrome/browser/feed/android/feed_surface_renderer_bridge.cc
@@ -145,25 +145,20 @@ env, java_ref_, base::android::ConvertUTF8ToJavaString(env, key)); } -void FeedSurfaceRendererBridge::LoadMore(JNIEnv* env, - const JavaRef<jobject>& callback_obj) { +void FeedSurfaceRendererBridge::LoadMore( + base::OnceCallback<void(bool)> callback) { if (!feed_stream_api_) { return; } - feed_stream_api_->LoadMore( - surface_id_, base::BindOnce(&base::android::RunBooleanCallbackAndroid, - ScopedJavaGlobalRef<jobject>(callback_obj))); + feed_stream_api_->LoadMore(surface_id_, std::move(callback)); } void FeedSurfaceRendererBridge::ManualRefresh( - JNIEnv* env, - const JavaRef<jobject>& callback_obj) { + base::OnceCallback<void(bool)> callback) { if (!feed_stream_api_) { return; } - feed_stream_api_->ManualRefresh( - surface_id_, base::BindOnce(&base::android::RunBooleanCallbackAndroid, - ScopedJavaGlobalRef<jobject>(callback_obj))); + feed_stream_api_->ManualRefresh(surface_id_, std::move(callback)); } static void JNI_FeedSurfaceRendererBridge_ProcessThereAndBackAgain(
diff --git a/chrome/browser/feed/android/feed_surface_renderer_bridge.h b/chrome/browser/feed/android/feed_surface_renderer_bridge.h index 63e2ca4..e7eb0bdc 100644 --- a/chrome/browser/feed/android/feed_surface_renderer_bridge.h +++ b/chrome/browser/feed/android/feed_surface_renderer_bridge.h
@@ -52,11 +52,9 @@ void OnStreamUpdated(const feedui::StreamUpdate& stream_update); - void LoadMore(JNIEnv* env, - const base::android::JavaRef<jobject>& callback_obj); + void LoadMore(base::OnceCallback<void(bool)> callback); - void ManualRefresh(JNIEnv* env, - const base::android::JavaRef<jobject>& callback_obj); + void ManualRefresh(base::OnceCallback<void(bool)> callback); void SurfaceOpened(JNIEnv* env); void SurfaceClosed(JNIEnv* env);
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSurfaceRendererBridge.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSurfaceRendererBridge.java index 7949afb5..920fa82d 100644 --- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSurfaceRendererBridge.java +++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedSurfaceRendererBridge.java
@@ -280,9 +280,13 @@ // Member functions, must not be called after destroy(). void destroy(long nativeFeedSurfaceRendererBridge); - void loadMore(long nativeFeedSurfaceRendererBridge, Callback<Boolean> callback); + void loadMore( + long nativeFeedSurfaceRendererBridge, + @JniType("base::OnceCallback<void(bool)>") Callback<Boolean> callback); - void manualRefresh(long nativeFeedSurfaceRendererBridge, Callback<Boolean> callback); + void manualRefresh( + long nativeFeedSurfaceRendererBridge, + @JniType("base::OnceCallback<void(bool)>") Callback<Boolean> callback); int getSurfaceId(long nativeFeedSurfaceRendererBridge);
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json index 5e1ada88..b7eb3b39 100644 --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json
@@ -596,11 +596,6 @@ "expiry_milestone": 133 }, { - "name": "ash-capture-mode-education-bypass-limits", - "owners": [ "hewer@chromium.org", "meganlfu@google.com"], - "expiry_milestone": 133 - }, - { "name": "ash-debug-shortcuts", "owners": [ "//ash/OWNERS" ], // Used by developers for debugging and to dump extra information to logs @@ -4436,11 +4431,6 @@ "expiry_milestone": 150 }, { - "name": "event-based-log-upload", - "owners": [ "iremuguz@google.com", "chromeos-commercial-supportability@google.com" ], - "expiry_milestone": 140 - }, - { "name": "ewallet-payments", "owners": [ "junhuihe@google.com", "chrome-payments-eng@google.com", "payments-autofill-team@google.com" ], "expiry_milestone": 151 @@ -5837,6 +5827,11 @@ "expiry_milestone": 150 }, { + "name": "ios-save-to-drive-signed-out", + "owners": [ "josephlanza@google.com", "qpubert@google.com", "bling-flags@google.com"], + "expiry_milestone": 153 + }, + { "name": "ios-segmentation-ephemeral-card-ranker", "owners": [ "thegreenfrog@google.com", "bling-pandamonium@google.com" ], "expiry_milestone": 144 @@ -6812,11 +6807,6 @@ "expiry_milestone": 140 }, { - "name": "ntp-realbox-match-searchbox-theme", - "owners": [ "mahmadi@chromium.org", "mfacey@chromium.org" ], - "expiry_milestone": 125 - }, - { "name": "ntp-realbox-next", "owners": [ "romanarora@chromium.com", "chrome-desktop-ntp@google.com" ], "expiry_milestone": 155
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h index 5a502bbd6..50c22f6 100644 --- a/chrome/browser/flag_descriptions.h +++ b/chrome/browser/flag_descriptions.h
@@ -6070,11 +6070,6 @@ inline constexpr char kNtpRealboxCr23ThemingDescription[] = "CR23 theming will be applied in Realbox when enabled."; -inline constexpr char kNtpRealboxMatchSearchboxThemeName[] = - "NTP Realbox Matches Searchbox Theme"; -inline constexpr char kNtpRealboxMatchSearchboxThemeDescription[] = - "Makes NTP Realbox drop shadow match that of the Searchbox when enabled."; - inline constexpr char kNtpRealboxUseGoogleGIconName[] = "NTP Realbox Google G Icon"; inline constexpr char kNtpRealboxUseGoogleGIconDescription[] = @@ -6614,13 +6609,6 @@ "of the screenshot keyboard shortcut and the screen capture tool in the " "quick settings menu."; -inline constexpr char kCaptureModeEducationBypassLimitsName[] = - "Enable Capture Mode Education bypass limits"; -inline constexpr char kCaptureModeEducationBypassLimitsDescription[] = - "Enables bypassing the 3 times / 24 hours show limit for Capture Mode " - "Education nudges and tutorials, so they can be viewed repeatedly for " - "testing purposes."; - inline constexpr char kCrosContentAdjustedRefreshRateName[] = "Content Adjusted Refresh Rate"; inline constexpr char kCrosContentAdjustedRefreshRateDescription[] = @@ -7052,12 +7040,6 @@ "Reset the end of life notification prefs to their default value, at the " "start of the user session. This is meant to make manual testing easier."; -inline constexpr char kEventBasedLogUpload[] = "Enable event based log uploads"; -inline constexpr char kEventBasedLogUploadDescription[] = - "Uploads relevant logs to device management server when unexpected events " - "(e.g. crashes) occur on the device. The feature is guarded by " - "LogUploadEnabled policy."; - inline constexpr char kExoGamepadVibrationName[] = "Gamepad Vibration for Exo Clients"; inline constexpr char kExoGamepadVibrationDescription[] =
diff --git a/chrome/browser/glic/BUILD.gn b/chrome/browser/glic/BUILD.gn index ffbd735d..b20de33e 100644 --- a/chrome/browser/glic/BUILD.gn +++ b/chrome/browser/glic/BUILD.gn
@@ -401,7 +401,10 @@ deps += [ "//components/guest_view/browser" ] if (!enable_extensions_core) { - deps += [ "//components/guest_view/browser/slim_web_view" ] + deps += [ + "//chrome/browser/resources/guest_view_shared:resources", + "//components/guest_view/browser/slim_web_view", + ] } }
diff --git a/chrome/browser/glic/host/glic.mojom b/chrome/browser/glic/host/glic.mojom index 2171d079..9df889e 100644 --- a/chrome/browser/glic/host/glic.mojom +++ b/chrome/browser/glic/host/glic.mojom
@@ -252,9 +252,8 @@ GetInternalsDataPayload() => (InternalsDataPayload internals_data); // Sets the guest URL presets for the internals page. - SetGuestUrlPresets(url.mojom.Url autopush, - url.mojom.Url preprod, - url.mojom.Url prod); + SetGuestUrlPresets( + url.mojom.Url autopush, url.mojom.Url preprod, url.mojom.Url prod); }; // @generate glic_api @@ -609,6 +608,7 @@ // Skill created by an end-user. kUserCreated = 2, }; + // LINT.ThenChange(//depot/chromium/components/skills/public/skill.mojom:SkillSource, // //depot/chromium/components/sync/protocol/skill_specifics.proto:SkillSource) @@ -623,6 +623,8 @@ string icon; // The source of the skill which can be 1P or user created. SkillSource source; + // The description of the skill. + string description; }; // A single skill. @@ -632,7 +634,9 @@ // The prompt of the skill. string prompt; }; -// LINT.ThenChange(//depot/chromium/components/skills/public/skill.mojom:Skill, //depot/chromium/components/skills/public/skill.h:Skill) + +// LINT.ThenChange(//depot/chromium/components/skills/public/skill.mojom:Skill, +// //depot/chromium/components/skills/public/skill.h:Skill) // Request to create a skill. struct CreateSkillRequest {
diff --git a/chrome/browser/glic/host/glic_api_browsertest.cc b/chrome/browser/glic/host/glic_api_browsertest.cc index 57deb8f4..fcd5d7c2 100644 --- a/chrome/browser/glic/host/glic_api_browsertest.cc +++ b/chrome/browser/glic/host/glic_api_browsertest.cc
@@ -3838,17 +3838,17 @@ std::vector<mojom::SkillPreviewPtr> skills_batch_1; skills_batch_1.push_back(mojom::SkillPreview::New( "contextual_skill_id_1", "contextual_skill_1", "contextual_skill_icon_1", - mojom::SkillSource::kFirstParty)); + mojom::SkillSource::kFirstParty, "contextual_skill_description_1")); skills_batch_1.push_back(mojom::SkillPreview::New( "contextual_skill_id_2", "contextual_skill_2", "contextual_skill_icon_2", - mojom::SkillSource::kFirstParty)); + mojom::SkillSource::kFirstParty, "contextual_skill_description_2")); GetHost()->NotifyContextualSkillsChanged(std::move(skills_batch_1)); ContinueJsTest(); std::vector<mojom::SkillPreviewPtr> skills_batch_2; skills_batch_2.push_back(mojom::SkillPreview::New( "contextual_skill_id_3", "contextual_skill_3", "contextual_skill_icon_3", - mojom::SkillSource::kFirstParty)); + mojom::SkillSource::kFirstParty, "contextual_skill_description_3")); GetHost()->NotifyContextualSkillsChanged(std::move(skills_batch_2)); ContinueJsTest(); }
diff --git a/chrome/browser/glic/host/glic_page_handler.cc b/chrome/browser/glic/host/glic_page_handler.cc index 9b18f1a..eed9194b 100644 --- a/chrome/browser/glic/host/glic_page_handler.cc +++ b/chrome/browser/glic/host/glic_page_handler.cc
@@ -206,7 +206,8 @@ return nullptr; } return mojom::SkillPreview::New(skill->id, skill->name, skill->icon, - ToMojomSkillSource(skill->source)); + ToMojomSkillSource(skill->source), + skill->description); } #endif // !BUILDFLAG(IS_ANDROID) @@ -1355,14 +1356,22 @@ // NEEDS_ANDROID_IMPL: (crbug.com/477622144) Remove desktop-only restrictions // from Skills backend. #if !BUILDFLAG(IS_ANDROID) + auto scoped_callback = + mojo::WrapCallbackWithDefaultInvokeIfNotRun(std::move(callback), false); + if (!base::FeatureList::IsEnabled(features::kSkillsEnabled)) { receiver_.ReportBadMessage( "UpdateSkill cannot be called without Skills enabled."); return; } - // TODO(crbug.com/471796872): Add the actual implementation. - NOTIMPLEMENTED(); - std::move(callback).Run(true); + // Get skill by ID from the SkillsService. + skills::SkillsService* skills_service = + skills::SkillsServiceFactory::GetForProfile(profile_); + if (const skills::Skill* skill = + skills_service->GetSkillById(request->id)) { + host().skills_manager().LaunchSkillsDialog(profile_, *skill, + std::move(scoped_callback)); + } #else receiver_.ReportBadMessage("UpdateSkill isn't supported on Android."); #endif // !BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/glic/host/glic_ui.cc b/chrome/browser/glic/host/glic_ui.cc index 2a50009d..4bffcfc8 100644 --- a/chrome/browser/glic/host/glic_ui.cc +++ b/chrome/browser/glic/host/glic_ui.cc
@@ -49,6 +49,10 @@ #include "ui/webui/webui_allowlist.h" #include "ui/webui/webui_util.h" +#if !BUILDFLAG(ENABLE_EXTENSIONS_CORE) +#include "chrome/grit/guest_view_shared_resources_map.h" // nogncheck +#endif // !BUILDFLAG(ENABLE_EXTENSIONS_CORE) + namespace glic { // Enables sending bitmaps across glic for favicons instead of converting to @@ -146,6 +150,13 @@ webui::SetupWebUIDataSource(source, kGlicResources, IDR_GLIC_GLIC_HTML); ConfigureSharedWebUISource(*source); +#if !BUILDFLAG(ENABLE_EXTENSIONS_CORE) + auto bindings = web_ui->GetBindings(); + bindings.Put(content::BindingsPolicyValue::kSlimWebView); + web_ui->SetBindings(bindings); + source->AddResourcePaths(kGuestViewSharedResources); +#endif // !BUILDFLAG(ENABLE_EXTENSIONS_CORE) + for (const auto& resource : kGlicFreResources) { source->AddResourcePath(base::StrCat({"fre/", resource.path}), resource.id); }
diff --git a/chrome/browser/language/android/java/src/org/chromium/chrome/browser/language/AppLocaleUtilsTest.java b/chrome/browser/language/android/java/src/org/chromium/chrome/browser/language/AppLocaleUtilsTest.java index c15a448..1774e15a 100644 --- a/chrome/browser/language/android/java/src/org/chromium/chrome/browser/language/AppLocaleUtilsTest.java +++ b/chrome/browser/language/android/java/src/org/chromium/chrome/browser/language/AppLocaleUtilsTest.java
@@ -24,6 +24,8 @@ /** Tests for the AppLocalUtils class. */ @RunWith(BaseRobolectricTestRunner.class) @Batch(Batch.UNIT_TESTS) +// Fails on SDK 33+ because AppLocaleUtils uses system-managed locales (Android 13+). +@Config(sdk = {29, 32}) public class AppLocaleUtilsTest { @Before public void setUp() { @@ -51,8 +53,6 @@ } // Test setAppLanguagePref. - // TODO(crbug.com/450954710): This test fails on SDK 36. - @Config(sdk = 29) @Test @SmallTest public void testSetAppLanguagePref() {
diff --git a/chrome/browser/metrics/android/background_upload_task.cc b/chrome/browser/metrics/android/background_upload_task.cc index 2cc5cb8..28f9a6b 100644 --- a/chrome/browser/metrics/android/background_upload_task.cc +++ b/chrome/browser/metrics/android/background_upload_task.cc
@@ -26,6 +26,8 @@ base::flat_map<MetricsLogUploader::MetricServiceType, ReportingService*>> g_reporting_service_overrides_for_testing; +static base::NoDestructor<base::OnceClosure> g_task_done_callback_for_testing; + MetricsLogUploader::MetricServiceType TaskIdToMetricServiceType( background_task::TaskIds task_id) { switch (task_id) { @@ -69,15 +71,23 @@ void BackgroundUploadTask::SetReportingServiceForTesting( MetricsLogUploader::MetricServiceType service_type, ReportingService* service) { - if (!service) { - // Empty `service` means to delete the override instead. - g_reporting_service_overrides_for_testing->erase(service_type); - return; - } g_reporting_service_overrides_for_testing->insert_or_assign(service_type, service); } +// static +bool BackgroundUploadTask::UnsetReportingServiceForTesting( + MetricsLogUploader::MetricServiceType service_type) { + return g_reporting_service_overrides_for_testing->erase(service_type); +} + +// static +void BackgroundUploadTask::SetTaskDoneCallbackForTesting( + base::OnceClosure callback) { + CHECK(g_task_done_callback_for_testing->is_null()); + *g_task_done_callback_for_testing = std::move(callback); +} + void BackgroundUploadTask::OnStartTaskInReducedMode( const background_task::TaskParameters& task_params, background_task::TaskFinishedCallback callback, @@ -115,8 +125,36 @@ // services already have their own rescheduling mechanisms. base::OnceClosure done_callback = base::BindOnce(std::move(callback), /*reschedule=*/false); - GetReportingService(task_id_)->SendNextLogNow( - base::PassKey<BackgroundUploadTask>(), std::move(done_callback)); + if (!g_task_done_callback_for_testing->is_null()) { + done_callback = std::move(done_callback) + .Then(std::move(*g_task_done_callback_for_testing)); + } + + ReportingService* reporting_service = GetReportingService(task_id_); + + // Tasks posted to the JobScheduler will actually persist even when the app is + // killed/closed. For example, if a task is pending, but the app is closed, + // the OS will re-start the application whenever it deems the task ready to + // run, and the task will run in this completely new process. This means that + // we may be trying to run a task here that was posted by a previous session, + // and the ReportingService may not be ready for it (e.g. not fully + // initialized yet). In fact, the target ReportingService may not even exist + // if, say, it is gated behind a feature flag (e.g. task was posted in a + // previous session when the feature was enabled, but ran in a new session + // where the feature was disabled). As a result, only actually start the + // upload if the corresponding ReportingService is actually expecting an + // upload. This keeps the behaviour more consistent across platforms, and + // ensures the ReportingService is actually ready to upload (though it is + // technically possible to adapt ReportingService to gracefully handle these + // "unplanned" uploads). + if (!reporting_service || + !reporting_service->background_upload_task_scheduled()) { + std::move(done_callback).Run(); + return; + } + + reporting_service->SendNextLogNow(base::PassKey<BackgroundUploadTask>(), + std::move(done_callback)); } } // namespace metrics
diff --git a/chrome/browser/metrics/android/background_upload_task.h b/chrome/browser/metrics/android/background_upload_task.h index 5d4cc5a..dced472 100644 --- a/chrome/browser/metrics/android/background_upload_task.h +++ b/chrome/browser/metrics/android/background_upload_task.h
@@ -26,11 +26,18 @@ // registered with the browser, but in tests, it may actually have been // scheduled through a custom ReportingService that we manually instantiated. // This helper allows overriding which ReportingService will have its upload - // started. If `service` is null, the override will be deleted instead. + // started. static void SetReportingServiceForTesting( MetricsLogUploader::MetricServiceType service_type, ReportingService* service); + // Removes an override set by SetReportingServiceForTesting(). + static bool UnsetReportingServiceForTesting( + MetricsLogUploader::MetricServiceType service_type); + + // Sets a callback to run when the next upload task has finished running. + static void SetTaskDoneCallbackForTesting(base::OnceClosure callback); + private: // background_task::BackgroundTask: void OnStartTaskInReducedMode(
diff --git a/chrome/browser/metrics/android/background_upload_task_browsertest.cc b/chrome/browser/metrics/android/background_upload_task_browsertest.cc index e1f4977..f9d6ee5 100644 --- a/chrome/browser/metrics/android/background_upload_task_browsertest.cc +++ b/chrome/browser/metrics/android/background_upload_task_browsertest.cc
@@ -11,10 +11,15 @@ #include "base/functional/bind.h" #include "base/functional/callback.h" #include "base/memory/raw_ptr.h" +#include "base/run_loop.h" #include "base/test/metrics/histogram_tester.h" #include "base/test/run_until.h" #include "chrome/browser/android/background_task_scheduler/chrome_background_task_factory.h" #include "chrome/test/base/platform_browser_test.h" +#include "components/background_task_scheduler/background_task_scheduler.h" +#include "components/background_task_scheduler/background_task_scheduler_factory.h" +#include "components/background_task_scheduler/task_ids.h" +#include "components/background_task_scheduler/task_info.h" #include "components/metrics/log_decoder.h" #include "components/metrics/log_store.h" #include "components/metrics/metrics_features.h" @@ -191,8 +196,7 @@ void TearDownOnMainThread() override { // Remove override for good measure. - BackgroundUploadTask::SetReportingServiceForTesting(service_type(), - nullptr); + BackgroundUploadTask::UnsetReportingServiceForTesting(service_type()); PlatformBrowserTest::TearDownOnMainThread(); } @@ -270,6 +274,77 @@ ASSERT_FALSE(client()->uploader()->is_uploading()); } +// Verifies that if the ReportingService is not expecting an upload, the +// background upload task is a no-op. This can happen an upload task for a +// service was scheduled in a previous session/process, and then run in a newly +// run session/process where the ReportingService is not expecting any uploads +// (i.e. hasn't itself triggered the task). +IN_PROC_BROWSER_TEST_P(BackgroundUploadTaskBrowserTest, + ReportingServiceNotReady) { + base::HistogramTester histogram_tester; + + base::RunLoop run_loop; + BackgroundUploadTask::SetTaskDoneCallbackForTesting(run_loop.QuitClosure()); + + // Manually schedule an upload task here, outside of the typical uploading + // loop handled by MetricsUploadScheduler of ReportingService. This simulates + // an "external" task being run (e.g. one scheduled by a previous session). + background_task::OneOffInfo one_off; + background_task::TaskInfo task_info( + reporting_service()->background_upload_task_id(), one_off); + ASSERT_TRUE( + background_task::BackgroundTaskSchedulerFactory::GetScheduler()->Schedule( + task_info)); + // Wait until the upload task runs and terminates. + run_loop.Run(); + + // Verify that we still have the two logs present (none were uploaded), and no + // timing histograms were emitted. + ASSERT_FALSE(client()->uploader()); + ASSERT_FALSE(log_store()->has_staged_log()); + ASSERT_TRUE(log_store()->has_unsent_logs()); + log_store()->StageNextLog(); + log_store()->DiscardStagedLog(); + ASSERT_FALSE(log_store()->has_staged_log()); + ASSERT_TRUE(log_store()->has_unsent_logs()); + log_store()->StageNextLog(); + log_store()->DiscardStagedLog(); + EXPECT_FALSE(log_store()->has_unsent_logs()); + histogram_tester.ExpectTotalCount(expected_histogram_name(), 0); +} + +// Verifies that if the ReportingService does not exist, the background upload +// task is a no-op. This can happen an upload task for a service was scheduled +// in a previous session/process, and then run in a newly run session/process +// where said service does not exist anymore (e.g. due to being disabled by a +// feature). +IN_PROC_BROWSER_TEST_P(BackgroundUploadTaskBrowserTest, ReportingServiceNull) { + base::HistogramTester histogram_tester; + + base::RunLoop run_loop; + BackgroundUploadTask::SetTaskDoneCallbackForTesting(run_loop.QuitClosure()); + // Set the ReportingService to null. + BackgroundUploadTask::SetReportingServiceForTesting(service_type(), nullptr); + + // Manually schedule an upload task here, outside of the typical uploading + // loop handled by MetricsUploadScheduler of ReportingService. This simulates + // an "external" task being run (e.g. one scheduled by a previous session). + background_task::OneOffInfo one_off; + background_task::TaskInfo task_info( + reporting_service()->background_upload_task_id(), one_off); + ASSERT_TRUE( + background_task::BackgroundTaskSchedulerFactory::GetScheduler()->Schedule( + task_info)); + // Wait until the upload task runs and terminates. + run_loop.Run(); + + // We did not crash, which indicates that the upload task was properly + // ignored. Verify further that no timing histograms were emitted. + histogram_tester.ExpectTotalCount(expected_histogram_name(), 0); + + // The ReportingService override is unset in TearDownOnMainThread(). +} + INSTANTIATE_TEST_SUITE_P( BackgroundUploadTaskBrowserTests, BackgroundUploadTaskBrowserTest,
diff --git a/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/StandardNotificationBuilderTest.java b/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/StandardNotificationBuilderTest.java index 2297977..41231c3 100644 --- a/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/StandardNotificationBuilderTest.java +++ b/chrome/browser/notifications/android/java/src/org/chromium/chrome/browser/notifications/StandardNotificationBuilderTest.java
@@ -177,7 +177,7 @@ notification.extras.getString(Notification.EXTRA_TEMPLATE)); } - // TODO(crbug.com/450954710): This test fails on SDK 36. + // TODO(crbug.com/481748850): Fix failure on SDK 30+ due to icon bitmap comparison differences. @Config(sdk = 29) @Test @Feature({"Browser", "Notifications"})
diff --git a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/edge_to_edge/TopInsetCoordinatorUnitTest.java b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/edge_to_edge/TopInsetCoordinatorUnitTest.java index ce86b9d5..017b8a8 100644 --- a/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/edge_to_edge/TopInsetCoordinatorUnitTest.java +++ b/chrome/browser/ntp_customization/junit/src/org/chromium/chrome/browser/ntp_customization/edge_to_edge/TopInsetCoordinatorUnitTest.java
@@ -46,6 +46,7 @@ import org.chromium.base.supplier.SettableNullableObservableSupplier; import org.chromium.base.test.BaseRobolectricTestRule; import org.chromium.base.test.BaseRobolectricTestRunner; +import org.chromium.base.test.util.Restriction; import org.chromium.chrome.browser.layouts.LayoutStateProvider; import org.chromium.chrome.browser.layouts.LayoutType; import org.chromium.chrome.browser.ntp_customization.NtpCustomizationConfigManager; @@ -60,10 +61,12 @@ import org.chromium.chrome.browser.ui.edge_to_edge.TopInsetProvider; import org.chromium.chrome.browser.ui.native_page.NativePage; import org.chromium.ui.insets.InsetObserver; +import org.chromium.ui.test.util.DeviceRestriction; import org.chromium.url.JUnitTestGURLs; /** Unit tests for {@link TopInsetCoordinator} */ @RunWith(BaseRobolectricTestRunner.class) +@Restriction(DeviceRestriction.RESTRICTION_TYPE_NON_AUTO) public class TopInsetCoordinatorUnitTest { @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); @@ -127,7 +130,8 @@ mTopInsetCoordinator.destroy(); } - // TODO(crbug.com/450954710): This test fails on SDK 36. + // TODO(crbug.com/481750031): Fix failure on SDK 30+ due to window inset consumption + // differences. @Config(sdk = 29) @Test @SuppressWarnings("DirectInvocationOnMock")
diff --git a/chrome/browser/password_manager/android/pwm_disabled/java/src/org/chromium/chrome/browser/pwm_disabled/PasswordCsvDownloadFlowControllerTest.java b/chrome/browser/password_manager/android/pwm_disabled/java/src/org/chromium/chrome/browser/pwm_disabled/PasswordCsvDownloadFlowControllerTest.java index f86fcdd..7163dfb1 100644 --- a/chrome/browser/password_manager/android/pwm_disabled/java/src/org/chromium/chrome/browser/pwm_disabled/PasswordCsvDownloadFlowControllerTest.java +++ b/chrome/browser/password_manager/android/pwm_disabled/java/src/org/chromium/chrome/browser/pwm_disabled/PasswordCsvDownloadFlowControllerTest.java
@@ -298,8 +298,6 @@ histogramWatcher.assertExpected(); } - // TODO(crbug.com/450954710): This test fails on SDK 36. - @Config(sdk = 29) @Test public void testDownloadFlow() throws IOException { HistogramWatcher histogramWatcher =
diff --git a/chrome/browser/permissions/permission_blocked_message_delegate_android.cc b/chrome/browser/permissions/permission_blocked_message_delegate_android.cc index fa20022..4af4ec6f 100644 --- a/chrome/browser/permissions/permission_blocked_message_delegate_android.cc +++ b/chrome/browser/permissions/permission_blocked_message_delegate_android.cc
@@ -13,8 +13,11 @@ #include "chrome/browser/permissions/quiet_notification_permission_ui_state.h" #include "chrome/browser/permissions/quiet_permission_prompt_model_android.h" #include "chrome/grit/generated_resources.h" +#include "components/content_settings/core/common/content_settings.h" #include "components/content_settings/core/common/content_settings_types.h" #include "components/messages/android/message_dispatcher_bridge.h" +#include "components/permissions/android/android_permission_util.h" +#include "components/permissions/android/permission_prompt/permission_dialog_controller.h" #include "components/permissions/android/permission_prompt/permission_prompt_android.h" #include "components/permissions/permission_request.h" #include "components/permissions/permission_request_manager.h" @@ -24,9 +27,31 @@ #include "components/strings/grit/components_strings.h" #include "components/url_formatter/elide_url.h" #include "content/public/browser/web_contents.h" +#include "ui/android/window_android.h" #include "ui/base/l10n/l10n_util.h" #include "ui/strings/grit/ui_strings.h" +namespace { + +using PrimaryButtonBehavior = + QuietPermissionPromptModelAndroid::PrimaryButtonBehavior; +using SecondaryButtonBehavior = + QuietPermissionPromptModelAndroid::SecondaryButtonBehavior; + +void ShowLoudClapperDialogResultIcon(content::WebContents* web_contents, + ContentSetting setting) { + ui::WindowAndroid* window = web_contents->GetTopLevelNativeWindow(); + if (!window) { + return; + } + + permissions::PermissionDialogController::ShowLoudClapperDialogResultIcon( + base::android::AttachCurrentThread(), window->GetJavaObject(), + static_cast<int>(setting)); +} + +} // namespace + PermissionBlockedMessageDelegate::PermissionBlockedMessageDelegate( content::WebContents* web_contents, std::unique_ptr<Delegate> delegate) @@ -139,6 +164,20 @@ l10n_util::GetStringFUTF16(IDS_NOTIFICATION_DESCRIPTION_MESSAGE_UI, requesting_origin_string_formatted)); message_->SetPrimaryButtonText( + l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW)); + + message_->SetSecondaryIconResourceId( + ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_MESSAGE_SETTINGS)); + + message_->SetSecondaryMenuItemSelectedCallback(base::BindRepeating( + &PermissionBlockedMessageDelegate::HandleLoudUiSecondayMenuItemClicked, + base::Unretained(this))); + message_->AddSecondaryMenuItem( + static_cast<int>(LoudUiSecondayMenuItems::kDeny), + /*resource_id=*/0, l10n_util::GetStringUTF16(IDS_PERMISSION_DONT_ALLOW)); + message_->AddSecondaryMenuItem( + static_cast<int>(LoudUiSecondayMenuItems::kManage), + /*resource_id=*/0, l10n_util::GetStringUTF16(IDS_NOTIFICATION_CTA_MESSAGE_UI)); messages::MessageDispatcherBridge::Get()->EnqueueMessage( @@ -192,6 +231,30 @@ delegate_->Deny(); } +void PermissionBlockedMessageDelegate::HandleLoudUiSecondayMenuItemClicked( + int command_id) { + switch (static_cast<LoudUiSecondayMenuItems>(command_id)) { + case LoudUiSecondayMenuItems::kDeny: + ShowLoudClapperDialogResultIcon(web_contents_, CONTENT_SETTING_BLOCK); + delegate_->Deny(); + break; + case LoudUiSecondayMenuItems::kManage: + if (!dialog_controller_) { + dialog_controller_ = + std::make_unique<PermissionBlockedDialogController>( + /*delegate=*/this, web_contents_); + } + dialog_controller_->ShowPageInfo(); + if (message_) { + messages::MessageDispatcherBridge::Get()->DismissMessage( + message_.get(), messages::DismissReason::SECONDARY_ACTION); + } + break; + default: + NOTREACHED(); + } +} + void PermissionBlockedMessageDelegate::HandleManageClick() { if (!dialog_controller_) { dialog_controller_ = std::make_unique<PermissionBlockedDialogController>( @@ -227,26 +290,26 @@ } void PermissionBlockedMessageDelegate::HandleLoudPrimaryActionClick() { - if (!dialog_controller_) { - dialog_controller_ = std::make_unique<PermissionBlockedDialogController>( - this, web_contents_); - } - - base::UmaHistogramBoolean("Permissions.ClapperLoud.MessageUI.Manage", true); - - dialog_controller_->ShowPageInfo(); messages::MessageDispatcherBridge::Get()->DismissMessage( message_.get(), messages::DismissReason::PRIMARY_ACTION); + + ResolveWithOSPrompt(GetContentSettingsType()); +} + +void PermissionBlockedMessageDelegate::ResolveWithOSPrompt( + ContentSettingsType content_settings_type) { + permissions::ResolvePermissionWithOSPrompt(web_contents_, + content_settings_type); } void PermissionBlockedMessageDelegate::HandleLoudDismissCallback( messages::DismissReason reason) { message_.reset(); - // There is no secondary action for the message UI for loud prompts. - // There is only the primary "Manage" action. + // When message is dismissed by secondary action, |permission_prompt_| should + // be reset when the dialog is dismissed. if (reason == messages::DismissReason::SECONDARY_ACTION) { - NOTREACHED(); + return; } dialog_controller_.reset(); @@ -261,6 +324,7 @@ base::UmaHistogramBoolean("Permissions.ClapperLoud.MessageUI.Dismiss", true); + ShowLoudClapperDialogResultIcon(web_contents_, CONTENT_SETTING_BLOCK); delegate_->Deny(); }
diff --git a/chrome/browser/permissions/permission_blocked_message_delegate_android.h b/chrome/browser/permissions/permission_blocked_message_delegate_android.h index 21afe247..e78ba18 100644 --- a/chrome/browser/permissions/permission_blocked_message_delegate_android.h +++ b/chrome/browser/permissions/permission_blocked_message_delegate_android.h
@@ -58,7 +58,7 @@ }; PermissionBlockedMessageDelegate(content::WebContents* web_contents, - std::unique_ptr<Delegate> delegate); + std::unique_ptr<Delegate> delegate); ~PermissionBlockedMessageDelegate() override; protected: @@ -74,9 +74,16 @@ void OnWebContentsFocused( content::RenderWidgetHost* render_widget_host) override; + virtual void ResolveWithOSPrompt(ContentSettingsType content_settings_type); + private: friend class PermissionBlockedMessageDelegateAndroidTest; + enum class LoudUiSecondayMenuItems { + kDeny = 0, + kManage = 1, + }; + void InitializeLoudUI(); void InitializeQuietUI(); void HandleQuietPrimaryActionClick(); @@ -84,6 +91,7 @@ void HandleManageClick(); void HandleLoudPrimaryActionClick(); void HandleLoudDismissCallback(messages::DismissReason reason); + void HandleLoudUiSecondayMenuItemClicked(int command_id); void DismissInternal(); @@ -92,11 +100,13 @@ std::unique_ptr<messages::MessageWrapper> message_; std::unique_ptr<PermissionBlockedDialogController> dialog_controller_; raw_ptr<content::WebContents> web_contents_ = nullptr; - std::unique_ptr<Delegate> delegate_; // Whether we should re-show the dialog to users when users return to the tab. bool should_reshow_dialog_on_focus_ = false; bool has_interacted_with_dialog_ = false; + + protected: + std::unique_ptr<Delegate> delegate_; }; #endif // CHROME_BROWSER_PERMISSIONS_PERMISSION_BLOCKED_MESSAGE_DELEGATE_ANDROID_H_
diff --git a/chrome/browser/permissions/permission_blocked_message_delegate_android_unittest.cc b/chrome/browser/permissions/permission_blocked_message_delegate_android_unittest.cc index 3fede3b..3642042 100644 --- a/chrome/browser/permissions/permission_blocked_message_delegate_android_unittest.cc +++ b/chrome/browser/permissions/permission_blocked_message_delegate_android_unittest.cc
@@ -71,6 +71,20 @@ MOCK_METHOD(ContentSettingsType, GetContentSettingsType, (), (override)); }; +class TestPermissionBlockedMessageDelegate + : public PermissionBlockedMessageDelegate { + public: + TestPermissionBlockedMessageDelegate(content::WebContents* web_contents, + std::unique_ptr<Delegate> delegate) + : PermissionBlockedMessageDelegate(web_contents, std::move(delegate)) {} + ~TestPermissionBlockedMessageDelegate() override = default; + + void ResolveWithOSPrompt(ContentSettingsType content_settings_type) override { + // Simulate Java callback calling Accept on native. + delegate_->Accept(); + } +}; + class PermissionBlockedMessageDelegateAndroidTest : public ChromeRenderViewHostTestHarness { public: @@ -91,7 +105,7 @@ } void ShowMessage(std::unique_ptr<MockDelegate> delegate) { - controller_ = std::make_unique<PermissionBlockedMessageDelegate>( + controller_ = std::make_unique<TestPermissionBlockedMessageDelegate>( web_contents(), std::move(delegate)); } @@ -120,8 +134,8 @@ std::unique_ptr<MockPermissionPromptAndroid>& prompt_storage, const std::vector<base::WeakPtr<permissions::PermissionRequest>>& requests) { - prompt_storage = std::make_unique<MockPermissionPromptAndroid>( - web_contents(), manager_); + prompt_storage = + std::make_unique<MockPermissionPromptAndroid>(web_contents(), manager_); EXPECT_CALL(*prompt_storage, Requests) .WillRepeatedly(testing::ReturnRef(requests)); @@ -289,7 +303,7 @@ EXPECT_NE(std::u16string::npos, message->GetDescription().find(origin)); // Verify Primary Button - EXPECT_EQ(l10n_util::GetStringUTF16(IDS_NOTIFICATION_CTA_MESSAGE_UI), + EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PERMISSION_ALLOW), message->GetPrimaryButtonText()); } @@ -312,20 +326,19 @@ EXPECT_CALL(*delegate, GetContentSettingsType) .WillRepeatedly(testing::Return(ContentSettingsType::NOTIFICATIONS)); + // Expect Accept() to be called on primary action (Allow) + EXPECT_CALL(*delegate, Accept()).Times(1); + ExpectEnqueued(); // Show Message ShowMessage(std::move(delegate)); - // Trigger Primary Action (Manage) + // Trigger Primary Action (Allow) TriggerLoudPrimaryAction(); // Message should be dismissed EXPECT_EQ(nullptr, GetMessageWrapper()); - - // Verify Histogram - histogram_tester.ExpectBucketCount("Permissions.ClapperLoud.MessageUI.Manage", - true, 1); } TEST_F(PermissionBlockedMessageDelegateAndroidTest, LoudUI_DismissByGesture) {
diff --git a/chrome/browser/resources/contextual_tasks/app.html.ts b/chrome/browser/resources/contextual_tasks/app.html.ts index a8525e33..65ccd15 100644 --- a/chrome/browser/resources/contextual_tasks/app.html.ts +++ b/chrome/browser/resources/contextual_tasks/app.html.ts
@@ -21,7 +21,7 @@ </top-toolbar> </div> `} - <webview id="threadFrame" allowtransparency="on"></webview> + <webview id="threadFrame" allowtransparency="on" partition="persist:contextual-tasks"></webview> <ghost-loader id="ghostLoader"></ghost-loader> ${this.isErrorDialogVisible_ ? html`<contextual-tasks-error-dialog></contextual-tasks-error-dialog>` : ''}
diff --git a/chrome/browser/resources/contextual_tasks/icons.html.ts b/chrome/browser/resources/contextual_tasks/icons.html.ts index 6670607..d8481a3 100644 --- a/chrome/browser/resources/contextual_tasks/icons.html.ts +++ b/chrome/browser/resources/contextual_tasks/icons.html.ts
@@ -14,8 +14,8 @@ <g id="edit_square"> <path d="M 5.101562 20.773438 C 4.585938 20.773438 4.144531 20.589844 3.777344 20.222656 C 3.410156 19.855469 3.226562 19.414062 3.226562 18.898438 L 3.226562 5.101562 C 3.226562 4.585938 3.410156 4.144531 3.777344 3.777344 C 4.144531 3.410156 4.585938 3.226562 5.101562 3.226562 L 13.949219 3.226562 L 12.074219 5.101562 L 5.101562 5.101562 L 5.101562 18.898438 L 18.898438 18.898438 L 18.898438 11.886719 L 20.773438 10.011719 L 20.773438 18.898438 C 20.773438 19.414062 20.589844 19.855469 20.222656 20.222656 C 19.855469 20.589844 19.414062 20.773438 18.898438 20.773438 Z M 12 12 Z M 9.0625 14.9375 L 9.0625 10.773438 L 18.273438 1.5625 C 18.464844 1.371094 18.675781 1.230469 18.898438 1.144531 C 19.125 1.054688 19.355469 1.011719 19.59375 1.011719 C 19.847656 1.011719 20.085938 1.054688 20.3125 1.144531 C 20.539062 1.230469 20.746094 1.371094 20.9375 1.5625 L 22.425781 3.074219 C 22.601562 3.265625 22.734375 3.476562 22.824219 3.699219 C 22.917969 3.925781 22.960938 4.160156 22.960938 4.398438 C 22.960938 4.640625 22.921875 4.871094 22.835938 5.09375 C 22.753906 5.316406 22.617188 5.519531 22.425781 5.710938 L 13.226562 14.9375 Z M 21.113281 4.398438 L 19.613281 2.886719 Z M 10.9375 13.0625 L 12.4375 13.0625 L 18.300781 7.1875 L 17.550781 6.4375 L 16.789062 5.699219 L 10.9375 11.539062 Z M 17.550781 6.4375 L 16.789062 5.699219 L 17.550781 6.4375 L 18.300781 7.1875 Z M 17.550781 6.4375"/> </g> - <g id="schedule_auto"> - <path d="M 12 12 Z M 12 2.25 C 13.347656 2.25 14.617188 2.507812 15.800781 3.019531 C 16.988281 3.53125 18.019531 4.226562 18.898438 5.105469 C 19.773438 5.984375 20.46875 7.015625 20.980469 8.203125 C 21.492188 9.386719 21.75 10.652344 21.75 12 C 21.75 13.347656 21.492188 14.617188 20.980469 15.800781 C 20.46875 16.988281 19.773438 18.019531 18.894531 18.898438 C 18.015625 19.773438 16.984375 20.46875 15.796875 20.980469 C 14.613281 21.492188 13.347656 21.75 12 21.75 L 11.789062 21.75 L 11.789062 19.875 L 12 19.875 C 14.191406 19.875 16.050781 19.109375 17.582031 17.582031 C 19.109375 16.050781 19.875 14.191406 19.875 12 C 19.875 9.808594 19.109375 7.949219 17.582031 6.417969 C 16.050781 4.890625 14.191406 4.125 12 4.125 C 9.808594 4.125 7.949219 4.890625 6.417969 6.417969 C 4.890625 7.949219 4.125 9.808594 4.125 12 L 4.125 12.210938 L 2.25 12.210938 L 2.25 12 C 2.25 10.652344 2.507812 9.382812 3.019531 8.199219 C 3.53125 7.011719 4.226562 5.980469 5.105469 5.101562 C 5.984375 4.226562 7.015625 3.53125 8.203125 3.019531 C 9.386719 2.507812 10.652344 2.25 12 2.25 Z M 5.460938 22.988281 C 5.445312 22.988281 5.378906 22.9375 5.265625 22.839844 C 5 21.832031 4.503906 20.960938 3.773438 20.226562 C 3.039062 19.492188 2.171875 18.996094 1.160156 18.734375 C 1.128906 18.714844 1.078125 18.652344 1.011719 18.535156 C 1.011719 18.503906 1.0625 18.4375 1.160156 18.339844 C 2.167969 18.078125 3.039062 17.578125 3.773438 16.847656 C 4.507812 16.117188 5.003906 15.246094 5.265625 14.238281 C 5.285156 14.203125 5.347656 14.152344 5.464844 14.085938 C 5.496094 14.085938 5.5625 14.136719 5.660156 14.234375 C 5.941406 15.242188 6.441406 16.113281 7.164062 16.847656 C 7.886719 17.582031 8.753906 18.082031 9.761719 18.34375 C 9.796875 18.34375 9.847656 18.40625 9.914062 18.539062 C 9.914062 18.554688 9.863281 18.621094 9.765625 18.734375 C 8.757812 19 7.886719 19.496094 7.152344 20.226562 C 6.417969 20.960938 5.917969 21.828125 5.65625 22.839844 C 5.65625 22.871094 5.59375 22.921875 5.460938 22.988281 Z M 15.273438 16.601562 L 16.601562 15.273438 L 12.949219 11.621094 L 12.949219 7.074219 L 11.074219 7.074219 L 11.074219 12.375 Z M 15.273438 16.601562"/> + <g id="notes_spark" viewBox="0 -960 960 960"> + <path d="M733.98-46q-.98 0-7.89-5.94-15.78-60.42-59.69-104.49-43.9-44.07-104.44-59.76-1.99-.98-5.96-7.85 0-1.96 5.94-7.88 60.42-15.78 104.49-59.68 44.07-43.9 59.76-104.44.98-1.99 7.85-5.96 1.96 0 7.88 5.94 16.77 60.42 60.17 104.49 43.41 44.07 103.95 59.75 1.99 0 5.96 7.84 0 .98-5.94 7.89-60.42 15.78-104.49 59.69-44.07 43.9-59.75 104.44 0 1.99-7.84 5.96ZM130-288v-75h340v75H130Zm0-195v-75h700v75H130Zm0-195v-75h700v75H130Z"/> </g> <g id="open_in_full_tab" viewBox="0 -960 960 960"> <path d="M144-144v-288h72v165l477-477H528v-72h288v288h-72v-165L267-216h165v72H144Z"/></path>
diff --git a/chrome/browser/resources/contextual_tasks/top_toolbar.html.ts b/chrome/browser/resources/contextual_tasks/top_toolbar.html.ts index 08c5c130..70afb76 100644 --- a/chrome/browser/resources/contextual_tasks/top_toolbar.html.ts +++ b/chrome/browser/resources/contextual_tasks/top_toolbar.html.ts
@@ -32,7 +32,7 @@ </cr-icon-button> <cr-icon-button id="threadHistoryButton" @click="${this.onThreadHistoryClick_}" - iron-icon="contextual_tasks:schedule_auto" + iron-icon="contextual_tasks:notes_spark" class="no-overlap" title="$i18n{threadHistoryTooltip}" aria-label="i18n{threadHistoryTooltip}"> </cr-icon-button>
diff --git a/chrome/browser/resources/glic/BUILD.gn b/chrome/browser/resources/glic/BUILD.gn index e00d448..8b4117ac 100644 --- a/chrome/browser/resources/glic/BUILD.gn +++ b/chrome/browser/resources/glic/BUILD.gn
@@ -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("//components/guest_view/buildflags/buildflags.gni") +import("//extensions/buildflags/buildflags.gni") import("//ui/webui/resources/tools/build_webui.gni") import("//ui/webui/resources/tools/optimize_webui.gni") @@ -87,6 +89,15 @@ "/fre/*|./fre/tsc/*", ] + if (enable_guest_view && !enable_extensions_core) { + ts_deps += [ "//chrome/browser/resources/guest_view_shared:build_ts" ] + + ts_path_mappings += + [ "/shared/guest_view/*|" + rebase_path( + "$root_gen_dir/chrome/browser/resources/guest_view_shared/tsc/*", + target_gen_dir) ] + } + ts_composite = true }
diff --git a/chrome/browser/resources/glic/fre/fre_app_controller.ts b/chrome/browser/resources/glic/fre/fre_app_controller.ts index 572819d..736f9f7 100644 --- a/chrome/browser/resources/glic/fre/fre_app_controller.ts +++ b/chrome/browser/resources/glic/fre/fre_app_controller.ts
@@ -5,6 +5,8 @@ import {EventTracker} from '//resources/js/event_tracker.js'; import {loadTimeData} from '//resources/js/load_time_data.js'; import {GlicRequestHeaderInjector} from '/shared/glic_request_headers.js'; +import {createWebView, isFullWebView} from '/shared/web_view_type.js'; +import type {WebViewType} from '/shared/web_view_type.js'; import {assert} from 'chrome://resources/js/assert.js'; import {getRequiredElement} from 'chrome://resources/js/util.js'; @@ -62,9 +64,9 @@ // Created from constructor and never null since the destructor replaces it // with an empty <webview>. - private webview: chrome.webviewTag.WebView; + private webview: WebViewType; private webviewEventTracker = new EventTracker(); - private glicRequestHeaderInjector: GlicRequestHeaderInjector|undefined; + private glicRequestHeaderInjector?: GlicRequestHeaderInjector; private freHandler: FrePageHandlerRemote; // When entering loading state, this represents the earliest timestamp at @@ -416,9 +418,8 @@ window.resizeTo(e.newWidth, e.newHeight); } - private createWebview(): chrome.webviewTag.WebView { - const webview = - document.createElement('webview') as chrome.webviewTag.WebView; + private createWebview(): WebViewType { + const webview = createWebView(); webview.id = 'freGuestFrame'; // TODO(crbug.com/408475473): Update the webviewTag definition to be able to // define properties rather than using setAttribute. @@ -438,10 +439,13 @@ webview.setAttribute('minheight', MIN_HEIGHT.toString()); webview.setAttribute('maxheight', window.screen.availHeight.toString()); } - this.glicRequestHeaderInjector = new GlicRequestHeaderInjector( - webview, loadTimeData.getString('chromeVersion'), - loadTimeData.getString('chromeChannel'), - loadTimeData.getString('glicHeaderRequestTypes')); + + if (isFullWebView(webview)) { + this.glicRequestHeaderInjector = new GlicRequestHeaderInjector( + webview, loadTimeData.getString('chromeVersion'), + loadTimeData.getString('chromeChannel'), + loadTimeData.getString('glicHeaderRequestTypes')); + } this.webviewContainer.appendChild(webview); @@ -519,7 +523,7 @@ destroyWebview(): void { this.webviewEventTracker.removeAll(); - if (this.glicRequestHeaderInjector) { + if (this.glicRequestHeaderInjector !== undefined) { this.glicRequestHeaderInjector.destroy(); this.glicRequestHeaderInjector = undefined; }
diff --git a/chrome/browser/resources/glic/glic_api/glic_api.ts b/chrome/browser/resources/glic/glic_api/glic_api.ts index ec29594..4a273a9 100644 --- a/chrome/browser/resources/glic/glic_api/glic_api.ts +++ b/chrome/browser/resources/glic/glic_api/glic_api.ts
@@ -2078,6 +2078,8 @@ icon: string; /** The source of the skill. */ source: SkillSource; + /** The description of the skill. */ + description?: string; } /** Represents a single skill. */
diff --git a/chrome/browser/resources/glic/shared/BUILD.gn b/chrome/browser/resources/glic/shared/BUILD.gn index 2f15879..6ee7dbd 100644 --- a/chrome/browser/resources/glic/shared/BUILD.gn +++ b/chrome/browser/resources/glic/shared/BUILD.gn
@@ -2,20 +2,26 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//components/guest_view/buildflags/buildflags.gni") +import("//extensions/buildflags/buildflags.gni") import("//ui/webui/resources/tools/build_webui.gni") build_webui("build") { grd_prefix = "glic_shared" grd_resource_path_prefix = "shared" - ts_files = [ "glic_request_headers.ts" ] - - ts_deps = [ - "//ui/webui/resources/cr_elements:build_ts", - "//ui/webui/resources/js:build_ts", - "//ui/webui/resources/mojo:build_ts", + ts_files = [ + "glic_request_headers.ts", + "web_view_type.ts", ] + ts_deps = [ "//chrome/browser/resources/guest_view_shared:build_ts" ] + + ts_path_mappings = + [ "/shared/guest_view/*|" + rebase_path( + "$root_gen_dir/chrome/browser/resources/guest_view_shared/tsc/*", + target_gen_dir) ] + ts_definitions = [ "//tools/typescript/definitions/chrome_event.d.ts", "//tools/typescript/definitions/web_request.d.ts",
diff --git a/chrome/browser/resources/glic/shared/web_view_type.ts b/chrome/browser/resources/glic/shared/web_view_type.ts new file mode 100644 index 0000000..aa930d21 --- /dev/null +++ b/chrome/browser/resources/glic/shared/web_view_type.ts
@@ -0,0 +1,30 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This library allows you to obtain a chrome.webviewTab.WebView element when +// extensions are enabled, or a SlimWebViewElement when they are not. + +// <if expr="not enable_extensions_core"> +import '/shared/guest_view/slim_web_view.js'; + +// </if> +// Importing a type doesn't have a runtime effect, because these imports are +// removed at compile time. We add this import just to satisfy the type checker. +import type {SlimWebViewElement} from '/shared/guest_view/slim_web_view.js'; + +export type WebViewType = chrome.webviewTag.WebView|SlimWebViewElement; + +export function createWebView(): WebViewType { + if ('WebView' in window) { + return document.createElement('webview') as WebViewType; + } + return document.createElement('slim-webview'); +} + +export function isFullWebView(webview: WebViewType): + webview is chrome.webviewTag.WebView { + // Bypass field check because WebView is added dynamically to the window + // object. + return webview.constructor === (window as any).WebView; +}
diff --git a/chrome/browser/resources/glic/webview.ts b/chrome/browser/resources/glic/webview.ts index f7d6be26..2a16e848f 100644 --- a/chrome/browser/resources/glic/webview.ts +++ b/chrome/browser/resources/glic/webview.ts
@@ -5,6 +5,8 @@ import {EventTracker} from '//resources/js/event_tracker.js'; import {loadTimeData} from '//resources/js/load_time_data.js'; import {GlicRequestHeaderInjector} from '/shared/glic_request_headers.js'; +import {createWebView, isFullWebView} from '/shared/web_view_type.js'; +import type {WebViewType} from '/shared/web_view_type.js'; import type {ChromeEvent} from '/tools/typescript/definitions/chrome_event.js'; import type {BrowserProxyImpl} from './browser_proxy.js'; @@ -113,7 +115,7 @@ // Creates and manages the <webview> element, and the GlicApiHost which // communicates with it. export class WebviewController { - webview: chrome.webviewTag.WebView; + webview: WebViewType; private host?: GlicApiHost; private communicator?: GlicApiCommunicator; private hostSubscriber?: Subscriber; @@ -122,7 +124,7 @@ private webClientState = ObservableValue.withValue(WebClientState.UNINITIALIZED); private oneMinuteTimer = new OneShotTimer(1000 * 60); - private glicRequestHeaderInjector: GlicRequestHeaderInjector; + private glicRequestHeaderInjector?: GlicRequestHeaderInjector; constructor( private readonly container: HTMLElement, @@ -131,26 +133,31 @@ private hostEmbedder: ApiHostEmbedder, private persistentState: WebviewPersistentState, ) { - this.webview = - document.createElement('webview') as chrome.webviewTag.WebView; + this.webview = createWebView(); - this.glicRequestHeaderInjector = new GlicRequestHeaderInjector( - this.webview, loadTimeData.getString('chromeVersion'), - loadTimeData.getString('chromeChannel'), - loadTimeData.getString('glicHeaderRequestTypes')); + if (isFullWebView(this.webview)) { + this.glicRequestHeaderInjector = new GlicRequestHeaderInjector( + this.webview, loadTimeData.getString('chromeVersion'), + loadTimeData.getString('chromeChannel'), + loadTimeData.getString('glicHeaderRequestTypes')); - // Intercept all main frame requests, and block them if they are not allowed - // origins. - const onBeforeRequest = this.onBeforeRequest.bind(this); - this.webview.request.onBeforeRequest.addListener( - onBeforeRequest, { - types: [ResourceType.MAIN_FRAME], - urls: ['<all_urls>'], - }, - ['blocking']); - this.onDestroy.push(() => { - this.webview.request.onBeforeRequest.removeListener(onBeforeRequest); - }); + // Intercept all main frame requests, and block them if they are not + // allowed origins. + const onBeforeRequest = this.onBeforeRequest.bind(this); + this.webview.request.onBeforeRequest.addListener( + onBeforeRequest, { + types: [ResourceType.MAIN_FRAME], + urls: ['<all_urls>'], + }, + ['blocking']); + this.onDestroy.push(() => { + // Need to check the type again as this function runs in a different + // scope. + if (isFullWebView(this.webview)) { + this.webview.request.onBeforeRequest.removeListener(onBeforeRequest); + } + }); + } this.webview.id = 'guestFrame'; this.webview.setAttribute('partition', 'persist:glicpart'); @@ -186,7 +193,10 @@ } destroy() { - this.glicRequestHeaderInjector.destroy(); + if (this.glicRequestHeaderInjector !== undefined) { + this.glicRequestHeaderInjector.destroy(); + this.glicRequestHeaderInjector = undefined; + } this.oneMinuteTimer.reset(); if (this.host) { chrome.metricsPrivate.recordEnumerationValue(
diff --git a/chrome/browser/resources/guest_view_shared/BUILD.gn b/chrome/browser/resources/guest_view_shared/BUILD.gn new file mode 100644 index 0000000..db1eb1f1 --- /dev/null +++ b/chrome/browser/resources/guest_view_shared/BUILD.gn
@@ -0,0 +1,27 @@ +# Copyright 2026 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//components/guest_view/buildflags/buildflags.gni") +import("//ui/webui/resources/tools/build_webui.gni") + +assert(enable_guest_view) + +build_webui("build") { + grd_prefix = "guest_view_shared" + grd_resource_path_prefix = "shared/guest_view" + + css_files = [ "slim_web_view.css" ] + + ts_files = [ + "slim_web_view.ts", + "slim_web_view.html.ts", + ] + + ts_definitions = + [ "//tools/typescript/definitions/chrome_slim_web_view_private.d.ts" ] + + ts_deps = [ "//third_party/lit/v3_0:build_ts" ] + + ts_composite = true +}
diff --git a/chrome/browser/resources/guest_view_shared/DIR_METADATA b/chrome/browser/resources/guest_view_shared/DIR_METADATA new file mode 100644 index 0000000..38cda2d --- /dev/null +++ b/chrome/browser/resources/guest_view_shared/DIR_METADATA
@@ -0,0 +1 @@ +mixins: "//components/guest_view/COMMON_METADATA"
diff --git a/chrome/browser/resources/guest_view_shared/OWNERS b/chrome/browser/resources/guest_view_shared/OWNERS new file mode 100644 index 0000000..b6265060 --- /dev/null +++ b/chrome/browser/resources/guest_view_shared/OWNERS
@@ -0,0 +1 @@ +file://components/guest_view/browser/slim_web_view/OWNERS
diff --git a/chrome/browser/resources/guest_view_shared/slim_web_view.css b/chrome/browser/resources/guest_view_shared/slim_web_view.css new file mode 100644 index 0000000..b344bf905 --- /dev/null +++ b/chrome/browser/resources/guest_view_shared/slim_web_view.css
@@ -0,0 +1,16 @@ +/* Copyright 2026 The Chromium Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + + /* #css_wrapper_metadata_start + * #type=style-lit + * #scheme=relative + * #css_wrapper_metadata_end */ + + iframe { + width: 100%; + height: 100%; + border: 0; + margin: 0; + padding: 0; + }
diff --git a/chrome/browser/resources/guest_view_shared/slim_web_view.html.ts b/chrome/browser/resources/guest_view_shared/slim_web_view.html.ts new file mode 100644 index 0000000..f17c806a --- /dev/null +++ b/chrome/browser/resources/guest_view_shared/slim_web_view.html.ts
@@ -0,0 +1,11 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import {html} from '//resources/lit/v3_0/lit.rollup.js'; + +import type {SlimWebViewElement} from './slim_web_view.js'; + +export function getHtml(this: SlimWebViewElement) { + return html`<iframe></iframe>`; +}
diff --git a/chrome/browser/resources/guest_view_shared/slim_web_view.ts b/chrome/browser/resources/guest_view_shared/slim_web_view.ts new file mode 100644 index 0000000..a91cb5d3 --- /dev/null +++ b/chrome/browser/resources/guest_view_shared/slim_web_view.ts
@@ -0,0 +1,56 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js'; + +import {getCss} from './slim_web_view.css.js'; +import {getHtml} from './slim_web_view.html.js'; + +export interface SlimWebViewElement { + $: { + input: HTMLElement, + }; +} + +export class SlimWebViewElement extends CrLitElement { + static get is() { + // TODO(crbug.com/460804848): Rename to webview, which is a restricted name. + return 'slim-webview'; + } + + static override get styles() { + return getCss(); + } + + override render() { + return getHtml.bind(this)(); + } + + static override get properties() { + return { + src: {type: String, reflect: true}, + }; + } + + accessor src: string = ''; + + contentWindow: WindowProxy|null = null; + + private viewInstanceId: number; + + constructor() { + super(); + + this.viewInstanceId = chrome.slimWebViewPrivate.getNextId(); + chrome.slimWebViewPrivate.registerView(this.viewInstanceId, this); + } +} + +declare global { + interface HTMLElementTagNameMap { + 'slim-webview': SlimWebViewElement; + } +} + +customElements.define(SlimWebViewElement.is, SlimWebViewElement);
diff --git a/chrome/browser/resources/settings/route.ts b/chrome/browser/resources/settings/route.ts index 2f3b180..00a5445 100644 --- a/chrome/browser/resources/settings/route.ts +++ b/chrome/browser/resources/settings/route.ts
@@ -227,7 +227,7 @@ if (loadTimeData.getBoolean('enableYourSavedInfoSettingsPage')) { if (visibility.yourSavedInfo !== false) { r.YOUR_SAVED_INFO = r.BASIC.createSection( - '/yourSavedInfo', 'yourSavedInfo', + '/autofill', 'yourSavedInfo', loadTimeData.getString('autofillPageTitle')); r.PAYMENTS = r.YOUR_SAVED_INFO.createChild('/payments');
diff --git a/chrome/browser/resources/settings/settings_menu/settings_menu.html b/chrome/browser/resources/settings/settings_menu/settings_menu.html index e1bd8ba..24686940 100644 --- a/chrome/browser/resources/settings/settings_menu/settings_menu.html +++ b/chrome/browser/resources/settings/settings_menu/settings_menu.html
@@ -57,20 +57,7 @@ $i18n{peoplePageTitle} <cr-ripple></cr-ripple> </a> - <a role="menuitem" id="yourSavedInfo" href="/yourSavedInfo" - hidden="[[!showYourSavedInfoPageMenuItem_( - enableYourSavedInfoSettingsPage_, - pageVisibility_.yourSavedInfo)]]" - on-click="onYourSavedInfoClick_" - class="cr-nav-menu-item"> - <cr-icon icon="settings:assignment"></cr-icon> - $i18n{autofillPageTitle} - <cr-ripple></cr-ripple> - </a> <a role="menuitem" id="autofill" href="/autofill" - hidden="[[!showAutofillPageMenuItem_( - enableYourSavedInfoSettingsPage_, - pageVisibility_.autofill)]]" on-click="onAutofillClick_" class="cr-nav-menu-item"> <cr-icon icon="settings:assignment"></cr-icon>
diff --git a/chrome/browser/resources/settings/settings_menu/settings_menu.ts b/chrome/browser/resources/settings/settings_menu/settings_menu.ts index 7f2a76c..5d0385c 100644 --- a/chrome/browser/resources/settings/settings_menu/settings_menu.ts +++ b/chrome/browser/resources/settings/settings_menu/settings_menu.ts
@@ -90,18 +90,6 @@ (!this.pageVisibility_ || this.pageVisibility_.ai !== false); } - private showYourSavedInfoPageMenuItem_(): boolean { - return this.enableYourSavedInfoSettingsPage_ && - (!this.pageVisibility_ || - this.pageVisibility_.yourSavedInfo !== false); - } - - private showAutofillPageMenuItem_(): boolean { - return !this.enableYourSavedInfoSettingsPage_ && - (!this.pageVisibility_ || - this.pageVisibility_.autofill !== false); - } - override currentRouteChanged(newRoute: Route) { // Focus the initially selected path. const anchors = this.shadowRoot!.querySelectorAll('a'); @@ -161,9 +149,11 @@ } private onAutofillClick_() { + const metricName = this.enableYourSavedInfoSettingsPage_ ? + 'Autofill.YourSavedInfoSettingsPage.VisitReferrer' : + 'Autofill.AutofillAndPasswordsSettingsPage.VisitReferrer'; this.metricsBrowserProxy_.recordAutofillSettingsReferrer( - 'Autofill.AutofillAndPasswordsSettingsPage.VisitReferrer', - AutofillSettingsReferrer.SETTINGS_MENU); + metricName, AutofillSettingsReferrer.SETTINGS_MENU); } private onYourSavedInfoClick_() {
diff --git a/chrome/browser/resources/side_panel/read_anything/app/read_anything_toolbar.html.ts b/chrome/browser/resources/side_panel/read_anything/app/read_anything_toolbar.html.ts index 490a043..720ebc49 100644 --- a/chrome/browser/resources/side_panel/read_anything/app/read_anything_toolbar.html.ts +++ b/chrome/browser/resources/side_panel/read_anything/app/read_anything_toolbar.html.ts
@@ -21,7 +21,8 @@ function getCloseButton(this: ReadAnythingToolbarElement) { if (this.isImmersiveMode) { return html` - <cr-icon-button id="close" tabindex="0" + <cr-icon-button id="close" tabindex="-1" + class="toolbar-button" aria-label="$i18n{readingModeLanguageMenuClose}" title="$i18n{readingModeLanguageMenuClose}" iron-icon="cr:close"
diff --git a/chrome/browser/resources/skills/card.ts b/chrome/browser/resources/skills/card.ts index d561723..9899b90 100644 --- a/chrome/browser/resources/skills/card.ts +++ b/chrome/browser/resources/skills/card.ts
@@ -48,6 +48,7 @@ prompt: '', // Default to user created since these are added by the user via the UI. source: SkillSource.kUserCreated, + description: '', creationTime: {internalValue: 0n}, lastUpdateTime: {internalValue: 0n}, };
diff --git a/chrome/browser/resources/skills/skills_dialog_app.ts b/chrome/browser/resources/skills/skills_dialog_app.ts index 107e86c..9ecfb9c 100644 --- a/chrome/browser/resources/skills/skills_dialog_app.ts +++ b/chrome/browser/resources/skills/skills_dialog_app.ts
@@ -53,6 +53,7 @@ prompt: '', // Default to user created since these are added by the user via the UI. source: SkillSource.kUserCreated, + description: '', creationTime: {internalValue: 0n}, lastUpdateTime: {internalValue: 0n}, };
diff --git a/chrome/browser/supervised_user/supervised_user_url_filtering_service_android_browsertest.cc b/chrome/browser/supervised_user/supervised_user_url_filtering_service_android_browsertest.cc index 9c67043..cf01ef7 100644 --- a/chrome/browser/supervised_user/supervised_user_url_filtering_service_android_browsertest.cc +++ b/chrome/browser/supervised_user/supervised_user_url_filtering_service_android_browsertest.cc
@@ -12,7 +12,7 @@ #include "base/functional/callback_forward.h" #include "base/strings/utf_string_conversions.h" #include "base/test/bind.h" -#include "base/test/metrics/histogram_tester.h" +#include "base/test/with_feature_override.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_key.h" #include "chrome/browser/signin/identity_manager_factory.h" @@ -53,13 +53,22 @@ namespace supervised_user { namespace { +class SupervisedUserBrowserCasedTestBase + : public SupervisedUserBrowserTestBase, + public base::test::WithFeatureOverride { + protected: + SupervisedUserBrowserCasedTestBase() + : base::test::WithFeatureOverride(kSupervisedUserUseUrlFilteringService) { + } +}; + // A suite for regular users (most of the time should assert that features are // initially disabled and provide neutral, default browsing experience, unless // users transition to supervised state). class RegularUserUrlFilteringServiceAndroidBrowserTest - : public SupervisedUserBrowserTestBase {}; + : public SupervisedUserBrowserCasedTestBase {}; -IN_PROC_BROWSER_TEST_F(RegularUserUrlFilteringServiceAndroidBrowserTest, +IN_PROC_BROWSER_TEST_P(RegularUserUrlFilteringServiceAndroidBrowserTest, EnablingAndroidParentalControlsEnablesUrlFiltering) { GetDeviceParentalControls().SetBrowserContentFiltersEnabledForTesting(true); EXPECT_EQ( @@ -68,9 +77,12 @@ ->GetWebFilterType()); } -// A suite for supervised users configured by Family Link. +INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE( + RegularUserUrlFilteringServiceAndroidBrowserTest); + +// A suite for supervised users with configured by Family Link. class FamilyLinkUrlFilteringServiceAndroidBrowserTest - : public SupervisedUserBrowserTestBase { + : public SupervisedUserBrowserCasedTestBase { public: FamilyLinkUrlFilteringServiceAndroidBrowserTest() { SetInitialSupervisedUserState( @@ -78,8 +90,12 @@ } }; -IN_PROC_BROWSER_TEST_F(FamilyLinkUrlFilteringServiceAndroidBrowserTest, +IN_PROC_BROWSER_TEST_P(FamilyLinkUrlFilteringServiceAndroidBrowserTest, AndroidParentalControlsAreIgnored) { + if (base::FeatureList::IsEnabled(kSupervisedUserUseUrlFilteringService)) { + GTEST_SKIP() << "Test for legacy implementation only."; + } + // Android parental controls only can set web filter to // kTryToBlockMatureSites; so let's reconfigure to something else to make sure // that the change is ignored. @@ -98,9 +114,35 @@ ->GetWebFilterType()); } +IN_PROC_BROWSER_TEST_P(FamilyLinkUrlFilteringServiceAndroidBrowserTest, + AndroidParentalControlsArePreferred) { + if (!base::FeatureList::IsEnabled(kSupervisedUserUseUrlFilteringService)) { + GTEST_SKIP() << "Test for experimental implementation only."; + } + + // Android parental controls only can set web filter to + // kTryToBlockMatureSites; so let's reconfigure to something else to make sure + // that the change is ignored. + supervised_user_test_util::SetWebFilterType(GetProfile(), + WebFilterType::kAllowAllSites); + ASSERT_EQ( + WebFilterType::kAllowAllSites, + SupervisedUserUrlFilteringServiceFactory::GetForProfile(GetProfile()) + ->GetWebFilterType()); + + GetDeviceParentalControls().SetBrowserContentFiltersEnabledForTesting(true); + EXPECT_EQ( + WebFilterType::kTryToBlockMatureSites, + SupervisedUserUrlFilteringServiceFactory::GetForProfile(GetProfile()) + ->GetWebFilterType()); +} + +INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE( + FamilyLinkUrlFilteringServiceAndroidBrowserTest); + // A suite for supervised users configured by Android parental controls. class AndroidParentalControlsUrlFilteringServiceAndroidBrowserTest - : public SupervisedUserBrowserTestBase { + : public SupervisedUserBrowserCasedTestBase { public: AndroidParentalControlsUrlFilteringServiceAndroidBrowserTest() { SetInitialSupervisedUserState( @@ -111,9 +153,7 @@ } }; -// Family Link will disable Android parental controls (but default setting for -// both systems is kTryToBlockMatureSites). -IN_PROC_BROWSER_TEST_F( +IN_PROC_BROWSER_TEST_P( AndroidParentalControlsUrlFilteringServiceAndroidBrowserTest, FamilyLinkDisablesAndroidParentalControls) { ASSERT_TRUE( @@ -133,9 +173,7 @@ ->GetWebFilterType()); } -// Family Link will disable Android parental controls (but default setting for -// both systems is kTryToBlockMatureSites). -IN_PROC_BROWSER_TEST_F( +IN_PROC_BROWSER_TEST_P( AndroidParentalControlsUrlFilteringServiceAndroidBrowserTest, DisablingAndroidParentalControlsSupervisionDisablesUrlFiltering) { GetDeviceParentalControls().SetBrowserContentFiltersEnabledForTesting(false); @@ -146,5 +184,23 @@ ->GetWebFilterType()); } +IN_PROC_BROWSER_TEST_P( + AndroidParentalControlsUrlFilteringServiceAndroidBrowserTest, + DisablingWebFilterOnlyPutsUrlFilterininInAllowAllMode) { + if (!base::FeatureList::IsEnabled(kSupervisedUserUseUrlFilteringService)) { + GTEST_SKIP() << "Test for experimental implementation only."; + } + + GetDeviceParentalControls().SetBrowserContentFiltersEnabledForTesting(false); + GetDeviceParentalControls().SetSearchContentFiltersEnabledForTesting(true); + EXPECT_EQ( + WebFilterType::kAllowAllSites, + SupervisedUserUrlFilteringServiceFactory::GetForProfile(GetProfile()) + ->GetWebFilterType()); +} + +INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE( + AndroidParentalControlsUrlFilteringServiceAndroidBrowserTest); + } // namespace } // namespace supervised_user
diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/StorageCollectionSynchronizer.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/StorageCollectionSynchronizer.java index 60dd276..1c42e6e 100644 --- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/StorageCollectionSynchronizer.java +++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/StorageCollectionSynchronizer.java
@@ -8,6 +8,7 @@ import org.jni_zero.JniType; import org.jni_zero.NativeMethods; +import org.chromium.base.Token; import org.chromium.base.lifetime.Destroyable; import org.chromium.build.annotations.NullMarked; import org.chromium.chrome.browser.profiles.Profile; @@ -58,6 +59,20 @@ StorageCollectionSynchronizerJni.get().fullSave(mNativePtr); } + /** Saves a tab to storage through the observer associated with the synchronizer. */ + public void saveTab(Tab tab) { + assert mNativePtr != 0; + StorageCollectionSynchronizerJni.get().saveTab(mNativePtr, tab); + } + + /** + * Saves a tab group payload to storage through the observer associated with the synchronizer. + */ + public void saveTabGroupPayload(Token groupId) { + assert mNativePtr != 0; + StorageCollectionSynchronizerJni.get().saveTabGroupPayload(mNativePtr, groupId); + } + @NativeMethods interface Natives { long init( @@ -67,6 +82,13 @@ void fullSave(long nativeStorageCollectionSynchronizerAndroid); + void saveTab( + long nativeStorageCollectionSynchronizerAndroid, @JniType("TabAndroid*") Tab tab); + + void saveTabGroupPayload( + long nativeStorageCollectionSynchronizerAndroid, + @JniType("base::Token") Token groupId); + void destroy(long nativeStorageCollectionSynchronizerAndroid); void consumeRestoreOrchestratorFactory(
diff --git a/chrome/browser/tab/storage_collection_synchronizer.cc b/chrome/browser/tab/storage_collection_synchronizer.cc index 16d0530..cd7b858 100644 --- a/chrome/browser/tab/storage_collection_synchronizer.cc +++ b/chrome/browser/tab/storage_collection_synchronizer.cc
@@ -12,8 +12,11 @@ #include "base/types/pass_key.h" #include "chrome/browser/tab/collection_storage_observer.h" #include "chrome/browser/tab/tab_state_storage_service.h" +#include "components/tab_groups/tab_group_id.h" #include "components/tabs/public/direct_child_walker.h" +#include "components/tabs/public/tab_collection.h" #include "components/tabs/public/tab_collection_types.h" +#include "components/tabs/public/tab_interface.h" #include "components/tabs/public/tab_strip_collection.h" namespace tabs { @@ -49,6 +52,20 @@ walker.Walk(); } +void StorageCollectionSynchronizer::SaveTab(TabInterface* tab) { + TabHandle tab_handle = tab->GetHandle(); + observer_->SaveChildNodeOnly(tab_handle); +} + +void StorageCollectionSynchronizer::SaveTabGroupPayload( + tab_groups::TabGroupId group_id) { + TabCollection* group_collection = + collection_->GetTabGroupCollection(group_id); + if (group_collection) { + observer_->SaveChildNodeOnly(group_collection->GetHandle()); + } +} + void StorageCollectionSynchronizer::SetCollectionObserver( std::unique_ptr<CollectionSynchronizerObserver> new_observer) { if (observer_) {
diff --git a/chrome/browser/tab/storage_collection_synchronizer.h b/chrome/browser/tab/storage_collection_synchronizer.h index 6ad5995e..ed39934 100644 --- a/chrome/browser/tab/storage_collection_synchronizer.h +++ b/chrome/browser/tab/storage_collection_synchronizer.h
@@ -10,8 +10,11 @@ #include "base/memory/raw_ptr.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/tab/tab_state_storage_service.h" +#include "components/tab_groups/tab_group_id.h" #include "components/tabs/public/tab_collection_observer.h" +#include "components/tabs/public/tab_interface.h" #include "components/tabs/public/tab_strip_collection.h" + namespace tabs { // Provides updates to storage to match the state of a TabStripCollection. @@ -35,6 +38,12 @@ // Saves the entire collection and its descendants to the service. void FullSave(); + // Used to manually save a tab. + void SaveTab(TabInterface* tab); + + // Used to manually save a tab group payload. + void SaveTabGroupPayload(tab_groups::TabGroupId group_id); + // Sets the TabCollectionObserver. If an observer is already set, it will be // unregistered before the new one is registered. void SetCollectionObserver(
diff --git a/chrome/browser/tab/storage_restore_orchestrator.cc b/chrome/browser/tab/storage_restore_orchestrator.cc index 4582eed..0611beb 100644 --- a/chrome/browser/tab/storage_restore_orchestrator.cc +++ b/chrome/browser/tab/storage_restore_orchestrator.cc
@@ -12,6 +12,7 @@ #include "chrome/browser/tab/restore_entity_tracker.h" #include "chrome/browser/tab/storage_id.h" #include "chrome/browser/tab/storage_loaded_data.h" +#include "chrome/browser/tab/tab_state_storage_service.h" #include "chrome/browser/tab/tab_storage_util.h" #include "components/tabs/public/pinned_tab_collection.h" #include "components/tabs/public/tab_collection.h" @@ -59,14 +60,15 @@ if (!std::holds_alternative<TabHandle>(handle)) { return; } - - TabHandle tab_handle = std::get<TabHandle>(handle); - const TabInterface* tab = tab_handle.Get(); + const TabInterface* tab = std::get<TabHandle>(handle).Get(); const TabCollection* parent = tab->GetParentCollection(); if (!parent) { return; } + TabCanonicalizer canonicalizer = service_->GetCanonicalizer(); + TabHandle tab_handle = canonicalizer.Run(tab)->GetHandle(); + RestoreEntityTracker* tracker = loaded_data_->GetTracker(); bool was_tab_on_disk = tracker->AssociateTabAndAncestors(tab); @@ -76,11 +78,12 @@ if (!was_inserted) { service_->Save(tab); - } else if (!was_tab_on_disk || restored_nodes_.contains(handle)) { + return; + } else if (!was_tab_on_disk || restored_nodes_.contains(tab_handle)) { service_->Save(tab); MaybeAddModifiedParent(parent_id, parent_handle); } else if (was_tab_on_disk) { - restored_nodes_.insert(handle); + restored_nodes_.insert(tab_handle); } if (modified_parents_.contains(parent_id)) {
diff --git a/chrome/browser/tab/tab_state_storage_service.cc b/chrome/browser/tab/tab_state_storage_service.cc index 87dedc08..967c8d7 100644 --- a/chrome/browser/tab/tab_state_storage_service.cc +++ b/chrome/browser/tab/tab_state_storage_service.cc
@@ -249,6 +249,10 @@ return key; } +TabCanonicalizer TabStateStorageService::GetCanonicalizer() const { + return tab_canonicalizer_; +} + #if defined(NDEBUG) void TabStateStorageService::PrintAll() { tab_backend_.PrintAll();
diff --git a/chrome/browser/tab/tab_state_storage_service.h b/chrome/browser/tab/tab_state_storage_service.h index 87ba1510a..19f9cf4 100644 --- a/chrome/browser/tab/tab_state_storage_service.h +++ b/chrome/browser/tab/tab_state_storage_service.h
@@ -114,6 +114,8 @@ // Generates a new key for encryption. std::vector<uint8_t> GenerateKey(std::string_view window_tag); + TabCanonicalizer GetCanonicalizer() const; + #if defined(NDEBUG) void PrintAll(); #endif
diff --git a/chrome/browser/ui/android/edge_to_edge/internal/junit/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeFieldTrialUnitTest.java b/chrome/browser/ui/android/edge_to_edge/internal/junit/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeFieldTrialUnitTest.java index ea5c409..33dd8493 100644 --- a/chrome/browser/ui/android/edge_to_edge/internal/junit/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeFieldTrialUnitTest.java +++ b/chrome/browser/ui/android/edge_to_edge/internal/junit/src/org/chromium/chrome/browser/ui/edge_to_edge/EdgeToEdgeFieldTrialUnitTest.java
@@ -14,9 +14,12 @@ import org.robolectric.shadows.ShadowBuild; import org.chromium.base.test.BaseRobolectricTestRunner; +import org.chromium.base.test.util.Restriction; import org.chromium.chrome.browser.flags.ChromeFeatureList; +import org.chromium.ui.test.util.DeviceRestriction; @RunWith(BaseRobolectricTestRunner.class) +@Restriction(DeviceRestriction.RESTRICTION_TYPE_NON_AUTO) @Config(shadows = ShadowBuild.class) public class EdgeToEdgeFieldTrialUnitTest { @@ -35,8 +38,10 @@ .isEnabledForManufacturerVersion()); } - @Test + // Pinned to SDK 29 because the test expects the feature to be + // disabled below SDK 30. @Config(sdk = 29) + @Test public void noOverrides_notMeetMinVersion() { assertFalse( "Default manufacturer has min version override as 30.", @@ -175,7 +180,8 @@ everywhereOverrides.isEnabledForManufacturerVersion()); } - // TODO(crbug.com/450954710): This test fails on SDK 36. + // Pinned to SDK 29 because the test expects the feature to be + // disabled below SDK 30. @Config(sdk = 29) @Test public void testInvalidInputs_unevenLength() { @@ -188,7 +194,8 @@ .isEnabledForManufacturerVersion()); } - // TODO(crbug.com/450954710): This test fails on SDK 36. + // Pinned to SDK 29 because the test expects the feature to be + // disabled below SDK 30. @Config(sdk = 29) @Test public void testInvalidInputs_unevenLength_2() { @@ -201,7 +208,8 @@ .isEnabledForManufacturerVersion()); } - // TODO(crbug.com/450954710): This test fails on SDK 36. + // Pinned to SDK 29 because the test expects the feature to be + // disabled below SDK 30. @Config(sdk = 29) @Test public void testInvalidInputs_versionInvalid() {
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/AutocompleteEditTextTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/AutocompleteEditTextTest.java index 7fd4da9..de35fdc 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/AutocompleteEditTextTest.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/AutocompleteEditTextTest.java
@@ -1276,7 +1276,9 @@ } // crbug.com/759876 - // TODO(crbug.com/450954710): This test fails on SDK 36. + + // TODO(crbug.com/481750046): Fix failure on SDK 30+ due to focus and selection behavior changes + // in Robolectric. @Config(sdk = 29) @Test public void testFocusInAndSelectAll() { @@ -1328,7 +1330,9 @@ } // crbug.com/768323 - // TODO(crbug.com/450954710): This test fails on SDK 36. + + // TODO(crbug.com/481750046): Fix failure on SDK 30+ due to focus and selection behavior changes + // in Robolectric. @Config(sdk = 29) @Test public void testFocusLossHidesCursor() { @@ -1431,7 +1435,9 @@ } // crbug.com/759876 - // TODO(crbug.com/450954710): This test fails on SDK 36. + + // TODO(crbug.com/481750046): Fix failure on SDK 30+ due to focus and selection behavior changes + // in Robolectric. @Config(sdk = 29) @Test public void testTextSelectionGetsAnnouncedAgainOnFocus() {
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinderUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinderUnitTest.java index 7ec9320..81d8dbd3 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinderUnitTest.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarViewBinderUnitTest.java
@@ -82,7 +82,8 @@ Assert.assertEquals(newExpectColor, mUrlBar.getHintTextColors().getDefaultColor()); } - // TODO(crbug.com/450954710): This test fails on SDK 36. + // TODO(crbug.com/481749859): Fix failure on SDK 30+ due to focus behavior changes in + // Robolectric. @Config(sdk = 29) @Test @SmallTest @@ -94,7 +95,8 @@ /* expectSelection= */ true); } - // TODO(crbug.com/450954710): This test fails on SDK 36. + // TODO(crbug.com/481749859): Fix failure on SDK 30+ due to focus behavior changes in + // Robolectric. @Config(sdk = 29) @Test @SmallTest @@ -105,7 +107,8 @@ /* expectSelection= */ false); } - // TODO(crbug.com/450954710): This test fails on SDK 36. + // TODO(crbug.com/481749859): Fix failure on SDK 30+ due to focus behavior changes in + // Robolectric. @Config(sdk = 29) @Test @SmallTest @@ -116,7 +119,8 @@ /* expectSelection= */ false); } - // TODO(crbug.com/450954710): This test fails on SDK 36. + // TODO(crbug.com/481749859): Fix failure on SDK 30+ due to focus behavior changes in + // Robolectric. @Config(sdk = 29) @Test @SmallTest
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/answer/CalculatorAnswerTextLayoutUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/answer/CalculatorAnswerTextLayoutUnitTest.java index f8e1ca15..82fc99f 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/answer/CalculatorAnswerTextLayoutUnitTest.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/answer/CalculatorAnswerTextLayoutUnitTest.java
@@ -41,7 +41,7 @@ .TextAppearance_TextMedium_Secondary); } - // TODO(crbug.com/450954710): This test fails on SDK 36. + // TODO(crbug.com/481749158): Fix failure on SDK 30+ due to layout/rendering differences. @Config(sdk = 29) @Test @SmallTest
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tabgroup/TabGroupSuggestionProcessorUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tabgroup/TabGroupSuggestionProcessorUnitTest.java index 7f97bb8..877e492 100644 --- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tabgroup/TabGroupSuggestionProcessorUnitTest.java +++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/tabgroup/TabGroupSuggestionProcessorUnitTest.java
@@ -113,7 +113,7 @@ mProcessor.populateModel(mInput, mSuggestion, mModel, 0); } - // TODO(crbug.com/450954710): This test fails on SDK 36. + // TODO(crbug.com/481749357): Fix failure on SDK 30+ due to drawable comparison failures. @Config(sdk = 29) @Test @SmallTest
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/signin_promo/NtpSigninPromoDelegate.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/signin_promo/NtpSigninPromoDelegate.java index 25328cb..dabdc85 100644 --- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/signin_promo/NtpSigninPromoDelegate.java +++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/signin_promo/NtpSigninPromoDelegate.java
@@ -12,6 +12,7 @@ import androidx.annotation.IntDef; import androidx.annotation.VisibleForTesting; +import org.chromium.base.TimeUtils; import org.chromium.build.annotations.NullMarked; import org.chromium.build.annotations.Nullable; import org.chromium.chrome.browser.preferences.ChromePreferenceKeys; @@ -48,10 +49,14 @@ int SIGNIN = 1; } - @VisibleForTesting static final int MAX_IMPRESSIONS_NTP = 5; + static final int MAX_IMPRESSIONS_NTP = 5; + // 14 days in hours. - @VisibleForTesting static final int NTP_SYNC_PROMO_NTP_SINCE_FIRST_TIME_SHOWN_LIMIT_HOURS = 336; - @VisibleForTesting static final int NTP_SYNC_PROMO_RESET_AFTER_DAYS = 30; + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public static final int NTP_SYNC_PROMO_NTP_SINCE_FIRST_TIME_SHOWN_LIMIT_HOURS = 336; + + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public static final int NTP_SYNC_PROMO_RESET_AFTER_DAYS = 30; /** Period for which promos are suppressed if signin is refused in FRE. */ @VisibleForTesting static final long SUPPRESSION_PERIOD_MS = DateUtils.DAY_IN_MILLIS; @@ -65,8 +70,10 @@ * ChromePreferenceKeys#SIGNIN_PROMO_NTP_FIRST_SHOWN_TIME} and {@link * ChromePreferenceKeys#SIGNIN_PROMO_NTP_LAST_SHOWN_TIME} to allow the promo card to show again. */ + // TODO(crbug.com/469775981): make this private once Seamless sign-in is launched and the class + // SignInPromo has been removed. public static void resetNtpSyncPromoLimitsIfHiddenForTooLong() { - final long currentTime = System.currentTimeMillis(); + final long currentTime = TimeUtils.currentTimeMillis(); final long resetAfterMs = NTP_SYNC_PROMO_RESET_AFTER_DAYS * DateUtils.DAY_IN_MILLIS; final long lastShownTime = ChromeSharedPreferences.getInstance() @@ -88,6 +95,7 @@ SigninAndHistorySyncActivityLauncher launcher, Runnable onPromoStateChange) { super(context, profile, launcher, onPromoStateChange); + resetNtpSyncPromoLimitsIfHiddenForTooLong(); } @Override @@ -174,7 +182,7 @@ @Override void recordImpression() { - final long currentTime = System.currentTimeMillis(); + final long currentTime = TimeUtils.currentTimeMillis(); final long lastShownTime = ChromeSharedPreferences.getInstance() .readLong(ChromePreferenceKeys.SIGNIN_PROMO_NTP_LAST_SHOWN_TIME, 0L); @@ -213,7 +221,7 @@ private static boolean timeElapsedSinceFirstShownExceedsLimit() { final long timeSinceFirstShownLimitMs = NTP_SYNC_PROMO_NTP_SINCE_FIRST_TIME_SHOWN_LIMIT_HOURS * DateUtils.HOUR_IN_MILLIS; - final long currentTime = System.currentTimeMillis(); + final long currentTime = TimeUtils.currentTimeMillis(); final long firstShownTime = ChromeSharedPreferences.getInstance() .readLong(ChromePreferenceKeys.SIGNIN_PROMO_NTP_FIRST_SHOWN_TIME, 0L); @@ -269,7 +277,7 @@ SigninPreferencesManager.getInstance() .getNewTabPageSigninPromoSuppressionPeriodStart(); if (suppressedFrom == 0) return false; - long currentTime = System.currentTimeMillis(); + long currentTime = TimeUtils.currentTimeMillis(); long suppressedTo = suppressedFrom + SUPPRESSION_PERIOD_MS; if (suppressedFrom <= currentTime && currentTime < suppressedTo) { return true;
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/signin_promo/SigninPromoDelegateTest.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/signin_promo/SigninPromoDelegateTest.java index 0c20ff5..c655b76 100644 --- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/signin_promo/SigninPromoDelegateTest.java +++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/signin_promo/SigninPromoDelegateTest.java
@@ -604,6 +604,76 @@ assertTrue(config.shouldShowSigninSnackbar); } + @Test + public void testResetNtpSyncPromoLimitsIfHiddenForTooLong_resetsLimits() { + // Last promo shown time set so that the elapsed time is more than + // NTP_SYNC_PROMO_RESET_AFTER_DAYS. + long promoShownTime = + System.currentTimeMillis() + - (NtpSigninPromoDelegate.NTP_SYNC_PROMO_RESET_AFTER_DAYS + 1) + * DateUtils.DAY_IN_MILLIS; + ChromeSharedPreferences.getInstance() + .writeLong(ChromePreferenceKeys.SIGNIN_PROMO_NTP_LAST_SHOWN_TIME, promoShownTime); + ChromeSharedPreferences.getInstance() + .writeLong(ChromePreferenceKeys.SIGNIN_PROMO_NTP_FIRST_SHOWN_TIME, promoShownTime); + String ntpPromoShowCountPreferenceName = + ChromePreferenceKeys.SYNC_PROMO_SHOW_COUNT.createKey( + SigninPreferencesManager.SigninPromoAccessPointId.NTP); + int promoShowCount = NtpSigninPromoDelegate.MAX_IMPRESSIONS_NTP - 1; + ChromeSharedPreferences.getInstance() + .writeInt(ntpPromoShowCountPreferenceName, promoShowCount); + + NtpSigninPromoDelegate.resetNtpSyncPromoLimitsIfHiddenForTooLong(); + + assertEquals( + 0, ChromeSharedPreferences.getInstance().readInt(ntpPromoShowCountPreferenceName)); + assertEquals( + 0, + ChromeSharedPreferences.getInstance() + .readLong(ChromePreferenceKeys.SIGNIN_PROMO_NTP_FIRST_SHOWN_TIME)); + assertEquals( + 0, + ChromeSharedPreferences.getInstance() + .readLong(ChromePreferenceKeys.SIGNIN_PROMO_NTP_LAST_SHOWN_TIME)); + } + + @Test + public void testResetNtpSyncPromoLimitsIfHiddenForTooLong_doesNotResetLimits() { + // Last promo shown time set so that the elapsed time is less than + // NTP_SYNC_PROMO_RESET_AFTER_DAYS. + long promoShownTime = + System.currentTimeMillis() + - (NtpSigninPromoDelegate.NTP_SYNC_PROMO_RESET_AFTER_DAYS - 1) + * DateUtils.DAY_IN_MILLIS; + ChromeSharedPreferences.getInstance() + .writeLong(ChromePreferenceKeys.SIGNIN_PROMO_NTP_LAST_SHOWN_TIME, promoShownTime); + ChromeSharedPreferences.getInstance() + .writeLong(ChromePreferenceKeys.SIGNIN_PROMO_NTP_FIRST_SHOWN_TIME, promoShownTime); + String ntpPromoShowCountPreferenceName = + ChromePreferenceKeys.SYNC_PROMO_SHOW_COUNT.createKey( + SigninPreferencesManager.SigninPromoAccessPointId.NTP); + int promoShowCount = NtpSigninPromoDelegate.MAX_IMPRESSIONS_NTP - 1; + ChromeSharedPreferences.getInstance() + .writeInt(ntpPromoShowCountPreferenceName, promoShowCount); + + NtpSigninPromoDelegate.resetNtpSyncPromoLimitsIfHiddenForTooLong(); + + assertEquals( + promoShowCount, + ChromeSharedPreferences.getInstance() + .readInt( + ChromePreferenceKeys.SYNC_PROMO_SHOW_COUNT.createKey( + SigninPreferencesManager.SigninPromoAccessPointId.NTP))); + assertEquals( + promoShownTime, + ChromeSharedPreferences.getInstance() + .readLong(ChromePreferenceKeys.SIGNIN_PROMO_NTP_FIRST_SHOWN_TIME)); + assertEquals( + promoShownTime, + ChromeSharedPreferences.getInstance() + .readLong(ChromePreferenceKeys.SIGNIN_PROMO_NTP_LAST_SHOWN_TIME)); + } + private void setupDelegate( @SigninAccessPoint int accessPoint, @Nullable CoreAccountInfo visibleAccount) { mDelegate =
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonViewTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonViewTest.java index 64e1179d..780e7e0a 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonViewTest.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonViewTest.java
@@ -519,7 +519,8 @@ assertEquals(actionChipLabel, mActionChipLabel.getText()); } - // TODO(crbug.com/450954710): This test fails on SDK 36. + // TODO(crbug.com/481750542): Fix failure on SDK 30+ due to animation/transition callback + // verification failures. @Config(sdk = 29) @Test public void testSetIconDrawableWithAnimation_expandAndCollapseActionChipFromHidden() { @@ -583,7 +584,8 @@ assertEquals(View.VISIBLE, mActionChipLabel.getVisibility()); } - // TODO(crbug.com/450954710): This test fails on SDK 36. + // TODO(crbug.com/481750542): Fix failure on SDK 30+ due to animation/transition callback + // verification failures. @Config(sdk = 29) @Test public void testUpdateButtonWithAnimation_actionChipWithAlternativeColor() { @@ -643,7 +645,8 @@ assertEquals(View.GONE, mActionChipLabel.getVisibility()); } - // TODO(crbug.com/450954710): This test fails on SDK 36. + // TODO(crbug.com/481750542): Fix failure on SDK 30+ due to animation/transition callback + // verification failures. @Config(sdk = 29) @Test public void testTransitionCallbacks() {
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherActionMenuCoordinator.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherActionMenuCoordinator.java index 66f91d09..3e7f9b04 100644 --- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherActionMenuCoordinator.java +++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/TabSwitcherActionMenuCoordinator.java
@@ -262,7 +262,7 @@ .withStartIconRes( IncognitoUtils.shouldOpenIncognitoAsWindow() ? R.drawable.ic_add_box_rounded_corner - : R.drawable.ic_incognito_fill_24dp) + : R.drawable.ic_incognito) .withEnabled(enabled) .build(); case MenuItemType.CLOSE_ALL_INCOGNITO_TABS:
diff --git a/chrome/browser/ui/omnibox/omnibox_next_features.cc b/chrome/browser/ui/omnibox/omnibox_next_features.cc index 979701e..ecd77ce6 100644 --- a/chrome/browser/ui/omnibox/omnibox_next_features.cc +++ b/chrome/browser/ui/omnibox/omnibox_next_features.cc
@@ -325,7 +325,7 @@ const base::FeatureParam<bool> kShowSmartCompose( &internal::kWebUIOmniboxAimPopup, "Omnibox_ShowSmartCompose", - true); + false); const base::FeatureParam<bool> kShowSubmit(&internal::kWebUIOmniboxAimPopup, "ShowSubmit", true); @@ -349,10 +349,12 @@ &internal::kWebUIOmniboxAimPopup, "ShowVoiceSearchInExpandedComposebox", true); +// TODO(b/481079194): Remove `kAutoSubmitVoiceSearchQuery` and the code that +// respects its disabled state. const base::FeatureParam<bool> kAutoSubmitVoiceSearchQuery( &internal::kWebUIOmniboxAimPopup, "Omnibox_AutoSubmitVoiceSearchQuery", - false); + true); const base::FeatureParam<bool> kEnableContextDragAndDrop( &internal::kWebUIOmniboxAimPopup, "EnableContextDragAndDrop",
diff --git a/chrome/browser/ui/tabs/glic_actor_nudge_controller.cc b/chrome/browser/ui/tabs/glic_actor_nudge_controller.cc index 64f8622..072a2c2 100644 --- a/chrome/browser/ui/tabs/glic_actor_nudge_controller.cc +++ b/chrome/browser/ui/tabs/glic_actor_nudge_controller.cc
@@ -42,7 +42,6 @@ UpdateCurrentActorNudgeState(); } - if (base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator)) { ActorTaskListBubbleController* bubble_controller = ActorTaskListBubbleController::From(browser_); bubble_visibility_change_subscription_.push_back( @@ -53,7 +52,6 @@ bubble_controller->RegisterBubbleDestroyedCallback(base::BindRepeating( &GlicActorNudgeController::OnBubbleVisibilityChange, weak_ptr_factory_.GetWeakPtr(), /*is_bubble_open=*/false))); - } } GlicActorNudgeController::~GlicActorNudgeController() = default; @@ -79,25 +77,16 @@ GlicActorTaskIconManager* manager = GlicActorTaskIconManagerFactory::GetForProfile(profile_); DCHECK(manager); - if (base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator) && - manager->actor_task_list_bubble_rows().empty()) { + if (manager->actor_task_list_bubble_rows().empty()) { tab_strip_action_container_->HideGlicActorTaskIcon(); CloseBubble(); return; } - size_t num_tasks_need_processing = - base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator) - ? manager->GetNumActorTasksNeedProcessing() - : manager->actor_task_list_bubble_rows().size(); + size_t num_tasks_need_processing = manager->GetNumActorTasksNeedProcessing(); switch (actor_task_nudge_state.text) { case ActorTaskNudgeState::Text::kDefault: - if (base::FeatureList::IsEnabled( - features::kGlicActorUiGlobalTaskIndicator)) { tab_strip_action_container_->ShowGlicActorTaskIcon(); - } else { - tab_strip_action_container_->HideGlicActorTaskIcon(); - } // In either case, close the bubble as the nudge has been either hidden or // reset. CloseBubble(); @@ -107,26 +96,18 @@ IDS_ACTOR_TASK_NUDGE_CHECK_TASK_LABEL, num_tasks_need_processing)); break; case ActorTaskNudgeState::Text::kCompleteTasks: - if (base::FeatureList::IsEnabled( - features::kGlicActorUiGlobalTaskIndicator)) { UpdateNudgeLabelOrRetrigger(l10n_util::GetPluralStringFUTF16( IDS_ACTOR_TASK_NUDGE_TASK_COMPLETE_LABEL, actor::ActorKeyedService::Get(profile_) ->GetActorUiStateManager() ->GetInactiveTaskCount())); - } break; default: NOTREACHED(); } if (tab_strip_action_container_->GetIsShowingGlicActorTaskIconNudge()) { - if (base::FeatureList::IsEnabled( - features::kGlicActorUiGlobalTaskIndicator)) { actor::ui::RecordGlobalTaskIndicatorNudgeShown(actor_task_nudge_state); - } else { - actor::ui::RecordTaskNudgeShown(actor_task_nudge_state); - } } }
diff --git a/chrome/browser/ui/tabs/glic_actor_task_icon_manager.cc b/chrome/browser/ui/tabs/glic_actor_task_icon_manager.cc index 8f8ca23..c27e29c5 100644 --- a/chrome/browser/ui/tabs/glic_actor_task_icon_manager.cc +++ b/chrome/browser/ui/tabs/glic_actor_task_icon_manager.cc
@@ -20,12 +20,8 @@ using Text = ActorTaskNudgeState::Text; bool RequiresTaskProcessing(TaskState state) { - if (base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator)) { return GlicActorTaskIconManager::RequiresAttention(state) || state == TaskState::kFinished || state == TaskState::kFailed; - } else { - return GlicActorTaskIconManager::RequiresAttention(state); - } } } // namespace @@ -50,13 +46,11 @@ base::BindRepeating( &GlicActorTaskIconManager::UpdateTaskIconComponents, base::Unretained(this)))); - if (base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator)) { callback_subscriptions_.push_back( actor_service_->GetActorUiStateManager()->RegisterActorTaskRemoved( base::BindRepeating( &GlicActorTaskIconManager::UpdateTaskIconComponents, base::Unretained(this)))); - } } void GlicActorTaskIconManager::UpdateTaskIconComponents(actor::TaskId task_id) { @@ -75,18 +69,10 @@ void GlicActorTaskIconManager::Shutdown() {} void GlicActorTaskIconManager::UpdateTaskNudge() { - // TODO(mjenn): Remove this once kGlicActorUiGlobalTaskIndicator is removed. - auto paused_or_yielded_actor_tasks = - actor_service_->FindTaskIdsInActive([](const ActorTask& task) { - return (task.GetState() == TaskState::kPausedByActor || - task.GetState() == TaskState::kWaitingOnUser); - }); - ActorTaskNudgeState old_state = current_actor_task_nudge_state_; bool needs_attention = false; bool tasks_complete = false; - if (base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator)) { for (const auto [task_id, requires_processing] : actor_task_list_bubble_rows_) { // Tasks that are processed will show the default nudge. @@ -114,10 +100,6 @@ tasks_complete = true; } } - } else { - needs_attention = !paused_or_yielded_actor_tasks.empty() && - !actor_task_list_bubble_rows_.empty(); - } current_actor_task_nudge_state_.text = needs_attention ? Text::kNeedsAttention : tasks_complete ? Text::kCompleteTasks @@ -135,14 +117,10 @@ void GlicActorTaskIconManager::ProcessRowInTaskListBubble( actor::TaskId task_id) { - if (base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator)) { if (auto it = actor_task_list_bubble_rows_.find(task_id); it != actor_task_list_bubble_rows_.end()) { it->second = false; } - } else { - actor_task_list_bubble_rows_.erase(task_id); - } UpdateTaskNudge(); } @@ -155,19 +133,10 @@ // If the task was cancelled, it should also be removed from the bubble. actor_task_list_bubble_rows_.erase(task_id); } else { - const bool icon_v3_enabled = - base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator); const bool requires_processing = RequiresTaskProcessing(state.value()); - if (icon_v3_enabled) { actor_task_list_bubble_rows_[task_id] = requires_processing; - } if (requires_processing) { - if (!icon_v3_enabled) { - // Old implementation does not use this field, but it needs to be set to - // show the row in the bubble. - actor_task_list_bubble_rows_[task_id] = false; - } // Notify the bubble only if a task now requires processing. This callback // will open the task list bubble and make it active, in order to bring it // to the user's attention. This is also necessary for when a user
diff --git a/chrome/browser/ui/tabs/glic_actor_task_icon_manager_unittest.cc b/chrome/browser/ui/tabs/glic_actor_task_icon_manager_unittest.cc index fe61b3d..d95255e 100644 --- a/chrome/browser/ui/tabs/glic_actor_task_icon_manager_unittest.cc +++ b/chrome/browser/ui/tabs/glic_actor_task_icon_manager_unittest.cc
@@ -46,20 +46,12 @@ // testing::Test: void SetUp() override { - base::test::FeatureRefAndParams enable_glic_policy = { - features::kGlicActor, - {{features::kGlicActorPolicyControlExemption.name, "true"}}}; - if (GetParam()) { - feature_list_.InitWithFeaturesAndParameters( - /*enabled_features=*/{enable_glic_policy, - {features::kGlicActorUiGlobalTaskIndicator, - {}}}, - /*disabled_features=*/{}); - } else { - feature_list_.InitWithFeaturesAndParameters( - /*enabled_features=*/{enable_glic_policy}, - /*disabled_features=*/{features::kGlicActorUiGlobalTaskIndicator}); - } + std::vector<base::test::FeatureRefAndParams> enabled_features = { + {features::kGlicActor, + {{features::kGlicActorPolicyControlExemption.name, "true"}}}}; + feature_list_.InitWithFeaturesAndParameters(std::move(enabled_features), + {}); + profile_ = std::make_unique<TestingProfile>(); actor_service_ = std::make_unique<ActorKeyedServiceFake>(profile_.get()); manager_ = std::make_unique<GlicActorTaskIconManager>(profile_.get(), @@ -102,17 +94,17 @@ base::test::ScopedFeatureList feature_list_; }; -TEST_P(GlicActorTaskIconManagerTest, DefaultState) { +TEST_F(GlicActorTaskIconManagerTest, DefaultState) { EXPECT_EQ(manager()->GetCurrentActorTaskNudgeState().text, ActorTaskNudgeState::Text::kDefault); } -TEST_P(GlicActorTaskIconManagerTest, NoActiveTasks_ReturnDefaultState) { +TEST_F(GlicActorTaskIconManagerTest, NoActiveTasks_ReturnDefaultState) { EXPECT_EQ(manager()->GetCurrentActorTaskNudgeState().text, ActorTaskNudgeState::Text::kDefault); } -TEST_P(GlicActorTaskIconManagerTest, NoDuplicatedTaskNudgeStateUpdates) { +TEST_F(GlicActorTaskIconManagerTest, NoDuplicatedTaskNudgeStateUpdates) { EXPECT_CALL( mock_nudge_subscriber_, OnStateChanged(AllOf(Field(&ActorTaskNudgeState::text, @@ -143,7 +135,7 @@ ActorTaskNudgeState::Text::kDefault); } -TEST_P(GlicActorTaskIconManagerTest, NudgeShowsDefaultTextOnComplete) { +TEST_F(GlicActorTaskIconManagerTest, NudgeShowsDefaultTextOnComplete) { EXPECT_CALL(mock_nudge_subscriber_, OnStateChanged(testing::_)).Times(0); TaskId task_id_1 = actor_service()->CreateTaskForTesting(); @@ -154,12 +146,8 @@ ActorTaskNudgeState::Text::kDefault); } -TEST_P(GlicActorTaskIconManagerTest, +TEST_F(GlicActorTaskIconManagerTest, PausedTaskUpdatesNudgeAndBubbleSubscribers) { - base::test::ScopedFeatureList scoped_features; - scoped_features.InitAndEnableFeature( - features::kGlicActorUiGlobalTaskIndicator); - EXPECT_CALL(mock_nudge_subscriber_, OnStateChanged(ActorTaskNudgeState{ .text = ActorTaskNudgeState::Text::kNeedsAttention})); @@ -175,7 +163,7 @@ EXPECT_EQ(manager()->GetNumActorTasksNeedProcessing(), 1u); } -TEST_P(GlicActorTaskIconManagerTest, +TEST_F(GlicActorTaskIconManagerTest, ProcessingTaskInBubbleAlsoUpdatesTaskNudge) { EXPECT_CALL(mock_nudge_subscriber_, OnStateChanged(ActorTaskNudgeState{ @@ -196,20 +184,11 @@ manager()->ProcessRowInTaskListBubble(task_id_1); EXPECT_EQ(manager()->GetCurrentActorTaskNudgeState().text, ActorTaskNudgeState::Text::kDefault); - if (GetParam()) { - EXPECT_EQ(manager()->actor_task_list_bubble_rows().size(), 1u); - } else { - // If GlicActorUiGlobalTaskIndicator is disabled, row will be removed. - EXPECT_EQ(manager()->actor_task_list_bubble_rows().size(), 0u); - } + EXPECT_EQ(manager()->actor_task_list_bubble_rows().size(), 1u); } -TEST_P(GlicActorTaskIconManagerTest, +TEST_F(GlicActorTaskIconManagerTest, MultipleTasksNeedAttentionNudgeShowsMultipleTasksText) { - base::test::ScopedFeatureList scoped_features; - scoped_features.InitAndEnableFeatureWithParameters( - features::kGlicActorUiGlobalTaskIndicator, {}); - EXPECT_CALL(mock_bubble_subscriber_, OnStateChanged()).Times(2); TaskId task_id_1 = actor_service()->CreateTaskForTesting(); @@ -227,12 +206,8 @@ EXPECT_EQ(manager()->GetNumActorTasksNeedProcessing(), 1u); } -TEST_P(GlicActorTaskIconManagerTest, +TEST_F(GlicActorTaskIconManagerTest, MultipleTasksNeedAttentionRemainsInPopoverUntilAllClicked) { - base::test::ScopedFeatureList scoped_features; - scoped_features.InitAndEnableFeatureWithParameters( - features::kGlicActorUiGlobalTaskIndicator, {}); - TaskId task_id_1 = actor_service()->CreateTaskForTesting(); TaskId task_id_2 = actor_service()->CreateTaskForTesting(); actor_service()->PauseTaskForTesting(task_id_1, /*from_actor=*/true); @@ -267,12 +242,8 @@ EXPECT_EQ(manager()->actor_task_list_bubble_rows().size(), 2u); } -TEST_P(GlicActorTaskIconManagerTest, +TEST_F(GlicActorTaskIconManagerTest, OnActorTaskRemoved_RemovesTaskAndUpdatesBubbleAndNudge) { - base::test::ScopedFeatureList scoped_features; - scoped_features.InitAndEnableFeature( - features::kGlicActorUiGlobalTaskIndicator); - // Create a task. TaskId task_id_1 = actor_service()->CreateTaskForTesting(); actor_service()->PauseTaskForTesting(task_id_1, /*from_actor=*/true); @@ -293,12 +264,8 @@ EXPECT_EQ(manager()->actor_task_list_bubble_rows().size(), 0u); } -TEST_P(GlicActorTaskIconManagerTest, +TEST_F(GlicActorTaskIconManagerTest, OnActorTaskStopped_ProcessStoppedTasksAndUpdatesBubbleAndNudge) { - base::test::ScopedFeatureList scoped_features; - scoped_features.InitAndEnableFeature( - features::kGlicActorUiGlobalTaskIndicator); - // Create tasks. TaskId task_id_1 = actor_service()->CreateTaskForTesting(); actor_service()->PauseTaskForTesting(task_id_1, /*from_actor=*/false); @@ -331,12 +298,9 @@ ActorTaskNudgeState::Text::kCompleteTasks); } -TEST_P(GlicActorTaskIconManagerTest, +TEST_F(GlicActorTaskIconManagerTest, NeedsAttentionNudgePrioritizesCompleteTasksNudge) { base::test::ScopedFeatureList scoped_features; - scoped_features.InitAndEnableFeature( - features::kGlicActorUiGlobalTaskIndicator); - // Create tasks. TaskId task_id_1 = actor_service()->CreateTaskForTesting(); actor_service()->StopTaskForTesting( @@ -349,12 +313,4 @@ ActorTaskNudgeState::Text::kNeedsAttention); } -INSTANTIATE_TEST_SUITE_P(All, - GlicActorTaskIconManagerTest, - testing::Bool(), - [](const testing::TestParamInfo<bool>& info) { - return info.param ? "GlobalIndicatorEnabled" - : "GlobalIndicatorDisabled"; - }); - } // namespace tabs
diff --git a/chrome/browser/ui/ui_features.cc b/chrome/browser/ui/ui_features.cc index 21e318b..e757592 100644 --- a/chrome/browser/ui/ui_features.cc +++ b/chrome/browser/ui/ui_features.cc
@@ -97,7 +97,6 @@ BASE_FEATURE(kSideBySide, base::FEATURE_ENABLED_BY_DEFAULT); - BASE_FEATURE(kSideBySideLinkMenuNewBadge, base::FEATURE_ENABLED_BY_DEFAULT); BASE_FEATURE(kTabDuplicateMetrics, base::FEATURE_ENABLED_BY_DEFAULT); @@ -335,7 +334,13 @@ kPageActionsMigrationIntentPicker, &kPageActionsMigration, "intent_picker", - true); +// TODOD(crbug.com/480035938): Enable on ChromeOS. +#if BUILDFLAG(IS_CHROMEOS) + false +#else + true +#endif +); BASE_FEATURE_PARAM(bool, kPageActionsMigrationZoom,
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc index bb3d3a0..0ed6bff4 100644 --- a/chrome/browser/ui/views/frame/browser_view.cc +++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -660,6 +660,9 @@ void UpdateUIForTabFullscreen() override { browser_view_->GetFrameView()->UpdateFullscreenTopUI(); + // Layout can change in vertical tabstrip mode when going from fullscreen + // to tab fullscreen and vice-versa. + browser_view_->InvalidateLayout(); } WebContents* GetWebContentsForExclusiveAccess() override {
diff --git a/chrome/browser/ui/views/frame/layout/browser_view_tabbed_layout_impl.cc b/chrome/browser/ui/views/frame/layout/browser_view_tabbed_layout_impl.cc index 75d9ee5b..7a4a640 100644 --- a/chrome/browser/ui/views/frame/layout/browser_view_tabbed_layout_impl.cc +++ b/chrome/browser/ui/views/frame/layout/browser_view_tabbed_layout_impl.cc
@@ -395,6 +395,15 @@ return TabStripType::kWebUi; } if (delegate().ShouldDrawVerticalTabStrip()) { +#if BUILDFLAG(IS_MAC) + // Do not lay out the vertical tabstrip in content-fullscreen on Mac. This + // check cannot be done in BrowserView because the immersive mode controller + // itself relies on BrowserView reporting which tab strip it *would* draw, + // creating a circular dependency/race condition. + if (fullscreen_utils::IsInContentFullscreen(browser())) { + return TabStripType::kNone; + } +#endif return TabStripType::kVertical; } return delegate().ShouldDrawTabStrip() ? TabStripType::kHorizontal @@ -1147,7 +1156,8 @@ // directly above the toolbar, so no corners are needed. break; default: - // Ideally this should not be reached. + // This can happen in content fullscreen on Mac, but otherwise doesn't + // happen. break; } toolbar_background->SetCorners(toolbar_corners);
diff --git a/chrome/browser/ui/views/tabs/glic/glic_actor_task_icon.cc b/chrome/browser/ui/views/tabs/glic/glic_actor_task_icon.cc index 72e0869..698b878 100644 --- a/chrome/browser/ui/views/tabs/glic/glic_actor_task_icon.cc +++ b/chrome/browser/ui/views/tabs/glic/glic_actor_task_icon.cc
@@ -31,12 +31,10 @@ const gfx::VectorIcon& GetTaskIcon() { #if BUILDFLAG(ENABLE_GLIC) - if (base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator)) { - return glic::GlicVectorIconManager::GetVectorIcon( - IDR_ACTOR_AUTO_BROWSE_ICON); - } -#endif + return glic::GlicVectorIconManager::GetVectorIcon(IDR_ACTOR_AUTO_BROWSE_ICON); +#else return gfx::VectorIcon::EmptyIcon(); +#endif } constexpr int kActorNudgeLabelMargin = 6; @@ -64,14 +62,13 @@ SetTaskIconToDefault(); - if (base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator)) { // The task icon will only ever be shown with the GlicButton, so can always // set the corner radii for split button styling. SetLeftRightCornerRadii(kSplitButtonFlatEdgeRadius, kSplitButtonRoundedEdgeRadius); TabStripControlButton::SetInkdropHoverColorId( kColorTabBackgroundInactiveHoverFrameActive); - } + UpdateColors(); SetFocusBehavior(FocusBehavior::ALWAYS); @@ -98,10 +95,7 @@ break; case AnimationMode::kNudge: int min_width = 0; - if (base::FeatureList::IsEnabled( - features::kGlicActorUiGlobalTaskIndicator)) { min_width = icon_only_width; - } width = std::lerp(min_width, full_width, GetWidthFactor()); break; } @@ -131,21 +125,7 @@ kColorNewTabButtonCRBackgroundFrameInactive); } -// TODO(crbug.com/470120703): Remove this method when GlobalTaskIndicator is -// enabled by default. -// NOTE: This method is only used for the nudge and has a misleading name. -void GlicActorTaskIcon::HighlightTaskIcon() { - SetBackgroundFrameActiveColorId(kColorTabBackgroundInactiveHoverFrameActive); - SetBackgroundFrameInactiveColorId( - kColorTabBackgroundInactiveHoverFrameInactive); -} - void GlicActorTaskIcon::SetPressedColor(bool is_pressed) { - if (!base::FeatureList::IsEnabled( - features::kGlicActorUiGlobalTaskIndicator)) { - return; - } - SetHighlighted(is_pressed); UpdateColors(); } @@ -155,11 +135,7 @@ // if we're using the ink drop to show the button's pressed state, skip // TabStripControlButton::NotifyClick() and just call the base // NotifyClick(). - if (base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator)) { LabelButton::NotifyClick(event); - } else { - TabStripNudgeButton::NotifyClick(event); - } } void GlicActorTaskIcon::SetTaskIconToDefault() { @@ -169,10 +145,6 @@ } void GlicActorTaskIcon::ShowNudgeLabel(const std::u16string nudge_label) { - if (!base::FeatureList::IsEnabled( - features::kGlicActorUiGlobalTaskIndicator)) { - HighlightTaskIcon(); - } SetText(nudge_label); SetTooltipText(nudge_label); } @@ -217,12 +189,10 @@ } void GlicActorTaskIcon::UpdateInkdropHoverColor(bool is_frame_active) { - if (base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator)) { SetInkdropHoverColorId(is_frame_active ? kColorTabBackgroundInactiveHoverFrameActive : kColorTabBackgroundInactiveHoverFrameInactive); UpdateColors(); - } } gfx::Rect GlicActorTaskIcon::GetAnchorBoundsInScreen() const {
diff --git a/chrome/browser/ui/views/tabs/glic/glic_actor_task_icon.h b/chrome/browser/ui/views/tabs/glic/glic_actor_task_icon.h index 3ea7b8f..e135d15 100644 --- a/chrome/browser/ui/views/tabs/glic/glic_actor_task_icon.h +++ b/chrome/browser/ui/views/tabs/glic/glic_actor_task_icon.h
@@ -38,9 +38,6 @@ // Sets the task icon back to its default colors. void SetDefaultColors(); - // Sets the task icon to its highlighted state. - void HighlightTaskIcon(); - // Sets the task icon's color to its pressed state color if `is_pressed` is // true, or to its default color otherwise. void SetPressedColor(bool is_pressed);
diff --git a/chrome/browser/ui/views/tabs/glic/glic_and_actor_buttons_container.cc b/chrome/browser/ui/views/tabs/glic/glic_and_actor_buttons_container.cc index d3b49af..2f5cba2 100644 --- a/chrome/browser/ui/views/tabs/glic/glic_and_actor_buttons_container.cc +++ b/chrome/browser/ui/views/tabs/glic/glic_and_actor_buttons_container.cc
@@ -46,13 +46,6 @@ SetCollapseMargins(true); SetCrossAxisAlignment(views::LayoutAlignment::kCenter); - if (!base::FeatureList::IsEnabled( - features::kGlicActorUiGlobalTaskIndicator)) { - // Remove background for global task indicator. - SetBackground(views::CreateRoundedRectBackground( - kColorNewTabButtonCRBackgroundFrameActive, gfx::RoundedCornersF(12), - gfx::Insets::VH(4, 8))); - } } GlicAndActorButtonsContainer::~GlicAndActorButtonsContainer() = default;
diff --git a/chrome/browser/ui/views/tabs/glic/glic_button.cc b/chrome/browser/ui/views/tabs/glic/glic_button.cc index 558b821..7efa7cdd 100644 --- a/chrome/browser/ui/views/tabs/glic/glic_button.cc +++ b/chrome/browser/ui/views/tabs/glic/glic_button.cc
@@ -736,8 +736,7 @@ return; } // If the label was previously collapsed, return to the collapsed state. - if (base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator) && - collapsed_before_nudge_shown_) { + if (collapsed_before_nudge_shown_) { Collapse(); return; } @@ -817,8 +816,7 @@ // margin is included. new_width += kLabelRightMargin; } - if (base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator) && - last_width_state_ == WidthState::kCollapsed) { + if (last_width_state_ == WidthState::kCollapsed) { // Add extra margin if the label was previously collapsed, as the old_width // is smaller. new_width += kCloseButtonMargin; @@ -903,9 +901,7 @@ bool GlicButton::IsHidingNudge() const { return (width_state_ == WidthState::kNormal || - (base::FeatureList::IsEnabled( - features::kGlicActorUiGlobalTaskIndicator) && - width_state_ == WidthState::kCollapsed)) && + width_state_ == WidthState::kCollapsed) && last_width_state_ == WidthState::kNudge; } @@ -951,12 +947,10 @@ } void GlicButton::UpdateInkdropHoverColor(bool is_frame_active) { - if (base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator)) { SetInkdropHoverColorId(is_frame_active ? kColorTabBackgroundInactiveHoverFrameActive : kColorTabBackgroundInactiveHoverFrameInactive); UpdateColors(); - } } BEGIN_METADATA(GlicButton)
diff --git a/chrome/browser/ui/views/tabs/tab_strip_action_container.cc b/chrome/browser/ui/views/tabs/tab_strip_action_container.cc index 4630608a..11ef7418 100644 --- a/chrome/browser/ui/views/tabs/tab_strip_action_container.cc +++ b/chrome/browser/ui/views/tabs/tab_strip_action_container.cc
@@ -525,11 +525,7 @@ controller->ShowBubble(glic_actor_task_icon_); auto current_task_nudge_state = icon_manager->GetCurrentActorTaskNudgeState(); - if (base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator)) { actor::ui::LogGlobalTaskIndicatorClick(current_task_nudge_state); - } else { - actor::ui::LogTaskNudgeClick(current_task_nudge_state); - } } #endif // BUILDFLAG(ENABLE_GLIC) @@ -611,10 +607,8 @@ glic_button_ = glic_actor_button_container_->InsertGlicButton(glic_button_); glic_actor_task_icon_->SetVisible(true); glic_actor_button_container_->SetVisible(true); - if (base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator)) { - glic_button_->Collapse(); - glic_button_->SetSplitButtonCornerStyling(); - } + glic_button_->Collapse(); + glic_button_->SetSplitButtonCornerStyling(); UpdateGlicActorButtonContainerBorders(); // If in entry mode, attempt to animate the icon's appearance. If the tab @@ -647,8 +641,6 @@ CHECK(glic_button_); CHECK(glic_actor_task_icon_); - // If feature is enabled, try to animate first. - if (base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator)) { // If it's already hidden, do nothing. if (!glic_actor_task_icon_->GetVisible()) { return; @@ -668,7 +660,6 @@ animation_session_->Start(); return; } - } // If animation isn't possible, snap hide immediately. FinalizeHideGlicActorTaskIcon(); #else @@ -684,23 +675,13 @@ animation_session_.reset(); } glic_actor_task_icon_->SetIsShowingNudge(false); - // Once we hide the nudge we want to bring the glic button default label - // back. - // TODO(mjenn): Remove when GlicActorUiGlobalTaskIndicator is launched. - if (!base::FeatureList::IsEnabled( - features::kGlicActorUiGlobalTaskIndicator)) { - glic_button_->Expand(); - } } glic_actor_task_icon_->SetVisible(false); glic_actor_task_icon_->SetTaskIconToDefault(); glic_button_ = AddChildView(std::move(glic_button_)); glic_actor_button_container_->SetVisible(false); - - if (base::FeatureList::IsEnabled(features::kGlicActorUiGlobalTaskIndicator)) { - glic_button_->Expand(); - glic_button_->ResetSplitButtonCornerStyling(); - } + glic_button_->Expand(); + glic_button_->ResetSplitButtonCornerStyling(); // Reset the animation mode for the next time the icon is shown. glic_actor_task_icon_->SetAnimationMode(TaskIconAnimationMode::kEntry); UpdateGlicActorButtonContainerBorders();
diff --git a/chrome/browser/ui/views/tabs/tab_strip_action_container_browsertest.cc b/chrome/browser/ui/views/tabs/tab_strip_action_container_browsertest.cc index 78cfbeb..1a0db3a 100644 --- a/chrome/browser/ui/views/tabs/tab_strip_action_container_browsertest.cc +++ b/chrome/browser/ui/views/tabs/tab_strip_action_container_browsertest.cc
@@ -96,7 +96,6 @@ {features::kGlicFreWarming, {}}, {features::kGlicActorUi, { {features::kGlicActorUiTaskIconName, "true"} }}, - {features::kGlicActorUiGlobalTaskIndicator, {}}, #endif // BUILDFLAG(ENABLE_GLIC) {contextual_cueing::kContextualCueing, {}}, }, @@ -536,28 +535,8 @@ IDS_ACTOR_TASK_NUDGE_CHECK_TASK_LABEL, 2)); } -class GlicActorGlobalFlagEnabledBrowserTest - : public TabStripActionContainerBrowserTest { - public: - GlicActorGlobalFlagEnabledBrowserTest() { - features_.InitWithFeaturesAndParameters( - { - {features::kGlicRollout, {}}, - {features::kGlicFreWarming, {}}, - {features::kGlicActorUiGlobalTaskIndicator, {}}, - {features::kGlicActorUi, - {{features::kGlicActorUiTaskIconName, "true"}}}, - {contextual_cueing::kContextualCueing, {}}, - }, - {}); - } - - private: - base::test::ScopedFeatureList features_; -}; - -IN_PROC_BROWSER_TEST_F(GlicActorGlobalFlagEnabledBrowserTest, - GlicActorCompleteShowsNudgeWithGlobalFlagEnabled) { +IN_PROC_BROWSER_TEST_F(TabStripActionContainerBrowserTest, + GlicActorCompleteShowsNudge) { base::HistogramTester histogram_tester; EXPECT_EQ(GlicActorTaskIcon()->GetText(), std::u16string()); ASSERT_FALSE(tab_strip_action_container()->animation_session_for_testing()); @@ -623,123 +602,6 @@ "Actor.Ui.TaskNudge.NeedsAttention.Click")); } -class GlicActorGlobalFlagDisabledBrowserTest - : public TabStripActionContainerBrowserTest { - public: - GlicActorGlobalFlagDisabledBrowserTest() { - features_.InitWithFeaturesAndParameters( - { - {features::kTabOrganization, {}}, - {features::kGlicRollout, {}}, - {features::kGlicFreWarming, {}}, - {features::kGlicActorUi, - {{features::kGlicActorUiTaskIconName, "true"}}}, - {features::kTabstripDeclutter, {}}, - {contextual_cueing::kContextualCueing, {}}, - }, - {features::kGlicActorUiGlobalTaskIndicator}); - } - - private: - base::test::ScopedFeatureList features_; -}; - -// TODO(crbug.com/444706814): Fix flaky test for Linux or remove when feature is -// launched. -IN_PROC_BROWSER_TEST_F( - GlicActorGlobalFlagDisabledBrowserTest, - ActivatesTabAndRemoveRowOnGlicActorTaskListBubbleRowClick) { - ASSERT_TRUE(embedded_https_test_server().Start()); - // Navigate the active tab to a new page. - ASSERT_TRUE(ui_test_utils::NavigateToURL( - browser(), embedded_https_test_server().GetURL("/actor/blank.html"))); - actor::TaskId task_id = CreateTask(); - - // Add and activate the non-actuation tab. - ASSERT_TRUE(AddTabAtIndexToBrowser(browser(), 1, - GURL(chrome::kChromeUINewTabURL), - ui::PAGE_TRANSITION_LINK)); - browser()->GetTabStripModel()->ActivateTabAt(1); - - actor_service()->GetTask(task_id)->Pause(/*from_actor=*/true); - ASSERT_TRUE(base::test::RunUntil( - [&] { return GlicActorTaskIcon()->GetIsShowingNudge(); })); - - ResetAnimation(1); - - auto* bubble_controller = ActorTaskListBubbleController::From(browser()); - auto* content_view = bubble_controller->GetBubbleWidget() - ->widget_delegate() - ->AsBubbleDialogDelegate() - ->GetContentsView(); - EXPECT_EQ(1u, content_view->children().size()); - auto* button = static_cast<RichHoverButton*>( - content_view->children().front()->children().front()); - Click(button); - - // Nudge should hide and row list should be emptied. - ASSERT_TRUE(base::test::RunUntil( - [&] { return !GlicActorTaskIcon()->GetIsShowingNudge(); })); - auto* manager = tabs::GlicActorTaskIconManagerFactory::GetForProfile( - browser()->GetProfile()); - EXPECT_EQ(0u, manager->actor_task_list_bubble_rows().size()); -} - -IN_PROC_BROWSER_TEST_F(GlicActorGlobalFlagDisabledBrowserTest, - GlicActorCompleteDoesNotShowTaskNudge) { - base::HistogramTester histogram_tester; - EXPECT_EQ(GlicActorTaskIcon()->GetText(), std::u16string()); - ASSERT_FALSE(tab_strip_action_container()->animation_session_for_testing()); - EXPECT_FALSE(GlicActorButtonContainer()->GetVisible()); - - auto* actor_nudge_controller = - tabs::GlicActorNudgeController::From(browser()); - auto actor_task_nudge_state = ActorTaskNudgeState(); - actor_task_nudge_state.text = ActorTaskNudgeState::Text::kCompleteTasks; - actor_nudge_controller->OnStateUpdate(actor_task_nudge_state); - - EXPECT_EQ(GlicActorTaskIcon()->GetText(), std::u16string()); - EXPECT_FALSE(GlicActorButtonContainer()->GetVisible()); - EXPECT_FALSE(GlicActorTaskIcon()->GetIsShowingNudge()); - - EXPECT_TRUE(RunUntil([&]() { return !GlicActorTaskIcon()->GetVisible(); })); - EXPECT_EQ(histogram_tester.GetBucketCount( - "Actor.Ui.TaskNudge.Shown", - ActorTaskNudgeState::Text::kCompleteTasks), - 0); -} - -IN_PROC_BROWSER_TEST_F(GlicActorGlobalFlagDisabledBrowserTest, - LogsWhenGlicActorTaskNudgeClicked) { - base::HistogramTester histogram_tester; - EXPECT_FALSE(GlicActorButtonContainer()->GetVisible()); - ASSERT_THAT(GlicActorButtonContainer()->children(), SizeIs(2)); - - actor::TaskId task_id = CreateTask(); - - auto* manager = tabs::GlicActorTaskIconManagerFactory::GetForProfile( - browser()->GetProfile()); - - actor_service()->GetTask(task_id)->SetState(actor::ActorTask::State::kActing); - actor_service()->GetTask(task_id)->Interrupt(); - manager->UpdateTaskIconComponents(task_id); - - EXPECT_TRUE( - RunUntil([&]() { return GlicActorTaskIcon()->GetIsShowingNudge(); })); - EXPECT_TRUE(GlicActorButtonContainer()->GetVisible()); - - ASSERT_TRUE(base::test::RunUntil([&]() { - return histogram_tester.GetBucketCount( - "Actor.Ui.TaskNudge.Shown", - ActorTaskNudgeState::Text::kNeedsAttention) == 1; - })); - - base::UserActionTester user_action_tester; - OnButtonClicked(GlicActorTaskIcon()); - EXPECT_EQ(1, user_action_tester.GetActionCount( - "Actor.Ui.TaskNudge.NeedsAttention.Click")); -} - #if !BUILDFLAG(IS_ANDROID) class TabStripActionContainerLegionBrowserTest : public TabStripActionContainerBrowserTest {
diff --git a/chrome/browser/ui/views/tabs/vertical/root_tab_collection_node.cc b/chrome/browser/ui/views/tabs/vertical/root_tab_collection_node.cc index 0ffb54fa9..a285736 100644 --- a/chrome/browser/ui/views/tabs/vertical/root_tab_collection_node.cc +++ b/chrome/browser/ui/views/tabs/vertical/root_tab_collection_node.cc
@@ -140,14 +140,22 @@ return; } - std::set<tabs::TabInterface*> changed_tabs; + std::set<TabCollectionNode*> changed_tabs; if (selection.active_tab_changed()) { if (selection.old_tab) { - changed_tabs.insert(selection.old_tab); + TabCollectionNode* old_tab_node = + GetNodeForHandle(selection.old_tab->GetHandle()); + if (old_tab_node) { + changed_tabs.insert(old_tab_node); + } } if (selection.new_tab) { - changed_tabs.insert(selection.new_tab); + TabCollectionNode* new_tab_node = + GetNodeForHandle(selection.new_tab->GetHandle()); + if (new_tab_node) { + changed_tabs.insert(new_tab_node); + } } } @@ -162,10 +170,11 @@ auto new_selections = base::STLSetDifference<SelectionHandles>(selected_tabs, selected_tabs_); - for (tabs::TabHandle tab_handle : + for (auto tab_handle : base::STLSetUnion<SelectionHandles>(old_selections, new_selections)) { - if (auto* tab = tab_handle.Get()) { - changed_tabs.insert(tab); + TabCollectionNode* tab_node = GetNodeForHandle(tab_handle); + if (tab_node) { + changed_tabs.insert(tab_node); } } selected_tabs_ = selected_tabs; @@ -175,29 +184,13 @@ // Discarding a tab causes a replace change notification to be sent. Add any // replaced tab to the list of tabs to update. auto* replace = change.GetReplace(); - changed_tabs.insert(tab_strip_model->GetTabAtIndex(replace->index)); + TabCollectionNode* tab_node = GetNodeForHandle( + tab_strip_model->GetTabAtIndex(replace->index)->GetHandle()); + changed_tabs.insert(tab_node); } - std::set<TabCollectionNode*> final_nodes_to_notify; - for (auto* tab : changed_tabs) { - if (auto* node = GetNodeForHandle(tab->GetHandle())) { - final_nodes_to_notify.insert(node); - } - - if (tab->IsSplit()) { - if (const auto* split_data = - tab_strip_model->GetSplitData(tab->GetSplit().value())) { - for (auto* sibling : split_data->ListTabs()) { - if (auto* sibling_node = GetNodeForHandle(sibling->GetHandle())) { - final_nodes_to_notify.insert(sibling_node); - } - } - } - } - } - - for (auto* node : final_nodes_to_notify) { - node->NotifyDataChanged(); + for (auto* tab_node : changed_tabs) { + tab_node->NotifyDataChanged(); } }
diff --git a/chrome/browser/ui/webui/cr_components/searchbox/searchbox_handler.cc b/chrome/browser/ui/webui/cr_components/searchbox/searchbox_handler.cc index 218cdb1..7f3456b 100644 --- a/chrome/browser/ui/webui/cr_components/searchbox/searchbox_handler.cc +++ b/chrome/browser/ui/webui/cr_components/searchbox/searchbox_handler.cc
@@ -401,10 +401,6 @@ u"activitycontrols?settings=search&utm_source=" u"aim&utm_campaign=aim_str")); - source->AddBoolean( - "searchboxMatchSearchboxTheme", - base::FeatureList::IsEnabled(ntp_features::kRealboxMatchSearchboxTheme)); - DefineChromeRefreshRealboxIcons(); source->AddString( "searchboxDefaultIcon",
diff --git a/chrome/browser/url_constants/android/BUILD.gn b/chrome/browser/url_constants/android/BUILD.gn index 2fbeff17..f67e376 100644 --- a/chrome/browser/url_constants/android/BUILD.gn +++ b/chrome/browser/url_constants/android/BUILD.gn
@@ -9,6 +9,7 @@ "java/src/org/chromium/chrome/browser/url_constants/ExtensionsUrlOverrideRegistry.java", "java/src/org/chromium/chrome/browser/url_constants/UrlConstantResolver.java", "java/src/org/chromium/chrome/browser/url_constants/UrlConstantResolverFactory.java", + "java/src/org/chromium/chrome/browser/url_constants/UrlOverrideUtils.java", ] deps = [ "//base:base_shared_preferences_java",
diff --git a/chrome/browser/url_constants/android/java/src/org/chromium/chrome/browser/url_constants/UrlConstantResolverFactory.java b/chrome/browser/url_constants/android/java/src/org/chromium/chrome/browser/url_constants/UrlConstantResolverFactory.java index 4a061ea9..ff29798 100644 --- a/chrome/browser/url_constants/android/java/src/org/chromium/chrome/browser/url_constants/UrlConstantResolverFactory.java +++ b/chrome/browser/url_constants/android/java/src/org/chromium/chrome/browser/url_constants/UrlConstantResolverFactory.java
@@ -12,6 +12,11 @@ import static org.chromium.chrome.browser.url_constants.UrlConstantResolver.getOriginalNonNativeHistoryUrl; import static org.chromium.chrome.browser.url_constants.UrlConstantResolver.getOriginalNonNativeNtpGurl; import static org.chromium.chrome.browser.url_constants.UrlConstantResolver.getOriginalNonNativeNtpUrl; +import static org.chromium.chrome.browser.url_constants.UrlOverrideUtils.isBookmarksPageOverrideEnabled; +import static org.chromium.chrome.browser.url_constants.UrlOverrideUtils.isHistoryPageOverrideEnabled; +import static org.chromium.chrome.browser.url_constants.UrlOverrideUtils.isIncognitoBookmarksPageOverrideEnabled; +import static org.chromium.chrome.browser.url_constants.UrlOverrideUtils.isIncognitoNtpOverrideEnabled; +import static org.chromium.chrome.browser.url_constants.UrlOverrideUtils.isNtpOverrideEnabled; import androidx.annotation.VisibleForTesting; @@ -75,22 +80,13 @@ resolver.registerOverride( getOriginalNativeNtpUrl(), - () -> - ExtensionsUrlOverrideRegistry.getNtpOverrideEnabled() - ? getOriginalNonNativeNtpUrl() - : null); + () -> isNtpOverrideEnabled() ? getOriginalNonNativeNtpUrl() : null); resolver.registerOverride( getOriginalNativeBookmarksUrl(), - () -> - ExtensionsUrlOverrideRegistry.getBookmarksPageOverrideEnabled() - ? getOriginalNonNativeBookmarksUrl() - : null); + () -> isBookmarksPageOverrideEnabled() ? getOriginalNonNativeBookmarksUrl() : null); resolver.registerOverride( getOriginalNativeHistoryUrl(), - () -> - ExtensionsUrlOverrideRegistry.getHistoryPageOverrideEnabled() - ? getOriginalNonNativeHistoryUrl() - : null); + () -> isHistoryPageOverrideEnabled() ? getOriginalNonNativeHistoryUrl() : null); return resolver; } @@ -100,14 +96,11 @@ resolver.registerOverride( getOriginalNativeNtpUrl(), - () -> - ExtensionsUrlOverrideRegistry.getIncognitoNtpOverrideEnabled() - ? getOriginalNonNativeNtpUrl() - : null); + () -> isIncognitoNtpOverrideEnabled() ? getOriginalNonNativeNtpUrl() : null); resolver.registerOverride( getOriginalNativeBookmarksUrl(), () -> - ExtensionsUrlOverrideRegistry.getIncognitoBookmarksPageOverrideEnabled() + isIncognitoBookmarksPageOverrideEnabled() ? getOriginalNonNativeBookmarksUrl() : null); return resolver;
diff --git a/chrome/browser/url_constants/android/java/src/org/chromium/chrome/browser/url_constants/UrlOverrideUtils.java b/chrome/browser/url_constants/android/java/src/org/chromium/chrome/browser/url_constants/UrlOverrideUtils.java new file mode 100644 index 0000000..407d237 --- /dev/null +++ b/chrome/browser/url_constants/android/java/src/org/chromium/chrome/browser/url_constants/UrlOverrideUtils.java
@@ -0,0 +1,39 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.url_constants; + +import org.chromium.build.annotations.NullMarked; + +/** + * Contains various predicates for whether a given URL is overridden. Hides various registries + * backing the given overrides. + */ +@NullMarked +public class UrlOverrideUtils { + /** Returns true if the NTP is overridden. */ + public static boolean isNtpOverrideEnabled() { + return ExtensionsUrlOverrideRegistry.getNtpOverrideEnabled(); + } + + /** Returns true if the bookmarks page is overridden. */ + public static boolean isBookmarksPageOverrideEnabled() { + return ExtensionsUrlOverrideRegistry.getBookmarksPageOverrideEnabled(); + } + + /** Returns true if the history page is overridden. */ + public static boolean isHistoryPageOverrideEnabled() { + return ExtensionsUrlOverrideRegistry.getHistoryPageOverrideEnabled(); + } + + /** Returns true if the NTP is overridden for incognito. */ + public static boolean isIncognitoNtpOverrideEnabled() { + return ExtensionsUrlOverrideRegistry.getIncognitoNtpOverrideEnabled(); + } + + /** Returns true if the bookmarks page is overridden for incognito. */ + public static boolean isIncognitoBookmarksPageOverrideEnabled() { + return ExtensionsUrlOverrideRegistry.getIncognitoBookmarksPageOverrideEnabled(); + } +}
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt index e4cddef4..d039bba 100644 --- a/chrome/build/mac-arm.pgo.txt +++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@ -chrome-mac-arm-main-1770299978-0394280cc451bcafe40bc9f23e5295ce3342266f-20b0cbed004f322af4a94525a50ab551170340e4.profdata +chrome-mac-arm-main-1770307167-2964bbf9be7cab928e9d078d4e849fb451c42d0b-959483b6df284d32c094e9a8f4ab0c47377664c2.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt index 07a187e..380e625 100644 --- a/chrome/build/win64.pgo.txt +++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@ -chrome-win64-main-1770260120-6fb56ff762c9f7d09ff7e8ea73a8b38461a6352e-7c527170a17cb758d33610153bbe2ed590bb8b70.profdata +chrome-win64-main-1770281816-b48710f96a60c6d93f237f6d69533bd182d212e7-280254c35dc9c001da5d3767bee360c685008fe9.profdata
diff --git a/chrome/chrome_paks.gni b/chrome/chrome_paks.gni index d0fb786..399674a 100644 --- a/chrome/chrome_paks.gni +++ b/chrome/chrome_paks.gni
@@ -528,6 +528,9 @@ "//chrome/browser/resources/extensions:resources", "//extensions:extensions_resources", ] + } else if (is_android) { + sources += [ "$root_gen_dir/chrome/guest_view_shared_resources.pak" ] + deps += [ "//chrome/browser/resources/guest_view_shared:resources" ] } if (enable_pdf) {
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc index 4c22e12..370fdf0f 100644 --- a/chrome/common/chrome_features.cc +++ b/chrome/common/chrome_features.cc
@@ -282,8 +282,6 @@ // Controls whether the Actor UI components are enabled. BASE_FEATURE(kGlicActorUi, base::FEATURE_ENABLED_BY_DEFAULT); -// Controls whether the global task indicator and related features are enabled. -BASE_FEATURE(kGlicActorUiGlobalTaskIndicator, base::FEATURE_ENABLED_BY_DEFAULT); // Controls whether we ignore users preference of reduced motion enabled and // still show the tab indicator spinner. No-op if kGlicActorUiTabIndicator is // disabled. @@ -1936,10 +1934,6 @@ #endif // BUILDFLAG(IS_WIN) #if BUILDFLAG(IS_CHROMEOS) -// A feature to enable event based log uploads. See -// go/cros-eventbasedlogcollection-dd. -BASE_FEATURE(kEventBasedLogUpload, base::FEATURE_ENABLED_BY_DEFAULT); - // A feature to enable periodic log upload migration. This includes using new // mechanism for collecting, exporting and uploading logs. See // go/legacy-log-upload-migration.
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h index 633737d..b672672 100644 --- a/chrome/common/chrome_features.h +++ b/chrome/common/chrome_features.h
@@ -200,8 +200,6 @@ COMPONENT_EXPORT(CHROME_FEATURES) BASE_DECLARE_FEATURE(kEnterpriseReportingInChromeOS); COMPONENT_EXPORT(CHROME_FEATURES) -BASE_DECLARE_FEATURE(kEventBasedLogUpload); -COMPONENT_EXPORT(CHROME_FEATURES) BASE_DECLARE_FEATURE(kFileTransferEnterpriseConnector); COMPONENT_EXPORT(CHROME_FEATURES) BASE_DECLARE_FEATURE(kFileTransferEnterpriseConnectorUI); @@ -220,8 +218,6 @@ extern const base::FeatureParam<base::TimeDelta> kGlicActorClickDelay; COMPONENT_EXPORT(CHROME_FEATURES) BASE_DECLARE_FEATURE(kGlicActorUi); COMPONENT_EXPORT(CHROME_FEATURES) -BASE_DECLARE_FEATURE(kGlicActorUiGlobalTaskIndicator); -COMPONENT_EXPORT(CHROME_FEATURES) BASE_DECLARE_FEATURE(kGlicActorUiTabIndicatorSpinnerIgnoreReducedMotion); COMPONENT_EXPORT(CHROME_FEATURES) BASE_DECLARE_FEATURE(kActorUiThemed);
diff --git a/chrome/installer/setup/install.cc b/chrome/installer/setup/install.cc index 3d3aac91..aa11a38 100644 --- a/chrome/installer/setup/install.cc +++ b/chrome/installer/setup/install.cc
@@ -14,7 +14,6 @@ #include "base/base_paths.h" #include "base/command_line.h" -#include "base/debug/dump_without_crashing.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/files/important_file_writer.h" @@ -22,7 +21,6 @@ #include "base/numerics/safe_conversions.h" #include "base/path_service.h" #include "base/process/launch.h" -#include "base/rand_util.h" #include "base/strings/strcat.h" #include "base/strings/strcat_win.h" #include "base/strings/string_number_conversions.h" @@ -30,7 +28,6 @@ #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" -#include "base/version_info/channel.h" #include "base/win/shortcut.h" #include "chrome/install_static/install_details.h" #include "chrome/install_static/install_util.h" @@ -167,30 +164,6 @@ } } -// Returns true if a diagnostic crash dump should be uploaded in case of a -// failure while installing or updating the browser. -bool ShouldSampleFailures() { - // Sample across channels based on historic error rates. - double report_probability = 0.0; - switch (install_static::GetChromeChannel()) { - case version_info::Channel::CANARY: - report_probability = 0.10; - break; - case version_info::Channel::DEV: - report_probability = 0.05; - break; - case version_info::Channel::BETA: - report_probability = 0.10; - break; - case version_info::Channel::STABLE: - report_probability = 0.00067; - break; - default: - return false; - } - return base::RandDouble() < report_probability; -} - // This function installs a new version of Chrome to the specified location. // // install_params: See install_params.h @@ -222,10 +195,6 @@ installer_state.SetStage(EXECUTING); if (!install_list->Do()) { - if (ShouldSampleFailures()) { - // TODO(crbug.com/40462942): Remove after analyzing results. - base::debug::DumpWithoutCrashing(); - } installer_state.SetStage(ROLLINGBACK); InstallStatus result = base::PathExists(new_chrome_exe) && current_version.IsValid() &&
diff --git a/chrome/renderer/BUILD.gn b/chrome/renderer/BUILD.gn index f5cd8b1..ccc22d2 100644 --- a/chrome/renderer/BUILD.gn +++ b/chrome/renderer/BUILD.gn
@@ -5,6 +5,7 @@ import("//build/config/buildflags_paint_preview.gni") import("//chrome/common/features.gni") import("//chrome/common/request_header_integrity/buildflags.gni") +import("//components/guest_view/buildflags/buildflags.gni") import("//components/offline_pages/buildflags/features.gni") import("//components/optimization_guide/features.gni") import("//components/signin/features.gni") @@ -295,6 +296,8 @@ "//chrome/renderer/extensions", "//extensions/renderer", ] + } else if (enable_guest_view) { + deps += [ "//components/guest_view/renderer/slim_web_view" ] } if (enable_extensions) {
diff --git a/chrome/renderer/chrome_render_frame_observer.cc b/chrome/renderer/chrome_render_frame_observer.cc index 5743756f..99fac96ec 100644 --- a/chrome/renderer/chrome_render_frame_observer.cc +++ b/chrome/renderer/chrome_render_frame_observer.cc
@@ -36,6 +36,8 @@ #include "chrome/renderer/media/media_feeds.h" #include "chrome/renderer/process_state.h" #include "components/crash/core/common/crash_key.h" +#include "components/guest_view/buildflags/buildflags.h" +#include "components/guest_view/renderer/slim_web_view/slim_web_view_bindings.h" #include "components/lens/lens_metadata.mojom.h" #include "components/no_state_prefetch/renderer/no_state_prefetch_helper.h" #include "components/no_state_prefetch/renderer/no_state_prefetch_utils.h" @@ -348,6 +350,9 @@ ReadAnythingAppController::Install(render_frame()); } #endif // !BUILDFLAG(IS_ANDROID) +#if BUILDFLAG(ENABLE_GUEST_VIEW) && !BUILDFLAG(ENABLE_EXTENSIONS_CORE) + guest_view::SlimWebViewBindings::MaybeInstall(*render_frame()); +#endif // BUILDFLAG(ENABLE_GUEST_VIEW) && !BUILDFLAG(ENABLE_EXTENSIONS_CORE) } void ChromeRenderFrameObserver::DidMeaningfulLayout(
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index 95766cc..92aeffcb 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn
@@ -2017,6 +2017,7 @@ "//components/autofill/content/renderer:test_support", "//components/autofill/core/common", "//components/back_forward_cache", + "//components/background_task_scheduler", "//components/blocked_content", "//components/browser_ui/widget/android:java", "//components/browsing_data/content",
diff --git a/chrome/test/data/webui/contextual_tasks/composebox_test.ts b/chrome/test/data/webui/contextual_tasks/composebox_test.ts index d3ced3d33..89aec2b 100644 --- a/chrome/test/data/webui/contextual_tasks/composebox_test.ts +++ b/chrome/test/data/webui/contextual_tasks/composebox_test.ts
@@ -1662,7 +1662,6 @@ mockSpeechRecognition.onresult!(result2); await microtasksFinished(); - assertEquals('hellogoodbye', composebox.$.input.value); assertEquals( 1, metrics.count( @@ -1700,7 +1699,6 @@ /* VOICE_SEARCH_TRANSCRIPTION_SUCCESS */ 1), 'Voice search transcription success\ metric count is incorrect for idle timeout'); - assertEquals('test', composebox.$.input.value); }); test('on error shows error container for NOT_ALLOWED', async () => {
diff --git a/chrome/test/data/webui/contextual_tasks/contextual_tasks_pixel_interactive_ui_test.cc b/chrome/test/data/webui/contextual_tasks/contextual_tasks_pixel_interactive_ui_test.cc index f442195..9491573 100644 --- a/chrome/test/data/webui/contextual_tasks/contextual_tasks_pixel_interactive_ui_test.cc +++ b/chrome/test/data/webui/contextual_tasks/contextual_tasks_pixel_interactive_ui_test.cc
@@ -316,7 +316,7 @@ SetOnIncompatibleAction(OnIncompatibleAction::kIgnoreAndContinue, "Screenshots not captured on this platform."), ScreenshotWebUi(kActiveTab, kApp, "ContextualTasksApp", - /*baseline_cl=*/"7531588")); + /*baseline_cl=*/"7530305")); } enum class TitleType { kNone, kShort, kLong }; @@ -431,11 +431,11 @@ OnIncompatibleAction::kIgnoreAndContinue, "Screenshots not captured on this platform."), ScreenshotWebUi(kActiveTab, menu, "ContextualTasksToolbarMenu", - /*baseline_cl=*/"7531588")), + /*baseline_cl=*/"7530305")), Else(WaitForWebContentsPainted(kActiveTab), SetOnIncompatibleAction( OnIncompatibleAction::kIgnoreAndContinue, "Screenshots not captured on this platform."), ScreenshotWebUi(kActiveTab, toolbar, "ContextualTasksToolbar", - /*baseline_cl=*/"7531588")))); + /*baseline_cl=*/"7530305")))); }
diff --git a/chrome/test/data/webui/glic/browser_tests/glic_api_browsertest.ts b/chrome/test/data/webui/glic/browser_tests/glic_api_browsertest.ts index 505faaf8..5740ef1 100644 --- a/chrome/test/data/webui/glic/browser_tests/glic_api_browsertest.ts +++ b/chrome/test/data/webui/glic/browser_tests/glic_api_browsertest.ts
@@ -2763,10 +2763,14 @@ skills.find(s => s.id === 'contextual_skill_id_1'); assertDefined(contextual_skill_1); assertEquals('contextual_skill_1', contextual_skill_1.name); + assertEquals( + 'contextual_skill_description_1', contextual_skill_1.description); const contextual_skill_2 = skills.find(s => s.id === 'contextual_skill_id_2'); assertDefined(contextual_skill_2); assertEquals('contextual_skill_2', contextual_skill_2.name); + assertEquals( + 'contextual_skill_description_2', contextual_skill_2.description); assertDefined(skills.find(s => s.name === 'user_skill_1')); assertDefined(skills.find(s => s.name === 'user_skill_2')); await this.advanceToNextStep(); @@ -2779,6 +2783,8 @@ skills.find(s => s.id === 'contextual_skill_id_3'); assertDefined(contextual_skill_3); assertEquals('contextual_skill_3', contextual_skill_3.name); + assertEquals( + 'contextual_skill_description_3', contextual_skill_3.description); assertDefined(skills.find(s => s.name === 'user_skill_1')); assertDefined(skills.find(s => s.name === 'user_skill_2')); }
diff --git a/chrome/test/data/webui/settings/settings_menu_interactive_ui_test.ts b/chrome/test/data/webui/settings/settings_menu_interactive_ui_test.ts index b4d3876..bfaf43e3 100644 --- a/chrome/test/data/webui/settings/settings_menu_interactive_ui_test.ts +++ b/chrome/test/data/webui/settings/settings_menu_interactive_ui_test.ts
@@ -56,6 +56,6 @@ createMenu(); settingsMenu.focusFirstItem(); assertEquals( - settingsMenu.$.yourSavedInfo, settingsMenu.shadowRoot!.activeElement); + settingsMenu.$.autofill, settingsMenu.shadowRoot!.activeElement); }); });
diff --git a/chrome/test/data/webui/settings/settings_menu_test.ts b/chrome/test/data/webui/settings/settings_menu_test.ts index 49885326..87bdead0 100644 --- a/chrome/test/data/webui/settings/settings_menu_test.ts +++ b/chrome/test/data/webui/settings/settings_menu_test.ts
@@ -202,18 +202,6 @@ assertEquals(routes.AUTOFILL, Router.getInstance().getCurrentRoute()); }); - test('yourSavedInfoHiddenWhenFeatureDisabled', async function() { - loadTimeData.overrideValues({enableYourSavedInfoSettingsPage: false}); - resetRouterForTesting(); - createSettingsMenu(); - await flushTasks(); - - const entry = settingsMenu.shadowRoot!.querySelector<HTMLElement>( - 'a[href=\'/yourSavedInfo\']'); - assertTrue(!!entry); - assertFalse(isVisible(entry)); - }); - test('yourSavedInfoMenuItemClick', async function() { loadTimeData.overrideValues({enableYourSavedInfoSettingsPage: true}); resetRouterForTesting(); @@ -221,7 +209,7 @@ await flushTasks(); const entry = settingsMenu.shadowRoot!.querySelector<HTMLElement>( - 'a[href=\'/yourSavedInfo\']'); + 'a[href=\'/autofill\']'); assertTrue(!!entry); assertTrue(isVisible(entry)); @@ -235,7 +223,7 @@ const selector = settingsMenu.$.menu; assertTrue(!!selector.selected); - assertEquals('/yourSavedInfo', selector.selected.toString()); + assertEquals('/autofill', selector.selected.toString()); assertEquals( routes.YOUR_SAVED_INFO, Router.getInstance().getCurrentRoute()); });
diff --git a/chrome/test/data/webui/skills/discover_skills_page_test.ts b/chrome/test/data/webui/skills/discover_skills_page_test.ts index 6319ca5..73185a6 100644 --- a/chrome/test/data/webui/skills/discover_skills_page_test.ts +++ b/chrome/test/data/webui/skills/discover_skills_page_test.ts
@@ -39,6 +39,7 @@ name: 'Top Skill', icon: '', prompt: '', + description: '', source: SkillSource.kFirstParty, creationTime: {internalValue: 0n}, lastUpdateTime: {internalValue: 0n}, @@ -51,6 +52,7 @@ name: 'Write', icon: '', prompt: '', + description: '', source: SkillSource.kFirstParty, creationTime: {internalValue: 0n}, lastUpdateTime: {internalValue: 0n}, @@ -92,6 +94,7 @@ name: 'Shopping', icon: '', prompt: '', + description: '', source: SkillSource.kFirstParty, creationTime: {internalValue: 0n}, lastUpdateTime: {internalValue: 0n}, @@ -104,6 +107,7 @@ name: 'Writing', icon: '', prompt: '', + description: '', source: SkillSource.kFirstParty, creationTime: {internalValue: 0n}, lastUpdateTime: {internalValue: 0n},
diff --git a/chrome/test/data/webui/skills/skills_dialog_test.ts b/chrome/test/data/webui/skills/skills_dialog_test.ts index 8165157..40ae019 100644 --- a/chrome/test/data/webui/skills/skills_dialog_test.ts +++ b/chrome/test/data/webui/skills/skills_dialog_test.ts
@@ -44,6 +44,7 @@ name: 'test skill', icon: '', prompt: 'test prompt', + description: 'test description', source: SkillSource.kUserCreated, creationTime: {internalValue: 0n}, lastUpdateTime: {internalValue: 0n},
diff --git a/chrome/test/data/webui/skills/user_skills_page_test.ts b/chrome/test/data/webui/skills/user_skills_page_test.ts index 985b96c8..70c2357 100644 --- a/chrome/test/data/webui/skills/user_skills_page_test.ts +++ b/chrome/test/data/webui/skills/user_skills_page_test.ts
@@ -63,6 +63,7 @@ icon: 'icon', prompt: 'prompt', source: SkillSource.kUserCreated, + description: 'description', creationTime: {internalValue: 0n}, lastUpdateTime: {internalValue: 0n}, }; @@ -82,6 +83,7 @@ name: 'Test Skill', icon: 'icon', prompt: 'prompt', + description: 'description', source: SkillSource.kUserCreated, creationTime: {internalValue: 0n}, lastUpdateTime: {internalValue: 0n}, @@ -106,6 +108,7 @@ name: 'A', icon: '', prompt: '', + description: '', source: SkillSource.kUserCreated, creationTime: {internalValue: 0n}, lastUpdateTime: {internalValue: 0n}, @@ -115,6 +118,7 @@ name: 'B', icon: '', prompt: '', + description: '', source: SkillSource.kUserCreated, creationTime: {internalValue: 0n}, lastUpdateTime: {internalValue: 0n}, @@ -159,6 +163,7 @@ name: 'Apple', icon: '', prompt: 'A tasty fruit', + description: '', source: SkillSource.kUserCreated, creationTime: {internalValue: 0n}, lastUpdateTime: {internalValue: 0n}, @@ -168,6 +173,7 @@ name: 'Banana', icon: '', prompt: 'Yellow fruit', + description: '', source: SkillSource.kUserCreated, creationTime: {internalValue: 0n}, lastUpdateTime: {internalValue: 0n}, @@ -177,6 +183,7 @@ name: 'Carrot', icon: '', prompt: 'Orange vegetable', + description: '', source: SkillSource.kUserCreated, creationTime: {internalValue: 0n}, lastUpdateTime: {internalValue: 0n},
diff --git a/chromeos/ash/components/network/onc/onc_merger.cc b/chromeos/ash/components/network/onc/onc_merger.cc index 8231700..b2422fc0 100644 --- a/chromeos/ash/components/network/onc/onc_merger.cc +++ b/chromeos/ash/components/network/onc/onc_merger.cc
@@ -20,6 +20,7 @@ #include "chromeos/ash/components/network/policy_util.h" #include "chromeos/components/onc/onc_signature.h" #include "components/onc/onc_constants.h" +#include "third_party/abseil-cpp/absl/container/flat_hash_set.h" namespace ash::onc { namespace { @@ -139,7 +140,7 @@ // paths. The resulting dictionary doesn't contain empty dictionaries. base::DictValue MergeDictionaries(const DictPointers& dicts) { base::DictValue result; - std::set<std::string> visited; + absl::flat_hash_set<std::string> visited; for (const base::DictValue* dict_outer : dicts) { if (!dict_outer) { continue;
diff --git a/chromeos/profiles/atom.afdo.newest.txt b/chromeos/profiles/atom.afdo.newest.txt index be063246..8b3c785 100644 --- a/chromeos/profiles/atom.afdo.newest.txt +++ b/chromeos/profiles/atom.afdo.newest.txt
@@ -1 +1 @@ -chromeos-chrome-amd64-atom-146-7649.0-1770008489-benchmark-146.0.7667.0_pre1578531-r1-redacted.afdo.xz +chromeos-chrome-amd64-atom-146-7649.0-1770008489-benchmark-146.0.7670.0_pre1579686-r1-redacted.afdo.xz
diff --git a/chromeos/profiles/bigcore.afdo.newest.txt b/chromeos/profiles/bigcore.afdo.newest.txt index d1d25be3..56652aa 100644 --- a/chromeos/profiles/bigcore.afdo.newest.txt +++ b/chromeos/profiles/bigcore.afdo.newest.txt
@@ -1 +1 @@ -chromeos-chrome-amd64-bigcore-146-7649.0-1770000930-benchmark-146.0.7667.0_pre1578531-r1-redacted.afdo.xz +chromeos-chrome-amd64-bigcore-146-7649.0-1770000930-benchmark-146.0.7670.0_pre1579686-r1-redacted.afdo.xz
diff --git a/clank b/clank index c4cbefe..b00a574 160000 --- a/clank +++ b/clank
@@ -1 +1 @@ -Subproject commit c4cbefe19894a5496065cd0b48d182056b062684 +Subproject commit b00a5740a69683220ef77346e7b220345f3a8ed0
diff --git a/components/BUILD.gn b/components/BUILD.gn index 565cfd83..7d1bd66 100644 --- a/components/BUILD.gn +++ b/components/BUILD.gn
@@ -56,12 +56,13 @@ if (use_blink) { bundle_data("components_tests_distiller_bundle_data") { testonly = true + deps = [ "//components/dom_distiller/core:dom_distiller_viewer_js" ] sources = [ + "$root_gen_dir/components/dom_distiller/core/javascript/dom_distiller_viewer.js", "//third_party/dom_distiller_js/dist/test/data/out/domdistillerjstest.js", "//third_party/dom_distiller_js/dist/test/data/war/test.html", "//third_party/node/node_modules/chai/index.js", "//third_party/node/node_modules/mocha/mocha.js", - "dom_distiller/core/javascript/dom_distiller_viewer.js", "dom_distiller/core/javascript/domdistiller.js", "dom_distiller/core/javascript/extract_features.js", "dom_distiller/core/javascript/readability_distiller.js",
diff --git a/components/contextual_tasks/public/features.cc b/components/contextual_tasks/public/features.cc index 2832cd4..717ef825 100644 --- a/components/contextual_tasks/public/features.cc +++ b/components/contextual_tasks/public/features.cc
@@ -63,6 +63,11 @@ "ContextualTasksBasicModeZOrder", true); +const base::FeatureParam<bool> kContextualTasksEnableCookieSync( + &kContextualTasks, + "ContextualTasksEnableCookieSync", + true); + const base::FeatureParam<bool> kOnlyUseTitlesForSimilarity( &kContextualTasksContext, "ContextualTasksContextOnlyUseTitles", @@ -140,10 +145,12 @@ "ContextualTasksEnableExpandedComposeboxVoiceSearch", true); +// TODO(b/481079194): Remove `kAutoSubmitVoiceSearchQuery` and the code that +// respects its disabled state. const base::FeatureParam<bool> kAutoSubmitVoiceSearchQuery( &kContextualTasks, "ContextualTasksAutoSubmitVoiceSearchQuery", - false); + true); const base::FeatureParam<std::string> kContextualTasksHelpUrl( &kContextualTasks, @@ -347,6 +354,10 @@ return kContextualTasksBasicModeZOrder.Get(); } +bool ShouldEnableCookieSync() { + return kContextualTasksEnableCookieSync.Get(); +} + namespace flag_descriptions { const char kContextualTasksName[] = "Contextual Tasks";
diff --git a/components/contextual_tasks/public/features.h b/components/contextual_tasks/public/features.h index 993a70f..d080536 100644 --- a/components/contextual_tasks/public/features.h +++ b/components/contextual_tasks/public/features.h
@@ -197,6 +197,9 @@ // Returns whether the z-order of the composebox should be changed in basic mode. extern bool ShouldEnableBasicModeZOrder(); +// Returns whether the cookie sync should be enabled. +extern bool ShouldEnableCookieSync(); + namespace flag_descriptions { extern const char kContextualTasksName[];
diff --git a/components/dom_distiller/core/BUILD.gn b/components/dom_distiller/core/BUILD.gn index f08acb4..4f3d5124 100644 --- a/components/dom_distiller/core/BUILD.gn +++ b/components/dom_distiller/core/BUILD.gn
@@ -26,6 +26,22 @@ } } +action("dom_distiller_viewer_js") { + script = "//mojo/public/tools/bindings/concatenate-files.py" + sources = [ + "javascript/content_processing.js", + "javascript/dom_distiller_viewer_main.js", + "javascript/font_size_slider.js", + "javascript/image_classifier.js", + "javascript/list_classifier.js", + "javascript/pinch_handler.js", + "javascript/settings_dialog.js", + ] + outputs = [ "$target_gen_dir/javascript/dom_distiller_viewer.js" ] + args = rebase_path(sources, root_build_dir) + + rebase_path(outputs, root_build_dir) +} + static_library("core") { sources = [ "article_distillation_update.cc", @@ -74,6 +90,7 @@ "//third_party/dom_distiller_js:proto", ] deps = [ + ":dom_distiller_viewer_js", "//base", "//build:blink_buildflags", "//components/dom_distiller/core/mojom",
diff --git a/components/dom_distiller/core/javascript/content_processing.js b/components/dom_distiller/core/javascript/content_processing.js new file mode 100644 index 0000000..fccf86c48 --- /dev/null +++ b/components/dom_distiller/core/javascript/content_processing.js
@@ -0,0 +1,174 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * Visits all links on the page, preserve http and https links and have them + * open to new tab. Remove (i.e., unwrap) otherwise. + * @param {HTMLElement} element The element to sanitize links in. + */ +function sanitizeLinks(element) { + const allLinks = element.querySelectorAll('a'); + + allLinks.forEach(linkElement => { + const href = linkElement.getAttribute('href'); + + if (href) { + let keepLink = false; + // Use a try-catch block to handle malformed URLs gracefully. + try { + if (href) { + const url = new URL(href, window.location.href); + // In particular, reject javascript: and #in-page links. + if (url.protocol === 'http:' || url.protocol === 'https:') { + keepLink = true; + // Open to new tab. + linkElement.target = '_blank'; + } + } + } catch (error) { + // URL is malformed. + } + + if (!keepLink) { + // If the protocol is invalid or the URL is malformed, unwrap the link. + const parent = linkElement.parentNode; + + if (parent) { + // Iterate through the link's child nodes and move them to the parent. + // Using a spread operator to create a copy, as childNodes is a live + // list. + [...linkElement.childNodes].forEach(node => { + parent.insertBefore(node, linkElement); + }); + + // Remove the original anchor tag. + linkElement.remove(); + } + } + } + // With href, an anchor can be a placeholder. Leave these alone. + }); +} + +/** + * Finds SVGs that use a local resource pointer (e.g. <use xlink:href="#...") + * and adds a class to them for styling. This is necessary because CSS + * selectors for namespaced attributes like `xlink:href` are not reliably + * supported across all renderers. + * @param {HTMLElement} element The element to search for SVGs in. + */ +function identifyEmptySVGs(element) { + const svgs = element.getElementsByTagName('svg'); + for (const svg of svgs) { + const useElement = svg.querySelector('use'); + if (!useElement) { + continue; + } + + const href = useElement.getAttribute('href'); + const xlinkHref = useElement.getAttribute('xlink:href'); + + if (href?.startsWith('#') || xlinkHref?.startsWith('#')) { + svg.classList.add('distilled-svg-with-local-ref'); + } + } +} + +/** + * Locates youtube embeds generated by DomDistiller, and creates an iframe for + * each. + * @param {HTMLElement} element The element to search for placeholders in. + */ +function fillYouTubePlaceholders(element) { + const placeholders = element.getElementsByClassName('embed-placeholder'); + for (let i = 0; i < placeholders.length; i++) { + if (!placeholders[i].hasAttribute('data-type') || + placeholders[i].getAttribute('data-type') !== 'youtube' || + !placeholders[i].hasAttribute('data-id')) { + continue; + } + const embed = document.createElement('iframe'); + const url = 'http://www.youtube.com/embed/' + + placeholders[i].getAttribute('data-id'); + embed.setAttribute('src', url); + embed.setAttribute('type', 'text/html'); + embed.setAttribute('frameborder', '0'); + embedYoutubeIFrame(embed); + } +} + +/** + * Locates existing youtube iframes and applies viewer stylings to them. This + * is only relevant to readability which leave iframes in the result. + * DomDistiller leaves behind placeholders, which are handled by + * #fillYouTubePlaceholders. + * @param {HTMLElement} element The element to search for iframes in. + */ +function addClassesToYoutubeIFrames(element) { + const iframes = element.getElementsByTagName('iframe'); + for (let i = 0; i < iframes.length; i++) { + const iframe = iframes[i]; + if (!isYouTubeIframe(iframe.src)) { + continue; + } + embedYoutubeIFrame(iframe); + } +} + +/** + * Checks if an iframe element is a YouTube video embed. + * @param src The iframe element to check. + * @returns True if the iframe is a YouTube video, false otherwise. + */ +function isYouTubeIframe(src) { + try { + const url = new URL(src); + const hostname = url.hostname; + + // Check for standard youtube.com or the privacy-enhanced + // youtube-nocookie.com + return ( + hostname === 'www.youtube.com' || hostname === 'youtube.com' || + hostname === 'www.youtube-nocookie.com'); + } catch (error) { + // Invalid URL in src, so it's not a valid YouTube embed + return false; + } +} + +/** + * Takes the given youtube iframe, adds a class and embeds it in a div. This is + * used to apply consistent styling for all youtube embeds. + * @param element The iframe element to embed within a container. + */ +function embedYoutubeIFrame(element) { + const parent = element.parentElement; + const container = document.createElement('div'); + element.setAttribute('class', 'youtubeIframe'); + container.setAttribute('class', 'youtubeContainer'); + parent.replaceChild(container, element); + container.appendChild(element); +} + +/** + * Finds all tables within an element and wraps each in a div with the + * 'scrollable-container' class to enable horizontal scrolling. + * @param {HTMLElement} element The element to search for tables in. +*/ +function wrapTables(element) { + const containerClass = 'distilled-scrollable-container'; + const tables = element.querySelectorAll('table'); + tables.forEach(table => { + const tableParent = table.parentElement; + if (!tableParent || tableParent.classList.contains(containerClass)) { + return; + } + + const wrapper = document.createElement('div'); + wrapper.className = containerClass; + + tableParent.insertBefore(wrapper, table); + wrapper.appendChild(table); + }); +}
diff --git a/components/dom_distiller/core/javascript/dom_distiller_viewer.js b/components/dom_distiller/core/javascript/dom_distiller_viewer.js deleted file mode 100644 index 401f51f..0000000 --- a/components/dom_distiller/core/javascript/dom_distiller_viewer.js +++ /dev/null
@@ -1,1240 +0,0 @@ -// Copyright 2014 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// LINT.IfChange(JSThemesAndFonts) - -// These classes must agree with the font classes in distilledpage_common.css. -const themeClasses = ['light', 'dark', 'sepia']; -const fontFamilyClasses = ['sans-serif', 'serif', 'monospace', 'Lexend']; - -// LINT.ThenChange(//components/dom_distiller/core/viewer.cc:JSThemesAndFonts) - -// On iOS, |distillerOnIos| was set to true before this script. -// eslint-disable-next-line no-var -var distillerOnIos; -if (typeof distillerOnIos === 'undefined') { - distillerOnIos = false; -} - -// The style guide recommends preferring $() to getElementById(). Chrome's -// standard implementation of $() is imported from chrome://resources, which the -// distilled page is prohibited from accessing. A version of it is -// re-implemented here to allow stylistic consistency with other JS code. -function $(id) { - return document.getElementById(id); -} - -/** - * A helper function that calls the post-processing functions on a given - * element. - * @param {HTMLElement} element The container element of the article. - */ -function postProcessElement(element) { - // Wrap tables to make them scrollable. - wrapTables(element); - - // Readability will leave iframes around, but they need the proper structure - // and classes to be styled correctly. - addClassesToYoutubeIFrames(element); - // DomDistiller will leave placeholders, which need to be replaced with - // actual iframes. - fillYouTubePlaceholders(element); - sanitizeLinks(element); - identifyEmptySVGs(element); - ImageClassifier.processImagesIn(element); - ListClassifier.processListsIn(element); -} - -function addToPage(html) { - const div = document.createElement('div'); - div.innerHTML = html; - $('content').appendChild(div); - postProcessElement(div); -} - -/** - * A utility class for classifying images in distilled content. - * - * Uses a prioritized cascade of heuristics to classify an image as either - * inline (e.g., icon) or full-width (e.g., feature image). The checks are: - * 1. Rendered size vs. viewport size (for visually dominant images). - * 2. Intrinsic size and metadata (for small or decorative images). - * 3. Structural context (e.g., inside a <figure>). - * 4. A final fallback based on intrinsic width. - * - * All checks use density-independent units (CSS pixels). - */ -class ImageClassifier { - static INLINE_CLASS = 'distilled-inline-img'; - static FULL_WIDTH_CLASS = 'distilled-full-width-img'; - static DOMINANT_IMAGE_MIN_VIEWPORT_RATIO = 0.8; - static SRC_CANDIDATES = [ - // Used by lazysizes and UI Frameworks like Bootstrap. - 'data-src', - // Mostly used in websites that depend on jQuery LazyLoad. - 'data-original', - // WordPress standard, injected by plugins like WP Rocket or Smush. - 'data-lazy-src', - // Other implementations. - 'data-url', - 'data-image', - ]; - static SRCSET_CANDIDATES = [ - 'data-srcset', - 'data-lazy-srcset', - 'data-original-set', - ]; - static SIZES_CANDIDATES = [ - 'data-sizes', - 'data-lazy-sizes', - ]; - static IMG_BLOCK_LEVEL_CONTAINER_TAGS = ['P', 'DIV', 'FIGURE', 'BODY']; - static NON_WHITESPACE_REGEXP = /\S/; - - constructor() { - // Baseline thresholds in density-independent units (CSS pixels). - this.smallAreaUpperBoundDp = 64 * 64; - this.inlineWidthFallbackUpperBoundDp = 300; - this.loneImageMinWidthDp = 150; - - // Matches common keywords for icons or mathematical formulas. - const mathyKeywords = - ['math', 'latex', 'equation', 'formula', 'tex', 'icon']; - this._mathyKeywordsRegex = - new RegExp('\\b(' + mathyKeywords.join('|') + ')\\b', 'i'); - - // Matches characters commonly found in inline formulas. - this._mathyAltTextRegex = /[+\-=_^{}\\]/; - - // Extracts the filename from a URL path. - this._filenameRegex = /(?:.*\/)?([^?#]*)/; - - // Cache for _getContainerStats() to avoid re-computing for the same - // container. - this._containerStatsCache = new Map(); - } - - /** - * Checks quickly whether an image is visually dominant. - * @param {HTMLImageElement} img The image element to check. - * @return {boolean} Whether the image is visually dominant. - * @private - */ - _isImageVisuallyDominant(img) { - const renderedWidth = img.getBoundingClientRect().width; - if (renderedWidth > 0 && window.innerWidth > 0 && - (renderedWidth / window.innerWidth) > - ImageClassifier.DOMINANT_IMAGE_MIN_VIEWPORT_RATIO) { - return ImageClassifier.FULL_WIDTH_CLASS; - } - } - - /** - * Checks for strong signals that the image is INLINE based on its intrinsic - * properties. - * @param {HTMLImageElement} img The image element to check. - * @return {boolean} True if the image should be inline. - * @private - */ - _isDefinitelyInline(img) { - // Use natural dimensions (in CSS pixels) to check for small area. - const area = img.naturalWidth * img.naturalHeight; - if (area > 0 && area < this.smallAreaUpperBoundDp) { - return true; - } - - // "Mathy" or decorative clues in attributes. - const classAndId = (img.className + ' ' + img.id); - if (this._mathyKeywordsRegex.test(classAndId)) { - return true; - } - - // Check the filename of the src URL, ignoring data URIs. - if (img.src && !img.src.startsWith('data:')) { - const filename = img.src.match(this._filenameRegex)?.[1] || ''; - if (filename && this._mathyKeywordsRegex.test(filename)) { - return true; - } - } - - // "Mathy" alt text. - const alt = img.getAttribute('alt') || ''; - if (alt.length > 0 && alt.length < 80 && - this._mathyAltTextRegex.test(alt)) { - return true; - } - - return false; - } - - /** - * Computes various stats on a container and unconditionally writes to cache. - * @param {Element} container The containing elements. - * @return {{imgCount: number, hasText: boolean}} Container stats. - * @private - */ - _addContainerStats(container) { - const stats = { - imgCount: container.querySelectorAll('img').length, - hasText: - ImageClassifier.NON_WHITESPACE_REGEXP.test(container.textContent), - }; - this._containerStatsCache.set(container, stats); - return stats; - } - - /** - * Gets stats (image count, text presence) for a container, with caching. - * @param {Element} container The container element. - * @return {{imgCount: number, hasText: boolean}} Container stats. - * @private - */ - _getContainerStats(container) { - return this._containerStatsCache.get(container) ?? - this._addContainerStats(container); - } - - /** - * Checks if the given image is the only significant content within its - * nearest block-level container. - * @param {HTMLImageElement} img The image element to check. - * @return {boolean} Whether the image is the lone significant content. - * @private - */ - _isLoneImageInContainer(img) { - let container = img.parentElement; - // Find `img`'s nearest relevant block-level container. - while (container && - !ImageClassifier.IMG_BLOCK_LEVEL_CONTAINER_TAGS.includes( - container.tagName)) { - container = container.parentElement; - } - if (!container) { - return false; - } - - const {imgCount, hasText} = this._getContainerStats(container); - // `img` should be alone, and all text should be whitespace. - return imgCount === 1 && !hasText; - } - - /** - * Checks if the image is the primary content of its container. - * @param {HTMLImageElement} img The image element to check. - * @return {boolean} True if the image should be full-width. - * @private - */ - _isDefinitelyFullWidth(img) { - const parent = img.parentElement; - - // Image is in a <figure> with a <figcaption>. - if (parent && parent.tagName === 'FIGURE' && - parent.querySelector('figcaption')) { - return true; - } - - // Image is a medium-to-large standalone image. - if (img.naturalWidth > this.loneImageMinWidthDp && - this._isLoneImageInContainer(img)) { - return true; - } - - return false; - } - - /** - * Classifies the image based on a simple intrinsic width fallback. - * @param {HTMLImageElement} img The image element to check. - * @return {string} The CSS class to apply. - * @private - */ - _classifyByFallback(img) { - // Use naturalWidth (in CSS pixels) and compare against the dp threshold. - return img.naturalWidth > this.inlineWidthFallbackUpperBoundDp ? - ImageClassifier.FULL_WIDTH_CLASS : - ImageClassifier.INLINE_CLASS; - } - - /** - * Detects lazy-loading attributes and moves them to standard attributes - * (src, srcset, sizes) to trigger native loading. - * This is necessary for static environments where the original page's - * lazy-loading JavaScript does not run, ensuring the real content is loaded - * instead of a placeholder. - * @param {HTMLImageElement} img The image element to check. - * @private - */ - _loadLazyImageAttributes(img) { - if (!img.src || img.src.startsWith('data:')) { - const srcAttribute = - ImageClassifier.SRC_CANDIDATES.find(el => img.hasAttribute(el)); - if (srcAttribute) { - img.src = img.getAttribute(srcAttribute); - img.removeAttribute(srcAttribute); - } - } - - const srcsetAttribute = - ImageClassifier.SRCSET_CANDIDATES.find(el => img.hasAttribute(el)); - if (srcsetAttribute) { - img.srcset = img.getAttribute(srcsetAttribute); - img.removeAttribute(srcsetAttribute); - } - - const sizeAttribute = - ImageClassifier.SIZES_CANDIDATES.find(el => img.hasAttribute(el)); - if (sizeAttribute) { - img.sizes = img.getAttribute(sizeAttribute); - img.removeAttribute(sizeAttribute); - } - } - - /** - * Determines an image's display style using a prioritized cascade of checks. - * @param {HTMLImageElement} img The image element to classify. - * @return {string} The CSS class to apply. - */ - classify(img) { - // Check for visually dominant images first, as this is the most reliable - // signal and overrides all other heuristics. - if (this._isImageVisuallyDominant(img)) { - return ImageClassifier.FULL_WIDTH_CLASS; - } - - // Fall back to checks based on intrinsic properties and structure. - if (this._isDefinitelyInline(img)) { - return ImageClassifier.INLINE_CLASS; - } - - if (this._isDefinitelyFullWidth(img)) { - return ImageClassifier.FULL_WIDTH_CLASS; - } - - return this._classifyByFallback(img); - } - - /** - * Applies classification to all images within an element. - * @param {HTMLElement} element The element to search for images in. - */ - static processImagesIn(element) { - const classifier = new ImageClassifier(); - const images = element.getElementsByTagName('img'); - - const imageLoadHandler = (event) => { - const img = event.currentTarget; - const classification = classifier.classify(img); - img.classList.add(classification); - }; - - for (const img of images) { - classifier._loadLazyImageAttributes(img); - img.onload = imageLoadHandler; - - // If the image is already loaded (e.g., from cache), manually trigger. - if (img.complete) { - // We use .call() to ensure `this` is correctly bound if the handler - // were a traditional function, and to pass a mock event object. - imageLoadHandler.call(img, {currentTarget: img}); - } - } - } -} - -/** - * A utility class to classify lists in distilled content. - * - * By default, list styling (bullets and numbering) is removed to avoid styling - * navigational or UI elements. This class heuristically determines when to - * restore styling for content lists. - */ -class ListClassifier { - // The following thresholds are used in the content analysis stage for - // ambiguous <ul> elements. - - // A high ratio (> this value) of items ending in punctuation often indicates - // sentence-like content. - static PUNCTUATION_RATIO_THRESHOLD = 0.5; - - // A `<ul>` where every `<li>` is a single link is considered content if - // the average link text length is > this value. - static AVG_LINK_TEXT_LENGTH_THRESHOLD = 15; - - // A low ratio (< this value) of link text to total text often indicates - // a content list. - static LINK_DENSITY_ACCEPTANCE_THRESHOLD = 0.5; - - // A high ratio (> this value) of link text to total text may indicate a - // list of links. - static LINK_DOMINANT_THRESHOLD = 0.5; - - // A selector for elements that are considered substantive content, used to - // determine if a list item is more than just a simple link. - static SUBSTANTIVE_ELEMENTS_SELECTOR = - 'p, div, img, h1, h2, h3, h4, h5, h6, table, pre, blockquote'; - - constructor() { - this.nonContentKeywords = new RegExp( - 'nav|menu|sidebar|footer|links|social|pagination|pager|breadcrumbs', - 'i'); - // Matches if a string is any single Unicode punctuation character. - this.isPunctuation = new RegExp('^\\p{P}$', 'u'); - } - - /** - * Checks for strong signals that a list is for navigation or UI. - * @param {HTMLElement} list The list element. - * @return {boolean} True if the list is navigational. - * @private - */ - _isNavigational(list) { - // Check for explicit navigation roles or containing elements. - if (list.closest( - 'nav, [role="navigation"], [role="menubar"], [role="menu"]')) { - return true; - } - - // Check for non-content keywords in id or class. - const attributes = (list.id + ' ' + list.className).toLowerCase(); - if (this.nonContentKeywords.test(attributes)) { - return true; - } - - return false; - } - - /** - * Performs deeper content analysis on a <ul> list. - * @param {HTMLElement} list The UL element. - * @return {boolean} True if the list should be styled. - * @private - */ - _analyzeContent(list) { - const listItems = list.querySelectorAll('li'); - const numListItems = listItems.length; - - if (numListItems === 0) { - return false; - } - - // In a single pass, collect all metrics needed for the heuristics. - let itemsEndingWithPunctuation = 0; - let totalTextLength = 0; - let linkTextLength = 0; - let hasSubstantiveElements = false; - - for (const li of listItems) { - const itemText = li.textContent.trim(); - totalTextLength += itemText.length; - - if (itemText.length > 0 && this.isPunctuation.test(itemText.slice(-1))) { - itemsEndingWithPunctuation++; - } - - const links = li.querySelectorAll('a'); - for (const link of links) { - // Don't trim() here; whitespace inside a link can be significant. - linkTextLength += link.textContent.length; - } - - if (!hasSubstantiveElements && - li.querySelector(ListClassifier.SUBSTANTIVE_ELEMENTS_SELECTOR)) { - hasSubstantiveElements = true; - } - } - - // Heuristic A: A high punctuation ratio is a strong signal for - // sentence-based content. - const punctuationRatio = itemsEndingWithPunctuation / numListItems; - if (punctuationRatio > ListClassifier.PUNCTUATION_RATIO_THRESHOLD) { - return true; - } - - const linkDensity = - totalTextLength > 0 ? (linkTextLength / totalTextLength) : 0; - - // Heuristic B: A list that is dominated by links is content if the links - // are long enough to be titles. This allows for some non-link text. - if (!hasSubstantiveElements && - linkDensity > ListClassifier.LINK_DOMINANT_THRESHOLD) { - const avgLinkTextLength = - numListItems > 0 ? (linkTextLength / numListItems) : 0; - return avgLinkTextLength >= ListClassifier.AVG_LINK_TEXT_LENGTH_THRESHOLD; - } - - // Heuristic C: A list with a low density of links is likely content. - if (linkDensity < ListClassifier.LINK_DENSITY_ACCEPTANCE_THRESHOLD) { - return true; - } - - // Default to false if undecided. - return false; - } - - /** - * Main classification logic. - * @param {HTMLElement} list The list element (ul or ol). - * @return {boolean} True if the list should be styled. - */ - classify(list) { - // An empty list is never content. - if (list.children.length === 0) { - return false; - } - - // Stage 1: Reject navigational lists immediately. - if (this._isNavigational(list)) { - return false; - } - - // Stage 2: Accept lists that are clearly content. - // An <ol> that isn't navigational is always considered content. - if (list.tagName === 'OL') { - return true; - } - // Any list with block-level elements is also considered content. - if (list.querySelector('li p, li ul, li ol')) { - return true; - } - - // Stage 3: For remaining ambiguous <ul> elements, perform deeper analysis. - // Other element types that reach this point are not considered content. - if (list.tagName === 'UL') { - return this._analyzeContent(list); - } - - return false; - } - - /** - * Post-processes all lists in an element to apply classification classes. - * @param {HTMLElement} element The element to search for lists in. - */ - static processListsIn(element) { - const classifier = new ListClassifier(); - const lists = element.querySelectorAll('ul, ol'); - for (const list of lists) { - if (classifier.classify(list)) { - list.classList.add('distilled-content-list'); - } - } - } -} - -/** - * Visits all links on the page, preserve http and https links and have them - * open to new tab. Remove (i.e., unwrap) otherwise. - * @param {HTMLElement} element The element to sanitize links in. - */ -function sanitizeLinks(element) { - const allLinks = element.querySelectorAll('a'); - - allLinks.forEach(linkElement => { - const href = linkElement.getAttribute('href'); - - if (href) { - let keepLink = false; - // Use a try-catch block to handle malformed URLs gracefully. - try { - if (href) { - const url = new URL(href, window.location.href); - // In particular, reject javascript: and #in-page links. - if (url.protocol === 'http:' || url.protocol === 'https:') { - keepLink = true; - // Open to new tab. - linkElement.target = '_blank'; - } - } - } catch (error) { - // URL is malformed. - } - - if (!keepLink) { - // If the protocol is invalid or the URL is malformed, unwrap the link. - const parent = linkElement.parentNode; - - if (parent) { - // Iterate through the link's child nodes and move them to the parent. - // Using a spread operator to create a copy, as childNodes is a live - // list. - [...linkElement.childNodes].forEach(node => { - parent.insertBefore(node, linkElement); - }); - - // Remove the original anchor tag. - linkElement.remove(); - } - } - } - // With href, an anchor can be a placeholder. Leave these alone. - }); -} - -/** - * Finds SVGs that use a local resource pointer (e.g. <use xlink:href="#...") - * and adds a class to them for styling. This is necessary because CSS - * selectors for namespaced attributes like `xlink:href` are not reliably - * supported across all renderers. - * @param {HTMLElement} element The element to search for SVGs in. - */ -function identifyEmptySVGs(element) { - const svgs = element.getElementsByTagName('svg'); - for (const svg of svgs) { - const useElement = svg.querySelector('use'); - if (!useElement) { - continue; - } - - const href = useElement.getAttribute('href'); - const xlinkHref = useElement.getAttribute('xlink:href'); - - if (href?.startsWith('#') || xlinkHref?.startsWith('#')) { - svg.classList.add('distilled-svg-with-local-ref'); - } - } -} - -/** - * Locates youtube embeds generated by DomDistiller, and creates an iframe for - * each. - * @param {HTMLElement} element The element to search for placeholders in. - */ -function fillYouTubePlaceholders(element) { - const placeholders = element.getElementsByClassName('embed-placeholder'); - for (let i = 0; i < placeholders.length; - i++) { - if (!placeholders[i].hasAttribute('data-type') || - placeholders[i].getAttribute('data-type') !== 'youtube' || - !placeholders[i].hasAttribute('data-id')) { - continue; - } - const embed = document.createElement('iframe'); - const url = 'http://www.youtube.com/embed/' + - placeholders[i].getAttribute('data-id'); - embed.setAttribute('src', url); - embed.setAttribute('type', 'text/html'); - embed.setAttribute('frameborder', '0'); - embedYoutubeIFrame(embed); - } -} - -/** - * Locates existing youtube iframes and applies viewer stylings to them. This - * is only relevant to readability which leave iframes in the result. - * DomDistiller leaves behind placeholders, which are handled by - * #fillYouTubePlaceholders. - * @param {HTMLElement} element The element to search for iframes in. - */ -function addClassesToYoutubeIFrames(element) { - const iframes = element.getElementsByTagName('iframe'); - for (let i = 0; i < iframes.length; i++) { - const iframe = iframes[i]; - if (!isYouTubeIframe(iframe.src)) { - continue; - } - embedYoutubeIFrame(iframe); - } -} - -/** - * Checks if an iframe element is a YouTube video embed. - * @param src The iframe element to check. - * @returns True if the iframe is a YouTube video, false otherwise. - */ -function isYouTubeIframe(src) { - try { - const url = new URL(src); - const hostname = url.hostname; - - // Check for standard youtube.com or the privacy-enhanced - // youtube-nocookie.com - return ( - hostname === 'www.youtube.com' || hostname === 'youtube.com' || - hostname === 'www.youtube-nocookie.com'); - } catch (error) { - // Invalid URL in src, so it's not a valid YouTube embed - return false; - } -} - -/** - * Takes the given youtube iframe, adds a class and embeds it in a div. This is - * used to apply consistent styling for all youtube embeds. - * @param element The iframe element to embed within a container. - */ -function embedYoutubeIFrame(element) { - const parent = element.parentElement; - const container = document.createElement('div'); - element.setAttribute('class', 'youtubeIframe'); - container.setAttribute('class', 'youtubeContainer'); - parent.replaceChild(container, element); - container.appendChild(element); -} - -/** - * Finds all tables within an element and wraps each in a div with the - * 'scrollable-container' class to enable horizontal scrolling. - * @param {HTMLElement} element The element to search for tables in. -*/ -function wrapTables(element) { - const containerClass = 'distilled-scrollable-container'; - const tables = element.querySelectorAll('table'); - tables.forEach(table => { - const tableParent = table.parentElement; - if (!tableParent || tableParent.classList.contains(containerClass)) { - return; - } - - const wrapper = document.createElement('div'); - wrapper.className = containerClass; - - tableParent.insertBefore(wrapper, table); - wrapper.appendChild(table); - }); -} - -function showLoadingIndicator(isLastPage) { - $('loading-indicator').className = isLastPage ? 'hidden' : 'visible'; -} - -// Sets the title. -function setTitle(title, documentTitleSuffix) { - $('title-holder').textContent = title; - if (documentTitleSuffix) { - document.title = title + documentTitleSuffix; - } else { - document.title = title; - } -} - -// Set the text direction of the document ('ltr', 'rtl', or 'auto'). -function setTextDirection(direction) { - document.body.setAttribute('dir', direction); -} - -// Get the currently applied appearance setting. -function getAppearanceSetting(settingClasses) { - const cls = Array.from(document.body.classList) - .find((cls) => settingClasses.includes(cls)); - return cls ? cls : settingClasses[0]; -} - -function useTheme(theme) { - SettingsDialog.getInstance().useTheme(theme); -} - -function useFontFamily(fontFamily) { - SettingsDialog.getInstance().useFontFamily(fontFamily); -} - -function setLinksEnabled(enabled) { - document.body.classList.toggle('links-hidden', !enabled); -} - -function updateToolbarColor(theme) { - let toolbarColor; - if (theme === 'sepia') { - toolbarColor = '#BF9A73'; - } else if (theme === 'dark') { - toolbarColor = '#1A1A1A'; - } else { - toolbarColor = '#F5F5F5'; - } - $('theme-color').content = toolbarColor; -} - -// TODO(crbug.com/40108835): Consider making this a custom HTML element. -// The font size slider is visible on desktop platforms. -class FontSizeSlider { - static #instance = null; - - static getInstance() { - return FontSizeSlider.#instance ?? - (FontSizeSlider.#instance = new FontSizeSlider()); - } - - constructor() { - this.element = $('font-size-selection'); - this.baseSize = 16; - // These scales are applied to a base size of 16px. - this.fontSizeScale = [0.875, 0.9375, 1, 1.125, 1.25, 1.5, 1.75, 2, 2.5, 3]; - - this.element.addEventListener('input', (e) => { - const scale = this.fontSizeScale[e.target.value]; - this.useFontScaling(scale); - distiller.storeFontScalingPref(parseFloat(scale)); - }); - - this.tickmarks = document.createElement('datalist'); - this.tickmarks.setAttribute('class', 'tickmarks'); - this.element.after(this.tickmarks); - - for (let i = 0; i < this.fontSizeScale.length; i++) { - const option = document.createElement('option'); - option.setAttribute('value', i); - option.textContent = this.fontSizeScale[i] * this.baseSize; - this.tickmarks.appendChild(option); - } - this.element.value = 2; - this.update(this.element.value); - } - - // TODO(meredithl): validate |scale| and snap to nearest supported font size. - useFontScaling(scale, restoreCenter = true) { - this.element.value = this.fontSizeScale.indexOf(scale); - document.documentElement.style.fontSize = scale * this.baseSize + 'px'; - this.update(this.element.value); - } - - update(position) { - this.element.style.setProperty( - '--fontSizePercent', - (position / (this.fontSizeScale.length - 1) * 100) + '%'); - this.element.setAttribute( - 'aria-valuetext', this.fontSizeScale[position] + 'px'); - for (let option = this.tickmarks.firstChild; option != null; - option = option.nextSibling) { - const isBeforeThumb = option.value < position; - option.classList.toggle('before-thumb', isBeforeThumb); - option.classList.toggle('after-thumb', !isBeforeThumb); - } - } - - useBaseFontSize(size) { - this.baseSize = size; - this.update(this.element.value); - } -} - -// The zooming speed relative to pinching speed. -const FONT_SCALE_MULTIPLIER = 0.5; - -const MIN_SPAN_LENGTH = 20; - -// Only defined on Android. -class Pincher { - // When users pinch in Reader Mode, the page would zoom in or out as if it - // is a normal web page allowing user-zoom. At the end of pinch gesture, the - // page would do text reflow. These pinch-to-zoom and text reflow effects - // are not native, but are emulated using CSS and JavaScript. - // - // In order to achieve near-native zooming and panning frame rate, fake 3D - // transform is used so that the layer doesn't repaint for each frame. - // - // After the text reflow, the web content shown in the viewport should - // roughly be the same paragraph before zooming. - // - // The control point of font size is the html element, so that both "em" and - // "rem" are adjusted. - // - // TODO(wychen): Improve scroll position when elementFromPoint is body. - - static #instance = null; - - static getInstance() { - return Pincher.#instance ?? (Pincher.#instance = new Pincher()); - } - - constructor() { - // This has to be in sync with largest 'font-size' in distilledpage_{}.css. - // This value is hard-coded because JS might be injected before CSS is - // ready. See crbug.com/1004663. - this.baseSize = 16; - this.pinching = false; - this.fontSizeAnchor = 1.0; - - this.focusElement = null; - this.focusPos = 0; - this.initClientMid = null; - - this.clampedScale = 1.0; - - this.lastSpan = null; - this.lastClientMid = null; - - this.scale = 1.0; - this.shiftX = 0; - this.shiftY = 0; - - window.addEventListener('touchstart', (e) => { - this.handleTouchStart(e); - }, {passive: false}); - window.addEventListener('touchmove', (e) => { - this.handleTouchMove(e); - }, {passive: false}); - window.addEventListener('touchend', (e) => { - this.handleTouchEnd(e); - }, {passive: false}); - window.addEventListener('touchcancel', (e) => { - this.handleTouchCancel(e); - }, {passive: false}); - } - - /** @private */ - refreshTransform_() { - const slowedScale = Math.exp(Math.log(this.scale) * FONT_SCALE_MULTIPLIER); - this.clampedScale = - Math.max($MIN_SCALE, Math.min($MAX_SCALE, this.fontSizeAnchor * slowedScale)); - - // Use "fake" 3D transform so that the layer is not repainted. - // With 2D transform, the frame rate would be much lower. - // clang-format off - document.body.style.transform = - 'translate3d(' + this.shiftX + 'px,' - + this.shiftY + 'px, 0px)' + - 'scale(' + this.clampedScale / this.fontSizeAnchor + ')'; - // clang-format on - } - - /** @private */ - saveCenter_(clientMid) { - // Try to preserve the pinching center after text reflow. - // This is accurate to the HTML element level. - this.focusElement = document.elementFromPoint(clientMid.x, clientMid.y); - const rect = this.focusElement.getBoundingClientRect(); - this.initClientMid = clientMid; - this.focusPos = - (this.initClientMid.y - rect.top) / (rect.bottom - rect.top); - } - - /** @private */ - restoreCenter_() { - const rect = this.focusElement.getBoundingClientRect(); - const targetTop = this.focusPos * (rect.bottom - rect.top) + rect.top + - document.scrollingElement.scrollTop - - (this.initClientMid.y + this.shiftY); - document.scrollingElement.scrollTop = targetTop; - } - - /** @private */ - endPinch_() { - this.pinching = false; - - document.body.style.transformOrigin = ''; - document.body.style.transform = ''; - document.documentElement.style.fontSize = - this.clampedScale * this.baseSize + 'px'; - - this.restoreCenter_(); - - let img = $('fontscaling-img'); - if (!img) { - img = document.createElement('img'); - img.id = 'fontscaling-img'; - img.style.display = 'none'; - document.body.appendChild(img); - } - img.src = '/savefontscaling/' + this.clampedScale; - } - - /** @private */ - touchSpan_(e) { - const count = e.touches.length; - const mid = this.touchClientMid_(e); - let sum = 0; - for (let i = 0; i < count; i++) { - const dx = (e.touches[i].clientX - mid.x); - const dy = (e.touches[i].clientY - mid.y); - sum += Math.hypot(dx, dy); - } - // Avoid very small span. - return Math.max(MIN_SPAN_LENGTH, sum / count); - } - - /** @private */ - touchClientMid_(e) { - const count = e.touches.length; - let sumX = 0; - let sumY = 0; - for (let i = 0; i < count; i++) { - sumX += e.touches[i].clientX; - sumY += e.touches[i].clientY; - } - return {x: sumX / count, y: sumY / count}; - } - - /** @private */ - touchPageMid_(e) { - const clientMid = this.touchClientMid_(e); - return { - x: clientMid.x - e.touches[0].clientX + e.touches[0].pageX, - y: clientMid.y - e.touches[0].clientY + e.touches[0].pageY, - }; - } - - handleTouchStart(e) { - if (e.touches.length < 2) { - return; - } - e.preventDefault(); - - const span = this.touchSpan_(e); - const clientMid = this.touchClientMid_(e); - - if (e.touches.length > 2) { - this.lastSpan = span; - this.lastClientMid = clientMid; - this.refreshTransform_(); - return; - } - - this.scale = 1; - this.shiftX = 0; - this.shiftY = 0; - - this.pinching = true; - this.fontSizeAnchor = - parseFloat(getComputedStyle(document.documentElement).fontSize) / - this.baseSize; - - const pinchOrigin = this.touchPageMid_(e); - document.body.style.transformOrigin = - pinchOrigin.x + 'px ' + pinchOrigin.y + 'px'; - - this.saveCenter_(clientMid); - - this.lastSpan = span; - this.lastClientMid = clientMid; - - this.refreshTransform_(); - } - - handleTouchMove(e) { - if (!this.pinching) { - return; - } - if (e.touches.length < 2) { - return; - } - e.preventDefault(); - - const span = this.touchSpan_(e); - const clientMid = this.touchClientMid_(e); - - this.scale *= this.touchSpan_(e) / this.lastSpan; - this.shiftX += clientMid.x - this.lastClientMid.x; - this.shiftY += clientMid.y - this.lastClientMid.y; - - this.refreshTransform_(); - - this.lastSpan = span; - this.lastClientMid = clientMid; - } - - handleTouchEnd(e) { - if (!this.pinching) { - return; - } - e.preventDefault(); - - const span = this.touchSpan_(e); - const clientMid = this.touchClientMid_(e); - - if (e.touches.length >= 2) { - this.lastSpan = span; - this.lastClientMid = clientMid; - this.refreshTransform_(); - return; - } - - this.endPinch_(); - } - - handleTouchCancel(e) { - if (!this.pinching) { - return; - } - this.endPinch_(); - } - - reset() { - this.scale = 1; - this.shiftX = 0; - this.shiftY = 0; - this.clampedScale = 1; - document.documentElement.style.fontSize = - this.clampedScale * this.baseSize + 'px'; - } - - status() { - return { - scale: this.scale, - clampedScale: this.clampedScale, - shiftX: this.shiftX, - shiftY: this.shiftY, - }; - } - - useFontScaling(scaling, restoreCenter = true) { - if (restoreCenter) { - this.saveCenter_({x: window.innerWidth / 2, y: window.innerHeight / 2}); - } - this.shiftX = 0; - this.shiftY = 0; - document.documentElement.style.fontSize = scaling * this.baseSize + 'px'; - this.clampedScale = scaling; - if (restoreCenter) { - this.restoreCenter_(); - } - } - - useBaseFontSize(size) { - this.baseSize = size; - this.reset(); - } -} - -function useBaseFontSize(size) { - if (navigator.userAgent.toLowerCase().indexOf('android') > -1) { - Pincher.getInstance().useBaseFontSize(size); - } else { - FontSizeSlider.getInstance().useBaseFontSize(size); - } -} - -function useFontScaling(scale, restoreCenter = true) { - if (navigator.userAgent.toLowerCase().indexOf('android') > -1) { - Pincher.getInstance().useFontScaling(scale, restoreCenter); - } else { - FontSizeSlider.getInstance().useFontScaling(scale, restoreCenter); - } -} - -/** - * Initializes the Dom Distiller viewer UI components based on the platform. - * This function should be called after the DOM is loaded. - */ -function initializeDomDistillerViewer() { - // The settings dialog is always present. - SettingsDialog.getInstance(); - if (navigator.userAgent.toLowerCase().indexOf('android') > -1) { - Pincher.getInstance(); - } else { - FontSizeSlider.getInstance(); - } -} - -/** - * Finds a paragraph with `innerText` matching `hash` and `charCount`, then - * scrolls to that paragraph with the provided `progress` corresponding to the - * location to scroll to wrt. that paragraph, 0 being the top of that - * paragraph, 1 being the bottom. - * @param {number} hash The hash of the paragraph's innerText. - * @param {number} charCount The character count of the paragraph's innerText. - * @param {number} progress The scroll progress within the paragraph (0-1). - */ -function scrollToParagraphByHash(hash, charCount, progress) { - const targetHash = hash; - const targetCharCount = charCount; - const paragraphs = document.querySelectorAll('p'); - for (let i = 0; i < paragraphs.length; i++) { - const p = paragraphs[i]; - const pText = p.innerText; - if (pText.length === targetCharCount) { - // Only compute hash if the length already matches. - const hashCode = (s) => - s.split('').reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0); - const pHash = hashCode(pText); - if (pHash === targetHash) { - const rect = p.getBoundingClientRect(); - const scrollOffset = (window.scrollY + rect.top) + - (rect.height * progress) - (window.innerHeight / 2); - window.scrollTo(0, scrollOffset); - break; - } - } - } -} - -class SettingsDialog { - static #instance = null; - - static getInstance() { - return SettingsDialog.#instance ?? - (SettingsDialog.#instance = new SettingsDialog( - $('settings-toggle'), $('settings-dialog'), $('dialog-backdrop'), - $('theme-selection'), $('font-family-selection'))); - } - - constructor( - toggleElement, dialogElement, backdropElement, themeFieldset, - fontFamilySelect) { - this._toggleElement = toggleElement; - this._dialogElement = dialogElement; - this._backdropElement = backdropElement; - this._themeFieldset = themeFieldset; - this._fontFamilySelect = fontFamilySelect; - - this._toggleElement.addEventListener('click', this.toggle.bind(this)); - this._dialogElement.addEventListener('close', this.close.bind(this)); - this._backdropElement.addEventListener('click', this.close.bind(this)); - - $('close-settings-button').addEventListener('click', this.close.bind(this)); - - this._themeFieldset.addEventListener('change', (e) => { - const newTheme = e.target.value; - this.useTheme(newTheme); - distiller.storeThemePref(themeClasses.indexOf(newTheme)); - }); - - this._fontFamilySelect.addEventListener('change', (e) => { - const newFontFamily = e.target.value; - this.useFontFamily(newFontFamily); - distiller.storeFontFamilyPref(fontFamilyClasses.indexOf(newFontFamily)); - }); - - // Appearance settings are loaded from user preferences, so on page load - // the controllers for these settings may need to be updated to reflect - // the active setting. - this._updateFontFamilyControls(getAppearanceSetting(fontFamilyClasses)); - const selectedTheme = getAppearanceSetting(themeClasses); - this._updateThemeControls(selectedTheme); - updateToolbarColor(selectedTheme); - } - - toggle() { - if (this._dialogElement.open) { - this.close(); - } else { - this.showModal(); - } - } - - showModal() { - this._toggleElement.classList.add('activated'); - this._backdropElement.style.display = 'block'; - this._dialogElement.showModal(); - } - - close() { - this._toggleElement.classList.remove('activated'); - this._backdropElement.style.display = 'none'; - this._dialogElement.close(); - } - - useTheme(theme) { - themeClasses.forEach( - (element) => - document.body.classList.toggle(element, element === theme)); - this._updateThemeControls(theme); - updateToolbarColor(theme); - } - - _updateThemeControls(theme) { - const queryString = `input[value=${theme}]`; - this._themeFieldset.querySelector(queryString).checked = true; - } - - useFontFamily(fontFamily) { - fontFamilyClasses.forEach( - (element) => - document.body.classList.toggle(element, element === fontFamily)); - this._updateFontFamilyControls(fontFamily); - } - - _updateFontFamilyControls(fontFamily) { - this._fontFamilySelect.selectedIndex = - fontFamilyClasses.indexOf(fontFamily); - } -}
diff --git a/components/dom_distiller/core/javascript/dom_distiller_viewer_main.js b/components/dom_distiller/core/javascript/dom_distiller_viewer_main.js new file mode 100644 index 0000000..e96ff998 --- /dev/null +++ b/components/dom_distiller/core/javascript/dom_distiller_viewer_main.js
@@ -0,0 +1,169 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// LINT.IfChange(JSThemesAndFonts) + +// These classes must agree with the font classes in distilledpage_common.css. +const themeClasses = ['light', 'dark', 'sepia']; +const fontFamilyClasses = ['sans-serif', 'serif', 'monospace', 'Lexend']; + +// LINT.ThenChange(//components/dom_distiller/core/viewer.cc:JSThemesAndFonts) + +// On iOS, |distillerOnIos| was set to true before this script. +// eslint-disable-next-line no-var +var distillerOnIos; +if (typeof distillerOnIos === 'undefined') { + distillerOnIos = false; +} + +// The style guide recommends preferring $() to getElementById(). Chrome's +// standard implementation of $() is imported from chrome://resources, which the +// distilled page is prohibited from accessing. A version of it is +// re-implemented here to allow stylistic consistency with other JS code. +function $(id) { + return document.getElementById(id); +} + +/** + * A helper function that calls the post-processing functions on a given + * element. + * @param {HTMLElement} element The container element of the article. + */ +function postProcessElement(element) { + // Wrap tables to make them scrollable. + wrapTables(element); + + // Readability will leave iframes around, but they need the proper structure + // and classes to be styled correctly. + addClassesToYoutubeIFrames(element); + // DomDistiller will leave placeholders, which need to be replaced with + // actual iframes. + fillYouTubePlaceholders(element); + sanitizeLinks(element); + identifyEmptySVGs(element); + ImageClassifier.processImagesIn(element); + ListClassifier.processListsIn(element); +} + +function addToPage(html) { + const div = document.createElement('div'); + div.innerHTML = html; + $('content').appendChild(div); + postProcessElement(div); +} + +function showLoadingIndicator(isLastPage) { + $('loading-indicator').className = isLastPage ? 'hidden' : 'visible'; +} + +// Sets the title. +function setTitle(title, documentTitleSuffix) { + $('title-holder').textContent = title; + if (documentTitleSuffix) { + document.title = title + documentTitleSuffix; + } else { + document.title = title; + } +} + +// Set the text direction of the document ('ltr', 'rtl', or 'auto'). +function setTextDirection(direction) { + document.body.setAttribute('dir', direction); +} + +// Get the currently applied appearance setting. +function getAppearanceSetting(settingClasses) { + const cls = Array.from(document.body.classList) + .find((cls) => settingClasses.includes(cls)); + return cls ? cls : settingClasses[0]; +} + +function useTheme(theme) { + SettingsDialog.getInstance().useTheme(theme); +} + +function useFontFamily(fontFamily) { + SettingsDialog.getInstance().useFontFamily(fontFamily); +} + +function setLinksEnabled(enabled) { + document.body.classList.toggle('links-hidden', !enabled); +} + +function updateToolbarColor(theme) { + let toolbarColor; + if (theme === 'sepia') { + toolbarColor = '#BF9A73'; + } else if (theme === 'dark') { + toolbarColor = '#1A1A1A'; + } else { + toolbarColor = '#F5F5F5'; + } + $('theme-color').content = toolbarColor; +} + +/** + * Called to set the baseFontSize for the pinch/slider (whichever is active). + */ +function useBaseFontSize(size) { + if (navigator.userAgent.toLowerCase().indexOf('android') > -1) { + Pincher.getInstance().useBaseFontSize(size); + } else { + FontSizeSlider.getInstance().useBaseFontSize(size); + } +} + +function useFontScaling(scale, restoreCenter = true) { + if (navigator.userAgent.toLowerCase().indexOf('android') > -1) { + Pincher.getInstance().useFontScaling(scale, restoreCenter); + } else { + FontSizeSlider.getInstance().useFontScaling(scale, restoreCenter); + } +} + +/** + * Finds a paragraph with `innerText` matching `hash` and `charCount`, then + * scrolls to that paragraph with the provided `progress` corresponding to the + * location to scroll to wrt. that paragraph, 0 being the top of that + * paragraph, 1 being the bottom. + * @param {number} hash The hash of the paragraph's innerText. + * @param {number} charCount The character count of the paragraph's innerText. + * @param {number} progress The scroll progress within the paragraph (0-1). + */ +function scrollToParagraphByHash(hash, charCount, progress) { + const targetHash = hash; + const targetCharCount = charCount; + const paragraphs = document.querySelectorAll('p'); + for (let i = 0; i < paragraphs.length; i++) { + const p = paragraphs[i]; + const pText = p.innerText; + if (pText.length === targetCharCount) { + // Only compute hash if the length already matches. + const hashCode = (s) => + s.split('').reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0); + const pHash = hashCode(pText); + if (pHash === targetHash) { + const rect = p.getBoundingClientRect(); + const scrollOffset = (window.scrollY + rect.top) + + (rect.height * progress) - (window.innerHeight / 2); + window.scrollTo(0, scrollOffset); + break; + } + } + } +} + +/** + * Initializes the Dom Distiller viewer UI components based on the platform. + * This function should be called after the DOM is loaded. + */ +function initializeDomDistillerViewer() { + // The settings dialog is always present. + SettingsDialog.getInstance(); + if (navigator.userAgent.toLowerCase().indexOf('android') > -1) { + Pincher.getInstance(); + } else { + FontSizeSlider.getInstance(); + } +}
diff --git a/components/dom_distiller/core/javascript/font_size_slider.js b/components/dom_distiller/core/javascript/font_size_slider.js new file mode 100644 index 0000000..8c983f84 --- /dev/null +++ b/components/dom_distiller/core/javascript/font_size_slider.js
@@ -0,0 +1,66 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// TODO(crbug.com/40108835): Consider making this a custom HTML element. +// The font size slider is visible on desktop platforms. +class FontSizeSlider { + static #instance = null; + + static getInstance() { + return FontSizeSlider.#instance ?? + (FontSizeSlider.#instance = new FontSizeSlider()); + } + + constructor() { + this.element = $('font-size-selection'); + this.baseSize = 16; + // These scales are applied to a base size of 16px. + this.fontSizeScale = [0.875, 0.9375, 1, 1.125, 1.25, 1.5, 1.75, 2, 2.5, 3]; + + this.element.addEventListener('input', (e) => { + const scale = this.fontSizeScale[e.target.value]; + this.useFontScaling(scale); + distiller.storeFontScalingPref(parseFloat(scale)); + }); + + this.tickmarks = document.createElement('datalist'); + this.tickmarks.setAttribute('class', 'tickmarks'); + this.element.after(this.tickmarks); + + for (let i = 0; i < this.fontSizeScale.length; i++) { + const option = document.createElement('option'); + option.setAttribute('value', i); + option.textContent = this.fontSizeScale[i] * this.baseSize; + this.tickmarks.appendChild(option); + } + this.element.value = 2; + this.update(this.element.value); + } + + // TODO(meredithl): validate |scale| and snap to nearest supported font size. + useFontScaling(scale, restoreCenter = true) { + this.element.value = this.fontSizeScale.indexOf(scale); + document.documentElement.style.fontSize = scale * this.baseSize + 'px'; + this.update(this.element.value); + } + + update(position) { + this.element.style.setProperty( + '--fontSizePercent', + (position / (this.fontSizeScale.length - 1) * 100) + '%'); + this.element.setAttribute( + 'aria-valuetext', this.fontSizeScale[position] + 'px'); + for (let option = this.tickmarks.firstChild; option != null; + option = option.nextSibling) { + const isBeforeThumb = option.value < position; + option.classList.toggle('before-thumb', isBeforeThumb); + option.classList.toggle('after-thumb', !isBeforeThumb); + } + } + + useBaseFontSize(size) { + this.baseSize = size; + this.update(this.element.value); + } +}
diff --git a/components/dom_distiller/core/javascript/image_classifier.js b/components/dom_distiller/core/javascript/image_classifier.js new file mode 100644 index 0000000..6705e43b --- /dev/null +++ b/components/dom_distiller/core/javascript/image_classifier.js
@@ -0,0 +1,292 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * A utility class for classifying images in distilled content. + * + * Uses a prioritized cascade of heuristics to classify an image as either + * inline (e.g., icon) or full-width (e.g., feature image). The checks are: + * 1. Rendered size vs. viewport size (for visually dominant images). + * 2. Intrinsic size and metadata (for small or decorative images). + * 3. Structural context (e.g., inside a <figure>). + * 4. A final fallback based on intrinsic width. + * + * All checks use density-independent units (CSS pixels). + */ +class ImageClassifier { + static INLINE_CLASS = 'distilled-inline-img'; + static FULL_WIDTH_CLASS = 'distilled-full-width-img'; + static DOMINANT_IMAGE_MIN_VIEWPORT_RATIO = 0.8; + static SRC_CANDIDATES = [ + // Used by lazysizes and UI Frameworks like Bootstrap. + 'data-src', + // Mostly used in websites that depend on jQuery LazyLoad. + 'data-original', + // WordPress standard, injected by plugins like WP Rocket or Smush. + 'data-lazy-src', + // Other implementations. + 'data-url', + 'data-image', + ]; + static SRCSET_CANDIDATES = [ + 'data-srcset', + 'data-lazy-srcset', + 'data-original-set', + ]; + static SIZES_CANDIDATES = [ + 'data-sizes', + 'data-lazy-sizes', + ]; + static IMG_BLOCK_LEVEL_CONTAINER_TAGS = ['P', 'DIV', 'FIGURE', 'BODY']; + static NON_WHITESPACE_REGEXP = /\S/; + + constructor() { + // Baseline thresholds in density-independent units (CSS pixels). + this.smallAreaUpperBoundDp = 64 * 64; + this.inlineWidthFallbackUpperBoundDp = 300; + this.loneImageMinWidthDp = 150; + + // Matches common keywords for icons or mathematical formulas. + const mathyKeywords = + ['math', 'latex', 'equation', 'formula', 'tex', 'icon']; + this._mathyKeywordsRegex = + new RegExp('\\b(' + mathyKeywords.join('|') + ')\\b', 'i'); + + // Matches characters commonly found in inline formulas. + this._mathyAltTextRegex = /[+\-=_^{}\\]/; + + // Extracts the filename from a URL path. + this._filenameRegex = /(?:.*\/)?([^?#]*)/; + + // Cache for _getContainerStats() to avoid re-computing for the same + // container. + this._containerStatsCache = new Map(); + } + + /** + * Checks quickly whether an image is visually dominant. + * @param {HTMLImageElement} img The image element to check. + * @return {boolean} Whether the image is visually dominant. + * @private + */ + _isImageVisuallyDominant(img) { + const renderedWidth = img.getBoundingClientRect().width; + if (renderedWidth > 0 && window.innerWidth > 0 && + (renderedWidth / window.innerWidth) > + ImageClassifier.DOMINANT_IMAGE_MIN_VIEWPORT_RATIO) { + return ImageClassifier.FULL_WIDTH_CLASS; + } + } + + /** + * Checks for strong signals that the image is INLINE based on its intrinsic + * properties. + * @param {HTMLImageElement} img The image element to check. + * @return {boolean} True if the image should be inline. + * @private + */ + _isDefinitelyInline(img) { + // Use natural dimensions (in CSS pixels) to check for small area. + const area = img.naturalWidth * img.naturalHeight; + if (area > 0 && area < this.smallAreaUpperBoundDp) { + return true; + } + + // "Mathy" or decorative clues in attributes. + const classAndId = (img.className + ' ' + img.id); + if (this._mathyKeywordsRegex.test(classAndId)) { + return true; + } + + // Check the filename of the src URL, ignoring data URIs. + if (img.src && !img.src.startsWith('data:')) { + const filename = img.src.match(this._filenameRegex)?.[1] || ''; + if (filename && this._mathyKeywordsRegex.test(filename)) { + return true; + } + } + + // "Mathy" alt text. + const alt = img.getAttribute('alt') || ''; + if (alt.length > 0 && alt.length < 80 && + this._mathyAltTextRegex.test(alt)) { + return true; + } + + return false; + } + + /** + * Computes various stats on a container and unconditionally writes to cache. + * @param {Element} container The containing elements. + * @return {{imgCount: number, hasText: boolean}} Container stats. + * @private + */ + _addContainerStats(container) { + const stats = { + imgCount: container.querySelectorAll('img').length, + hasText: + ImageClassifier.NON_WHITESPACE_REGEXP.test(container.textContent), + }; + this._containerStatsCache.set(container, stats); + return stats; + } + + /** + * Gets stats (image count, text presence) for a container, with caching. + * @param {Element} container The container element. + * @return {{imgCount: number, hasText: boolean}} Container stats. + * @private + */ + _getContainerStats(container) { + return this._containerStatsCache.get(container) ?? + this._addContainerStats(container); + } + + /** + * Checks if the given image is the only significant content within its + * nearest block-level container. + * @param {HTMLImageElement} img The image element to check. + * @return {boolean} Whether the image is the lone significant content. + * @private + */ + _isLoneImageInContainer(img) { + let container = img.parentElement; + // Find `img`'s nearest relevant block-level container. + while (container && + !ImageClassifier.IMG_BLOCK_LEVEL_CONTAINER_TAGS.includes( + container.tagName)) { + container = container.parentElement; + } + if (!container) { + return false; + } + + const {imgCount, hasText} = this._getContainerStats(container); + // `img` should be alone, and all text should be whitespace. + return imgCount === 1 && !hasText; + } + + /** + * Checks if the image is the primary content of its container. + * @param {HTMLImageElement} img The image element to check. + * @return {boolean} True if the image should be full-width. + * @private + */ + _isDefinitelyFullWidth(img) { + const parent = img.parentElement; + + // Image is in a <figure> with a <figcaption>. + if (parent && parent.tagName === 'FIGURE' && + parent.querySelector('figcaption')) { + return true; + } + + // Image is a medium-to-large standalone image. + if (img.naturalWidth > this.loneImageMinWidthDp && + this._isLoneImageInContainer(img)) { + return true; + } + + return false; + } + + /** + * Classifies the image based on a simple intrinsic width fallback. + * @param {HTMLImageElement} img The image element to check. + * @return {string} The CSS class to apply. + * @private + */ + _classifyByFallback(img) { + // Use naturalWidth (in CSS pixels) and compare against the dp threshold. + return img.naturalWidth > this.inlineWidthFallbackUpperBoundDp ? + ImageClassifier.FULL_WIDTH_CLASS : + ImageClassifier.INLINE_CLASS; + } + + /** + * Detects lazy-loading attributes and moves them to standard attributes + * (src, srcset, sizes) to trigger native loading. + * This is necessary for static environments where the original page's + * lazy-loading JavaScript does not run, ensuring the real content is loaded + * instead of a placeholder. + * @param {HTMLImageElement} img The image element to check. + * @private + */ + _loadLazyImageAttributes(img) { + if (!img.src || img.src.startsWith('data:')) { + const srcAttribute = + ImageClassifier.SRC_CANDIDATES.find(el => img.hasAttribute(el)); + if (srcAttribute) { + img.src = img.getAttribute(srcAttribute); + img.removeAttribute(srcAttribute); + } + } + + const srcsetAttribute = + ImageClassifier.SRCSET_CANDIDATES.find(el => img.hasAttribute(el)); + if (srcsetAttribute) { + img.srcset = img.getAttribute(srcsetAttribute); + img.removeAttribute(srcsetAttribute); + } + + const sizeAttribute = + ImageClassifier.SIZES_CANDIDATES.find(el => img.hasAttribute(el)); + if (sizeAttribute) { + img.sizes = img.getAttribute(sizeAttribute); + img.removeAttribute(sizeAttribute); + } + } + + /** + * Determines an image's display style using a prioritized cascade of checks. + * @param {HTMLImageElement} img The image element to classify. + * @return {string} The CSS class to apply. + */ + classify(img) { + // Check for visually dominant images first, as this is the most reliable + // signal and overrides all other heuristics. + if (this._isImageVisuallyDominant(img)) { + return ImageClassifier.FULL_WIDTH_CLASS; + } + + // Fall back to checks based on intrinsic properties and structure. + if (this._isDefinitelyInline(img)) { + return ImageClassifier.INLINE_CLASS; + } + + if (this._isDefinitelyFullWidth(img)) { + return ImageClassifier.FULL_WIDTH_CLASS; + } + + return this._classifyByFallback(img); + } + + /** + * Applies classification to all images within an element. + * @param {HTMLElement} element The element to search for images in. + */ + static processImagesIn(element) { + const classifier = new ImageClassifier(); + const images = element.getElementsByTagName('img'); + + const imageLoadHandler = (event) => { + const img = event.currentTarget; + const classification = classifier.classify(img); + img.classList.add(classification); + }; + + for (const img of images) { + classifier._loadLazyImageAttributes(img); + img.onload = imageLoadHandler; + + // If the image is already loaded (e.g., from cache), manually trigger. + if (img.complete) { + // We use .call() to ensure `this` is correctly bound if the handler + // were a traditional function, and to pass a mock event object. + imageLoadHandler.call(img, {currentTarget: img}); + } + } + } +}
diff --git a/components/dom_distiller/core/javascript/list_classifier.js b/components/dom_distiller/core/javascript/list_classifier.js new file mode 100644 index 0000000..2e52b74 --- /dev/null +++ b/components/dom_distiller/core/javascript/list_classifier.js
@@ -0,0 +1,183 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * A utility class to classify lists in distilled content. + * + * By default, list styling (bullets and numbering) is removed to avoid styling + * navigational or UI elements. This class heuristically determines when to + * restore styling for content lists. + */ +class ListClassifier { + // The following thresholds are used in the content analysis stage for + // ambiguous <ul> elements. + + // A high ratio (> this value) of items ending in punctuation often indicates + // sentence-like content. + static PUNCTUATION_RATIO_THRESHOLD = 0.5; + + // A `<ul>` where every `<li>` is a single link is considered content if + // the average link text length is > this value. + static AVG_LINK_TEXT_LENGTH_THRESHOLD = 15; + + // A low ratio (< this value) of link text to total text often indicates + // a content list. + static LINK_DENSITY_ACCEPTANCE_THRESHOLD = 0.5; + + // A high ratio (> this value) of link text to total text may indicate a + // list of links. + static LINK_DOMINANT_THRESHOLD = 0.5; + + // A selector for elements that are considered substantive content, used to + // determine if a list item is more than just a simple link. + static SUBSTANTIVE_ELEMENTS_SELECTOR = + 'p, div, img, h1, h2, h3, h4, h5, h6, table, pre, blockquote'; + + constructor() { + this.nonContentKeywords = new RegExp( + 'nav|menu|sidebar|footer|links|social|pagination|pager|breadcrumbs', + 'i'); + // Matches if a string is any single Unicode punctuation character. + this.isPunctuation = new RegExp('^\\p{P}$', 'u'); + } + + /** + * Checks for strong signals that a list is for navigation or UI. + * @param {HTMLElement} list The list element. + * @return {boolean} True if the list is navigational. + * @private + */ + _isNavigational(list) { + // Check for explicit navigation roles or containing elements. + if (list.closest( + 'nav, [role="navigation"], [role="menubar"], [role="menu"]')) { + return true; + } + + // Check for non-content keywords in id or class. + const attributes = (list.id + ' ' + list.className).toLowerCase(); + if (this.nonContentKeywords.test(attributes)) { + return true; + } + + return false; + } + + /** + * Performs deeper content analysis on a <ul> list. + * @param {HTMLElement} list The UL element. + * @return {boolean} True if the list should be styled. + * @private + */ + _analyzeContent(list) { + const listItems = list.querySelectorAll('li'); + const numListItems = listItems.length; + + if (numListItems === 0) { + return false; + } + + // In a single pass, collect all metrics needed for the heuristics. + let itemsEndingWithPunctuation = 0; + let totalTextLength = 0; + let linkTextLength = 0; + let hasSubstantiveElements = false; + + for (const li of listItems) { + const itemText = li.textContent.trim(); + totalTextLength += itemText.length; + + if (itemText.length > 0 && this.isPunctuation.test(itemText.slice(-1))) { + itemsEndingWithPunctuation++; + } + + const links = li.querySelectorAll('a'); + for (const link of links) { + // Don't trim() here; whitespace inside a link can be significant. + linkTextLength += link.textContent.length; + } + + if (!hasSubstantiveElements && + li.querySelector(ListClassifier.SUBSTANTIVE_ELEMENTS_SELECTOR)) { + hasSubstantiveElements = true; + } + } + + // Heuristic A: A high punctuation ratio is a strong signal for + // sentence-based content. + const punctuationRatio = itemsEndingWithPunctuation / numListItems; + if (punctuationRatio > ListClassifier.PUNCTUATION_RATIO_THRESHOLD) { + return true; + } + + const linkDensity = + totalTextLength > 0 ? (linkTextLength / totalTextLength) : 0; + + // Heuristic B: A list that is dominated by links is content if the links + // are long enough to be titles. This allows for some non-link text. + if (!hasSubstantiveElements && + linkDensity > ListClassifier.LINK_DOMINANT_THRESHOLD) { + const avgLinkTextLength = + numListItems > 0 ? (linkTextLength / numListItems) : 0; + return avgLinkTextLength >= ListClassifier.AVG_LINK_TEXT_LENGTH_THRESHOLD; + } + + // Heuristic C: A list with a low density of links is likely content. + if (linkDensity < ListClassifier.LINK_DENSITY_ACCEPTANCE_THRESHOLD) { + return true; + } + + // Default to false if undecided. + return false; + } + + /** + * Main classification logic. + * @param {HTMLElement} list The list element (ul or ol). + * @return {boolean} True if the list should be styled. + */ + classify(list) { + // An empty list is never content. + if (list.children.length === 0) { + return false; + } + + // Stage 1: Reject navigational lists immediately. + if (this._isNavigational(list)) { + return false; + } + + // Stage 2: Accept lists that are clearly content. + // An <ol> that isn't navigational is always considered content. + if (list.tagName === 'OL') { + return true; + } + // Any list with block-level elements is also considered content. + if (list.querySelector('li p, li ul, li ol')) { + return true; + } + + // Stage 3: For remaining ambiguous <ul> elements, perform deeper analysis. + // Other element types that reach this point are not considered content. + if (list.tagName === 'UL') { + return this._analyzeContent(list); + } + + return false; + } + + /** + * Post-processes all lists in an element to apply classification classes. + * @param {HTMLElement} element The element to search for lists in. + */ + static processListsIn(element) { + const classifier = new ListClassifier(); + const lists = element.querySelectorAll('ul, ol'); + for (const list of lists) { + if (classifier.classify(list)) { + list.classList.add('distilled-content-list'); + } + } + } +}
diff --git a/components/dom_distiller/core/javascript/pinch_handler.js b/components/dom_distiller/core/javascript/pinch_handler.js new file mode 100644 index 0000000..df99b6b --- /dev/null +++ b/components/dom_distiller/core/javascript/pinch_handler.js
@@ -0,0 +1,286 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// The zooming speed relative to pinching speed. +const FONT_SCALE_MULTIPLIER = 0.5; + +const MIN_SPAN_LENGTH = 20; + +// Only defined on Android. +class Pincher { + // When users pinch in Reader Mode, the page would zoom in or out as if it + // is a normal web page allowing user-zoom. At the end of pinch gesture, the + // page would do text reflow. These pinch-to-zoom and text reflow effects + // are not native, but are emulated using CSS and JavaScript. + // + // In order to achieve near-native zooming and panning frame rate, fake 3D + // transform is used so that the layer doesn't repaint for each frame. + // + // After the text reflow, the web content shown in the viewport should + // roughly be the same paragraph before zooming. + // + // The control point of font size is the html element, so that both "em" and + // "rem" are adjusted. + // + // TODO(wychen): Improve scroll position when elementFromPoint is body. + + static #instance = null; + + static getInstance() { + return Pincher.#instance ?? (Pincher.#instance = new Pincher()); + } + + constructor() { + // This has to be in sync with largest 'font-size' in distilledpage_{}.css. + // This value is hard-coded because JS might be injected before CSS is + // ready. See crbug.com/1004663. + this.baseSize = 16; + this.pinching = false; + this.fontSizeAnchor = 1.0; + + this.focusElement = null; + this.focusPos = 0; + this.initClientMid = null; + + this.clampedScale = 1.0; + + this.lastSpan = null; + this.lastClientMid = null; + + this.scale = 1.0; + this.shiftX = 0; + this.shiftY = 0; + + window.addEventListener('touchstart', (e) => { + this.handleTouchStart(e); + }, {passive: false}); + window.addEventListener('touchmove', (e) => { + this.handleTouchMove(e); + }, {passive: false}); + window.addEventListener('touchend', (e) => { + this.handleTouchEnd(e); + }, {passive: false}); + window.addEventListener('touchcancel', (e) => { + this.handleTouchCancel(e); + }, {passive: false}); + } + + clampScaleFun(v) { + // The line below is dynamically modified from C++, and the result is + // checked DomDistillerViewerTest by RegExp, so do not reformat! + return /* PINCH_SCALE */ Math.max($MIN_SCALE, Math.min($MAX_SCALE, v)); + } + + /** @private */ + refreshTransform_() { + const slowedScale = Math.exp(Math.log(this.scale) * FONT_SCALE_MULTIPLIER); + this.clampedScale = this.clampScaleFun(this.fontSizeAnchor * slowedScale); + + // Use "fake" 3D transform so that the layer is not repainted. + // With 2D transform, the frame rate would be much lower. + // clang-format off + document.body.style.transform = + 'translate3d(' + this.shiftX + 'px,' + + this.shiftY + 'px, 0px)' + + 'scale(' + this.clampedScale / this.fontSizeAnchor + ')'; + // clang-format on + } + + /** @private */ + saveCenter_(clientMid) { + // Try to preserve the pinching center after text reflow. + // This is accurate to the HTML element level. + this.focusElement = document.elementFromPoint(clientMid.x, clientMid.y); + const rect = this.focusElement.getBoundingClientRect(); + this.initClientMid = clientMid; + this.focusPos = + (this.initClientMid.y - rect.top) / (rect.bottom - rect.top); + } + + /** @private */ + restoreCenter_() { + const rect = this.focusElement.getBoundingClientRect(); + const targetTop = this.focusPos * (rect.bottom - rect.top) + rect.top + + document.scrollingElement.scrollTop - + (this.initClientMid.y + this.shiftY); + document.scrollingElement.scrollTop = targetTop; + } + + /** @private */ + endPinch_() { + this.pinching = false; + + document.body.style.transformOrigin = ''; + document.body.style.transform = ''; + document.documentElement.style.fontSize = + this.clampedScale * this.baseSize + 'px'; + + this.restoreCenter_(); + + let img = $('fontscaling-img'); + if (!img) { + img = document.createElement('img'); + img.id = 'fontscaling-img'; + img.style.display = 'none'; + document.body.appendChild(img); + } + img.src = '/savefontscaling/' + this.clampedScale; + } + + /** @private */ + touchSpan_(e) { + const count = e.touches.length; + const mid = this.touchClientMid_(e); + let sum = 0; + for (let i = 0; i < count; i++) { + const dx = (e.touches[i].clientX - mid.x); + const dy = (e.touches[i].clientY - mid.y); + sum += Math.hypot(dx, dy); + } + // Avoid very small span. + return Math.max(MIN_SPAN_LENGTH, sum / count); + } + + /** @private */ + touchClientMid_(e) { + const count = e.touches.length; + let sumX = 0; + let sumY = 0; + for (let i = 0; i < count; i++) { + sumX += e.touches[i].clientX; + sumY += e.touches[i].clientY; + } + return {x: sumX / count, y: sumY / count}; + } + + /** @private */ + touchPageMid_(e) { + const clientMid = this.touchClientMid_(e); + return { + x: clientMid.x - e.touches[0].clientX + e.touches[0].pageX, + y: clientMid.y - e.touches[0].clientY + e.touches[0].pageY, + }; + } + + handleTouchStart(e) { + if (e.touches.length < 2) { + return; + } + e.preventDefault(); + + const span = this.touchSpan_(e); + const clientMid = this.touchClientMid_(e); + + if (e.touches.length > 2) { + this.lastSpan = span; + this.lastClientMid = clientMid; + this.refreshTransform_(); + return; + } + + this.scale = 1; + this.shiftX = 0; + this.shiftY = 0; + + this.pinching = true; + this.fontSizeAnchor = + parseFloat(getComputedStyle(document.documentElement).fontSize) / + this.baseSize; + + const pinchOrigin = this.touchPageMid_(e); + document.body.style.transformOrigin = + pinchOrigin.x + 'px ' + pinchOrigin.y + 'px'; + + this.saveCenter_(clientMid); + + this.lastSpan = span; + this.lastClientMid = clientMid; + + this.refreshTransform_(); + } + + handleTouchMove(e) { + if (!this.pinching) { + return; + } + if (e.touches.length < 2) { + return; + } + e.preventDefault(); + + const span = this.touchSpan_(e); + const clientMid = this.touchClientMid_(e); + + this.scale *= this.touchSpan_(e) / this.lastSpan; + this.shiftX += clientMid.x - this.lastClientMid.x; + this.shiftY += clientMid.y - this.lastClientMid.y; + + this.refreshTransform_(); + + this.lastSpan = span; + this.lastClientMid = clientMid; + } + + handleTouchEnd(e) { + if (!this.pinching) { + return; + } + e.preventDefault(); + + const span = this.touchSpan_(e); + const clientMid = this.touchClientMid_(e); + + if (e.touches.length >= 2) { + this.lastSpan = span; + this.lastClientMid = clientMid; + this.refreshTransform_(); + return; + } + + this.endPinch_(); + } + + handleTouchCancel(e) { + if (!this.pinching) { + return; + } + this.endPinch_(); + } + + reset() { + this.scale = 1; + this.shiftX = 0; + this.shiftY = 0; + this.clampedScale = 1; + document.documentElement.style.fontSize = + this.clampedScale * this.baseSize + 'px'; + } + + status() { + return { + scale: this.scale, + clampedScale: this.clampedScale, + shiftX: this.shiftX, + shiftY: this.shiftY, + }; + } + + useFontScaling(scaling, restoreCenter = true) { + if (restoreCenter) { + this.saveCenter_({x: window.innerWidth / 2, y: window.innerHeight / 2}); + } + this.shiftX = 0; + this.shiftY = 0; + document.documentElement.style.fontSize = scaling * this.baseSize + 'px'; + this.clampedScale = scaling; + if (restoreCenter) { + this.restoreCenter_(); + } + } + + useBaseFontSize(size) { + this.baseSize = size; + this.reset(); + } +}
diff --git a/components/dom_distiller/core/javascript/settings_dialog.js b/components/dom_distiller/core/javascript/settings_dialog.js new file mode 100644 index 0000000..2745267 --- /dev/null +++ b/components/dom_distiller/core/javascript/settings_dialog.js
@@ -0,0 +1,95 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +class SettingsDialog { + static #instance = null; + + static getInstance() { + return SettingsDialog.#instance ?? + (SettingsDialog.#instance = new SettingsDialog( + $('settings-toggle'), $('settings-dialog'), $('dialog-backdrop'), + $('theme-selection'), $('font-family-selection'))); + } + + constructor( + toggleElement, dialogElement, backdropElement, themeFieldset, + fontFamilySelect) { + this._toggleElement = toggleElement; + this._dialogElement = dialogElement; + this._backdropElement = backdropElement; + this._themeFieldset = themeFieldset; + this._fontFamilySelect = fontFamilySelect; + + this._toggleElement.addEventListener('click', this.toggle.bind(this)); + this._dialogElement.addEventListener('close', this.close.bind(this)); + this._backdropElement.addEventListener('click', this.close.bind(this)); + + $('close-settings-button').addEventListener('click', this.close.bind(this)); + + this._themeFieldset.addEventListener('change', (e) => { + const newTheme = e.target.value; + this.useTheme(newTheme); + distiller.storeThemePref(themeClasses.indexOf(newTheme)); + }); + + this._fontFamilySelect.addEventListener('change', (e) => { + const newFontFamily = e.target.value; + this.useFontFamily(newFontFamily); + distiller.storeFontFamilyPref(fontFamilyClasses.indexOf(newFontFamily)); + }); + + // Appearance settings are loaded from user preferences, so on page load + // the controllers for these settings may need to be updated to reflect + // the active setting. + this._updateFontFamilyControls(getAppearanceSetting(fontFamilyClasses)); + const selectedTheme = getAppearanceSetting(themeClasses); + this._updateThemeControls(selectedTheme); + updateToolbarColor(selectedTheme); + } + + toggle() { + if (this._dialogElement.open) { + this.close(); + } else { + this.showModal(); + } + } + + showModal() { + this._toggleElement.classList.add('activated'); + this._backdropElement.style.display = 'block'; + this._dialogElement.showModal(); + } + + close() { + this._toggleElement.classList.remove('activated'); + this._backdropElement.style.display = 'none'; + this._dialogElement.close(); + } + + useTheme(theme) { + themeClasses.forEach( + (element) => + document.body.classList.toggle(element, element === theme)); + this._updateThemeControls(theme); + updateToolbarColor(theme); + } + + _updateThemeControls(theme) { + const queryString = `input[value=${theme}]`; + this._themeFieldset.querySelector(queryString).checked = true; + } + + useFontFamily(fontFamily) { + fontFamilyClasses.forEach( + (element) => + document.body.classList.toggle(element, element === fontFamily)); + this._updateFontFamilyControls(fontFamily); + } + + _updateFontFamilyControls(fontFamily) { + this._fontFamilySelect.selectedIndex = + fontFamilyClasses.indexOf(fontFamily); + } +}
diff --git a/components/dom_distiller/core/viewer.cc b/components/dom_distiller/core/viewer.cc index 108cb2a..23020a7 100644 --- a/components/dom_distiller/core/viewer.cc +++ b/components/dom_distiller/core/viewer.cc
@@ -53,7 +53,7 @@ const char kMonospaceJsFontFamily[] = "monospace"; const char kLexendJsFontFamily[] = "Lexend"; -// LINT.ThenChange(//components/dom_distiller/core/javascript/dom_distiller_viewer.js:JSThemesAndFonts) +// LINT.ThenChange(//components/dom_distiller/core/javascript/dom_distiller_viewer_main.js:JSThemesAndFonts) // LINT.IfChange
diff --git a/components/dom_distiller/core/viewer_unittest.cc b/components/dom_distiller/core/viewer_unittest.cc index 312a79a..4ec3af1 100644 --- a/components/dom_distiller/core/viewer_unittest.cc +++ b/components/dom_distiller/core/viewer_unittest.cc
@@ -185,7 +185,7 @@ #endif std::string output = viewer::GetJavaScript(); EXPECT_THAT(output, testing::ContainsRegex( - "this\\.clampedScale\\s*=\\s*Math\\.max\\(0\\.5,")); + "/\\* PINCH_SCALE \\*/ Math.max\\(0\\.5,")); } TEST_F(DomDistillerViewerTest, TestGetJavaScriptPinchMinMaxZoom_DistillInApp) { @@ -193,10 +193,9 @@ base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitAndEnableFeature(kReaderModeDistillInApp); std::string output = viewer::GetJavaScript(); - EXPECT_THAT( - output, - testing::ContainsRegex( - "this\\.clampedScale\\s*=\\s*Math\\.max\\(1, Math\\.min\\(2.5")); + EXPECT_THAT(output, + testing::ContainsRegex( + "/\\* PINCH_SCALE \\*/ Math\\.max\\(1, Math\\.min\\(2\\.5,")); #else SUCCEED(); #endif @@ -208,10 +207,9 @@ base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitAndDisableFeature(kReaderModeDistillInApp); std::string output = viewer::GetJavaScript(); - EXPECT_THAT( - output, - testing::ContainsRegex( - "this\\.clampedScale\\s*=\\s*Math\\.max\\(0.5, Math\\.min\\(2")); + EXPECT_THAT(output, + testing::ContainsRegex( + "/\\* PINCH_SCALE \\*/ Math\\.max\\(0\\.5, Math\\.min\\(2,")); #else SUCCEED(); #endif
diff --git a/components/enterprise/common/proto/connectors.proto b/components/enterprise/common/proto/connectors.proto index cd88b75..83c2f51 100644 --- a/components/enterprise/common/proto/connectors.proto +++ b/components/enterprise/common/proto/connectors.proto
@@ -105,6 +105,9 @@ // The parent URL chain of the frame from which the action was triggered, // ordered from current frame URL to tab URL, inclusive. repeated string frame_url_chain = 16; + + // File size in bytes. Only populated for file scanning events. + optional uint64 file_size = 18; } message ClientMetadata {
diff --git a/components/enterprise/common/proto/synced/browser_events.proto b/components/enterprise/common/proto/synced/browser_events.proto index 4a93928..84b985a5 100644 --- a/components/enterprise/common/proto/synced/browser_events.proto +++ b/components/enterprise/common/proto/synced/browser_events.proto
@@ -678,7 +678,7 @@ } // A DLP (Data Loss Prevention) event where sensitive data may have leaked. -// Next ID: 38. +// Next ID: 40. message DlpSensitiveDataEvent { reserved 6, 9, 10, 11, 12, 13, 15, 25; @@ -809,6 +809,27 @@ // ] // copybara:datapol_end ; + + // The Microsoft sensitivity (MIP) label for the file associated with the URL + // in the request. Only present for Microsoft M365 sites. + string microsoft_sensitivity_label_name = 38 + // copybara:datapol_begin + // [ + // (datapol.semantic_type) = ST_UNSTRUCTURED_ADMIN_SETTING + // ] + // copybara:datapol_end + ; + + // The Microsoft sensitivity (MIP) label for the file associated with the URL + // of the copied text in the request for paste event only. Only present for + // Microsoft M365 sites. + string source_microsoft_sensitivity_label_name = 39 + // copybara:datapol_begin + // [ + // (datapol.semantic_type) = ST_UNSTRUCTURED_ADMIN_SETTING + // ] + // copybara:datapol_end + ; } // Event where a large file could not be scanned for analysis e.g. malware or
diff --git a/components/enterprise/connectors/core/cloud_content_scanning/binary_upload_request.cc b/components/enterprise/connectors/core/cloud_content_scanning/binary_upload_request.cc index 82394fe..8585afe 100644 --- a/components/enterprise/connectors/core/cloud_content_scanning/binary_upload_request.cc +++ b/components/enterprise/connectors/core/cloud_content_scanning/binary_upload_request.cc
@@ -252,6 +252,10 @@ content_hash_in_final_call); } +void BinaryUploadRequest::set_file_size(uint64_t file_size) { + content_analysis_request_.mutable_request_data()->set_file_size(file_size); +} + std::string BinaryUploadRequest::SetRandomRequestToken() { DCHECK(request_token().empty()); content_analysis_request_.set_request_token( @@ -349,6 +353,10 @@ return content_analysis_request_.content_hash_in_final_call(); } +uint64_t BinaryUploadRequest::file_size() const { + return content_analysis_request_.request_data().file_size(); +} + void BinaryUploadRequest::StartRequest() { if (!request_start_callback_.is_null()) { std::move(request_start_callback_).Run(*this);
diff --git a/components/enterprise/connectors/core/cloud_content_scanning/binary_upload_request.h b/components/enterprise/connectors/core/cloud_content_scanning/binary_upload_request.h index c6063361..2948adb 100644 --- a/components/enterprise/connectors/core/cloud_content_scanning/binary_upload_request.h +++ b/components/enterprise/connectors/core/cloud_content_scanning/binary_upload_request.h
@@ -155,6 +155,7 @@ void set_frame_url_chain( const google::protobuf::RepeatedPtrField<std::string> frame_url_chain); void set_content_hash_in_final_call(bool content_hash_in_final_call); + void set_file_size(uint64_t file_size); std::string SetRandomRequestToken(); @@ -177,6 +178,7 @@ bool is_content_too_large() const; bool should_skip_malware_scan() const; bool content_hash_in_final_call() const; + uint64_t file_size() const; // Called when beginning to try upload. void StartRequest();
diff --git a/components/enterprise/connectors/core/cloud_content_scanning/file_analysis_request_base.cc b/components/enterprise/connectors/core/cloud_content_scanning/file_analysis_request_base.cc index a9d4be4..81a2fb9c 100644 --- a/components/enterprise/connectors/core/cloud_content_scanning/file_analysis_request_base.cc +++ b/components/enterprise/connectors/core/cloud_content_scanning/file_analysis_request_base.cc
@@ -406,6 +406,7 @@ DCHECK(!data.path.empty()); cached_data_ = std::move(data); + set_file_size(cached_data_.size); set_digest(cached_data_.hash); set_content_type(cached_data_.mime_type); }
diff --git a/components/enterprise/connectors/core/cloud_content_scanning/file_analysis_request_base_unittest.cc b/components/enterprise/connectors/core/cloud_content_scanning/file_analysis_request_base_unittest.cc index 2bacca1f..5be79f9 100644 --- a/components/enterprise/connectors/core/cloud_content_scanning/file_analysis_request_base_unittest.cc +++ b/components/enterprise/connectors/core/cloud_content_scanning/file_analysis_request_base_unittest.cc
@@ -410,6 +410,8 @@ "29644C10BD036866FCFD2BDACFF340DB5DE47A90002D6AB0C42DE6A22C26158B"); EXPECT_EQ(request->digest(), data.hash); EXPECT_EQ(request->content_type(), "fake/mimetype"); + EXPECT_EQ(data.size, 20UL); // printf "Normal file contents" | wc -c + EXPECT_EQ(request->file_size(), data.size); } TEST_F(FileAnalysisRequestBaseTest, DelayedFileOpening) {
diff --git a/components/feature_engagement/internal/android/java/src/org/chromium/components/feature_engagement/internal/TrackerImpl.java b/components/feature_engagement/internal/android/java/src/org/chromium/components/feature_engagement/internal/TrackerImpl.java index 07f4ba19..5ec17f36 100644 --- a/components/feature_engagement/internal/android/java/src/org/chromium/components/feature_engagement/internal/TrackerImpl.java +++ b/components/feature_engagement/internal/android/java/src/org/chromium/components/feature_engagement/internal/TrackerImpl.java
@@ -218,7 +218,9 @@ boolean isInitialized(long nativeTrackerImplAndroid); - void addOnInitializedCallback(long nativeTrackerImplAndroid, Callback<Boolean> callback); + void addOnInitializedCallback( + long nativeTrackerImplAndroid, + @JniType("base::OnceCallback<void(bool)>") Callback<Boolean> callback); void release(long nativeDisplayLockHandleAndroid); }
diff --git a/components/feature_engagement/internal/android/tracker_impl_android.cc b/components/feature_engagement/internal/android/tracker_impl_android.cc index 3404424..0b568b8 100644 --- a/components/feature_engagement/internal/android/tracker_impl_android.cc +++ b/components/feature_engagement/internal/android/tracker_impl_android.cc
@@ -218,11 +218,8 @@ } void TrackerImplAndroid::AddOnInitializedCallback( - JNIEnv* env, - const base::android::JavaRef<jobject>& j_callback_obj) { - tracker_->AddOnInitializedCallback(base::BindOnce( - &base::android::RunBooleanCallbackAndroid, - base::android::ScopedJavaGlobalRef<jobject>(j_callback_obj))); + base::OnceCallback<void(bool)> callback) { + tracker_->AddOnInitializedCallback(std::move(callback)); } DisplayLockHandleAndroid::DisplayLockHandleAndroid(
diff --git a/components/feature_engagement/internal/android/tracker_impl_android.h b/components/feature_engagement/internal/android/tracker_impl_android.h index 33d97c64..9239eaf 100644 --- a/components/feature_engagement/internal/android/tracker_impl_android.h +++ b/components/feature_engagement/internal/android/tracker_impl_android.h
@@ -112,8 +112,7 @@ const base::android::JavaRef<jstring>& jfeature); virtual bool IsInitialized(JNIEnv* env); virtual void AddOnInitializedCallback( - JNIEnv* env, - const base::android::JavaRef<jobject>& j_callback_obj); + base::OnceCallback<void(bool)> callback); private: // A map from the feature name to the base::Feature, to ensure that the Java
diff --git a/components/guest_view/browser/slim_web_view/BUILD.gn b/components/guest_view/browser/slim_web_view/BUILD.gn index eb3b6fa..84d5425 100644 --- a/components/guest_view/browser/slim_web_view/BUILD.gn +++ b/components/guest_view/browser/slim_web_view/BUILD.gn
@@ -1,6 +1,7 @@ # Copyright 2026 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. + import("//components/guest_view/buildflags/buildflags.gni") import("//tools/grit/grit_rule.gni")
diff --git a/components/guest_view/renderer/DEPS b/components/guest_view/renderer/DEPS index 1d39d04..cd8ad3e 100644 --- a/components/guest_view/renderer/DEPS +++ b/components/guest_view/renderer/DEPS
@@ -1,6 +1,7 @@ include_rules = [ "+content/public/renderer", "+content/public/common", + "+gin", "+ipc", "+mojo/public/cpp", "+third_party/blink/public",
diff --git a/components/guest_view/renderer/slim_web_view/BUILD.gn b/components/guest_view/renderer/slim_web_view/BUILD.gn new file mode 100644 index 0000000..d8e18085 --- /dev/null +++ b/components/guest_view/renderer/slim_web_view/BUILD.gn
@@ -0,0 +1,28 @@ +# Copyright 2026 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//components/guest_view/buildflags/buildflags.gni") + +assert(enable_guest_view) + +static_library("slim_web_view") { + sources = [ + "slim_web_view_bindings.cc", + "slim_web_view_bindings.h", + ] + + deps = [ + "//base", + "//components/guest_view/common", + "//components/guest_view/common:mojom", + "//content/public/common", + "//content/public/renderer", + "//gin", + "//mojo/public/cpp/bindings", + "//third_party/abseil-cpp:absl", + "//third_party/blink/public:blink", + "//third_party/blink/public/common", + "//v8", + ] +}
diff --git a/components/guest_view/renderer/slim_web_view/ONWERS b/components/guest_view/renderer/slim_web_view/ONWERS new file mode 100644 index 0000000..b6265060 --- /dev/null +++ b/components/guest_view/renderer/slim_web_view/ONWERS
@@ -0,0 +1 @@ +file://components/guest_view/browser/slim_web_view/OWNERS
diff --git a/components/guest_view/renderer/slim_web_view/slim_web_view_bindings.cc b/components/guest_view/renderer/slim_web_view/slim_web_view_bindings.cc new file mode 100644 index 0000000..f7c682a8 --- /dev/null +++ b/components/guest_view/renderer/slim_web_view/slim_web_view_bindings.cc
@@ -0,0 +1,127 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/guest_view/renderer/slim_web_view/slim_web_view_bindings.h" + +#include "base/no_destructor.h" +#include "components/guest_view/common/guest_view.mojom.h" +#include "components/guest_view/common/guest_view_constants.h" +#include "content/public/common/bindings_policy.h" +#include "content/public/renderer/chrome_object_extensions_utils.h" +#include "content/public/renderer/render_frame.h" +#include "gin/converter.h" +#include "gin/function_template.h" +#include "mojo/public/cpp/bindings/associated_remote.h" +#include "mojo/public/cpp/bindings/remote.h" +#include "third_party/abseil-cpp/absl/container/flat_hash_map.h" +#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" +#include "third_party/blink/public/platform/scheduler/web_agent_group_scheduler.h" +#include "third_party/blink/public/web/web_local_frame.h" +#include "v8/include/v8-context.h" +#include "v8/include/v8-function.h" +#include "v8/include/v8-isolate.h" +#include "v8/include/v8-local-handle.h" + +namespace guest_view { + +namespace { + +// This name matches the name in slim_web_view_guest.h, but we can't depend on +// that header file. +static constexpr char kSlimWebViewType[] = "SlimWebView"; + +static constexpr char kSlimWebViewPrivate[] = "slimWebViewPrivate"; + +struct ViewHolder { + v8::Global<v8::Object> view; + mojo::Remote<mojom::ViewHandle> keep_alive_handle_remote; +}; + +mojo::AssociatedRemote<guest_view::mojom::GuestViewHost> GetGuestViewHost( + content::RenderFrame* render_frame) { + mojo::AssociatedRemote<guest_view::mojom::GuestViewHost> guest_view_host; + render_frame->GetRemoteAssociatedInterfaces()->GetInterface(&guest_view_host); + + return guest_view_host; +} + +// A map from view instance ID to ViewHolder (managed via weak V8 reference to +// the view object). Views are registered into this map via RegisterView(). +using ViewMap = absl::flat_hash_map<int, ViewHolder>; + +ViewMap& GetViewMap() { + static base::NoDestructor<ViewMap> view_map; + return *view_map; +} + +void ResetMapEntry(const v8::WeakCallbackInfo<void>& data) { + auto view_instance_id = reinterpret_cast<uintptr_t>(data.GetParameter()); + ViewMap& view_map = GetViewMap(); + auto entry = view_map.find(static_cast<int>(view_instance_id)); + CHECK(entry != view_map.end()); + view_map.erase(entry); +} + +int GetNextId() { + static int next_id = 0; + return ++next_id; +} + +void RegisterView(v8::Isolate* isolate, + int view_instance_id, + v8::Local<v8::Object> view) { + CHECK_NE(view_instance_id, kInstanceIDNone); + v8::Local<v8::Context> context = isolate->GetCurrentContext(); + blink::WebLocalFrame* frame = blink::WebLocalFrame::FrameForContext(context); + content::RenderFrame* render_frame = + content::RenderFrame::FromWebFrame(frame); + + auto result = GetViewMap().insert( + {view_instance_id, ViewHolder{v8::Global<v8::Object>(isolate, view)}}); + CHECK(result.second); + auto& view_holder = result.first->second; + view_holder.view.SetWeak( + reinterpret_cast<void*>(static_cast<uintptr_t>(view_instance_id)), + &ResetMapEntry, v8::WeakCallbackType::kParameter); + + auto receiver = + view_holder.keep_alive_handle_remote.BindNewPipeAndPassReceiver(); + GetGuestViewHost(render_frame) + ->ViewCreated(view_instance_id, kSlimWebViewType, std::move(receiver)); +} + +} // namespace + +// static +void SlimWebViewBindings::MaybeInstall(content::RenderFrame& render_frame) { + if (!render_frame.GetEnabledBindings().Has( + content::BindingsPolicyValue::kSlimWebView)) { + return; + } + blink::WebLocalFrame* frame = render_frame.GetWebFrame(); + v8::Isolate* isolate = frame->GetAgentGroupScheduler()->Isolate(); + v8::HandleScope handle_scope(isolate); + v8::Local<v8::Context> context = frame->MainWorldScriptContext(); + if (context.IsEmpty()) { + return; + } + v8::Context::Scope context_scope(context); + v8::Local<v8::Object> chrome = + content::GetOrCreateChromeObject(isolate, context); + v8::Local<v8::Object> slim_web_view_internal = + content::GetOrCreateObject(isolate, context, chrome, kSlimWebViewPrivate); + auto bind = [&](const char* name, auto func) { + slim_web_view_internal + ->Set(context, gin::StringToSymbol(isolate, name), + gin::CreateFunctionTemplate(isolate, base::BindRepeating(func)) + ->GetFunction(context) + .ToLocalChecked()) + .Check(); + }; + + bind("getNextId", &GetNextId); + bind("registerView", &RegisterView); +} + +} // namespace guest_view
diff --git a/components/guest_view/renderer/slim_web_view/slim_web_view_bindings.h b/components/guest_view/renderer/slim_web_view/slim_web_view_bindings.h new file mode 100644 index 0000000..b9ec2b47 --- /dev/null +++ b/components/guest_view/renderer/slim_web_view/slim_web_view_bindings.h
@@ -0,0 +1,23 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_GUEST_VIEW_RENDERER_SLIM_WEB_VIEW_SLIM_WEB_VIEW_BINDINGS_H_ +#define COMPONENTS_GUEST_VIEW_RENDERER_SLIM_WEB_VIEW_SLIM_WEB_VIEW_BINDINGS_H_ + +#include "components/guest_view/common/guest_view.mojom-forward.h" + +namespace content { +class RenderFrame; +} // namespace content + +namespace guest_view { + +class SlimWebViewBindings { + public: + static void MaybeInstall(content::RenderFrame& render_frame); +}; + +} // namespace guest_view + +#endif // COMPONENTS_GUEST_VIEW_RENDERER_SLIM_WEB_VIEW_SLIM_WEB_VIEW_BINDINGS_H_
diff --git a/components/history_clusters/core/history_clusters_debug_jsons.cc b/components/history_clusters/core/history_clusters_debug_jsons.cc index f350250..eb590e9 100644 --- a/components/history_clusters/core/history_clusters_debug_jsons.cc +++ b/components/history_clusters/core/history_clusters_debug_jsons.cc
@@ -6,7 +6,6 @@ #include <string> #include <unordered_map> -#include <unordered_set> #include <utility> #include <vector> @@ -174,28 +173,6 @@ return debug_string; } -template <typename T> -std::string GetDebugJSONForUrlKeywordSet( - const std::unordered_set<T>& keyword_set) { - base::ListValue keyword_list; - for (const auto& keyword : keyword_set) { - keyword_list.Append(keyword); - } - - std::string debug_string; - if (!base::JSONWriter::WriteWithOptions( - keyword_list, base::JSONWriter::OPTIONS_PRETTY_PRINT, - &debug_string)) { - debug_string = "Error: Could not write keywords list to JSON."; - } - return debug_string; -} - -template std::string GetDebugJSONForUrlKeywordSet<std::u16string>( - const std::unordered_set<std::u16string>&); -template std::string GetDebugJSONForUrlKeywordSet<std::string>( - const std::unordered_set<std::string>&); - std::string GetDebugJSONForKeywordMap( const std::unordered_map<std::u16string, history::ClusterKeywordData>& keyword_to_data_map) {
diff --git a/components/history_clusters/core/history_clusters_debug_jsons.h b/components/history_clusters/core/history_clusters_debug_jsons.h index 199107b..5d29d50 100644 --- a/components/history_clusters/core/history_clusters_debug_jsons.h +++ b/components/history_clusters/core/history_clusters_debug_jsons.h
@@ -7,7 +7,6 @@ #include <string> #include <unordered_map> -#include <unordered_set> #include <vector> #include "components/history/core/browser/history_types.h" @@ -29,10 +28,6 @@ std::string GetDebugJSONForClusters( const std::vector<history::Cluster>& clusters); -template <typename T> -std::string GetDebugJSONForUrlKeywordSet( - const std::unordered_set<T>& keyword_set); - std::string GetDebugJSONForKeywordMap( const std::unordered_map<std::u16string, history::ClusterKeywordData>& keyword_to_data_map);
diff --git a/components/metrics/reporting_service.cc b/components/metrics/reporting_service.cc index 199765d..2e5e6fcd 100644 --- a/components/metrics/reporting_service.cc +++ b/components/metrics/reporting_service.cc
@@ -104,10 +104,12 @@ #if BUILDFLAG(IS_ANDROID) void ReportingService::SendNextLogNow(base::PassKey<BackgroundUploadTask>, base::OnceClosure done_callback) { - CHECK(!background_upload_task_scheduled_time_.is_null()); + CHECK(background_upload_task_scheduled_); + CHECK(background_upload_task_scheduled_time_.has_value()); + background_upload_task_scheduled_ = false; LogBackgroundUploadTaskPendingTime(base::TimeTicks::Now() - - background_upload_task_scheduled_time_); - background_upload_task_scheduled_time_ = base::TimeTicks(); + *background_upload_task_scheduled_time_); + background_upload_task_scheduled_time_ = std::nullopt; SendNextLogImpl(std::move(done_callback)); } #endif // BUILDFLAG(IS_ANDROID) @@ -179,8 +181,12 @@ // JobScheduler. See metrics::BackgroundUploadTask for implementation of the // background task. if (client_->IsJobSchedulerSupported()) { - // There should not be two upload tasks scheduled simultaneously. - CHECK(background_upload_task_scheduled_time_.is_null()); + // There should not be two upload tasks scheduled simultaneously. Note that + // the following fields are intentionally set *before* we call Schedule() in + // case that function can in-line the execution of the task immediately. + CHECK(!background_upload_task_scheduled_); + CHECK(!background_upload_task_scheduled_time_.has_value()); + background_upload_task_scheduled_ = true; background_upload_task_scheduled_time_ = base::TimeTicks::Now(); // For consistency with other platforms, we use OneOffInfo (rather than // PeriodicInfo), as we have our own scheduling mechanisms. When the task @@ -193,9 +199,19 @@ // is no connectivity, which we want to exercise for consistency with other // platforms). background_task::TaskInfo task_info(background_upload_task_id_, one_off); - background_task::BackgroundTaskSchedulerFactory::GetScheduler()->Schedule( - task_info); - return; + bool success = + background_task::BackgroundTaskSchedulerFactory::GetScheduler() + ->Schedule(task_info); + if (success) { + return; + } + + // If we couldn't schedule the task for whatever reason, fall back to + // uploading without JobScheduler (though the network request may fail if + // the browser is currently in the background). Clear the "pending" fields + // first. + background_upload_task_scheduled_ = false; + background_upload_task_scheduled_time_ = std::nullopt; } #endif // BUILDFLAG(IS_ANDROID) SendNextLogImpl(base::DoNothing());
diff --git a/components/metrics/reporting_service.h b/components/metrics/reporting_service.h index 24af874..d8dcea0 100644 --- a/components/metrics/reporting_service.h +++ b/components/metrics/reporting_service.h
@@ -88,6 +88,14 @@ // manager (see `SendNextLogImpl()` below). void SendNextLogNow(base::PassKey<BackgroundUploadTask>, base::OnceClosure done_callback); + + bool background_upload_task_scheduled() const { + return background_upload_task_scheduled_; + } + + background_task::TaskIds background_upload_task_id() const { + return background_upload_task_id_; + } #endif // BUILDFLAG(IS_ANDROID) // True iff reporting is currently enabled. @@ -210,10 +218,13 @@ [[maybe_unused]] const background_task::TaskIds background_upload_task_id_; #if BUILDFLAG(IS_ANDROID) + // If there is a currently a scheduled JobScheduler upload task pending. + bool background_upload_task_scheduled_ = false; + // The time a background upload task was scheduled/posted to the JobScheduler. // Used to track the time taken between the task being scheduled and the time // the task actually runs. - base::TimeTicks background_upload_task_scheduled_time_; + std::optional<base::TimeTicks> background_upload_task_scheduled_time_; #endif // BUILDFLAG(IS_ANDROID) SEQUENCE_CHECKER(sequence_checker_);
diff --git a/components/metrics_services_manager/metrics_services_manager.cc b/components/metrics_services_manager/metrics_services_manager.cc index 35f3eb65..b79c19e0 100644 --- a/components/metrics_services_manager/metrics_services_manager.cc +++ b/components/metrics_services_manager/metrics_services_manager.cc
@@ -84,28 +84,23 @@ switch (service_type) { case metrics::MetricsLogUploader::MetricServiceType::UMA: { auto* service = GetMetricsService(); - CHECK(service); - return service->reporting_service(); + return service ? service->reporting_service() : nullptr; } case metrics::MetricsLogUploader::MetricServiceType::UKM: { auto* service = GetUkmService(); - CHECK(service); - return service->reporting_service(); + return service ? service->reporting_service() : nullptr; } case metrics::MetricsLogUploader::MetricServiceType::DWA: { auto* service = GetDwaService(); - CHECK(service); - return service->reporting_service(); + return service ? service->reporting_service() : nullptr; } case metrics::MetricsLogUploader::MetricServiceType::PRIVATE_METRICS: { auto* service = GetPumaService(); - CHECK(service); - return service->reporting_service(); + return service ? service->reporting_service() : nullptr; } case metrics::MetricsLogUploader::MetricServiceType::STRUCTURED_METRICS: { auto* service = GetStructuredMetricsService(); - CHECK(service); - return service->reporting_service(); + return service ? service->reporting_service() : nullptr; } } }
diff --git a/components/permissions/android/android_permission_util.cc b/components/permissions/android/android_permission_util.cc index 7996214..4c2c799 100644 --- a/components/permissions/android/android_permission_util.cc +++ b/components/permissions/android/android_permission_util.cc
@@ -218,6 +218,18 @@ true); } +void ResolvePermissionWithOSPrompt(content::WebContents* web_contents, + ContentSettingsType content_settings_type) { + DCHECK(web_contents); + auto* window_android = web_contents->GetNativeView()->GetWindowAndroid(); + DCHECK(window_android); + + JNIEnv* env = base::android::AttachCurrentThread(); + Java_PermissionUtil_handlePermissionPromptAllow( + env, window_android->GetJavaObject(), web_contents->GetJavaWebContents(), + static_cast<int>(content_settings_type)); +} + namespace internal { void ResolveNotificationsPermissionRequest(content::WebContents* web_contents,
diff --git a/components/permissions/android/android_permission_util.h b/components/permissions/android/android_permission_util.h index 9c485d8..f54b6fc 100644 --- a/components/permissions/android/android_permission_util.h +++ b/components/permissions/android/android_permission_util.h
@@ -116,6 +116,12 @@ // Called from tests to temporarily set system location settings enabled. base::AutoReset<bool> EnableSystemLocationSettingForTesting(); +// Resolves a permission request by first checking/requesting the Android +// system permission. If granted, it accepts the request; otherwise, it +// dismisses it. +void ResolvePermissionWithOSPrompt(content::WebContents* web_contents, + ContentSettingsType content_settings_type); + } // namespace permissions #endif // COMPONENTS_PERMISSIONS_ANDROID_ANDROID_PERMISSION_UTIL_H_
diff --git a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogController.java b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogController.java index 2de4a20..00c12113 100644 --- a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogController.java +++ b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionDialogController.java
@@ -141,6 +141,38 @@ } /** + * Called by native code to show the loud permission icon. + * + * <p>This is part of the clapper loud permission prompt flow for notifications and triggers the + * omnibox icon after a permission request was decided via the message ui. + * + * @param window The {@link WindowAndroid} where the icon should be shown. + * @param result A ContentSetting type, indicating the result. + */ + @CalledByNative + public static void showLoudClapperDialogResultIcon( + WindowAndroid window, @ContentSetting int result) { + PermissionDialogController.getInstance() + .notifyObservers(window, new int[] {ContentSettingsType.NOTIFICATIONS}, result); + } + + /** + * Notifies observers of a permission result. + * + * @param window The {@link WindowAndroid} for the prompt that just finished. + * @param permissions An array of ContentSettingsType, indicating the permissions. + * @param result A ContentSetting type, indicating the result. + */ + public void notifyObservers( + WindowAndroid window, + @ContentSettingsType.EnumType int[] permissions, + @ContentSetting int result) { + for (Observer obs : mObservers) { + obs.onDialogResult(window, permissions, result); + } + } + + /** * Notifies observers that a quiet permission icon should be shown. * * @param window The {@link WindowAndroid} where the icon should be shown.
diff --git a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionUtil.java b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionUtil.java index 52ca429..14d5386 100644 --- a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionUtil.java +++ b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionUtil.java
@@ -268,6 +268,76 @@ } /** + * Handles the "Allow" action from a permission prompt (e.g. Message UI). Requests Android + * permission if needed, then resolves the site permission. + * + * @param window The WindowAndroid. + * @param webContents The WebContents. + * @param contentSettingsType The ContentSettingsType. + */ + @CalledByNative + public static void handlePermissionPromptAllow( + WindowAndroid window, + WebContents webContents, + @ContentSettingsType.EnumType int contentSettingsType) { + requestAndResolveNotificationsPermissionRequest( + window, + webContents, + () -> { + boolean granted = window.hasPermission(Manifest.permission.POST_NOTIFICATIONS); + PermissionDialogController.showLoudClapperDialogResultIcon( + window, granted ? ContentSetting.ALLOW : ContentSetting.BLOCK); + }); + } + + /** + * If necessary, requests a Android OS level notification permission and resolves the pending + * request based on the result. + * + * <p>This is used by the Clapper Loud code in the "Subscribe" button (PageInfo). If the Android + * permission was not granted on OS level, this method will asynchronously request the OS level + * permission using a dialog and run the callback when the user has made a decision. In case the + * OS level permission was already granted before, it accepts the notifications permission. + * + * @param window The WindowAndroid. + * @param webContents The WebContents. + * @param onResolved Callback runnable executed when the OS level permission is resolved + * (granted or denied). + */ + public static void requestAndResolveNotificationsPermissionRequest( + WindowAndroid window, WebContents webContents, Runnable onResolved) { + // Either returns directly in case the OS level notifications permission was already + // granted or asks for it asynchronously before granting/denying the request. + boolean requestSent = + AndroidPermissionRequester.requestAndroidPermissions( + window, + new int[] {ContentSettingsType.NOTIFICATIONS}, + new AndroidPermissionRequester.RequestDelegate() { + @Override + public void onAndroidPermissionAccepted() { + PermissionUtilJni.get() + .resolveNotificationsPermissionRequest( + webContents, ContentSetting.ALLOW); + onResolved.run(); + } + + @Override + public void onAndroidPermissionCanceled() { + PermissionUtilJni.get() + .dismissNotificationsPermissionRequest(webContents); + onResolved.run(); + } + }); + // The OS level permission for notifications was already granted; therefore, the + // permission request can be allowed. + if (!requestSent) { + PermissionUtilJni.get() + .resolveNotificationsPermissionRequest(webContents, ContentSetting.ALLOW); + onResolved.run(); + } + } + + /** * Grants a notifications permission if it is requested. * * <p>This method is called when the user clicks on the "Subscribe" button in the notifications
diff --git a/components/permissions/android/permission_prompt/permission_dialog_controller.cc b/components/permissions/android/permission_prompt/permission_dialog_controller.cc index 5f2885d..82d5c8c 100644 --- a/components/permissions/android/permission_prompt/permission_dialog_controller.cc +++ b/components/permissions/android/permission_prompt/permission_dialog_controller.cc
@@ -31,6 +31,15 @@ window); } +// static +void PermissionDialogController::ShowLoudClapperDialogResultIcon( + JNIEnv* env, + const base::android::JavaRef<jobject>& window, + int content_setting) { + Java_PermissionDialogController_showLoudClapperDialogResultIcon( + env, window, content_setting); +} + } // namespace permissions DEFINE_JNI(PermissionDialogController)
diff --git a/components/permissions/android/permission_prompt/permission_dialog_controller.h b/components/permissions/android/permission_prompt/permission_dialog_controller.h index 8f72cf3..eae8445 100644 --- a/components/permissions/android/permission_prompt/permission_dialog_controller.h +++ b/components/permissions/android/permission_prompt/permission_dialog_controller.h
@@ -26,6 +26,11 @@ static void DismissPermissionClapperQuietIcon( JNIEnv* env, const base::android::JavaRef<jobject>& window); + + static void ShowLoudClapperDialogResultIcon( + JNIEnv* env, + const base::android::JavaRef<jobject>& window, + int content_setting); }; } // namespace permissions
diff --git a/components/resources/BUILD.gn b/components/resources/BUILD.gn index 3b42c35..abb1244e 100644 --- a/components/resources/BUILD.gn +++ b/components/resources/BUILD.gn
@@ -86,6 +86,7 @@ deps = [ ":about_credits", + "//components/dom_distiller/core:dom_distiller_viewer_js", "//components/neterror/resources:bundle_js", "//components/security_interstitials/content/resources:build_ts", "//components/security_interstitials/core/browser/resources:bundle_js",
diff --git a/components/resources/dom_distiller_resources.grdp b/components/resources/dom_distiller_resources.grdp index 18cf341..6eccb97d7 100644 --- a/components/resources/dom_distiller_resources.grdp +++ b/components/resources/dom_distiller_resources.grdp
@@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <grit-part> <include name="IDR_DOM_DISTILLER_VIEWER_HTML" file="../dom_distiller/core/html/dom_distiller_viewer.html" type="BINDATA" preprocess="true"/> - <include name="IDR_DOM_DISTILLER_VIEWER_JS" file="../dom_distiller/core/javascript/dom_distiller_viewer.js" type="BINDATA" /> + <include name="IDR_DOM_DISTILLER_VIEWER_JS" file="${root_gen_dir}/components/dom_distiller/core/javascript/dom_distiller_viewer.js" use_base_dir="false" type="BINDATA" /> <include name="IDR_DISTILLER_JS" file="../dom_distiller/core/javascript/domdistiller.js" flattenhtml="true" type="BINDATA" /> <include name="IDR_DISTILLER_CSS" file="../dom_distiller/core/css/distilledpage.css" type="BINDATA" preprocess="true"/> <include name="IDR_DISTILLER_COMMON_CSS" file="../dom_distiller/core/css/distilledpage_common.css" type="BINDATA" preprocess="true"/>
diff --git a/components/search/ntp_features.cc b/components/search/ntp_features.cc index ff108ea..d6a22ec 100644 --- a/components/search/ntp_features.cc +++ b/components/search/ntp_features.cc
@@ -52,12 +52,6 @@ "NtpRealboxCr23Theming", base::FEATURE_DISABLED_BY_DEFAULT); -// If enabled, the NTP "realbox" will have same border/drop shadow in hover -// state as searchbox. -BASE_FEATURE(kRealboxMatchSearchboxTheme, - "NtpRealboxMatchSearchboxTheme", - base::FEATURE_DISABLED_BY_DEFAULT); - // If enabled, the real search box ("realbox") on the New Tab page will show a // Google (g) icon instead of the typical magnifying glass (aka loupe). BASE_FEATURE(kRealboxUseGoogleGIcon,
diff --git a/components/search/ntp_features.h b/components/search/ntp_features.h index d8498a8..d84548e 100644 --- a/components/search/ntp_features.h +++ b/components/search/ntp_features.h
@@ -27,7 +27,6 @@ BASE_DECLARE_FEATURE(kCustomizeChromeWallpaperSearchInspirationCard); BASE_DECLARE_FEATURE(kRealboxCr23Theming); BASE_DECLARE_FEATURE(kRealboxMatchOmniboxTheme); -BASE_DECLARE_FEATURE(kRealboxMatchSearchboxTheme); BASE_DECLARE_FEATURE(kRealboxUseGoogleGIcon); BASE_DECLARE_FEATURE(kNtpAlphaBackgroundCollections); BASE_DECLARE_FEATURE(kNtpBackgroundImageErrorDetection);
diff --git a/components/signin/ios/browser/BUILD.gn b/components/signin/ios/browser/BUILD.gn index 8c2cabf0..8b23806 100644 --- a/components/signin/ios/browser/BUILD.gn +++ b/components/signin/ios/browser/BUILD.gn
@@ -32,6 +32,20 @@ ] } +source_set("signin_enabled_datasource") { + sources = [ "signin_enabled_datasource.h" ] + deps = [] +} + +source_set("fake_signin_enabled_datasource") { + testonly = true + sources = [ + "fake_signin_enabled_datasource.cc", + "fake_signin_enabled_datasource.h", + ] + deps = [ ":signin_enabled_datasource" ] +} + source_set("features") { sources = [ "features.cc",
diff --git a/components/signin/ios/browser/account_consistency_service.mm b/components/signin/ios/browser/account_consistency_service.mm index 74becae..bb8fb90 100644 --- a/components/signin/ios/browser/account_consistency_service.mm +++ b/components/signin/ios/browser/account_consistency_service.mm
@@ -2,37 +2,37 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "components/signin/ios/browser/account_consistency_service.h" +#import "components/signin/ios/browser/account_consistency_service.h" #import <WebKit/WebKit.h> #import "base/apple/foundation_util.h" -#include "base/command_line.h" -#include "base/functional/bind.h" -#include "base/logging.h" +#import "base/command_line.h" +#import "base/functional/bind.h" +#import "base/logging.h" #import "base/memory/raw_ptr.h" -#include "base/metrics/histogram_functions.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/string_util.h" -#include "base/strings/sys_string_conversions.h" -#include "components/google/core/common/google_util.h" -#include "components/signin/core/browser/account_reconcilor.h" -#include "components/signin/core/browser/chrome_connected_header_helper.h" -#include "components/signin/core/browser/signin_header_helper.h" -#include "components/signin/ios/browser/features.h" -#include "components/signin/public/identity_manager/accounts_cookie_mutator.h" -#include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h" -#include "google_apis/gaia/gaia_constants.h" -#include "google_apis/gaia/gaia_urls.h" -#include "ios/web/common/web_view_creation_util.h" -#include "ios/web/public/browser_state.h" +#import "base/metrics/histogram_functions.h" +#import "base/strings/string_number_conversions.h" +#import "base/strings/string_util.h" +#import "base/strings/sys_string_conversions.h" +#import "components/google/core/common/google_util.h" +#import "components/signin/core/browser/account_reconcilor.h" +#import "components/signin/core/browser/chrome_connected_header_helper.h" +#import "components/signin/core/browser/signin_header_helper.h" +#import "components/signin/ios/browser/features.h" +#import "components/signin/public/identity_manager/accounts_cookie_mutator.h" +#import "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h" +#import "google_apis/gaia/gaia_constants.h" +#import "google_apis/gaia/gaia_urls.h" +#import "ios/web/common/web_view_creation_util.h" +#import "ios/web/public/browser_state.h" #import "ios/web/public/navigation/web_state_policy_decider.h" #import "ios/web/public/web_state.h" -#include "ios/web/public/web_state_observer.h" -#include "net/base/apple/url_conversions.h" -#include "net/base/registry_controlled_domains/registry_controlled_domain.h" -#include "net/cookies/canonical_cookie.h" -#include "url/gurl.h" +#import "ios/web/public/web_state_observer.h" +#import "net/base/apple/url_conversions.h" +#import "net/base/registry_controlled_domains/registry_controlled_domain.h" +#import "net/cookies/canonical_cookie.h" +#import "url/gurl.h" namespace { @@ -313,14 +313,12 @@ HandleAddAccountRequest(GURL url, const std::string& email, BOOL has_cookie_changed) { - if (!has_cookie_changed) { + if (!has_cookie_changed && delegate_ && delegate_->SigninEnabled()) { // If the cookies on the device did not need to be updated then the user // is not in an inconsistent state (where the identities on the device // are different than those on the web). Fallback to asking the user to // add an account. - if (delegate_) { - delegate_->OnAddAccount(url, email); - } + delegate_->OnAddAccount(url, email); return; } web_state_->OpenURL(web::WebState::OpenURLParams(
diff --git a/components/signin/ios/browser/account_consistency_service_unittest.mm b/components/signin/ios/browser/account_consistency_service_unittest.mm index 6276850a..ff40c6f 100644 --- a/components/signin/ios/browser/account_consistency_service_unittest.mm +++ b/components/signin/ios/browser/account_consistency_service_unittest.mm
@@ -115,6 +115,7 @@ show_promo_call_count_++; } void OnGoIncognito(const GURL& url) override { go_incognito_call_count_++; } + bool SigninEnabled() const override { return true; } int total_call_count() { return restore_cookies_call_count_ + manage_accounts_call_count_ +
diff --git a/components/signin/ios/browser/fake_signin_enabled_datasource.cc b/components/signin/ios/browser/fake_signin_enabled_datasource.cc new file mode 100644 index 0000000..7f7b0ca --- /dev/null +++ b/components/signin/ios/browser/fake_signin_enabled_datasource.cc
@@ -0,0 +1,13 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/signin/ios/browser/fake_signin_enabled_datasource.h" + +namespace signin { + +bool FakeSigninEnabledDataSource::SigninEnabled() const { + return true; +} + +} // namespace signin
diff --git a/components/signin/ios/browser/fake_signin_enabled_datasource.h b/components/signin/ios/browser/fake_signin_enabled_datasource.h new file mode 100644 index 0000000..154056c --- /dev/null +++ b/components/signin/ios/browser/fake_signin_enabled_datasource.h
@@ -0,0 +1,19 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SIGNIN_IOS_BROWSER_FAKE_SIGNIN_ENABLED_DATASOURCE_H_ +#define COMPONENTS_SIGNIN_IOS_BROWSER_FAKE_SIGNIN_ENABLED_DATASOURCE_H_ + +#import "components/signin/ios/browser/signin_enabled_datasource.h" + +namespace signin { + +// A class that states that signin is enabled. +class FakeSigninEnabledDataSource : public signin::SigninEnabledDataSource { + public: + bool SigninEnabled() const final; +}; + +} // namespace signin +#endif // COMPONENTS_SIGNIN_IOS_BROWSER_FAKE_SIGNIN_ENABLED_DATASOURCE_H_
diff --git a/components/signin/ios/browser/manage_accounts_delegate.h b/components/signin/ios/browser/manage_accounts_delegate.h index d588ed6..72c40b8 100644 --- a/components/signin/ios/browser/manage_accounts_delegate.h +++ b/components/signin/ios/browser/manage_accounts_delegate.h
@@ -48,6 +48,9 @@ // If it is not valid, then this delegate should open a new incognito tab. virtual void OnGoIncognito(const GURL& url) = 0; + // Whether the sign-in is not disabled. + virtual bool SigninEnabled() const = 0; + protected: ManageAccountsDelegate() = default; };
diff --git a/components/signin/ios/browser/signin_enabled_datasource.h b/components/signin/ios/browser/signin_enabled_datasource.h new file mode 100644 index 0000000..16dd7a6 --- /dev/null +++ b/components/signin/ios/browser/signin_enabled_datasource.h
@@ -0,0 +1,19 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SIGNIN_IOS_BROWSER_SIGNIN_ENABLED_DATASOURCE_H_ +#define COMPONENTS_SIGNIN_IOS_BROWSER_SIGNIN_ENABLED_DATASOURCE_H_ + +namespace signin { + +// A class which provides the information of whether signin is enabled. +class SigninEnabledDataSource { + public: + // Whether the sign-in is not disabled. + virtual bool SigninEnabled() const = 0; +}; + +} // namespace signin + +#endif // COMPONENTS_SIGNIN_IOS_BROWSER_SIGNIN_ENABLED_DATASOURCE_H_
diff --git a/components/skills/public/skill.cc b/components/skills/public/skill.cc index 6146691..3f01925 100644 --- a/components/skills/public/skill.cc +++ b/components/skills/public/skill.cc
@@ -11,11 +11,13 @@ Skill::Skill(const std::string& id, const std::string& name, const std::string& icon, - const std::string& prompt) + const std::string& prompt, + const std::string& description) : id(id), name(name), icon(icon), - prompt(prompt) {} + prompt(prompt), + description(description) {} Skill::Skill(const Skill&) = default; Skill& Skill::operator=(const Skill&) = default;
diff --git a/components/skills/public/skill.h b/components/skills/public/skill.h index 11b67e25..7025964 100644 --- a/components/skills/public/skill.h +++ b/components/skills/public/skill.h
@@ -31,6 +31,9 @@ // The source of the skill which can be 1P or user created. sync_pb::SkillSource source = sync_pb::SkillSource::SKILL_SOURCE_USER_CREATED; + // The description of the skill. + std::string description; + // The time when the skill was created. base::Time creation_time = base::Time::Now(); @@ -41,7 +44,8 @@ Skill(const std::string& id, const std::string& name, const std::string& icon, - const std::string& prompt); + const std::string& prompt, + const std::string& description = ""); Skill(const Skill&); Skill& operator=(const Skill&); Skill(Skill&&);
diff --git a/components/skills/public/skill.mojom b/components/skills/public/skill.mojom index 02f5ca4..355f9960 100644 --- a/components/skills/public/skill.mojom +++ b/components/skills/public/skill.mojom
@@ -37,6 +37,9 @@ // The source of the skill which can be 1P or user created. SkillSource source; + // The description of the skill. + string description; + // The Time when the skill was created. mojo_base.mojom.Time creation_time;
diff --git a/components/skills/public/skills_mojom_traits.cc b/components/skills/public/skills_mojom_traits.cc index 71901bd..1f49b712e 100644 --- a/components/skills/public/skills_mojom_traits.cc +++ b/components/skills/public/skills_mojom_traits.cc
@@ -15,6 +15,7 @@ return data.ReadId(&out->id) && data.ReadName(&out->name) && data.ReadIcon(&out->icon) && data.ReadPrompt(&out->prompt) && data.ReadSource(&out->source) && + data.ReadDescription(&out->description) && data.ReadCreationTime(&out->creation_time) && data.ReadLastUpdateTime(&out->last_update_time); }
diff --git a/components/skills/public/skills_mojom_traits.h b/components/skills/public/skills_mojom_traits.h index 1315f450..3394a54 100644 --- a/components/skills/public/skills_mojom_traits.h +++ b/components/skills/public/skills_mojom_traits.h
@@ -59,6 +59,9 @@ static sync_pb::SkillSource source(const skills::Skill& skill) { return skill.source; } + static const std::string& description(const skills::Skill& skill) { + return skill.description; + } static base::Time creation_time(const skills::Skill& skill) { return skill.creation_time; }
diff --git a/components/supervised_user/core/browser/BUILD.gn b/components/supervised_user/core/browser/BUILD.gn index d14219fa..be60888 100644 --- a/components/supervised_user/core/browser/BUILD.gn +++ b/components/supervised_user/core/browser/BUILD.gn
@@ -229,6 +229,11 @@ "supervised_user_service_unittest.cc", "supervised_user_utils_unittest.cc", ] + + if (is_android) { + sources += [ "supervised_user_url_filtering_service_android_unittest.cc" ] + } + deps = [ ":browser", ":device_parental_controls",
diff --git a/components/supervised_user/core/browser/supervised_user_url_filtering_service.cc b/components/supervised_user/core/browser/supervised_user_url_filtering_service.cc index f654b6c..92d2373 100644 --- a/components/supervised_user/core/browser/supervised_user_url_filtering_service.cc +++ b/components/supervised_user/core/browser/supervised_user_url_filtering_service.cc
@@ -41,6 +41,32 @@ .reason = supervised_user::FilteringBehaviorReason::ASYNC_CHECKER, .async_check_details = details}); } + +// Aggregates the web filter types from two delegates. The settings are rated in +// the following order and the most restrictive setting is returned: +// 1. kTryToBlockMatureSites +// 2. kCertainSites +// 3. kAllowAllSites +// 4. kDisabled +WebFilterType AggregateWebFilterType(const UrlFilteringDelegate& first, + const UrlFilteringDelegate& second) { + WebFilterType first_web_filter_type = first.GetWebFilterType(); + WebFilterType second_web_filter_type = second.GetWebFilterType(); + + if (first_web_filter_type == WebFilterType::kTryToBlockMatureSites || + second_web_filter_type == WebFilterType::kTryToBlockMatureSites) { + return WebFilterType::kTryToBlockMatureSites; + } + if (first_web_filter_type == WebFilterType::kCertainSites || + second_web_filter_type == WebFilterType::kCertainSites) { + return WebFilterType::kCertainSites; + } + if (first_web_filter_type == WebFilterType::kAllowAllSites || + second_web_filter_type == WebFilterType::kAllowAllSites) { + return WebFilterType::kAllowAllSites; + } + return WebFilterType::kDisabled; +} } // namespace // Creates a callback for safe search api that will invoke `callback` argument @@ -67,11 +93,21 @@ default; WebFilterType SupervisedUserUrlFilteringService::GetWebFilterType() const { + if (base::FeatureList::IsEnabled(kSupervisedUserUseUrlFilteringService)) { + return AggregateWebFilterType(*device_parental_controls_url_filter_, + *supervised_user_service_->GetURLFilter()); + } return supervised_user_service_->GetURLFilter()->GetWebFilterType(); } WebFilteringResult SupervisedUserUrlFilteringService::GetFilteringBehavior( const GURL& url) const { + if (base::FeatureList::IsEnabled(kSupervisedUserUseUrlFilteringService)) { + WebFilteringResult device_filtering_result = + device_parental_controls_url_filter_->GetFilteringBehavior(url); + CHECK(device_filtering_result.IsAllowed()) + << "Device filtering always passes synchronous checks."; + } return supervised_user_service_->GetURLFilter()->GetFilteringBehavior(url); }
diff --git a/components/supervised_user/core/browser/supervised_user_url_filtering_service_android_unittest.cc b/components/supervised_user/core/browser/supervised_user_url_filtering_service_android_unittest.cc new file mode 100644 index 0000000..a1ba662 --- /dev/null +++ b/components/supervised_user/core/browser/supervised_user_url_filtering_service_android_unittest.cc
@@ -0,0 +1,214 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/supervised_user/core/browser/supervised_user_url_filtering_service.h" + +#include <string> +#include <string_view> + +#include "base/feature_list.h" +#include "base/notreached.h" +#include "base/test/task_environment.h" +#include "components/supervised_user/core/browser/supervised_user_test_environment.h" +#include "components/supervised_user/core/common/features.h" +#include "components/supervised_user/test_support/features.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace supervised_user { +namespace { + +class SupervisedUserUrlFilteringServiceTestBase : public testing::Test { + protected: + void TearDown() override { test_environment_.Shutdown(); } + + SupervisedUserTestEnvironment& test_environment() { + return test_environment_; + } + + private: + base::test::TaskEnvironment task_environment_; + SupervisedUserTestEnvironment test_environment_; +}; + +struct WebFilterTypeTestParams { + std::string test_name; + WebFilterType family_link; + WebFilterType device; + WebFilterType result; + // Special exceptions for few cases that have different behavior in the MVP + // implementation of the URL filtering service (which is ignoring device + // settings in favor of family link settings). + std::optional<WebFilterType> mvp_result; +}; + +class SupervisedUserUrlFilteringServiceWebFilterTypeAndroidTest + : public WithFeatureOverrideAndParamInterface<WebFilterTypeTestParams>, + public SupervisedUserUrlFilteringServiceTestBase { + protected: + SupervisedUserUrlFilteringServiceWebFilterTypeAndroidTest() + : WithFeatureOverrideAndParamInterface( + kSupervisedUserUseUrlFilteringService) {} +}; + +TEST_P(SupervisedUserUrlFilteringServiceWebFilterTypeAndroidTest, + WebFilterTypeTest) { + if (GetTestCase().family_link != WebFilterType::kDisabled) { + EnableParentalControls(*test_environment().pref_service()); + test_environment().SetWebFilterType(GetTestCase().family_link); + } + + AndroidParentalControls& parental_controls = + test_environment().device_parental_controls(); + switch (GetTestCase().device) { + case WebFilterType::kTryToBlockMatureSites: + parental_controls.SetSearchContentFiltersEnabledForTesting(false); + parental_controls.SetBrowserContentFiltersEnabledForTesting(true); + break; + case WebFilterType::kAllowAllSites: + parental_controls.SetSearchContentFiltersEnabledForTesting(true); + parental_controls.SetBrowserContentFiltersEnabledForTesting(false); + break; + case WebFilterType::kDisabled: + parental_controls.SetSearchContentFiltersEnabledForTesting(false); + parental_controls.SetBrowserContentFiltersEnabledForTesting(false); + break; + default: + NOTREACHED() << "Not supported by Android parental controls."; + } + + if (!IsFeatureEnabled() && + GetTestCase().mvp_result.has_value()) { + // MVP implementation (pre-feature)didn't allow for specific combinations of + // settings and ignored the device settings in favor of the family link + // settings. + EXPECT_EQ(*GetTestCase().mvp_result, + test_environment().url_filtering_service()->GetWebFilterType()); + } else { + EXPECT_EQ(GetTestCase().result, + test_environment().url_filtering_service()->GetWebFilterType()); + } +} + +const WebFilterTypeTestParams kWebFilterTypeTestParams[] = { + // If any of the settings is kTryToBlockMatureSites, the result is + // kTryToBlockMatureSites. + {.test_name = "TryToBlockMatureSites_1", + .family_link = WebFilterType::kTryToBlockMatureSites, + .device = WebFilterType::kTryToBlockMatureSites, + .result = WebFilterType::kTryToBlockMatureSites}, + {.test_name = "TryToBlockMatureSites_2", + .family_link = WebFilterType::kTryToBlockMatureSites, + .device = WebFilterType::kAllowAllSites, + .result = WebFilterType::kTryToBlockMatureSites}, + {.test_name = "TryToBlockMatureSites_3", + .family_link = WebFilterType::kTryToBlockMatureSites, + .device = WebFilterType::kDisabled, + .result = WebFilterType::kTryToBlockMatureSites}, + {.test_name = "TryToBlockMatureSites_4", + .family_link = WebFilterType::kCertainSites, + .device = WebFilterType::kTryToBlockMatureSites, + .result = WebFilterType::kTryToBlockMatureSites, + .mvp_result = WebFilterType::kCertainSites}, + {.test_name = "TryToBlockMatureSites_5", + .family_link = WebFilterType::kAllowAllSites, + .device = WebFilterType::kTryToBlockMatureSites, + .result = WebFilterType::kTryToBlockMatureSites, + .mvp_result = WebFilterType::kAllowAllSites}, + {.test_name = "TryToBlockMatureSites_6", + .family_link = WebFilterType::kDisabled, + .device = WebFilterType::kTryToBlockMatureSites, + .result = WebFilterType::kTryToBlockMatureSites}, + + // Then if family link is kCertainSites, the result is kCertainSites. + {.test_name = "CertainSites_1", + .family_link = WebFilterType::kCertainSites, + .device = WebFilterType::kAllowAllSites, + .result = WebFilterType::kCertainSites}, + {.test_name = "CertainSites_2", + .family_link = WebFilterType::kCertainSites, + .device = WebFilterType::kDisabled, + .result = WebFilterType::kCertainSites}, + + // Then if any setting is kAllowAllSites, the result is kAllowAllSites. + {.test_name = "AllowAllSites_1", + .family_link = WebFilterType::kAllowAllSites, + .device = WebFilterType::kAllowAllSites, + .result = WebFilterType::kAllowAllSites}, + {.test_name = "AllowAllSites_2", + .family_link = WebFilterType::kAllowAllSites, + .device = WebFilterType::kDisabled, + .result = WebFilterType::kAllowAllSites}, + {.test_name = "AllowAllSites_3", + .family_link = WebFilterType::kDisabled, + .device = WebFilterType::kAllowAllSites, + .result = WebFilterType::kAllowAllSites, + .mvp_result = WebFilterType::kDisabled}, + + // Then both disabled, the result is kDisabled. + {.test_name = "Disabled_1", + .family_link = WebFilterType::kDisabled, + .device = WebFilterType::kDisabled, + .result = WebFilterType::kDisabled}, +}; + +INSTANTIATE_TEST_SUITE_P( + , + SupervisedUserUrlFilteringServiceWebFilterTypeAndroidTest, + testing::Combine(testing::Bool(), + testing::ValuesIn(kWebFilterTypeTestParams)), + [](const testing::TestParamInfo< + SupervisedUserUrlFilteringServiceWebFilterTypeAndroidTest::ParamType>& + info) { + return std::string(std::get<0>(info.param) ? "With" : "Without") + + std::string(kSupervisedUserUseUrlFilteringService.name) + "_" + + std::get<1>(info.param).test_name; + }); + +// This suite simply proves that the sync filtering behavior is not affected +// by the device parental controls. To see comprehensive suite for family link +// sync filtering behavior, see family link specific suites +// (family_link*unittest.cc). +class SupervisedUserUrlFilteringServiceSyncBehaviorAndroidTest + : public ::base::test::WithFeatureOverride, + public SupervisedUserUrlFilteringServiceTestBase { + protected: + SupervisedUserUrlFilteringServiceSyncBehaviorAndroidTest() + : ::base::test::WithFeatureOverride( + kSupervisedUserUseUrlFilteringService) {} + WebFilteringResult GetSyncFilteringBehavior(std::string_view url) { + return test_environment().url_filtering_service()->GetFilteringBehavior( + GURL(url)); + } +}; + +TEST_P(SupervisedUserUrlFilteringServiceSyncBehaviorAndroidTest, + EnabledDeviceParentalControls_DontAffectSyncBehavior) { + EnableParentalControls(*test_environment().pref_service()); + test_environment().SetWebFilterType(WebFilterType::kCertainSites); + test_environment().SetManualFilterForHost("http://google.com", + /*allowlist=*/true); + + EXPECT_TRUE(GetSyncFilteringBehavior("http://google.com").IsAllowed()); + EXPECT_FALSE(GetSyncFilteringBehavior("http://example.com").IsAllowed()); + + AndroidParentalControls& parental_controls = + test_environment().device_parental_controls(); + // Set device parental controls to allow all sites. + parental_controls.SetSearchContentFiltersEnabledForTesting(true); + EXPECT_TRUE(GetSyncFilteringBehavior("http://google.com").IsAllowed()); + EXPECT_FALSE(GetSyncFilteringBehavior("http://example.com").IsAllowed()); + + // Set device parental controls to use safe sites. + parental_controls.SetBrowserContentFiltersEnabledForTesting(true); + EXPECT_TRUE(GetSyncFilteringBehavior("http://google.com").IsAllowed()); + EXPECT_FALSE(GetSyncFilteringBehavior("http://example.com").IsAllowed()); +} + +INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE( + SupervisedUserUrlFilteringServiceSyncBehaviorAndroidTest); + +} // namespace +} // namespace supervised_user
diff --git a/components/sync_preferences/cross_device_pref_tracker/android/java/src/org/chromium/components/sync_preferences/cross_device_pref_tracker/CrossDevicePrefTracker.java b/components/sync_preferences/cross_device_pref_tracker/android/java/src/org/chromium/components/sync_preferences/cross_device_pref_tracker/CrossDevicePrefTracker.java index ede5679..aeea88c 100644 --- a/components/sync_preferences/cross_device_pref_tracker/android/java/src/org/chromium/components/sync_preferences/cross_device_pref_tracker/CrossDevicePrefTracker.java +++ b/components/sync_preferences/cross_device_pref_tracker/android/java/src/org/chromium/components/sync_preferences/cross_device_pref_tracker/CrossDevicePrefTracker.java
@@ -73,6 +73,12 @@ mObservers.removeObserver(observer); } + /** Returns the current status of the service ({@link ServiceStatus}). */ + public @ServiceStatus int getServiceStatus() { + if (mNativePtr == 0) return ServiceStatus.DEVICE_INFO_TRACKER_MISSING; + return CrossDevicePrefTrackerJni.get().getServiceStatus(mNativePtr); + } + @CalledByNative private void onRemotePrefChanged( @JniType("std::string") String prefName, @@ -134,6 +140,9 @@ @NativeMethods interface Natives { + @ServiceStatus + int getServiceStatus(long nativeCrossDevicePrefTracker); + TimestampedPrefValue[] getValues( long nativeCrossDevicePrefTracker, String prefName,
diff --git a/components/sync_preferences/cross_device_pref_tracker/cross_device_pref_tracker.h b/components/sync_preferences/cross_device_pref_tracker/cross_device_pref_tracker.h index eda4f49..8d7cded 100644 --- a/components/sync_preferences/cross_device_pref_tracker/cross_device_pref_tracker.h +++ b/components/sync_preferences/cross_device_pref_tracker/cross_device_pref_tracker.h
@@ -125,6 +125,8 @@ #if BUILDFLAG(IS_ANDROID) // Return the Java object that allows access to the CrossDevicePrefTracker. virtual base::android::ScopedJavaLocalRef<jobject> GetJavaObject() = 0; + // Returns the current status of the service. + virtual int GetServiceStatus(JNIEnv* env) const = 0; // Java versions of query methods. // `pref_name` can be either the tracked pref name or the cross-device pref // name.
diff --git a/components/sync_preferences/cross_device_pref_tracker/cross_device_pref_tracker_impl.cc b/components/sync_preferences/cross_device_pref_tracker/cross_device_pref_tracker_impl.cc index 8ecbe4ef..a95fa22 100644 --- a/components/sync_preferences/cross_device_pref_tracker/cross_device_pref_tracker_impl.cc +++ b/components/sync_preferences/cross_device_pref_tracker/cross_device_pref_tracker_impl.cc
@@ -1156,6 +1156,10 @@ } // Java versions of query methods. +int CrossDevicePrefTrackerImpl::GetServiceStatus(JNIEnv* env) const { + return static_cast<int>(GetServiceStatus()); +} + ScopedJavaLocalRef<jobjectArray> CrossDevicePrefTrackerImpl::GetValues( JNIEnv* env, const base::android::JavaRef<jstring>& pref_name,
diff --git a/components/sync_preferences/cross_device_pref_tracker/cross_device_pref_tracker_impl.h b/components/sync_preferences/cross_device_pref_tracker/cross_device_pref_tracker_impl.h index aaee4b3..72a503c 100644 --- a/components/sync_preferences/cross_device_pref_tracker/cross_device_pref_tracker_impl.h +++ b/components/sync_preferences/cross_device_pref_tracker/cross_device_pref_tracker_impl.h
@@ -80,6 +80,7 @@ #if BUILDFLAG(IS_ANDROID) // Return the java object that allows access to the SyncService. base::android::ScopedJavaLocalRef<jobject> GetJavaObject() override; + int GetServiceStatus(JNIEnv* env) const override; // Java versions of query methods. base::android::ScopedJavaLocalRef<jobjectArray> GetValues( JNIEnv* env,
diff --git a/components/sync_preferences/pref_model_associator.cc b/components/sync_preferences/pref_model_associator.cc index ed0f0498..64f5d8c 100644 --- a/components/sync_preferences/pref_model_associator.cc +++ b/components/sync_preferences/pref_model_associator.cc
@@ -214,7 +214,7 @@ } sync_changes->emplace_back(FROM_HERE, syncer::SyncChange::ACTION_ADD, sync_data); - synced_preferences_.insert(std::string(pref_name)); + synced_preferences_.emplace(pref_name); } // Else: This pref has neither a sync value nor a user-controlled value, so // ignore it for now. If it gets a new user-controlled value in the future, @@ -254,12 +254,13 @@ const sync_pb::PreferenceSpecifics& preference = GetSpecifics(sync_data); std::string sync_pref_name = preference.name(); - if (!remaining_preferences.contains(sync_pref_name)) { + auto it = remaining_preferences.find(sync_pref_name); + if (it == remaining_preferences.end()) { // We're not syncing this preference locally, ignore the sync data. continue; } - remaining_preferences.erase(sync_pref_name); + remaining_preferences.erase(it); InitPrefAndAssociate(sync_data, sync_pref_name, &new_changes); NotifyStartedSyncing(sync_pref_name); } @@ -444,7 +445,7 @@ bool PrefModelAssociator::IsPrefSyncedForTesting( const std::string& name) const { - return synced_preferences_.find(name) != synced_preferences_.end(); + return synced_preferences_.contains(name); } void PrefModelAssociator::RegisterPref(std::string_view name) { @@ -458,7 +459,7 @@ << " has not been added to syncable prefs allowlist, or has incorrect " "data."; - registered_preferences_.insert(std::string(name)); + registered_preferences_.emplace(name); } bool PrefModelAssociator::IsPrefRegistered(std::string_view name) const {
diff --git a/components/sync_preferences/pref_model_associator.h b/components/sync_preferences/pref_model_associator.h index cd022c7..f130729 100644 --- a/components/sync_preferences/pref_model_associator.h +++ b/components/sync_preferences/pref_model_associator.h
@@ -10,7 +10,6 @@ #include <set> #include <string> #include <string_view> -#include <unordered_map> #include "base/memory/raw_ptr.h" #include "base/memory/scoped_refptr.h" @@ -18,13 +17,14 @@ #include "base/observer_list.h" #include "base/sequence_checker.h" #include "base/values.h" -#include "components/prefs/transparent_unordered_string_map.h" #include "components/prefs/writeable_pref_store.h" #include "components/sync/base/data_type.h" #include "components/sync/model/sync_data.h" #include "components/sync/model/syncable_service.h" #include "components/sync_preferences/pref_model_associator_client.h" #include "components/sync_preferences/synced_pref_observer.h" +#include "third_party/abseil-cpp/absl/container/flat_hash_map.h" +#include "third_party/abseil-cpp/absl/container/flat_hash_set.h" namespace sync_pb { class EntitySpecifics; @@ -195,7 +195,7 @@ // to eventually match `registered_preferences_` as more preferences are // synced. It determines whether a preference change should update an existing // sync node or create a new sync node. - std::set<std::string, std::less<>> synced_preferences_; + absl::flat_hash_set<std::string> synced_preferences_; // Sync's handler for outgoing changes. Non-null between // MergeDataAndStartSyncing() and StopSyncing(). @@ -206,7 +206,7 @@ // from sync. using SyncedPrefObserverList = base::ObserverList<SyncedPrefObserver>::Unchecked; - TransparentUnorderedStringMap<std::unique_ptr<SyncedPrefObserverList>> + absl::flat_hash_map<std::string, std::unique_ptr<SyncedPrefObserverList>> synced_pref_observers_; SEQUENCE_CHECKER(sequence_checker_);
diff --git a/components/sync_preferences/synced_set_up/utils_unittest.cc b/components/sync_preferences/synced_set_up/utils_unittest.cc index 691c6e5..acda482 100644 --- a/components/sync_preferences/synced_set_up/utils_unittest.cc +++ b/components/sync_preferences/synced_set_up/utils_unittest.cc
@@ -76,6 +76,10 @@ return base::android::ScopedJavaLocalRef<jobject>(); } + int GetServiceStatus(JNIEnv* env) const override { + return static_cast<int>(GetServiceStatus()); + } + base::android::ScopedJavaLocalRef<jobjectArray> GetValues( JNIEnv* env, const base::android::JavaRef<jstring>& pref_name,
diff --git a/components/test/data/dom_distiller/font_size_slider_tester.js b/components/test/data/dom_distiller/font_size_slider_tester.js index 1b0571e..42c0645 100644 --- a/components/test/data/dom_distiller/font_size_slider_tester.js +++ b/components/test/data/dom_distiller/font_size_slider_tester.js
@@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +/** + * @fileoverview Test suite for the font size slider. + */ + suite('FontSizeSlider', function() { suiteSetup(function() { // The UI components are normally lazily initialized. We need to initialize
diff --git a/components/test/data/dom_distiller/image_classifier_tester.js b/components/test/data/dom_distiller/image_classifier_tester.js index a1b217d..1f61691 100644 --- a/components/test/data/dom_distiller/image_classifier_tester.js +++ b/components/test/data/dom_distiller/image_classifier_tester.js
@@ -6,7 +6,7 @@ /** * @fileoverview Test suite for the ImageClassifier class in - * dom_distiller_viewer.js. + * image_classifier.js. */ suite('ImageClassifier', function() {
diff --git a/components/test/data/dom_distiller/list_classifier_tester.js b/components/test/data/dom_distiller/list_classifier_tester.js index c479681..e043cfe 100644 --- a/components/test/data/dom_distiller/list_classifier_tester.js +++ b/components/test/data/dom_distiller/list_classifier_tester.js
@@ -4,7 +4,7 @@ /** * @fileoverview Test suite for the ListClassifier class in - * dom_distiller_viewer.js. + * list_classifier.js. */ suite('ListClassifier', function() {
diff --git a/components/test/data/dom_distiller/pinch_tester.js b/components/test/data/dom_distiller/pinch_tester.js index b5160c92..9ef94c7 100644 --- a/components/test/data/dom_distiller/pinch_tester.js +++ b/components/test/data/dom_distiller/pinch_tester.js
@@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +/** + * @fileoverview Test suite for the Pincher class in pinch_handler.js. + */ + // TODO(crbug.com/40108835): Consider replacing this class with // dispatched touch events. class Touch {
diff --git a/components/viz/service/display/overlay_processor_ozone_unittest.cc b/components/viz/service/display/overlay_processor_ozone_unittest.cc index 1556bcb..8df73d4 100644 --- a/components/viz/service/display/overlay_processor_ozone_unittest.cc +++ b/components/viz/service/display/overlay_processor_ozone_unittest.cc
@@ -64,7 +64,7 @@ uint32_t GetDmaBufPitch(size_t plane) const override { return 0; } size_t GetDmaBufOffset(size_t plane) const override { return 0; } size_t GetDmaBufPlaneSize(size_t plane) const override { return 0; } - uint64_t GetBufferFormatModifier() const override { return 0; } + uint64_t GetFormatModifier() const override { return 0; } SharedImageFormat GetSharedImageFormat() const override { return format_; } size_t GetNumberOfPlanes() const override { return 0; } bool SupportsZeroCopyWebGPUImport() const override { return false; }
diff --git a/components/webauthn/ios/ios_passkey_client.h b/components/webauthn/ios/ios_passkey_client.h index fbe4250..d8e4dc8d 100644 --- a/components/webauthn/ios/ios_passkey_client.h +++ b/components/webauthn/ios/ios_passkey_client.h
@@ -32,6 +32,9 @@ (webauthn::PasskeyWelcomeScreenAction) completion; +// Dismisses the passkey welcome screen. +- (void)dismissPasskeyWelcomeScreen; + @end namespace webauthn {
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn index 21897501..6a1fe912 100644 --- a/content/browser/BUILD.gn +++ b/content/browser/BUILD.gn
@@ -1637,7 +1637,6 @@ "preloading/prefetch/prefetch_type.cc", "preloading/prefetch/prefetch_type.h", "preloading/prefetch/prefetch_url_loader_helper.cc", - "preloading/prefetch/prefetch_url_loader_helper.h", "preloading/prefetch/prefetch_url_loader_interceptor.cc", "preloading/prefetch/prefetch_url_loader_interceptor.h", "preloading/prefetcher.cc",
diff --git a/content/browser/font_access/font_enumeration_data_source_linux.cc b/content/browser/font_access/font_enumeration_data_source_linux.cc index 3d34f6b..4dd8bf7 100644 --- a/content/browser/font_access/font_enumeration_data_source_linux.cc +++ b/content/browser/font_access/font_enumeration_data_source_linux.cc
@@ -7,7 +7,6 @@ #include <fontconfig/fontconfig.h> #include <memory> -#include <set> #include "base/check_op.h" #include "base/compiler_specific.h" @@ -18,6 +17,7 @@ #include "base/task/task_traits.h" #include "base/task/thread_pool.h" #include "base/threading/scoped_blocking_call.h" +#include "third_party/abseil-cpp/absl/container/flat_hash_set.h" #include "third_party/blink/public/common/font_access/font_enumeration_table.pb.h" namespace content { @@ -85,7 +85,7 @@ ListFonts(object_set.get()), FcFontSetDestroy); // Used to filter duplicates. - std::set<std::string> fonts_seen; + absl::flat_hash_set<std::string> fonts_seen; for (int i = 0; i < fontset->nfont; ++i) { char* postscript_name = nullptr; @@ -120,8 +120,7 @@ continue; } - auto it_and_success = fonts_seen.emplace(postscript_name); - if (!it_and_success.second) { + if (auto [it, success] = fonts_seen.emplace(postscript_name); !success) { // Skip duplicate. continue; }
diff --git a/content/browser/indexed_db/instance/cursor.cc b/content/browser/indexed_db/instance/cursor.cc index cd4d09ee..ebbab31 100644 --- a/content/browser/indexed_db/instance/cursor.cc +++ b/content/browser/indexed_db/instance/cursor.cc
@@ -109,6 +109,8 @@ if (!transaction_) { Close(); + } else { + CHECK(transaction_->IsAcceptingRequests()); } if (closed_) { const DatabaseError error(CreateCursorClosedError()); @@ -195,6 +197,8 @@ if (!transaction_) { Close(); + } else { + CHECK(transaction_->IsAcceptingRequests()); } if (closed_) { const DatabaseError error(CreateCursorClosedError()); @@ -272,6 +276,8 @@ if (!transaction_) { Close(); + } else { + CHECK(transaction_->IsAcceptingRequests()); } if (closed_) { const DatabaseError error(CreateCursorClosedError()); @@ -432,6 +438,7 @@ TRACE_EVENT_END("IndexedDB", perfetto::Track::FromPointer(this)); TRACE_EVENT0("IndexedDB", "Cursor::Close"); closed_ = true; + ptr_factory_.InvalidateWeakPtrs(); cursor_.reset(); if (transaction_) { transaction_->UnregisterOpenCursor(this);
diff --git a/content/browser/indexed_db/instance/transaction.cc b/content/browser/indexed_db/instance/transaction.cc index f24c981..300b0f79 100644 --- a/content/browser/indexed_db/instance/transaction.cc +++ b/content/browser/indexed_db/instance/transaction.cc
@@ -236,17 +236,7 @@ preemptive_task_queue_ = {}; pending_preemptive_events_ = 0; task_queue_ = {}; - - // Backing store resources (held via cursors) must be released - // before script callbacks are fired, as the script callbacks may - // release references and allow the backing store itself to be - // released, and order is critical. - CloseOpenCursors(); backing_store_transaction_.reset(); - - // Transactions must also be marked as completed before the - // front-end is notified, as the transaction completion unblocks - // operations like closing connections. locks_receiver_.locks.clear(); locks_receiver_.CancelLockRequest(); @@ -1280,6 +1270,9 @@ } else { scheduling_priority_at_last_state_change_ = std::nullopt; } + if (!IsAcceptingRequests()) { + CloseOpenCursors(); + } NotifyOfIdbInternalsRelevantChange(); }
diff --git a/content/browser/preloading/prefetch/prefetch_container_unittest.cc b/content/browser/preloading/prefetch/prefetch_container_unittest.cc index 9f2787b2..d7bd6fd 100644 --- a/content/browser/preloading/prefetch/prefetch_container_unittest.cc +++ b/content/browser/preloading/prefetch/prefetch_container_unittest.cc
@@ -492,7 +492,7 @@ EXPECT_FALSE(serving_handle.IsIsolatedCookieCopyInProgress()); - serving_handle.OnIsolatedCookieCopyStart(); + serving_handle.OnIsolatedCookieCopyStartForTesting(); EXPECT_TRUE(serving_handle.IsIsolatedCookieCopyInProgress()); @@ -502,7 +502,7 @@ EXPECT_FALSE(serving_handle.HaveDefaultContextCookiesChanged()); task_environment()->FastForwardBy(base::Milliseconds(10)); - serving_handle.OnIsolatedCookiesReadCompleteAndWriteStart(); + serving_handle.OnIsolatedCookiesReadCompleteAndWriteStartForTesting(); task_environment()->FastForwardBy(base::Milliseconds(20)); // The URL interceptor checks on the cookie copy status when trying to serve a @@ -516,7 +516,7 @@ base::BindOnce([](bool* callback_called) { *callback_called = true; }, &callback_called)); - serving_handle.OnIsolatedCookieCopyComplete(); + serving_handle.OnIsolatedCookieCopyCompleteForTesting(); EXPECT_FALSE(serving_handle.IsIsolatedCookieCopyInProgress()); EXPECT_TRUE(callback_called); @@ -555,7 +555,7 @@ EXPECT_EQ(serving_handle.GetCurrentURLToServe(), kTestUrl); EXPECT_FALSE(serving_handle.IsIsolatedCookieCopyInProgress()); - serving_handle.OnIsolatedCookieCopyStart(); + serving_handle.OnIsolatedCookieCopyStartForTesting(); EXPECT_TRUE(serving_handle.IsIsolatedCookieCopyInProgress()); // Once the cookie copy process has started, all cookie listeners are stopped. @@ -576,7 +576,7 @@ } task_environment()->FastForwardBy(base::Milliseconds(10)); - serving_handle.OnIsolatedCookiesReadCompleteAndWriteStart(); + serving_handle.OnIsolatedCookiesReadCompleteAndWriteStartForTesting(); task_environment()->FastForwardBy(base::Milliseconds(20)); // The URL interceptor checks on the cookie copy status when trying to serve a @@ -590,7 +590,7 @@ base::BindOnce([](bool* callback_called) { *callback_called = true; }, &callback_called)); - serving_handle.OnIsolatedCookieCopyComplete(); + serving_handle.OnIsolatedCookieCopyCompleteForTesting(); EXPECT_FALSE(serving_handle.IsIsolatedCookieCopyInProgress()); EXPECT_TRUE(callback_called); @@ -600,11 +600,11 @@ EXPECT_EQ(serving_handle.GetCurrentURLToServe(), kRedirectUrl1); EXPECT_FALSE(serving_handle.IsIsolatedCookieCopyInProgress()); - serving_handle.OnIsolatedCookieCopyStart(); + serving_handle.OnIsolatedCookieCopyStartForTesting(); EXPECT_TRUE(serving_handle.IsIsolatedCookieCopyInProgress()); task_environment()->FastForwardBy(base::Milliseconds(10)); - serving_handle.OnIsolatedCookiesReadCompleteAndWriteStart(); + serving_handle.OnIsolatedCookiesReadCompleteAndWriteStartForTesting(); task_environment()->FastForwardBy(base::Milliseconds(20)); EXPECT_TRUE(serving_handle.IsIsolatedCookieCopyInProgress()); @@ -616,7 +616,7 @@ base::BindOnce([](bool* callback_called) { *callback_called = true; }, &callback_called)); - serving_handle.OnIsolatedCookieCopyComplete(); + serving_handle.OnIsolatedCookieCopyCompleteForTesting(); EXPECT_FALSE(serving_handle.IsIsolatedCookieCopyInProgress()); EXPECT_TRUE(callback_called); @@ -625,11 +625,11 @@ EXPECT_EQ(serving_handle.GetCurrentURLToServe(), kRedirectUrl2); EXPECT_FALSE(serving_handle.IsIsolatedCookieCopyInProgress()); - serving_handle.OnIsolatedCookieCopyStart(); + serving_handle.OnIsolatedCookieCopyStartForTesting(); EXPECT_TRUE(serving_handle.IsIsolatedCookieCopyInProgress()); task_environment()->FastForwardBy(base::Milliseconds(10)); - serving_handle.OnIsolatedCookiesReadCompleteAndWriteStart(); + serving_handle.OnIsolatedCookiesReadCompleteAndWriteStartForTesting(); task_environment()->FastForwardBy(base::Milliseconds(20)); EXPECT_TRUE(serving_handle.IsIsolatedCookieCopyInProgress()); @@ -641,7 +641,7 @@ base::BindOnce([](bool* callback_called) { *callback_called = true; }, &callback_called)); - serving_handle.OnIsolatedCookieCopyComplete(); + serving_handle.OnIsolatedCookieCopyCompleteForTesting(); EXPECT_FALSE(serving_handle.IsIsolatedCookieCopyInProgress()); EXPECT_TRUE(callback_called);
diff --git a/content/browser/preloading/prefetch/prefetch_match_resolver.cc b/content/browser/preloading/prefetch/prefetch_match_resolver.cc index 74175f1..196226b 100644 --- a/content/browser/preloading/prefetch/prefetch_match_resolver.cc +++ b/content/browser/preloading/prefetch/prefetch_match_resolver.cc
@@ -736,9 +736,7 @@ // Basically, we can assume `PrefetchService` is available as waiting // `PrefetchContainer` is owned by it. But in unit tests, we use invalid // frame tree node id and this `prefetch_service` is not available. - if (prefetch_service_) { - prefetch_service_->CopyIsolatedCookies(serving_handle); - } + serving_handle.CopyIsolatedCookies(); } CHECK(serving_handle);
diff --git a/content/browser/preloading/prefetch/prefetch_service.cc b/content/browser/preloading/prefetch/prefetch_service.cc index 564f48f9..53b41745 100644 --- a/content/browser/preloading/prefetch/prefetch_service.cc +++ b/content/browser/preloading/prefetch/prefetch_service.cc
@@ -8,7 +8,6 @@ #include <string_view> #include "base/auto_reset.h" -#include "base/barrier_closure.h" #include "base/check_is_test.h" #include "base/check_op.h" #include "base/feature_list.h" @@ -31,7 +30,6 @@ #include "content/browser/preloading/prefetch/prefetch_request.h" #include "content/browser/preloading/prefetch/prefetch_scheduler.h" #include "content/browser/preloading/prefetch/prefetch_servable_state.h" -#include "content/browser/preloading/prefetch/prefetch_serving_handle.h" #include "content/browser/preloading/prefetch/prefetch_status.h" #include "content/browser/preloading/prefetch/prefetch_streaming_url_loader.h" #include "content/browser/preloading/preloading_attempt_impl.h" @@ -169,17 +167,6 @@ response_code); } -void RecordPrefetchProxyPrefetchMainframeCookiesToCopy( - size_t cookie_list_size) { - UMA_HISTOGRAM_COUNTS_100("PrefetchProxy.Prefetch.Mainframe.CookiesToCopy", - cookie_list_size); -} - -void CookieSetHelper(base::RepeatingClosure closure, - net::CookieAccessResult access_result) { - closure.Run(); -} - // Returns true if the prefetch is heldback, and set the holdback status // correspondingly. bool CheckAndSetPrefetchHoldbackStatus( @@ -282,12 +269,6 @@ "PrefetchProxy.Redirect.NetworkContextStateTransition", transition); } -void OnIsolatedCookieCopyComplete(PrefetchServingHandle serving_handle) { - if (serving_handle) { - serving_handle.OnIsolatedCookieCopyComplete(); - } -} - bool IsReferrerPolicySufficientlyStrict( const network::mojom::ReferrerPolicy& referrer_policy) { // https://github.com/WICG/nav-speculation/blob/main/prefetch.bs#L606 @@ -1969,62 +1950,6 @@ } } -void PrefetchService::CopyIsolatedCookies( - PrefetchServingHandle& serving_handle) { - DCHECK(serving_handle); - - // We only need to copy cookies if the prefetch used an isolated network - // context. - if (!serving_handle.IsIsolatedNetworkContextRequiredToServe()) { - return; - } - - serving_handle.OnIsolatedCookieCopyStart(); - - if (!serving_handle.GetCurrentNetworkContextToServe()) { - // Not set in unit tests. - CHECK_IS_TEST(); - return; - } - - net::CookieOptions options = net::CookieOptions::MakeAllInclusive(); - serving_handle.GetCurrentNetworkContextToServe() - ->GetCookieManager() - ->GetCookieList( - serving_handle.GetCurrentURLToServe(), options, - net::CookiePartitionKeyCollection::Todo(), - base::BindOnce(&PrefetchService::OnGotIsolatedCookiesForCopy, - weak_method_factory_.GetWeakPtr(), - serving_handle.Clone())); -} - -void PrefetchService::OnGotIsolatedCookiesForCopy( - PrefetchServingHandle serving_handle, - const net::CookieAccessResultList& cookie_list, - const net::CookieAccessResultList& excluded_cookies) { - serving_handle.OnIsolatedCookiesReadCompleteAndWriteStart(); - RecordPrefetchProxyPrefetchMainframeCookiesToCopy(cookie_list.size()); - - if (cookie_list.empty()) { - serving_handle.OnIsolatedCookieCopyComplete(); - return; - } - - const auto current_url = serving_handle.GetCurrentURLToServe(); - - base::RepeatingClosure barrier = base::BarrierClosure( - cookie_list.size(), - base::BindOnce(&OnIsolatedCookieCopyComplete, std::move(serving_handle))); - - net::CookieOptions options = net::CookieOptions::MakeAllInclusive(); - for (const net::CookieWithAccessResult& cookie : cookie_list) { - browser_context_->GetDefaultStoragePartition() - ->GetCookieManagerForBrowserProcess() - ->SetCanonicalCookie(cookie.cookie, current_url, options, - base::BindOnce(&CookieSetHelper, barrier)); - } -} - // TODO(crbug.com/406754449): Inline this function when removing the feature // flag. bool PrefetchService::IsPrefetchContainerInActiveSet(
diff --git a/content/browser/preloading/prefetch/prefetch_service.h b/content/browser/preloading/prefetch/prefetch_service.h index fbde26d..7e900bea 100644 --- a/content/browser/preloading/prefetch/prefetch_service.h +++ b/content/browser/preloading/prefetch/prefetch_service.h
@@ -106,10 +106,6 @@ virtual PrefetchOriginProber* GetPrefetchOriginProber() const; virtual void PrefetchUrl(base::WeakPtr<PrefetchContainer> prefetch_container); - // Copies any cookies in the isolated network context associated with - // |prefetch_container| to the default network context. - void CopyIsolatedCookies(PrefetchServingHandle& serving_handle); - // Adds a `PrefetchContainer` created from the `PrefetchRequest` under control // of `PrefetchService` and returns `PrefetchHandle` so that the caller can // control prefetch resources associated with this. @@ -387,14 +383,6 @@ const network::URLLoaderCompletionStatus& completion_status, const std::optional<int>& response_code) override; - // Called when the cookies from |prefetch_conatiner| are read from the - // isolated network context and are ready to be written to the default network - // context. - void OnGotIsolatedCookiesForCopy( - PrefetchServingHandle serving_handle, - const net::CookieAccessResultList& cookie_list, - const net::CookieAccessResultList& excluded_cookies); - enum class HandlePrefetchContainerResult { // No prefetch was available to be used. kNotAvailable,
diff --git a/content/browser/preloading/prefetch/prefetch_serving_handle.cc b/content/browser/preloading/prefetch/prefetch_serving_handle.cc index bbc4e90b..11b2a22 100644 --- a/content/browser/preloading/prefetch/prefetch_serving_handle.cc +++ b/content/browser/preloading/prefetch/prefetch_serving_handle.cc
@@ -4,21 +4,45 @@ #include "content/browser/preloading/prefetch/prefetch_serving_handle.h" +#include "base/barrier_closure.h" +#include "base/check_is_test.h" #include "base/metrics/histogram_macros.h" #include "base/notimplemented.h" #include "base/time/time.h" #include "content/browser/preloading/prefetch/prefetch_container.h" #include "content/browser/preloading/prefetch/prefetch_cookie_listener.h" +#include "content/browser/preloading/prefetch/prefetch_network_context.h" #include "content/browser/preloading/prefetch/prefetch_probe_result.h" +#include "content/browser/preloading/prefetch/prefetch_request.h" #include "content/browser/preloading/prefetch/prefetch_response_reader.h" #include "content/browser/preloading/prefetch/prefetch_single_redirect_hop.h" #include "content/browser/preloading/prefetch/prefetch_status.h" +#include "content/public/browser/browser_context.h" #include "content/public/browser/preloading.h" +#include "content/public/browser/storage_partition.h" #include "url/gurl.h" namespace content { namespace { +// Helper for `base::BindOnce()` + rvalue ref-qualified member method. +template <typename... UnboundArgs, + typename Method, + typename Receiver, + typename... BoundArgs> +auto BindOnceForRvalueMemberMethod(Method method, + Receiver&& receiver, + BoundArgs&&... bound_args) { + return base::BindOnce( + [](Method method, std::decay_t<Receiver> receiver, + std::decay_t<BoundArgs>... bound_args, UnboundArgs... unbound_args) { + (std::move(receiver).*method)( + std::forward<BoundArgs>(bound_args)..., + std::forward<UnboundArgs>(unbound_args)...); + }, + method, std::move(receiver), std::forward<BoundArgs>(bound_args)...); +} + PrefetchServingHandle::OnIsolatedCookieCopyStartCallbackForTesting& GetOnIsolatedCookieCopyStartCallbackForTesting() { static base::NoDestructor< @@ -45,6 +69,12 @@ base::Seconds(5), 50); } +void RecordPrefetchProxyPrefetchMainframeCookiesToCopy( + size_t cookie_list_size) { + UMA_HISTOGRAM_COUNTS_100("PrefetchProxy.Prefetch.Mainframe.CookiesToCopy", + cookie_list_size); +} + } // namespace PrefetchServingHandle::PrefetchServingHandle() @@ -146,12 +176,85 @@ void PrefetchServingHandle::OnIsolatedCookiesReadCompleteAndWriteStart() { DCHECK(IsIsolatedCookieCopyInProgress()); - GetCurrentSingleRedirectHopToServe().cookie_read_end_and_write_start_time_ = base::TimeTicks::Now(); } -void PrefetchServingHandle::OnIsolatedCookieCopyComplete() { +void PrefetchServingHandle::CopyIsolatedCookies() { + DCHECK(IsValid()); + + // We only need to copy cookies if the prefetch used an isolated network + // context. + if (!IsIsolatedNetworkContextRequiredToServe()) { + return; + } + + OnIsolatedCookieCopyStart(); + + if (!GetCurrentNetworkContextToServe()) { + CHECK_IS_TEST(); + // Not set in unit tests. + return; + } + + net::CookieOptions options = net::CookieOptions::MakeAllInclusive(); + GetCurrentNetworkContextToServe()->GetCookieManager()->GetCookieList( + GetCurrentURLToServe(), options, + net::CookiePartitionKeyCollection::Todo(), + BindOnceForRvalueMemberMethod<const net::CookieAccessResultList&, + const net::CookieAccessResultList&>( + &PrefetchServingHandle::OnGotIsolatedCookiesForCopy, Clone())); +} + +void PrefetchServingHandle::OnGotIsolatedCookiesForCopy( + const net::CookieAccessResultList& cookie_list, + const net::CookieAccessResultList& excluded_cookies) && { + if (!IsValid()) { + return; + } + + OnIsolatedCookiesReadCompleteAndWriteStart(); + + RecordPrefetchProxyPrefetchMainframeCookiesToCopy(cookie_list.size()); + + if (cookie_list.empty()) { + std::move(*this).OnIsolatedCookieCopyComplete(); + return; + } + + const auto current_url = GetCurrentURLToServe(); + + network::mojom::CookieManager* default_cookie_manager = + GetPrefetchContainer() + ->request() + .browser_context() + ->GetDefaultStoragePartition() + ->GetCookieManagerForBrowserProcess(); + + base::RepeatingClosure barrier = base::BarrierClosure( + cookie_list.size(), + BindOnceForRvalueMemberMethod( + &PrefetchServingHandle::OnIsolatedCookieCopyComplete, + std::move(*this))); + + // Do not touch `this` below, because `this` is already moved out here. + + net::CookieOptions options = net::CookieOptions::MakeAllInclusive(); + for (const net::CookieWithAccessResult& cookie : cookie_list) { + default_cookie_manager->SetCanonicalCookie( + cookie.cookie, current_url, options, + base::BindOnce( + [](base::RepeatingClosure closure, + net::CookieAccessResult access_result) { closure.Run(); }, + barrier)); + } +} + +void PrefetchServingHandle::OnIsolatedCookieCopyComplete() && { + if (!IsValid()) { + return; + } + DCHECK(IsIsolatedCookieCopyInProgress()); // Resumes `PrefetchCookieListener` so that we can keep monitoring the @@ -176,6 +279,25 @@ } } +// The `OnIsolatedCookie*ForTesting` methods are called different from the +// non-test code, e.g. `OnIsolatedCookieCopyCompleteForTesting()` is called on +// a cloned handle in the non-test code, but in the tests it is called on the +// original handle which is still used after this call. This can cause test-only +// inconsistencies but so far the tests are passing. +// TODO(crbug.com/480828677): Fix this. +void PrefetchServingHandle::OnIsolatedCookieCopyStartForTesting() { + OnIsolatedCookieCopyStart(); +} + +void PrefetchServingHandle:: + OnIsolatedCookiesReadCompleteAndWriteStartForTesting() { + OnIsolatedCookiesReadCompleteAndWriteStart(); +} + +void PrefetchServingHandle::OnIsolatedCookieCopyCompleteForTesting() { + Clone().OnIsolatedCookieCopyComplete(); +} + void PrefetchServingHandle::OnInterceptorCheckCookieCopy() { if (!GetCurrentSingleRedirectHopToServe().cookie_copy_start_time_) { return;
diff --git a/content/browser/preloading/prefetch/prefetch_serving_handle.h b/content/browser/preloading/prefetch/prefetch_serving_handle.h index b599cc0..3acea50f 100644 --- a/content/browser/preloading/prefetch/prefetch_serving_handle.h +++ b/content/browser/preloading/prefetch/prefetch_serving_handle.h
@@ -6,8 +6,11 @@ #define CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_SERVING_HANDLE_H_ #include "base/memory/weak_ptr.h" +#include "base/time/time.h" #include "content/browser/preloading/prefetch/prefetch_streaming_url_loader_common_types.h" #include "content/common/content_export.h" +#include "content/public/browser/frame_tree_node_id.h" +#include "net/cookies/cookie_util.h" class GURL; @@ -56,7 +59,8 @@ // Returns true if `this` is valid. // Do not call methods below if false. - explicit operator bool() const { return GetPrefetchContainer(); } + bool IsValid() const { return GetPrefetchContainer(); } + explicit operator bool() const { return IsValid(); } // Methods redirecting to `GetPrefetchContainer()`. PrefetchServableState GetServableState() const; @@ -83,9 +87,9 @@ // well as record metrics about how long this process takes. bool HasIsolatedCookieCopyStarted() const; bool IsIsolatedCookieCopyInProgress() const; - void OnIsolatedCookieCopyStart(); - void OnIsolatedCookiesReadCompleteAndWriteStart(); - void OnIsolatedCookieCopyComplete(); + void OnIsolatedCookieCopyStartForTesting(); + void OnIsolatedCookiesReadCompleteAndWriteStartForTesting(); + void OnIsolatedCookieCopyCompleteForTesting(); void OnInterceptorCheckCookieCopy(); void SetOnCookieCopyCompleteCallback(base::OnceClosure callback); @@ -119,6 +123,18 @@ bool MatchesCookieIndices( base::span<const std::pair<std::string, std::string>> cookies) const; + // Checks if `prefetch_container` can be used for the url of intercepted + // `tentative_resource_request`, and starts checking `PrefetchOriginProber` if + // needed. + void OnGotPrefetchToServe( + FrameTreeNodeId frame_tree_node_id, + const GURL& tentative_resource_request_url, + base::OnceCallback<void(PrefetchServingHandle)> get_prefetch_callback) &&; + + // Copies any cookies in the isolated network context associated with + // the current redirect hop to the default network context. + void CopyIsolatedCookies(); + using OnIsolatedCookieCopyStartCallbackForTesting = base::RepeatingCallback<void(const PrefetchServingHandle&)>; static void SetOnIsolatedCookieCopyStartCallbackForTesting( @@ -132,6 +148,33 @@ // Returns the `SingleRedirectHop` to be served next. const PrefetchSingleRedirectHop& GetCurrentSingleRedirectHopToServe() const; + // Isolated cookie copy methods. + void OnIsolatedCookieCopyStart(); + void OnIsolatedCookiesReadCompleteAndWriteStart(); + // Called when the cookies from |prefetch_conatiner| are read from the + // isolated network context and are ready to be written to the default network + // context. + void OnGotIsolatedCookiesForCopy( + const net::CookieAccessResultList& cookie_list, + const net::CookieAccessResultList& excluded_cookies) &&; + void OnIsolatedCookieCopyComplete() &&; + + // Validation methods. + struct OnGotPrefetchToServeState; + void ContinueOnGotPrefetchToServe( + std::unique_ptr<OnGotPrefetchToServeState> state) &&; + void StartCookieValidation( + std::unique_ptr<OnGotPrefetchToServeState> state) &&; + void OnGotCookiesForValidation( + std::unique_ptr<OnGotPrefetchToServeState> state, + const std::vector<net::CookieWithAccessResult>& cookies, + const std::vector<net::CookieWithAccessResult>& excluded_cookies) &&; + void OnProbeComplete(std::unique_ptr<OnGotPrefetchToServeState> state, + base::TimeTicks probe_start_time, + PrefetchProbeResult probe_result) &&; + void OnCookieCopyComplete(std::unique_ptr<OnGotPrefetchToServeState> state, + base::TimeTicks cookie_copy_start_time) &&; + base::WeakPtr<PrefetchContainer> prefetch_container_; // The index of the element in |GetPrefetchContainer()->redirect_chain()| that
diff --git a/content/browser/preloading/prefetch/prefetch_url_loader_helper.cc b/content/browser/preloading/prefetch/prefetch_url_loader_helper.cc index ef0b34e..cdf61f1 100644 --- a/content/browser/preloading/prefetch/prefetch_url_loader_helper.cc +++ b/content/browser/preloading/prefetch/prefetch_url_loader_helper.cc
@@ -2,17 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "content/browser/preloading/prefetch/prefetch_url_loader_helper.h" - #include "base/metrics/histogram_macros.h" #include "base/time/time.h" #include "content/browser/preloading/prefetch/prefetch_origin_prober.h" #include "content/browser/preloading/prefetch/prefetch_params.h" #include "content/browser/preloading/prefetch/prefetch_probe_result.h" +#include "content/browser/preloading/prefetch/prefetch_request.h" #include "content/browser/preloading/prefetch/prefetch_servable_state.h" #include "content/browser/preloading/prefetch/prefetch_service.h" #include "content/browser/preloading/prefetch/prefetch_serving_handle.h" #include "content/browser/preloading/prefetch/prefetch_serving_page_metrics_container.h" +#include "content/browser/preloading/prefetch/prefetch_single_redirect_hop.h" #include "content/browser/renderer_host/frame_tree_node.h" #include "content/browser/renderer_host/navigation_request.h" #include "content/public/browser/browser_context.h" @@ -22,6 +22,9 @@ #include "url/gurl.h" #include "url/scheme_host_port.h" +// TODO(https://crbug.com/437416134): Move this file's contents to +// `prefetch_serving_handle.cc`. + namespace content { namespace { @@ -44,14 +47,44 @@ base::TimeDelta(), base::Seconds(5), 50); } +// Helper for `base::BindOnce()` + rvalue ref-qualified member method. +// TODO(crbug.com/40254119): Remove this helper when the issue is fixed. +template <typename... UnboundArgs, + typename Method, + typename Receiver, + typename... BoundArgs> +auto BindOnceForRvalueMemberMethod(Method method, + Receiver receiver, + BoundArgs&&... bound_args) { + return base::BindOnce( + [](Method method, std::decay_t<Receiver> receiver, + std::decay_t<BoundArgs>... bound_args, UnboundArgs... unbound_args) { + (std::move(receiver).*method)( + std::forward<BoundArgs>(bound_args)..., + std::forward<UnboundArgs>(unbound_args)...); + }, + method, std::move(receiver), std::forward<BoundArgs>(bound_args)...); +} + +BrowserContext* BrowserContextFromFrameTreeNodeId( + FrameTreeNodeId frame_tree_node_id) { + WebContents* web_content = + WebContents::FromFrameTreeNodeId(frame_tree_node_id); + if (!web_content) { + return nullptr; + } + return web_content->GetBrowserContext(); +} + +} // namespace + // Stores state for the asynchronous work required to prepare a prefetch to // serve. -struct OnGotPrefetchToServeState { +struct PrefetchServingHandle::OnGotPrefetchToServeState final { // Inputs. const FrameTreeNodeId frame_tree_node_id; const GURL tentative_url; base::OnceCallback<void(PrefetchServingHandle)> callback; - PrefetchServingHandle serving_handle; // True if we've validated that cookies match (to the extent required). // False if they don't. Absent if we don't know yet. @@ -68,43 +101,29 @@ bool cookie_copy_complete_if_required = false; }; -// Forward declarations are required for these to call each other while -// appearing in the order they occur. -void ContinueOnGotPrefetchToServe( - std::unique_ptr<OnGotPrefetchToServeState> state); -void StartCookieValidation(std::unique_ptr<OnGotPrefetchToServeState> state); -void OnGotCookiesForValidation( - std::unique_ptr<OnGotPrefetchToServeState> state, - const std::vector<net::CookieWithAccessResult>& cookies, - const std::vector<net::CookieWithAccessResult>& excluded_cookies); -void OnProbeComplete(std::unique_ptr<OnGotPrefetchToServeState> state, - base::TimeTicks probe_start_time, - PrefetchProbeResult probe_result); -void OnCookieCopyComplete(std::unique_ptr<OnGotPrefetchToServeState> state, - base::TimeTicks cookie_copy_start_time); - // Overall structure of asynchronous execution (in coroutine style). -void ContinueOnGotPrefetchToServe( - std::unique_ptr<OnGotPrefetchToServeState> state) { +void PrefetchServingHandle::ContinueOnGotPrefetchToServe( + std::unique_ptr<OnGotPrefetchToServeState> state) && { // If the cookies need to be matched, fetch them and confirm that they're // correct. if (base::FeatureList::IsEnabled(features::kPrefetchCookieIndices)) { if (!state->cookies_matched.has_value()) { WebContents* web_contents = WebContents::FromFrameTreeNodeId(state->frame_tree_node_id); - if (!web_contents || !state->serving_handle) { + if (!web_contents || !IsValid()) { // We can't confirm that the cookies matched. But probably everything is // being torn down, anyway. state->cookies_matched = false; - } else if (!state->serving_handle.VariesOnCookieIndices()) { + } else if (!VariesOnCookieIndices()) { state->cookies_matched = true; } else { - StartCookieValidation(std::move(state)); + std::move(*this).StartCookieValidation(std::move(state)); // Fetching the cookies asynchronously. Continue later. return; } } + CHECK(state->cookies_matched.has_value()); if (!state->cookies_matched.value()) { // Cookies did not match, but needed to. We're done here. @@ -127,13 +146,14 @@ } PrefetchOriginProber* prober = prefetch_service->GetPrefetchOriginProber(); - if (state->serving_handle.IsIsolatedNetworkContextRequiredToServe() && + if (IsIsolatedNetworkContextRequiredToServe() && prober->ShouldProbeOrigins()) { GURL probe_url = url::SchemeHostPort(state->tentative_url).GetURL(); - prober->Probe( - probe_url, - base::BindOnce(&OnProbeComplete, std::move(state), - /*probe_start_time=*/base::TimeTicks::Now())); + prober->Probe(probe_url, + BindOnceForRvalueMemberMethod<PrefetchProbeResult>( + &PrefetchServingHandle::OnProbeComplete, + std::move(*this), std::move(state), + /*probe_start_time=*/base::TimeTicks::Now())); // The probe is happening asynchronously (it took ownership of |state|), // and this algorithm will continue later. return; @@ -152,23 +172,34 @@ // Ensures that the cookies for prefetch are copied from its isolated // network context to the default network context. if (!state->cookie_copy_complete_if_required) { - if (state->serving_handle) { - if (!state->serving_handle.HasIsolatedCookieCopyStarted()) { + if (IsValid()) { + if (!HasIsolatedCookieCopyStarted()) { + // Checks the same `BrowserContext` is used, just in case. We can remove + // this `CHECK` e.g. once we remove `frame_tree_node_id` access here. + CHECK_EQ(BrowserContextFromFrameTreeNodeId(state->frame_tree_node_id), + GetPrefetchContainer()->request().browser_context()); + // Start the cookie copy for the next redirect hop. - if (PrefetchService* prefetch_service = - PrefetchService::GetFromFrameTreeNodeId( - state->frame_tree_node_id)) { - prefetch_service->CopyIsolatedCookies(state->serving_handle); - } + CopyIsolatedCookies(); } - state->serving_handle.OnInterceptorCheckCookieCopy(); + OnInterceptorCheckCookieCopy(); - if (state->serving_handle.IsIsolatedCookieCopyInProgress()) { + if (IsIsolatedCookieCopyInProgress()) { // Cookie copy is happening and this function will continue later. - state->serving_handle.SetOnCookieCopyCompleteCallback( - base::BindOnce(&OnCookieCopyComplete, std::move(state), - /*cookie_copy_start_time=*/base::TimeTicks::Now())); + + // We first get a `current_redirect_hop` reference and then move out + // `*this` etc., to avoid use-after-move. + // TODO(https://crbug.com/437416134): Revamp this for better interfacing + // and fix potential bugs. + auto& current_redirect_hop = GetCurrentSingleRedirectHopToServe(); + DUMP_WILL_BE_CHECK( + !current_redirect_hop.on_cookie_copy_complete_callback_); + current_redirect_hop.on_cookie_copy_complete_callback_ = + BindOnceForRvalueMemberMethod<>( + &PrefetchServingHandle::OnCookieCopyComplete, std::move(*this), + std::move(state), + /*cookie_copy_start_time=*/base::TimeTicks::Now()); return; } } @@ -183,12 +214,12 @@ CHECK(PrefetchProbeResultIsSuccess(state->probe_result.value())); CHECK(state->cookie_copy_complete_if_required); - if (!state->serving_handle) { + if (!IsValid()) { std::move(state->callback).Run({}); return; } - switch (state->serving_handle.GetServableState()) { + switch (GetServableState()) { case PrefetchServableState::kNotServable: case PrefetchServableState::kShouldBlockUntilEligibilityGot: case PrefetchServableState::kShouldBlockUntilHeadReceived: @@ -200,22 +231,22 @@ // Delay updating the prefetch with the probe result in case it becomes not // servable. - state->serving_handle.OnPrefetchProbeResult(state->probe_result.value()); + OnPrefetchProbeResult(state->probe_result.value()); PrefetchServingPageMetricsContainer* serving_page_metrics_container = PrefetchServingPageMetricsContainerFromFrameTreeNodeId( state->frame_tree_node_id); if (serving_page_metrics_container) { - serving_page_metrics_container->SetPrefetchStatus( - state->serving_handle.GetPrefetchStatus()); + serving_page_metrics_container->SetPrefetchStatus(GetPrefetchStatus()); } - std::move(state->callback).Run(std::move(state->serving_handle)); + std::move(state->callback).Run(std::move(*this)); } // COOKIE VALIDATION -void StartCookieValidation(std::unique_ptr<OnGotPrefetchToServeState> state) { +void PrefetchServingHandle::StartCookieValidation( + std::unique_ptr<OnGotPrefetchToServeState> state) && { WebContents* web_contents = WebContents::FromFrameTreeNodeId(state->frame_tree_node_id); network::mojom::CookieManager* cookie_manager = @@ -227,7 +258,7 @@ // possible. CHECK(FrameTreeNode::GloballyFindByID(state->frame_tree_node_id) ->IsMainFrame()); - const GURL& url = state->serving_handle.GetCurrentURLToServe(); + const GURL& url = GetCurrentURLToServe(); net::SchemefulSite site(url); cookie_manager->GetCookieList( url, net::CookieOptions::MakeAllInclusive(), @@ -235,23 +266,25 @@ net::CookiePartitionKey::FromNetworkIsolationKey( net::NetworkIsolationKey(site, site), net::SiteForCookies(site), site, /*main_frame_navigation=*/true)), - base::BindOnce(&OnGotCookiesForValidation, std::move(state))); + BindOnceForRvalueMemberMethod< + const std::vector<net::CookieWithAccessResult>&, + const std::vector<net::CookieWithAccessResult>&>( + &PrefetchServingHandle::OnGotCookiesForValidation, std::move(*this), + std::move(state))); } -void OnGotCookiesForValidation( +void PrefetchServingHandle::OnGotCookiesForValidation( std::unique_ptr<OnGotPrefetchToServeState> state, const std::vector<net::CookieWithAccessResult>& cookies, - const std::vector<net::CookieWithAccessResult>& excluded_cookies) { + const std::vector<net::CookieWithAccessResult>& excluded_cookies) && { std::vector<std::pair<std::string, std::string>> cookie_values; cookie_values.reserve(cookies.size()); for (const net::CookieWithAccessResult& cookie : cookies) { cookie_values.emplace_back(cookie.cookie.Name(), cookie.cookie.Value()); } - state->cookies_matched = - state->serving_handle && - state->serving_handle.MatchesCookieIndices(cookie_values); - ContinueOnGotPrefetchToServe(std::move(state)); + state->cookies_matched = IsValid() && MatchesCookieIndices(cookie_values); + std::move(*this).ContinueOnGotPrefetchToServe(std::move(state)); } // ORIGIN PROBING @@ -259,9 +292,10 @@ // Called when the `PrefetchOriginProber` check is done (if performed). // `probe_start_time` is used to calculate probe latency which is // reported to the tab helper. -void OnProbeComplete(std::unique_ptr<OnGotPrefetchToServeState> state, - base::TimeTicks probe_start_time, - PrefetchProbeResult probe_result) { +void PrefetchServingHandle::OnProbeComplete( + std::unique_ptr<OnGotPrefetchToServeState> state, + base::TimeTicks probe_start_time, + PrefetchProbeResult probe_result) && { state->probe_result = probe_result; PrefetchServingPageMetricsContainer* serving_page_metrics_container = @@ -272,59 +306,55 @@ probe_start_time); } - if (!PrefetchProbeResultIsSuccess(probe_result) && state->serving_handle) { - state->serving_handle.OnPrefetchProbeResult(probe_result); + if (!PrefetchProbeResultIsSuccess(probe_result) && IsValid()) { + OnPrefetchProbeResult(probe_result); if (serving_page_metrics_container) { - serving_page_metrics_container->SetPrefetchStatus( - state->serving_handle.GetPrefetchStatus()); + serving_page_metrics_container->SetPrefetchStatus(GetPrefetchStatus()); } } - ContinueOnGotPrefetchToServe(std::move(state)); + std::move(*this).ContinueOnGotPrefetchToServe(std::move(state)); } // ISOLATED COOKIE COPYING -void OnCookieCopyComplete(std::unique_ptr<OnGotPrefetchToServeState> state, - base::TimeTicks cookie_copy_start_time) { +void PrefetchServingHandle::OnCookieCopyComplete( + std::unique_ptr<OnGotPrefetchToServeState> state, + base::TimeTicks cookie_copy_start_time) && { base::TimeDelta wait_time = base::TimeTicks::Now() - cookie_copy_start_time; CHECK_GE(wait_time, base::TimeDelta()); RecordCookieWaitTime(wait_time); state->cookie_copy_complete_if_required = true; - ContinueOnGotPrefetchToServe(std::move(state)); + std::move(*this).ContinueOnGotPrefetchToServe(std::move(state)); } -} // namespace - -void OnGotPrefetchToServe( +void PrefetchServingHandle::OnGotPrefetchToServe( FrameTreeNodeId frame_tree_node_id, const GURL& tentative_resource_request_url, - base::OnceCallback<void(PrefetchServingHandle)> get_prefetch_callback, - PrefetchServingHandle serving_handle) { + base::OnceCallback<void(PrefetchServingHandle)> get_prefetch_callback) && { // TODO(crbug.com/40274818): With multiple prefetches matching, we should // move some of the checks here in `PrefetchService::ReturnPrefetchToServe`. // Why ? Because we might be able to serve a different prefetch if the - // prefetch in the `serving_handle` cannot be served. + // prefetch in the `*this` cannot be served. // The `tentative_resource_request_url` might be different from // `GetCurrentURLToServe()` because of No-Vary-Search non-exact url match. #if DCHECK_IS_ON() - if (serving_handle) { + if (IsValid()) { GURL::Replacements replacements; replacements.ClearRef(); replacements.ClearQuery(); - DCHECK_EQ( - tentative_resource_request_url.ReplaceComponents(replacements), - serving_handle.GetCurrentURLToServe().ReplaceComponents(replacements)); + DCHECK_EQ(tentative_resource_request_url.ReplaceComponents(replacements), + GetCurrentURLToServe().ReplaceComponents(replacements)); } #endif - if (!serving_handle) { + if (!IsValid()) { std::move(get_prefetch_callback).Run({}); return; } - switch (serving_handle.GetServableState()) { + switch (GetServableState()) { case PrefetchServableState::kNotServable: case PrefetchServableState::kShouldBlockUntilEligibilityGot: case PrefetchServableState::kShouldBlockUntilHeadReceived: @@ -336,22 +366,22 @@ // We should not reach here if the cookies have changed. This should already // have been checked in one of the call sites: - // 1) PrefetchService::ReturnPrefetchToServe (in which case |serving_handle| - // should be empty) + // 1) PrefetchService::ReturnPrefetchToServe (in which case |this| should be + // empty) // 2) PrefetchURLLoaderInterceptor::MaybeCreateLoader (before serving the next // next redirect hop) - CHECK(!serving_handle.HaveDefaultContextCookiesChanged()); + CHECK(!HaveDefaultContextCookiesChanged()); // Asynchronous activity begins here. // We allocate an explicit "coroutine state" for this and manage it manually. // While slightly verbose, this avoids duplication of logic later on in // control flow. This function will asynchronously call itself until it's // done. - ContinueOnGotPrefetchToServe(base::WrapUnique(new OnGotPrefetchToServeState{ - .frame_tree_node_id = frame_tree_node_id, - .tentative_url = tentative_resource_request_url, - .callback = std::move(get_prefetch_callback), - .serving_handle = std::move(serving_handle)})); + std::move(*this).ContinueOnGotPrefetchToServe( + base::WrapUnique(new OnGotPrefetchToServeState{ + .frame_tree_node_id = frame_tree_node_id, + .tentative_url = tentative_resource_request_url, + .callback = std::move(get_prefetch_callback)})); } } // namespace content
diff --git a/content/browser/preloading/prefetch/prefetch_url_loader_helper.h b/content/browser/preloading/prefetch/prefetch_url_loader_helper.h deleted file mode 100644 index 8a4c8da..0000000 --- a/content/browser/preloading/prefetch/prefetch_url_loader_helper.h +++ /dev/null
@@ -1,29 +0,0 @@ -// Copyright 2023 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_URL_LOADER_HELPER_H_ -#define CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_URL_LOADER_HELPER_H_ - -#include "base/functional/callback_forward.h" -#include "content/common/content_export.h" -#include "content/public/browser/frame_tree_node_id.h" - -class GURL; - -namespace content { - -class PrefetchServingHandle; - -// Checks if `prefetch_container` can be used for the url of intercepted -// `tentative_resource_request`, and starts checking `PrefetchOriginProber` if -// needed. -void CONTENT_EXPORT OnGotPrefetchToServe( - FrameTreeNodeId frame_tree_node_id, - const GURL& tentative_resource_request_url, - base::OnceCallback<void(PrefetchServingHandle)> get_prefetch_callback, - PrefetchServingHandle serving_handle); - -} // namespace content - -#endif // CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_URL_LOADER_HELPER_H_
diff --git a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc index 54ab384..7e52db0 100644 --- a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc +++ b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc
@@ -13,7 +13,7 @@ #include "content/browser/preloading/prefetch/prefetch_match_resolver.h" #include "content/browser/preloading/prefetch/prefetch_params.h" #include "content/browser/preloading/prefetch/prefetch_service.h" -#include "content/browser/preloading/prefetch/prefetch_url_loader_helper.h" +#include "content/browser/preloading/prefetch/prefetch_serving_handle.h" #include "content/browser/renderer_host/frame_tree.h" #include "content/browser/renderer_host/frame_tree_node.h" #include "content/browser/renderer_host/navigation_request.h" @@ -47,6 +47,17 @@ return *get_prefetch_complete_callback_for_testing; } +// Just to call to a `PrefetchServingHandle&&` method via `base::BindOnce()`. +void OnGotPrefetchToServe( + FrameTreeNodeId frame_tree_node_id, + const GURL& url, + base::OnceCallback<void(PrefetchServingHandle)> get_prefetch_callback, + PrefetchServingHandle serving_handle) { + std::move(serving_handle) + .OnGotPrefetchToServe(frame_tree_node_id, url, + std::move(get_prefetch_callback)); +} + } // namespace // static @@ -139,16 +150,17 @@ std::nullopt); } else { TRACE_EVENT_END("loading"); - OnGotPrefetchToServe( - frame_tree_node_id_, tentative_resource_request.url, - base::BindOnce(&PrefetchURLLoaderInterceptor::OnGetPrefetchComplete, - weak_factory_.GetWeakPtr(), + std::move(redirect_serving_handle_) + .OnGotPrefetchToServe( + frame_tree_node_id_, tentative_resource_request.url, + base::BindOnce( + &PrefetchURLLoaderInterceptor::OnGetPrefetchComplete, + weak_factory_.GetWeakPtr(), - tentative_resource_request.url, - ServiceWorkerMainResourceHandle:: - TopFrameOriginForInitializeForRequest( - tentative_resource_request)), - std::move(redirect_serving_handle_)); + tentative_resource_request.url, + ServiceWorkerMainResourceHandle:: + TopFrameOriginForInitializeForRequest( + tentative_resource_request))); return; } }
diff --git a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc index d4a40ef70..bdbc359 100644 --- a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc +++ b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc
@@ -25,6 +25,7 @@ #include "content/browser/preloading/prefetch/prefetch_request.h" #include "content/browser/preloading/prefetch/prefetch_servable_state.h" #include "content/browser/preloading/prefetch/prefetch_service.h" +#include "content/browser/preloading/prefetch/prefetch_serving_handle.h" #include "content/browser/preloading/prefetch/prefetch_test_util_internal.h" #include "content/browser/preloading/prefetch/prefetch_type.h" #include "content/browser/preloading/preloading.h" @@ -419,9 +420,9 @@ PrefetchServingHandle serving_handle = prefetch_container.CreateServingHandle(); ASSERT_TRUE(serving_handle.IsIsolatedNetworkContextRequiredToServe()); - serving_handle.OnIsolatedCookieCopyStart(); + serving_handle.OnIsolatedCookieCopyStartForTesting(); task_environment()->FastForwardBy(base::Milliseconds(10)); - serving_handle.OnIsolatedCookieCopyComplete(); + serving_handle.OnIsolatedCookieCopyCompleteForTesting(); } // When prefetch is served for navigation (depending on the `GetParam()` @@ -550,7 +551,7 @@ // Simulate the cookie copy process starting, but not finishing until after // |MaybeCreateLoader| is called. auto serving_handle = prefetch_container->CreateServingHandle(); - serving_handle.OnIsolatedCookieCopyStart(); + serving_handle.OnIsolatedCookieCopyStartForTesting(); task_environment()->FastForwardBy(base::Milliseconds(10)); GetPrefetchService()->TakePrefetchOriginProber( @@ -567,7 +568,7 @@ task_environment()->FastForwardBy(base::Milliseconds(20)); - serving_handle.OnIsolatedCookieCopyComplete(); + serving_handle.OnIsolatedCookieCopyCompleteForTesting(); WaitForCallback(kTestUrl); EXPECT_TRUE(was_intercepted(kTestUrl).has_value()); @@ -1064,7 +1065,7 @@ // Simulate the cookie copy process starting, but not finishing until after // |MaybeCreateLoader| is called. auto serving_handle = prefetch_container->CreateServingHandle(); - serving_handle.OnIsolatedCookieCopyStart(); + serving_handle.OnIsolatedCookieCopyStartForTesting(); task_environment()->FastForwardBy(base::Milliseconds(10)); GetPrefetchService()->TakePrefetchOriginProber( @@ -1134,7 +1135,7 @@ task_environment()->RunUntilIdle(); - serving_handle.OnIsolatedCookieCopyComplete(); + serving_handle.OnIsolatedCookieCopyCompleteForTesting(); WaitForCallback(kTestUrl); EXPECT_TRUE(was_intercepted(kTestUrl).has_value()); @@ -1229,7 +1230,7 @@ task_environment()->FastForwardBy(base::Milliseconds(20)); auto serving_handle = prefetch_container->CreateServingHandle(); serving_handle.AdvanceCurrentURLToServe(); - serving_handle.OnIsolatedCookieCopyComplete(); + serving_handle.OnIsolatedCookieCopyCompleteForTesting(); WaitForCallback(kRedirectUrl); EXPECT_TRUE(was_intercepted(kTestUrl).has_value()); @@ -1305,7 +1306,7 @@ on_start_cookie_copy_run_loop.Run(); task_environment()->FastForwardBy(base::Milliseconds(20)); serving_handle.AdvanceCurrentURLToServe(); - serving_handle.OnIsolatedCookieCopyComplete(); + serving_handle.OnIsolatedCookieCopyCompleteForTesting(); WaitForCallback(kRedirectUrl); EXPECT_TRUE(was_intercepted(kTestUrl).has_value());
diff --git a/content/browser/renderer_host/render_frame_host_android.cc b/content/browser/renderer_host/render_frame_host_android.cc index aa3ada39..82258e8 100644 --- a/content/browser/renderer_host/render_frame_host_android.cc +++ b/content/browser/renderer_host/render_frame_host_android.cc
@@ -44,19 +44,6 @@ namespace content { namespace { -void OnGetCanonicalUrlForSharing( - const base::android::JavaRef<jobject>& jcallback, - const std::optional<GURL>& url) { - JNIEnv* env = base::android::AttachCurrentThread(); - if (!url) { - base::android::RunObjectCallbackAndroid(jcallback, - url::GURLAndroid::EmptyGURL(env)); - return; - } - - base::android::RunObjectCallbackAndroid( - jcallback, url::GURLAndroid::FromNativeGURL(env, url.value())); -} void JavaScriptResultCallback( const base::android::ScopedJavaGlobalRef<jobject>& callback, @@ -138,11 +125,8 @@ } void RenderFrameHostAndroid::GetCanonicalUrlForSharing( - JNIEnv* env, - const base::android::JavaRef<jobject>& jcallback) const { - render_frame_host_->GetCanonicalUrl(base::BindOnce( - &OnGetCanonicalUrlForSharing, - base::android::ScopedJavaGlobalRef<jobject>(env, jcallback))); + base::OnceCallback<void(const std::optional<GURL>&)> callback) const { + render_frame_host_->GetCanonicalUrl(std::move(callback)); } std::vector<ScopedJavaLocalRef<jobject>> @@ -320,11 +304,8 @@ } void RenderFrameHostAndroid::InsertVisualStateCallback( - JNIEnv* env, - const JavaRef<jobject>& jcallback) { - render_frame_host()->InsertVisualStateCallback( - base::BindOnce(&base::android::RunBooleanCallbackAndroid, - base::android::ScopedJavaGlobalRef<jobject>(jcallback))); + base::OnceCallback<void(bool)> callback) { + render_frame_host()->InsertVisualStateCallback(std::move(callback)); } bool RenderFrameHostAndroid::HasHitTestDataForTesting(JNIEnv* env) {
diff --git a/content/browser/renderer_host/render_frame_host_android.h b/content/browser/renderer_host/render_frame_host_android.h index 6da0e48..cf49d45f 100644 --- a/content/browser/renderer_host/render_frame_host_android.h +++ b/content/browser/renderer_host/render_frame_host_android.h
@@ -7,13 +7,18 @@ #include <jni.h> +#include <optional> + #include "base/android/jni_android.h" #include "base/android/jni_weak_ref.h" #include "base/android/scoped_java_ref.h" +#include "base/functional/callback.h" #include "base/memory/raw_ptr.h" #include "base/supports_user_data.h" #include "base/unguessable_token.h" +class GURL; + namespace content { class RenderFrameHostImpl; @@ -42,8 +47,7 @@ base::android::ScopedJavaLocalRef<jobject> GetMainFrame(JNIEnv* env); void GetCanonicalUrlForSharing( - JNIEnv* env, - const base::android::JavaRef<jobject>& jcallback) const; + base::OnceCallback<void(const std::optional<GURL>&)> callback) const; std::vector<jni_zero::ScopedJavaLocalRef<jobject>> GetAllRenderFrameHosts( JNIEnv* env) const; @@ -97,9 +101,7 @@ int32_t GetLifecycleState(JNIEnv* env) const; - void InsertVisualStateCallback( - JNIEnv* env, - const base::android::JavaRef<jobject>& jcallback); + void InsertVisualStateCallback(base::OnceCallback<void(bool)> callback); void ExecuteJavaScriptInIsolatedWorld( JNIEnv* env,
diff --git a/content/browser/renderer_host/text_input_client_mac_unittest.mm b/content/browser/renderer_host/text_input_client_mac_unittest.mm index 24756e8..4dc0fcb 100644 --- a/content/browser/renderer_host/text_input_client_mac_unittest.mm +++ b/content/browser/renderer_host/text_input_client_mac_unittest.mm
@@ -8,8 +8,10 @@ #include <stdint.h> #include <memory> -#include <type_traits> +#include <string> +#include <tuple> #include <utility> +#include <variant> #include "base/containers/queue.h" #include "base/functional/bind.h" @@ -21,6 +23,7 @@ #include "base/task/single_thread_task_runner.h" #include "base/test/scoped_feature_list.h" #include "base/test/test_future.h" +#include "base/test/test_timeouts.h" #include "base/time/time.h" #include "base/unguessable_token.h" #include "content/browser/renderer_host/text_input_host_impl.h" @@ -32,6 +35,7 @@ #include "content/public/test/browser_task_environment.h" #include "content/public/test/test_renderer_host.h" #include "testing/gtest/include/gtest/gtest.h" +#include "third_party/abseil-cpp/absl/strings/str_format.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/range/range.h" @@ -40,109 +44,46 @@ namespace { -constexpr base::TimeDelta kTaskDelay = base::Milliseconds(200); +using ::testing::Combine; +using ::testing::Values; + +// Value for kTextInputClientIPCTimeout. These aren't specified directly in +// INSTANTIATE_TEST_SUITE_P because TestTimeouts isn't initialized before the +// test suit constructor. +enum class TimeoutParam { + // Mosts tests use a long timeout, to make sure there's enough time for test + // responses to arrive. + kLongTimeout, + // Tests that expect a timeout to happen use a short timeout. + kShortTimeout, +}; // TextInputClientMacTest exercises two sync functions, // GetCharacterIndexAtPoint() and GetFirstRectForRange(), that should have -// identical behaviour except for their return type. To avoid repeated test -// code, this traits class implements only the code that must change to test -// each function, parameteriezd on the return type. -template <typename ResponseType> -struct TestTraits { - // Calls the TextInputClientMac sync getter method under test, with `rwh` as a - // parameter. - static ResponseType TextInputClientGetSync(RenderWidgetHost* rwh); - - // Calls the appropriate Got*() method of `host` with the given - // `request_token` and `response`, which will unblock the waiting - // TextInputClientMac. - static void TextInputHostGotResponse( - TextInputHostImpl* host, - const TextInputClientMac::RequestToken& request_token, - ResponseType response); - - // Synchronously calls a test-only setter method on TextInputClientMac, to - // simulate a response being received before returning from the delegate. - static void TextInputClientSetSync( - const TextInputClientMac::RequestToken& request_token, - ResponseType response); - - // Initializes a ResponseType value from an arbitrary integer. - static constexpr ResponseType CreateResponse(int value); - - // The ResponseType value that's used when the sync getter method times out. - static constexpr ResponseType kTimeoutResponse; +// identical behaviour except for their return type. +enum class FunctionToTest { + kGetCharacterIndexAtPoint, + kGetFirstRectForRange, }; -template <> -struct TestTraits<uint32_t> { - static uint32_t TextInputClientGetSync(RenderWidgetHost* rwh) { - return TextInputClientMac::GetInstance()->GetCharacterIndexAtPoint( - rwh, gfx::Point(2, 2)); - } - - static void TextInputHostGotResponse( - TextInputHostImpl* host, - const TextInputClientMac::RequestToken& request_token, - uint32_t response) { - host->GotCharacterIndexAtPoint(request_token.value(), response); - } - - static void TextInputClientSetSync( - const TextInputClientMac::RequestToken& request_token, - uint32_t response) { - TextInputClientMac::GetInstance()->SetCharacterIndexWhileLockedForTesting( - request_token, response); - } - - static constexpr uint32_t CreateResponse(int value) { return value; } - - static constexpr uint32_t kTimeoutResponse = UINT32_MAX; -}; - -template <> -struct TestTraits<gfx::Rect> { - static gfx::Rect TextInputClientGetSync(RenderWidgetHost* rwh) { - return TextInputClientMac::GetInstance()->GetFirstRectForRange( - rwh, gfx::Range(NSMakeRange(0, 32))); - } - - static void TextInputHostGotResponse( - TextInputHostImpl* host, - const TextInputClientMac::RequestToken& request_token, - gfx::Rect response) { - host->GotFirstRectForRange(request_token.value(), response); - } - - static void TextInputClientSetSync( - const TextInputClientMac::RequestToken& request_token, - gfx::Rect response) { - TextInputClientMac::GetInstance()->SetFirstRectWhileLockedForTesting( - request_token, response); - } - - static constexpr gfx::Rect CreateResponse(int value) { - return gfx::Rect(value, value, value, value); - } - - static constexpr gfx::Rect kTimeoutResponse = gfx::Rect(); -}; +// GetCharacterIndexAtPoint() returns uint32_t. +// GetFirstRectForRange() returns gfx::Rect. +using ResponseType = std::variant<uint32_t, gfx::Rect>; // Fake that replaces mojo messages sent through LocalFrame by posting tasks // directly to TextInputHostImpl on the IO thread. // // The standard way to implement the receiver of LocalFrame messages in unit -// tests is FakeLocalFrame, but its recievers are bound to the main test thread, +// tests is FakeLocalFrame, but its receivers are bound to the main test thread, // not the IO thread. Blocking TextInputClientMac methods are also called on the // main thread, and wait for the responses to those messages, so receivers bound // to FakeLocalFrame won't get called until after the blocking method times out. -template <typename ResponseType> class FakeAsyncRequestDelegate final : public TextInputClientMac::AsyncRequestDelegate { public: - using Traits = TestTraits<ResponseType>; - - FakeAsyncRequestDelegate(RenderWidgetHost* widget) : widget_(widget) { + FakeAsyncRequestDelegate(FunctionToTest function_to_test, + RenderWidgetHost* widget) + : function_to_test_(function_to_test), widget_(widget) { // Wait until `host_impl_` is created on the IO thread. base::test::TestFuture<std::unique_ptr<TextInputHostImpl>> host_future; GetIOThreadTaskRunner()->PostTaskAndReplyWithResult( @@ -162,7 +103,8 @@ FakeAsyncRequestDelegate(const FakeAsyncRequestDelegate&) = delete; FakeAsyncRequestDelegate& operator=(const FakeAsyncRequestDelegate&) = delete; - void AddResponse(ResponseType response, base::TimeDelta delay) { + void AddResponse(ResponseType response, + base::TimeDelta delay = TestTimeouts::tiny_timeout()) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); responses_.emplace(std::move(response), delay); } @@ -178,25 +120,43 @@ RenderFrameHost* rfh, const TextInputClientMac::RequestToken& request_token, const gfx::Point& point) final { - if constexpr (std::is_same_v<ResponseType, uint32_t>) { - SendNextResponse(rfh, request_token); - } else { - FAIL() << "Wrong test type for GetCharacterIndexAtPoint"; - } + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + ASSERT_EQ(function_to_test_, FunctionToTest::kGetCharacterIndexAtPoint); + SendNextResponse(rfh, request_token); } void GetFirstRectForRange( RenderFrameHost* rfh, const TextInputClientMac::RequestToken& request_token, const gfx::Range& range) final { - if constexpr (std::is_same_v<ResponseType, gfx::Rect>) { - SendNextResponse(rfh, request_token); - } else { - FAIL() << "Wrong test type for GetFirstRectForRange"; - } + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + ASSERT_EQ(function_to_test_, FunctionToTest::kGetFirstRectForRange); + SendNextResponse(rfh, request_token); } private: + // Calls the appropriate Got*() method of `host` based on `function_to_test`, + // with the given `request_token` and `response` as params. This will unblock + // the waiting TextInputClientMac. + static void TextInputHostGotResponse( + FunctionToTest function_to_test, + TextInputHostImpl* host, + const TextInputClientMac::RequestToken& request_token, + ResponseType response) { + switch (function_to_test) { + case FunctionToTest::kGetCharacterIndexAtPoint: + ASSERT_TRUE(std::holds_alternative<uint32_t>(response)); + host->GotCharacterIndexAtPoint(request_token.value(), + std::get<uint32_t>(response)); + break; + case FunctionToTest::kGetFirstRectForRange: + ASSERT_TRUE(std::holds_alternative<gfx::Rect>(response)); + host->GotFirstRectForRange(request_token.value(), + std::get<gfx::Rect>(response)); + break; + } + } + void SendNextResponse(RenderFrameHost* rfh, const TextInputClientMac::RequestToken& request_token) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); @@ -213,18 +173,32 @@ // which must be accessed on the IO thread. This simulates a response that // arrives while the calling thread is descheduled, before TextInputClient // blocks it. - Traits::TextInputClientSetSync(request_token, response); + switch (function_to_test_) { + case FunctionToTest::kGetCharacterIndexAtPoint: + ASSERT_TRUE(std::holds_alternative<uint32_t>(response)); + TextInputClientMac::GetInstance() + ->SetCharacterIndexWhileLockedForTesting( + request_token, std::get<uint32_t>(response)); + break; + case FunctionToTest::kGetFirstRectForRange: + ASSERT_TRUE(std::holds_alternative<gfx::Rect>(response)); + TextInputClientMac::GetInstance()->SetFirstRectWhileLockedForTesting( + request_token, std::get<gfx::Rect>(response)); + break; + } } else { // Unretained is safe since `host_impl_` is deleted on the IO thread. GetIOThreadTaskRunner()->PostDelayedTask( FROM_HERE, - base::BindOnce(&Traits::TextInputHostGotResponse, - base::Unretained(host_impl_.get()), request_token, - std::move(response)), + base::BindOnce(&FakeAsyncRequestDelegate::TextInputHostGotResponse, + function_to_test_, base::Unretained(host_impl_.get()), + request_token, std::move(response)), delay); } } + const FunctionToTest function_to_test_; + SEQUENCE_CHECKER(sequence_checker_); raw_ptr<RenderWidgetHost> widget_ GUARDED_BY_CONTEXT(sequence_checker_); @@ -243,23 +217,39 @@ // This test does not test the WebKit side of the dictionary system (which // performs the actual data fetching), but rather this just tests that the // service's signaling system works. -// -// Tests a different sync getter function depending on ResponseType: -// * uint32_t -> GetCharacterIndexAtPoint() -// * gfx::Rect -> GetFirstRectForRange() -template <typename ResponseType> -class TextInputClientMacTest : public content::RenderViewHostTestHarness { +class TextInputClientMacTest : public content::RenderViewHostTestHarness, + public ::testing::WithParamInterface< + std::tuple<FunctionToTest, TimeoutParam>> { public: - using Delegate = FakeAsyncRequestDelegate<ResponseType>; + using Delegate = FakeAsyncRequestDelegate; TextInputClientMacTest() - : RenderViewHostTestHarness(BrowserTaskEnvironment::REAL_IO_THREAD) {} + : RenderViewHostTestHarness(BrowserTaskEnvironment::REAL_IO_THREAD) { + TimeoutParam timeout_param; + std::tie(function_to_test_, timeout_param) = GetParam(); + base::TimeDelta ipc_timeout; + switch (timeout_param) { + case TimeoutParam::kLongTimeout: + ipc_timeout = TestTimeouts::action_max_timeout(); + break; + case TimeoutParam::kShortTimeout: + // See SyncGetter_StaleResult for the exact value. + ipc_timeout = TestTimeouts::tiny_timeout() * 1.5; + break; + } + feature_list_.InitAndEnableFeatureWithParameters( + features::kTextInputClient, + {{"ipc_timeout", + absl::StrFormat("%dms", ipc_timeout.InMilliseconds())}}); + } + + protected: void SetUp() override { RenderViewHostTestHarness::SetUp(); RenderViewHostTester::For(rvh())->CreateTestRenderView(); - auto delegate = std::make_unique<Delegate>(widget()); + auto delegate = std::make_unique<Delegate>(function_to_test_, widget()); request_delegate_ = delegate.get(); TextInputClientMac::GetInstance()->SetAsyncRequestDelegateForTesting( std::move(delegate)); @@ -275,6 +265,49 @@ RenderViewHostTestHarness::TearDown(); } + // Initializes a ResponseType value from an arbitrary integer. + ResponseType CreateResponse(unsigned value) const { + switch (function_to_test_) { + case FunctionToTest::kGetCharacterIndexAtPoint: + return value; + case FunctionToTest::kGetFirstRectForRange: + return gfx::Rect(value, value, value, value); + } + } + + // The ResponseType value that's used when there's no focused widget. + ResponseType NoFocusResponse() const { + switch (function_to_test_) { + case FunctionToTest::kGetCharacterIndexAtPoint: + return 0u; + case FunctionToTest::kGetFirstRectForRange: + return gfx::Rect(); + } + } + + // The ResponseType value that's used when the sync getter method times out. + ResponseType TimeoutResponse() const { + switch (function_to_test_) { + case FunctionToTest::kGetCharacterIndexAtPoint: + return UINT32_MAX; + case FunctionToTest::kGetFirstRectForRange: + return gfx::Rect(); + } + } + + // Calls the TextInputClientMac sync getter method under test, with `rwh` as a + // parameter. + ResponseType TextInputClientGetSync(RenderWidgetHost* rwh) const { + switch (function_to_test_) { + case FunctionToTest::kGetCharacterIndexAtPoint: + return TextInputClientMac::GetInstance()->GetCharacterIndexAtPoint( + rwh, gfx::Point(2, 2)); + case FunctionToTest::kGetFirstRectForRange: + return TextInputClientMac::GetInstance()->GetFirstRectForRange( + rwh, gfx::Range(NSMakeRange(0, 32))); + } + } + // Flush any tasks posted to the IO thread and their reply tasks. void FlushIOThreadAndReplies() { GetIOThreadTaskRunner()->PostTaskAndReply( @@ -287,133 +320,117 @@ Delegate& request_delegate() { return *request_delegate_; } private: - raw_ptr<Delegate> request_delegate_; + FunctionToTest function_to_test_; + base::test::ScopedFeatureList feature_list_; + raw_ptr<Delegate> request_delegate_ = nullptr; }; -TYPED_TEST_SUITE_P(TextInputClientMacTest); +using TextInputClientMacTimeoutTest = TextInputClientMacTest; + +INSTANTIATE_TEST_SUITE_P( + All, + TextInputClientMacTest, + Combine(Values(FunctionToTest::kGetCharacterIndexAtPoint, + FunctionToTest::kGetFirstRectForRange), + Values(TimeoutParam::kLongTimeout))); + +INSTANTIATE_TEST_SUITE_P( + All, + TextInputClientMacTimeoutTest, + Combine(Values(FunctionToTest::kGetCharacterIndexAtPoint, + FunctionToTest::kGetFirstRectForRange), + Values(TimeoutParam::kShortTimeout))); } // namespace // Test Cases ////////////////////////////////////////////////////////////////// -TYPED_TEST_P(TextInputClientMacTest, SyncGetter_NoFocus) { - using Traits = TestTraits<TypeParam>; - +TEST_P(TextInputClientMacTest, SyncGetter_NoFocus) { // Return this value if the client (incorrectly) sends a request to an // unfocused frame. - this->request_delegate().AddResponse(Traits::CreateResponse(42), kTaskDelay); - EXPECT_EQ(Traits::TextInputClientGetSync(this->widget()), TypeParam{}); + request_delegate().AddResponse(CreateResponse(42)); + EXPECT_EQ(TextInputClientGetSync(widget()), NoFocusResponse()); } -TYPED_TEST_P(TextInputClientMacTest, SyncGetter_Basic) { - using Traits = TestTraits<TypeParam>; +TEST_P(TextInputClientMacTest, SyncGetter_Basic) { + const ResponseType kSuccessValue = CreateResponse(42); + request_delegate().AddResponse(kSuccessValue); - constexpr TypeParam kSuccessValue = Traits::CreateResponse(42); - this->request_delegate().AddResponse(kSuccessValue, kTaskDelay); - - this->FocusWebContentsOnMainFrame(); - EXPECT_EQ(Traits::TextInputClientGetSync(this->widget()), kSuccessValue); + FocusWebContentsOnMainFrame(); + EXPECT_EQ(TextInputClientGetSync(widget()), kSuccessValue); } -TYPED_TEST_P(TextInputClientMacTest, SyncGetter_Timeout) { - using Traits = TestTraits<TypeParam>; - - base::test::ScopedFeatureList feature_list; - feature_list.InitAndEnableFeatureWithParameters(features::kTextInputClient, - {{"ipc_timeout", "300ms"}}); - - this->FocusWebContentsOnMainFrame(); - EXPECT_EQ(Traits::TextInputClientGetSync(this->widget()), - Traits::kTimeoutResponse); +TEST_P(TextInputClientMacTimeoutTest, SyncGetter_Timeout) { + FocusWebContentsOnMainFrame(); + EXPECT_EQ(TextInputClientGetSync(widget()), TimeoutResponse()); } // Tests that TextInputClient doesn't get confused if TextInputHost sends a // response that's also used if a request times out. (eg. // GetCharacterIndexAtPoint() can return NSNotFound, which is UINT32_MAX.) -TYPED_TEST_P(TextInputClientMacTest, SyncGetter_NotFound) { - using Traits = TestTraits<TypeParam>; - +TEST_P(TextInputClientMacTest, SyncGetter_NotFound) { // Set an arbitrary value to ensure the response doesn't just default to the // timeout value. - const TypeParam kPreviousValue = Traits::CreateResponse(42); - this->request_delegate().AddResponse(kPreviousValue, kTaskDelay); + const ResponseType kPreviousValue = CreateResponse(42); + request_delegate().AddResponse(kPreviousValue); // Set the response to the timeout value after the previous setting. - this->request_delegate().AddResponse(Traits::kTimeoutResponse, kTaskDelay); + request_delegate().AddResponse(TimeoutResponse()); - this->FocusWebContentsOnMainFrame(); - EXPECT_EQ(Traits::TextInputClientGetSync(this->widget()), kPreviousValue); - EXPECT_EQ(Traits::TextInputClientGetSync(this->widget()), - Traits::kTimeoutResponse); + FocusWebContentsOnMainFrame(); + EXPECT_EQ(TextInputClientGetSync(widget()), kPreviousValue); + EXPECT_EQ(TextInputClientGetSync(widget()), TimeoutResponse()); } // Tests that TextInputClient doesn't get confused if TextInputHost sends a // response before the calling thread blocks. -TYPED_TEST_P(TextInputClientMacTest, SyncGetter_Immediate) { - using Traits = TestTraits<TypeParam>; - +TEST_P(TextInputClientMacTest, SyncGetter_Immediate) { // A response with 0 delay is sent immediately, not posted. - const TypeParam kSuccessValue = Traits::CreateResponse(42); - this->request_delegate().AddResponse(kSuccessValue, base::TimeDelta()); + const ResponseType kSuccessValue = CreateResponse(42); + request_delegate().AddResponse(kSuccessValue, base::TimeDelta()); - this->FocusWebContentsOnMainFrame(); - EXPECT_EQ(Traits::TextInputClientGetSync(this->widget()), kSuccessValue); + FocusWebContentsOnMainFrame(); + EXPECT_EQ(TextInputClientGetSync(widget()), kSuccessValue); } // Tests that TextInputClient ignores replies that arrive after it times out. -TYPED_TEST_P(TextInputClientMacTest, SyncGetter_StaleResult) { - using Traits = TestTraits<TypeParam>; +TEST_P(TextInputClientMacTimeoutTest, SyncGetter_StaleResult) { + const ResponseType kStaleValue = CreateResponse(42); + const ResponseType kSuccessValue = CreateResponse(84); - const TypeParam kStaleValue = Traits::CreateResponse(42); - const TypeParam kSuccessValue = Traits::CreateResponse(84); - - // With kTaskDelay = 200ms and timeout = 300ms: + // The timeout must be set to 1.5 * delay. + // eg. With delay = 200ms and timeout = 300ms: // T=0: First request sent. // T=300ms: First request times out, second request sent. // T=400ms: First reply arrives. (Should be ignored.) // T=500ms: Second reply arrives, 200ms after the request. // T=600ms: Second request times out. (Shouldn't reach here, but see below...) + const base::TimeDelta delay = TestTimeouts::tiny_timeout(); + ASSERT_EQ(features::kTextInputClientIPCTimeout.Get(), delay * 1.5); - base::test::ScopedFeatureList feature_list; - feature_list.InitAndEnableFeatureWithParameters(features::kTextInputClient, - {{"ipc_timeout", "300ms"}}); + FocusWebContentsOnMainFrame(); - this->FocusWebContentsOnMainFrame(); - - // Because the timeout is so close to kTaskDelay, with unlucky thread + // Because the timeout is so close to `delay`, with unlucky thread // scheduling it's possible for the first response to arrive before the // timeout is checked, or for the second response to be delayed until after // the timeout. Raising the timeout would make the whole test unreasonably // slow, though. So on an unexpected timeout response, repeat the test. - TypeParam first_response; - TypeParam second_response; + ResponseType first_response; + ResponseType second_response; do { // Clear out responses from last time through the loop. - this->FlushIOThreadAndReplies(); - ASSERT_EQ(this->request_delegate().NumResponses(), 0u); + FlushIOThreadAndReplies(); + ASSERT_EQ(request_delegate().NumResponses(), 0u); - this->request_delegate().AddResponse(kStaleValue, kTaskDelay * 2); - this->request_delegate().AddResponse(kSuccessValue, kTaskDelay); + request_delegate().AddResponse(kStaleValue, delay * 2); + request_delegate().AddResponse(kSuccessValue, delay); - first_response = Traits::TextInputClientGetSync(this->widget()); - second_response = Traits::TextInputClientGetSync(this->widget()); - } while (first_response != Traits::kTimeoutResponse || - second_response == Traits::kTimeoutResponse); - EXPECT_EQ(first_response, Traits::kTimeoutResponse); // Replaces kStaleValue. + first_response = TextInputClientGetSync(widget()); + second_response = TextInputClientGetSync(widget()); + } while (first_response != TimeoutResponse() || + second_response == TimeoutResponse()); + EXPECT_EQ(first_response, TimeoutResponse()); // Replaces kStaleValue. EXPECT_EQ(second_response, kSuccessValue); } -REGISTER_TYPED_TEST_SUITE_P(TextInputClientMacTest, - SyncGetter_NoFocus, - SyncGetter_Basic, - SyncGetter_Timeout, - SyncGetter_NotFound, - SyncGetter_Immediate, - SyncGetter_StaleResult); - -using ResponseTypes = ::testing::Types<uint32_t, gfx::Rect>; -INSTANTIATE_TYPED_TEST_SUITE_P(ResponseType, - TextInputClientMacTest, - ResponseTypes); - } // namespace content
diff --git a/content/public/android/java/src/org/chromium/content/browser/framehost/RenderFrameHostImpl.java b/content/public/android/java/src/org/chromium/content/browser/framehost/RenderFrameHostImpl.java index 5185da1..1df84ff 100644 --- a/content/public/android/java/src/org/chromium/content/browser/framehost/RenderFrameHostImpl.java +++ b/content/public/android/java/src/org/chromium/content/browser/framehost/RenderFrameHostImpl.java
@@ -334,7 +334,9 @@ RenderFrameHost getMainFrame(long nativeRenderFrameHostAndroid); void getCanonicalUrlForSharing( - long nativeRenderFrameHostAndroid, Callback<@Nullable GURL> callback); + long nativeRenderFrameHostAndroid, + @JniType("base::OnceCallback<void(const std::optional<GURL>&)>") + Callback<@Nullable GURL> callback); @JniType("std::vector") List<RenderFrameHost> getAllRenderFrameHosts(long nativeRenderFrameHostAndroid); @@ -387,7 +389,8 @@ int getLifecycleState(long nativeRenderFrameHostAndroid); void insertVisualStateCallback( - long nativeRenderFrameHostAndroid, Callback<Boolean> callback); + long nativeRenderFrameHostAndroid, + @JniType("base::OnceCallback<void(bool)>") Callback<Boolean> callback); void executeJavaScriptInIsolatedWorld( long nativeRenderFrameHostAndroid,
diff --git a/content/public/common/bindings_policy.h b/content/public/common/bindings_policy.h index aab7bdf..e506188e 100644 --- a/content/public/common/bindings_policy.h +++ b/content/public/common/bindings_policy.h
@@ -31,9 +31,14 @@ // These bindings must not be exposed to normal web content. kWebUiHistograms, + // HTML-based UI bindings that allow access to the chrome.slimWebViewInternal + // API. + // These bindings must not be exposed to normal web content. + kSlimWebView, + // Other types of bindings in the future can go here. - kLastValue = kWebUiHistograms, + kLastValue = kSlimWebView, }; using BindingsPolicySet = base::EnumSet<BindingsPolicyValue, @@ -42,8 +47,8 @@ // The set of WebUI bindings. inline constexpr BindingsPolicySet kWebUIBindingsPolicySet = - BindingsPolicySet::FromRange(BindingsPolicyValue::kWebUi, - BindingsPolicyValue::kWebUiHistograms); + BindingsPolicySet::FromRange(BindingsPolicyValue::kFirstValue, + BindingsPolicyValue::kLastValue); } // namespace content
diff --git a/content/renderer/java/gin_java_bridge_value_converter.cc b/content/renderer/java/gin_java_bridge_value_converter.cc index 379ea333..91613cf8 100644 --- a/content/renderer/java/gin_java_bridge_value_converter.cc +++ b/content/renderer/java/gin_java_bridge_value_converter.cc
@@ -13,6 +13,7 @@ #include "base/check.h" #include "base/compiler_specific.h" +#include "base/containers/span.h" #include "base/memory/ptr_util.h" #include "base/values.h" #include "content/common/android/gin_java_bridge_value.h" @@ -65,7 +66,7 @@ virtual ~TypedArraySerializer() {} static std::unique_ptr<TypedArraySerializer> Create( v8::Local<v8::TypedArray> typed_array); - virtual void serializeTo(char* data, + virtual void serializeTo(uint8_t* data, size_t data_length, base::ListValue* out) = 0; @@ -85,7 +86,7 @@ TypedArraySerializerImpl(const TypedArraySerializerImpl&) = delete; TypedArraySerializerImpl& operator=(const TypedArraySerializerImpl&) = delete; - void serializeTo(char* data, + void serializeTo(uint8_t* data, size_t data_length, base::ListValue* out) override { DCHECK_EQ(data_length, typed_array_->Length() * sizeof(ElementType)); @@ -146,14 +147,12 @@ return true; } - char* data = nullptr; - size_t data_length = 0; + base::span<uint8_t> data; gin::ArrayBufferView view; if (ConvertFromV8(isolate, value.As<v8::ArrayBufferView>(), &view)) { - data = reinterpret_cast<char*>(view.bytes()); - data_length = view.num_bytes(); + data = view.span(); } - if (!data) { + if (data.empty()) { *out = GinJavaBridgeValue::CreateUndefinedValue(); return true; } @@ -161,7 +160,7 @@ base::ListValue result; std::unique_ptr<TypedArraySerializer> serializer( TypedArraySerializer::Create(value.As<v8::TypedArray>())); - serializer->serializeTo(data, data_length, &result); + serializer->serializeTo(data.data(), data.size(), &result); *out = std::make_unique<base::Value>(std::move(result)); return true; }
diff --git a/content/test/gpu/gpu_tests/gpu_process_integration_test.py b/content/test/gpu/gpu_tests/gpu_process_integration_test.py index 9482853..4cbd75ec 100644 --- a/content/test/gpu/gpu_tests/gpu_process_integration_test.py +++ b/content/test/gpu/gpu_tests/gpu_process_integration_test.py
@@ -54,6 +54,7 @@ class GpuProcessIntegrationTest(gpu_integration_test.GpuIntegrationTest): + @classmethod def Name(cls) -> str: """The name by which this test is invoked on the command line.""" @@ -149,7 +150,8 @@ def _WaitForTestCompletion(self, tab: ct.Tab) -> None: tab.action_runner.WaitForJavaScriptCondition( - 'window.domAutomationController._finished', timeout=10) + 'window.domAutomationController._finished', + timeout=(20 if self.browser.platform.GetOSName() == 'fuchsia' else 10)) if not tab.EvaluateJavaScript('window.domAutomationController._succeeded'): self.fail('Test reported that it failed') @@ -727,8 +729,8 @@ self.fail('High-performance WebGL context did not activate the ' 'high-performance GPU') - def _GpuProcess_mac_webgl_backgrounded_high_performance(self, test_path: str - ) -> None: + def _GpuProcess_mac_webgl_backgrounded_high_performance( + self, test_path: str) -> None: # Ensures that high-performance WebGL content in a background tab releases # the hold on the discrete GPU after 10 seconds. if not self.IsDualGPUMacLaptop(): @@ -807,9 +809,8 @@ @classmethod def ExpectationsFiles(cls) -> list[str]: return [ - os.path.join( - os.path.dirname(os.path.abspath(__file__)), 'test_expectations', - 'gpu_process_expectations.txt') + os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'test_expectations', 'gpu_process_expectations.txt') ]
diff --git a/content/web_test/renderer/test_runner.cc b/content/web_test/renderer/test_runner.cc index c83813be..dfb183a 100644 --- a/content/web_test/renderer/test_runner.cc +++ b/content/web_test/renderer/test_runner.cc
@@ -6,7 +6,6 @@ #include <stddef.h> -#include <algorithm> #include <clocale> #include <limits> #include <string_view> @@ -3562,9 +3561,9 @@ } void TestRunner::SetAudioData(const gin::ArrayBufferView& view) { - uint8_t* bytes = static_cast<uint8_t*>(view.bytes()); - audio_data_.resize(view.num_bytes()); - std::copy(bytes, UNSAFE_TODO(bytes + view.num_bytes()), audio_data_.begin()); + base::span<const uint8_t> src = view.span(); + audio_data_.resize(src.size()); + base::span(audio_data_).copy_from_nonoverlapping(src); dump_as_audio_ = true; }
diff --git a/extensions/browser/api/automation_internal/automation_event_router.cc b/extensions/browser/api/automation_internal/automation_event_router.cc index 55370a2b..09d4f4ac 100644 --- a/extensions/browser/api/automation_internal/automation_event_router.cc +++ b/extensions/browser/api/automation_internal/automation_event_router.cc
@@ -17,6 +17,7 @@ #include "extensions/browser/api/extensions_api_client.h" #include "extensions/browser/event_router.h" #include "extensions/browser/process_manager.h" +#include "extensions/browser/service_worker/worker_id.h" #include "extensions/common/api/automation_internal.h" #include "extensions/common/extension.h" #include "extensions/common/extension_id.h" @@ -247,7 +248,7 @@ std::vector<WorkerId> all_worker_ids = process_manager->GetServiceWorkersForExtension(extension_id); for (const WorkerId& worker_id : all_worker_ids) { - if (worker_id.render_process_id != listener_rph_id) { + if (worker_id.render_process_id.GetUnsafeValue() != listener_rph_id) { continue; } @@ -316,7 +317,7 @@ process_manager->GetServiceWorkersForExtension(extension_id); for (const WorkerId& worker_id : all_worker_ids) { - if (worker_id.render_process_id != rph_id) { + if (worker_id.render_process_id.GetUnsafeValue() != rph_id) { continue; } const auto& request_uuid_iter =
diff --git a/extensions/browser/api/messaging/channel_endpoint.cc b/extensions/browser/api/messaging/channel_endpoint.cc index df5b95e7..03aea98 100644 --- a/extensions/browser/api/messaging/channel_endpoint.cc +++ b/extensions/browser/api/messaging/channel_endpoint.cc
@@ -7,6 +7,7 @@ #include "content/public/browser/child_process_host.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" +#include "content/public/common/child_process_id.h" #include "extensions/browser/process_manager.h" #include "extensions/common/constants.h" @@ -50,7 +51,8 @@ WorkerId ChannelEndpoint::GetWorkerId() const { DCHECK(port_context_.is_for_service_worker()); - return {port_context_.worker->extension_id, render_process_id_, + return {port_context_.worker->extension_id, + content::ChildProcessId::FromUnsafeValue(render_process_id_), port_context_.worker->version_id, port_context_.worker->thread_id}; }
diff --git a/extensions/browser/api/messaging/extension_message_port.cc b/extensions/browser/api/messaging/extension_message_port.cc index 8d6593d..6d23ba89 100644 --- a/extensions/browser/api/messaging/extension_message_port.cc +++ b/extensions/browser/api/messaging/extension_message_port.cc
@@ -33,6 +33,7 @@ #include "extensions/browser/process_manager.h" #include "extensions/browser/process_manager_observer.h" #include "extensions/browser/service_worker/service_worker_host.h" +#include "extensions/browser/service_worker/worker_id.h" #include "extensions/common/api/messaging/message.h" #include "extensions/common/api/messaging/messaging_endpoint.h" #include "extensions/common/extension_id.h" @@ -538,16 +539,16 @@ "ForWorker", channel_type); - PortContext port_context = - PortContext::ForWorker(worker.thread_id, worker.version_id, - worker.render_process_id, worker.extension_id); + PortContext port_context = PortContext::ForWorker( + worker.thread_id, worker.version_id, + worker.render_process_id.GetUnsafeValue(), worker.extension_id); auto& receiver = service_workers_[worker]; receiver.Bind(message_port.InitWithNewEndpointAndPassRemote()); receiver.set_disconnect_handler(base::BindOnce( &ExtensionMessagePort::Prune, base::Unretained(this), port_context, open_channel_dispatch_for_worker_tracking_id)); AddReceiver(message_port_host.InitWithNewEndpointAndPassReceiver(), - worker.render_process_id, port_context); + worker.render_process_id.GetUnsafeValue(), port_context); pending_contexts_to_respond_.insert(port_context); @@ -808,7 +809,7 @@ // worker we are interested in. Since there will only be a handful of such // workers, this is OK. for (auto iter = service_workers_.begin(); iter != service_workers_.end();) { - if (iter->first.render_process_id == render_process_id && + if (iter->first.render_process_id.GetUnsafeValue() == render_process_id && iter->first.thread_id == worker_thread_id) { service_workers_.erase(iter); break;
diff --git a/extensions/browser/api/messaging/message_service.cc b/extensions/browser/api/messaging/message_service.cc index ad78813..355e183f 100644 --- a/extensions/browser/api/messaging/message_service.cc +++ b/extensions/browser/api/messaging/message_service.cc
@@ -53,6 +53,7 @@ #include "extensions/browser/pref_names.h" #include "extensions/browser/process_manager.h" #include "extensions/browser/process_manager_factory.h" +#include "extensions/browser/service_worker/worker_id.h" #include "extensions/common/api/messaging/messaging_endpoint.h" #include "extensions/common/api/messaging/port_context.h" #include "extensions/common/extension.h" @@ -385,10 +386,11 @@ absl::Overload{ [&](const WorkerId& worker) { return ChannelEndpoint( - context, worker.render_process_id, - PortContext::ForWorker(worker.thread_id, worker.version_id, - worker.render_process_id, - worker.extension_id)); + context, worker.render_process_id.GetUnsafeValue(), + PortContext::ForWorker( + worker.thread_id, worker.version_id, + worker.render_process_id.GetUnsafeValue(), + worker.extension_id)); }, [&](const content::RenderFrameHost* render_frame_host) { return ChannelEndpoint(
diff --git a/extensions/browser/event_listener_map.cc b/extensions/browser/event_listener_map.cc index a47ce2c..a559b7f6 100644 --- a/extensions/browser/event_listener_map.cc +++ b/extensions/browser/event_listener_map.cc
@@ -309,8 +309,7 @@ EventListener* listener) { return listener->extension_id() == worker_id.extension_id && listener->is_for_service_worker() && !listener->IsLazy() && - listener->process()->GetDeprecatedID() == - worker_id.render_process_id; + listener->process()->GetID() == worker_id.render_process_id; }, worker_id)); }
diff --git a/extensions/browser/event_router.cc b/extensions/browser/event_router.cc index 8a55636c..c3324ee 100644 --- a/extensions/browser/event_router.cc +++ b/extensions/browser/event_router.cc
@@ -29,6 +29,7 @@ #include "content/public/browser/render_process_host.h" #include "content/public/browser/service_worker_context.h" #include "content/public/browser/storage_partition.h" +#include "content/public/common/child_process_id.h" #include "extensions/browser/api_activity_monitor.h" #include "extensions/browser/bad_message.h" #include "extensions/browser/event_router_factory.h" @@ -268,7 +269,7 @@ callback = base::BindOnce( &EventRouter::DecrementInFlightEventsForServiceWorker, weak_factory_.GetWeakPtr(), - WorkerId{GenerateExtensionIdFromHostId(host_id), rph->GetDeprecatedID(), + WorkerId{GenerateExtensionIdFromHostId(host_id), rph->GetID(), service_worker_version_id, worker_thread_id}, event_id); } else if (BackgroundInfo::HasBackgroundPage(extension)) { @@ -1107,7 +1108,7 @@ callback = base::BindOnce(&EventRouter::DecrementInFlightEventsForServiceWorker, weak_factory_.GetWeakPtr(), - WorkerId{extension_id, process->GetDeprecatedID(), + WorkerId{extension_id, process->GetID(), service_worker_version_id, worker_thread_id}, event_id); } else if (BackgroundInfo::HasBackgroundPage(extension)) { @@ -1456,8 +1457,8 @@ storage_partition ? storage_partition->GetServiceWorkerContext() : nullptr; event_ack_data_.ClearUnackedEventsForWorker( - service_worker_context, worker_id.render_process_id, worker_id.version_id, - worker_id.thread_id); + service_worker_context, worker_id.render_process_id.GetUnsafeValue(), + worker_id.version_id, worker_id.thread_id); } void EventRouter::AddLazyEventListenerImpl(
diff --git a/extensions/browser/extension_function_dispatcher.cc b/extensions/browser/extension_function_dispatcher.cc index ed76a234..453f2838 100644 --- a/extensions/browser/extension_function_dispatcher.cc +++ b/extensions/browser/extension_function_dispatcher.cc
@@ -32,6 +32,7 @@ #include "content/public/browser/service_worker_context.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_observer.h" +#include "content/public/common/child_process_id.h" #include "content/public/common/result_codes.h" #include "content/public/common/url_constants.h" #include "extensions/browser/api_activity_monitor.h" @@ -46,6 +47,7 @@ #include "extensions/browser/quota_service.h" #include "extensions/browser/script_injection_tracker.h" #include "extensions/browser/service_worker/service_worker_keepalive.h" +#include "extensions/browser/service_worker/worker_id.h" #include "extensions/common/constants.h" #include "extensions/common/extension_api.h" #include "extensions/common/extension_features.h" @@ -232,9 +234,10 @@ return; } - WorkerId worker_id{params->extension_id, render_process_id, - params->service_worker_version_id, - params->worker_thread_id}; + WorkerId worker_id{ + params->extension_id, + content::ChildProcessId::FromUnsafeValue(render_process_id), + params->service_worker_version_id, params->worker_thread_id}; // Ignore if the worker has already stopped. if (!ProcessManager::Get(browser_context_)->HasServiceWorker(worker_id)) { std::move(callback).Run(/*kFailed=*/true, base::ListValue(), "No SW", @@ -585,7 +588,8 @@ WorkerId worker_id; worker_id.thread_id = params_without_args.worker_thread_id; worker_id.version_id = params_without_args.service_worker_version_id; - worker_id.render_process_id = requesting_process_id; + worker_id.render_process_id = + content::ChildProcessId::FromUnsafeValue(requesting_process_id); worker_id.extension_id = extension->id(); function->set_worker_id(std::move(worker_id)); } else {
diff --git a/extensions/browser/process_manager.cc b/extensions/browser/process_manager.cc index 26460c7..b34a952 100644 --- a/extensions/browser/process_manager.cc +++ b/extensions/browser/process_manager.cc
@@ -47,6 +47,7 @@ #include "extensions/browser/process_manager_observer.h" #include "extensions/browser/renderer_startup_helper.h" #include "extensions/browser/service_worker/service_worker_task_queue.h" +#include "extensions/browser/service_worker/worker_id.h" #include "extensions/browser/view_type_utils.h" #include "extensions/common/constants.h" #include "extensions/common/extension.h" @@ -974,13 +975,12 @@ worker_context_ids_[worker_id] = base::Uuid::GenerateRandomV4(); // Observe the RenderProcessHost for cleaning up on process shutdown. - int render_process_id = worker_id.render_process_id; - bool inserted = worker_process_to_extension_ids_[render_process_id] + bool inserted = worker_process_to_extension_ids_[worker_id.render_process_id] .insert(worker_id.extension_id) .second; if (inserted) { content::RenderProcessHost* render_process_host = - content::RenderProcessHost::FromID(render_process_id); + content::RenderProcessHost::FromID(worker_id.render_process_id); DCHECK(render_process_host); if (!process_observations_.IsObservingSource(render_process_host)) { // These will be cleaned up in RenderProcessExited(). @@ -996,7 +996,7 @@ const content::ChildProcessTerminationInfo& info) { DCHECK(process_observations_.IsObservingSource(host)); process_observations_.RemoveObservation(host); - const int render_process_id = host->GetDeprecatedID(); + const content::ChildProcessId render_process_id = host->GetID(); // Look up and then clean up the entries that are affected by // |render_process_id| destruction. //
diff --git a/extensions/browser/process_manager.h b/extensions/browser/process_manager.h index afb3033..1cfc4c7f 100644 --- a/extensions/browser/process_manager.h +++ b/extensions/browser/process_manager.h
@@ -25,6 +25,7 @@ #include "content/public/browser/render_process_host_observer.h" #include "content/public/browser/service_worker_external_request_result.h" #include "content/public/browser/service_worker_external_request_timeout_type.h" +#include "content/public/common/child_process_id.h" #include "extensions/browser/activity.h" #include "extensions/browser/extension_host_observer.h" #include "extensions/browser/extension_registry_observer.h" @@ -416,7 +417,8 @@ process_observations_{this}; // Maps render render_process_id -> extension_id for all Service Workers this // ProcessManager manages. - std::map<int, std::set<ExtensionId>> worker_process_to_extension_ids_; + std::map<content::ChildProcessId, std::set<ExtensionId>> + worker_process_to_extension_ids_; // A map of the active service worker keepalives. ServiceWorkerKeepaliveDataMap service_worker_keepalives_;
diff --git a/extensions/browser/service_worker/service_worker_host.cc b/extensions/browser/service_worker/service_worker_host.cc index 632771e..b13b311 100644 --- a/extensions/browser/service_worker/service_worker_host.cc +++ b/extensions/browser/service_worker/service_worker_host.cc
@@ -13,6 +13,7 @@ #include "content/public/browser/service_worker_context.h" #include "content/public/browser/service_worker_external_request_result.h" #include "content/public/browser/storage_partition.h" +#include "content/public/common/child_process_id.h" #include "extensions/browser/bad_message.h" #include "extensions/browser/event_router.h" #include "extensions/browser/extension_function_dispatcher.h" @@ -22,6 +23,7 @@ #include "extensions/browser/permissions_manager.h" #include "extensions/browser/process_map.h" #include "extensions/browser/service_worker/service_worker_task_queue.h" +#include "extensions/browser/service_worker/worker_id.h" #include "extensions/common/api/messaging/port_context.h" #include "extensions/common/constants.h" #include "extensions/common/mojom/frame.mojom.h" @@ -169,9 +171,10 @@ return; } - int render_process_id = render_process_host_->GetDeprecatedID(); + content::ChildProcessId render_process_id = render_process_host_->GetID(); auto* process_map = ProcessMap::Get(browser_context); - if (!process_map || !process_map->Contains(extension_id, render_process_id)) { + if (!process_map || !process_map->Contains( + extension_id, render_process_id.GetUnsafeValue())) { // We check the process in addition to the registry to guard against // situations in which an extension may still be enabled, but no longer // running in a given process. @@ -187,7 +190,8 @@ render_process_id, extension_id, service_worker_version_id, worker_thread_id, service_worker_token); EventRouter::Get(browser_context) - ->BindServiceWorkerEventDispatcher(render_process_id, worker_thread_id, + ->BindServiceWorkerEventDispatcher(render_process_id.GetUnsafeValue(), + worker_thread_id, std::move(event_dispatcher)); } @@ -204,9 +208,10 @@ } DCHECK_NE(kMainThreadId, worker_thread_id); - int render_process_id = render_process_host_->GetDeprecatedID(); + content::ChildProcessId render_process_id = render_process_host_->GetID(); auto* process_map = ProcessMap::Get(browser_context); - if (!process_map || !process_map->Contains(extension_id, render_process_id)) { + if (!process_map || !process_map->Contains( + extension_id, render_process_id.GetUnsafeValue())) { // We can legitimately get here if the extension was already unloaded. return; } @@ -232,9 +237,10 @@ } DCHECK_NE(kMainThreadId, worker_thread_id); - int render_process_id = render_process_host_->GetDeprecatedID(); + content::ChildProcessId render_process_id = render_process_host_->GetID(); auto* process_map = ProcessMap::Get(browser_context); - if (!process_map || !process_map->Contains(extension_id, render_process_id)) { + if (!process_map || !process_map->Contains( + extension_id, render_process_id.GetUnsafeValue())) { // We can legitimately get here if the extension was already unloaded. return; } @@ -257,9 +263,9 @@ return; } - dispatcher_->DispatchForServiceWorker(std::move(params), - render_process_host_->GetDeprecatedID(), - std::move(callback)); + dispatcher_->DispatchForServiceWorker( + std::move(params), render_process_host_->GetID().GetUnsafeValue(), + std::move(callback)); } void ServiceWorkerHost::WorkerResponseAck(const base::Uuid& request_uuid) {
diff --git a/extensions/browser/service_worker/service_worker_state.cc b/extensions/browser/service_worker/service_worker_state.cc index a6496f0c..60a3405e 100644 --- a/extensions/browser/service_worker/service_worker_state.cc +++ b/extensions/browser/service_worker/service_worker_state.cc
@@ -6,7 +6,9 @@ #include "base/metrics/histogram_macros.h" #include "content/public/browser/render_process_host.h" +#include "content/public/common/child_process_id.h" #include "extensions/browser/process_manager.h" +#include "extensions/browser/service_worker/worker_id.h" #include "extensions/common/extension.h" #include "extensions/common/extension_features.h" @@ -127,7 +129,9 @@ << "Worker was already loaded"; const ExtensionId& extension_id = context_id.extension_id; - const WorkerId worker_id = {extension_id, process_id, version_id, thread_id}; + const WorkerId worker_id = { + extension_id, content::ChildProcessId::FromUnsafeValue(process_id), + version_id, thread_id}; // HACK: The service worker layer might invoke this callback with an ID for a // RenderProcessHost that has already terminated. This isn't the right fix for @@ -137,7 +141,7 @@ // The proper fix here is that the service worker layer shouldn't be invoking // this callback with stale processes. // https://crbug.com/1335821. - if (!content::RenderProcessHost::FromID(process_id)) { + if (!content::RenderProcessHost::FromID(worker_id.render_process_id)) { // This is definitely hit, and often enough that we can't NOTREACHED(), // CHECK(), or DumpWithoutCrashing(). Instead, log an error and gracefully // return.
diff --git a/extensions/browser/service_worker/service_worker_task_queue.cc b/extensions/browser/service_worker/service_worker_task_queue.cc index f8e3cb3..7154f60c 100644 --- a/extensions/browser/service_worker/service_worker_task_queue.cc +++ b/extensions/browser/service_worker/service_worker_task_queue.cc
@@ -38,6 +38,7 @@ #include "extensions/browser/process_manager.h" #include "extensions/browser/renderer_startup_helper.h" #include "extensions/browser/service_worker/service_worker_task_queue_factory.h" +#include "extensions/browser/service_worker/worker_id.h" #include "extensions/common/constants.h" #include "extensions/common/extension_features.h" #include "extensions/common/extension_id.h" @@ -122,7 +123,7 @@ } void ServiceWorkerTaskQueue::RendererDidInitializeServiceWorkerContext( - int render_process_id, + content::ChildProcessId render_process_id, const ExtensionId& extension_id, int64_t service_worker_version_id, int thread_id, @@ -142,8 +143,8 @@ // active. CHECK(process_host); - util::InitializeFileSchemeAccessForExtension(render_process_id, extension_id, - browser_context_); + util::InitializeFileSchemeAccessForExtension( + render_process_id.GetUnsafeValue(), extension_id, browser_context_); // TODO(jlulejian): Do we need to start tracking this in initialization or // could we start in `RendererDidStartServiceWorkerContext()` instead since // this is for a running (started) worker? @@ -160,7 +161,7 @@ } void ServiceWorkerTaskQueue::RendererDidStartServiceWorkerContext( - int render_process_id, + content::ChildProcessId render_process_id, const ExtensionId& extension_id, const base::UnguessableToken& activation_token, const GURL& service_worker_scope, @@ -189,7 +190,7 @@ } void ServiceWorkerTaskQueue::RendererDidStopServiceWorkerContext( - int render_process_id, + content::ChildProcessId render_process_id, const ExtensionId& extension_id, const base::UnguessableToken& activation_token, const GURL& service_worker_scope,
diff --git a/extensions/browser/service_worker/service_worker_task_queue.h b/extensions/browser/service_worker/service_worker_task_queue.h index 12e3213..b06094d 100644 --- a/extensions/browser/service_worker/service_worker_task_queue.h +++ b/extensions/browser/service_worker/service_worker_task_queue.h
@@ -20,6 +20,7 @@ #include "components/keyed_service/core/keyed_service.h" #include "content/public/browser/service_worker_context.h" #include "content/public/browser/service_worker_context_observer.h" +#include "content/public/common/child_process_id.h" #include "extensions/browser/lazy_context_id.h" #include "extensions/browser/lazy_context_task_queue.h" #include "extensions/browser/service_worker/sequenced_context_id.h" @@ -183,7 +184,7 @@ // Called once an extension Service Worker context was initialized but not // necessarily started executing its JavaScript. void RendererDidInitializeServiceWorkerContext( - int render_process_id, + content::ChildProcessId render_process_id, const ExtensionId& extension_id, int64_t service_worker_version_id, int thread_id, @@ -192,7 +193,7 @@ // This can be thought as "loadstop", i.e. the global JS script of the worker // has completed executing. void RendererDidStartServiceWorkerContext( - int render_process_id, + content::ChildProcessId render_process_id, const ExtensionId& extension_id, const base::UnguessableToken& activation_token, const GURL& service_worker_scope, @@ -200,7 +201,7 @@ int thread_id); // Called once an extension Service Worker was destroyed. void RendererDidStopServiceWorkerContext( - int render_process_id, + content::ChildProcessId render_process_id, const ExtensionId& extension_id, const base::UnguessableToken& activation_token, const GURL& service_worker_scope,
diff --git a/extensions/browser/service_worker/worker_id.cc b/extensions/browser/service_worker/worker_id.cc index 855f741..d4f060b 100644 --- a/extensions/browser/service_worker/worker_id.cc +++ b/extensions/browser/service_worker/worker_id.cc
@@ -15,7 +15,7 @@ WorkerId::WorkerId() = default; WorkerId::WorkerId(const ExtensionId& extension_id, - int render_process_id, + content::ChildProcessId render_process_id, int64_t version_id, int thread_id) : extension_id(extension_id), @@ -24,7 +24,7 @@ thread_id(thread_id) {} WorkerId::WorkerId(const ExtensionId& extension_id, - int render_process_id, + content::ChildProcessId render_process_id, int64_t version_id, int thread_id, const blink::ServiceWorkerToken& start_token) @@ -56,7 +56,7 @@ std::ostream& operator<<(std::ostream& out, const WorkerId& id) { return out << "WorkerId{extension_id: " << id.extension_id - << ", process_id: " << id.render_process_id + << ", process_id: " << id.render_process_id.GetUnsafeValue() << ", service_worker_version_id: " << id.version_id << ", thread_id: " << id.thread_id << "}"; }
diff --git a/extensions/browser/service_worker/worker_id.h b/extensions/browser/service_worker/worker_id.h index d100480..d5b3ab2c 100644 --- a/extensions/browser/service_worker/worker_id.h +++ b/extensions/browser/service_worker/worker_id.h
@@ -10,6 +10,7 @@ #include <optional> #include <ostream> +#include "content/public/common/child_process_id.h" #include "extensions/common/extension_id.h" #include "third_party/blink/public/common/tokens/tokens.h" @@ -20,18 +21,18 @@ WorkerId(); WorkerId(const ExtensionId& extension_id, - int render_process_id, + content::ChildProcessId render_process_id, int64_t version_id, int thread_id); WorkerId(const ExtensionId& extension_id, - int render_process_id, + content::ChildProcessId render_process_id, int64_t version_id, int thread_id, const blink::ServiceWorkerToken& start_token); ExtensionId extension_id; - int render_process_id; + content::ChildProcessId render_process_id; int64_t version_id; int thread_id;
diff --git a/extensions/browser/service_worker/worker_id_set.cc b/extensions/browser/service_worker/worker_id_set.cc index 37f8079c..f08bbdd 100644 --- a/extensions/browser/service_worker/worker_id_set.cc +++ b/extensions/browser/service_worker/worker_id_set.cc
@@ -15,6 +15,7 @@ #include "content/public/browser/child_process_host.h" #include "content/public/browser/service_worker_context.h" #include "content/public/browser/service_worker_running_info.h" +#include "content/public/common/child_process_id.h" #include "extensions/browser/extension_util.h" #include "extensions/browser/service_worker/worker_id.h" #include "extensions/common/extension_id.h" @@ -127,8 +128,10 @@ // Construct a key that is guaranteed to be smaller than any key given a // |render_process_id|. This facilitates the usage of lower_bound to achieve // lg(n) runtime. - WorkerId lowest_id{extension_id, kSmallestRenderProcessId, kSmallestVersionId, - kSmallestThreadId}; + WorkerId lowest_id{ + extension_id, + content::ChildProcessId::FromUnsafeValue(kSmallestRenderProcessId), + kSmallestVersionId, kSmallestThreadId}; auto begin_range = workers_.lower_bound(lowest_id); if (begin_range == workers_.end() || begin_range->extension_id != extension_id) { @@ -160,7 +163,7 @@ std::vector<WorkerId> WorkerIdSet::GetAllForExtension( const ExtensionId& extension_id, - int render_process_id) const { + content::ChildProcessId render_process_id) const { // See other GetAllForExtension's notes for |id| construction. WorkerId id{extension_id, render_process_id, kSmallestVersionId, kSmallestThreadId};
diff --git a/extensions/browser/service_worker/worker_id_set.h b/extensions/browser/service_worker/worker_id_set.h index b7301c5..f958ec0f 100644 --- a/extensions/browser/service_worker/worker_id_set.h +++ b/extensions/browser/service_worker/worker_id_set.h
@@ -34,8 +34,9 @@ bool Contains(const WorkerId& worker_id) const; std::vector<WorkerId> GetAllForExtension( const ExtensionId& extension_id) const; - std::vector<WorkerId> GetAllForExtension(const ExtensionId& extension_id, - int render_process_id) const; + std::vector<WorkerId> GetAllForExtension( + const ExtensionId& extension_id, + content::ChildProcessId render_process_id) const; std::vector<WorkerId> GetAllForExtension(const ExtensionId& extension_id, int64_t worker_version_id) const;
diff --git a/extensions/browser/service_worker/worker_id_set_unittest.cc b/extensions/browser/service_worker/worker_id_set_unittest.cc index 067118d7..b1ff869 100644 --- a/extensions/browser/service_worker/worker_id_set_unittest.cc +++ b/extensions/browser/service_worker/worker_id_set_unittest.cc
@@ -8,6 +8,7 @@ #include <memory> #include <string> +#include "content/public/common/child_process_id.h" #include "extensions/browser/extensions_test.h" #include "extensions/common/extension_id.h" #include "testing/gtest/include/gtest/gtest.h" @@ -25,6 +26,10 @@ constexpr char kIdG[] = "gggggggggggggggggggggggggggggggg"; constexpr char kIdX[] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; +content::ChildProcessId Id(int id) { + return content::ChildProcessId::FromUnsafeValue(id); +} + // A vector based implementation of WorkerIdSet. // GetAllForExtension()/Contains() are O(n). class VectorWorkerIdListImpl { @@ -37,8 +42,9 @@ ~VectorWorkerIdListImpl() = default; - std::vector<WorkerId> GetAllForExtension(const ExtensionId& extension_id, - int render_process_id) const { + std::vector<WorkerId> GetAllForExtension( + const ExtensionId& extension_id, + content::ChildProcessId render_process_id) const { std::vector<WorkerId> matching_workers; for (const WorkerId& worker_id : workers_) { if (worker_id.extension_id == extension_id && @@ -59,12 +65,13 @@ std::vector<WorkerId> GenerateWorkerIds( const std::vector<ExtensionId>& extension_ids, - const std::vector<int>& render_process_ids, + const std::vector<content::ChildProcessId>& render_process_ids, const std::vector<int64_t>& worker_version_ids, const std::vector<int>& worker_thread_ids) { std::vector<WorkerId> worker_ids; for (const ExtensionId& extension_id : extension_ids) { - for (int render_process_id : render_process_ids) { + for (const content::ChildProcessId& render_process_id : + render_process_ids) { for (int64_t worker_version_id : worker_version_ids) { for (int worker_thread_id : worker_thread_ids) { worker_ids.push_back(WorkerId({extension_id, render_process_id, @@ -114,48 +121,50 @@ }; TEST_F(WorkerIdSetTest, GetAllForExtension) { - std::unique_ptr<WorkerIdSet> workers = CreateWorkerIdSet({{kIdA, 1, 100, 1}, - {kIdA, 1, 101, 2}, - {kIdA, 1, 102, 1}, - {kIdA, 1, 103, 3}, - {kIdB, 2, 100, 3}, - {kIdB, 2, 100, 4}, - {kIdC, 2, 110, 5}, - {kIdA, 9, 100, 4}}); + std::unique_ptr<WorkerIdSet> workers = + CreateWorkerIdSet({{kIdA, Id(1), 100, 1}, + {kIdA, Id(1), 101, 2}, + {kIdA, Id(1), 102, 1}, + {kIdA, Id(1), 103, 3}, + {kIdB, Id(2), 100, 3}, + {kIdB, Id(2), 100, 4}, + {kIdC, Id(2), 110, 5}, + {kIdA, Id(9), 100, 4}}); - EXPECT_TRUE(AreWorkerIdsEqual({{kIdA, 1, 100, 1}, - {kIdA, 1, 101, 2}, - {kIdA, 1, 102, 1}, - {kIdA, 1, 103, 3}}, - workers->GetAllForExtension(kIdA, 1))); - EXPECT_TRUE(AreWorkerIdsEqual({{kIdB, 2, 100, 4}, {kIdB, 2, 100, 3}}, - workers->GetAllForExtension(kIdB, 2))); - EXPECT_TRUE(AreWorkerIdsEqual({{kIdC, 2, 110, 5}}, - workers->GetAllForExtension(kIdC, 2))); - EXPECT_TRUE(AreWorkerIdsEqual({{kIdA, 9, 100, 4}}, - workers->GetAllForExtension(kIdA, 9))); + EXPECT_TRUE(AreWorkerIdsEqual({{kIdA, Id(1), 100, 1}, + {kIdA, Id(1), 101, 2}, + {kIdA, Id(1), 102, 1}, + {kIdA, Id(1), 103, 3}}, + workers->GetAllForExtension(kIdA, Id(1)))); + EXPECT_TRUE(AreWorkerIdsEqual({{kIdB, Id(2), 100, 4}, {kIdB, Id(2), 100, 3}}, + workers->GetAllForExtension(kIdB, Id(2)))); + EXPECT_TRUE(AreWorkerIdsEqual({{kIdC, Id(2), 110, 5}}, + workers->GetAllForExtension(kIdC, Id(2)))); + EXPECT_TRUE(AreWorkerIdsEqual({{kIdA, Id(9), 100, 4}}, + workers->GetAllForExtension(kIdA, Id(9)))); // No matches. - EXPECT_TRUE(workers->GetAllForExtension(kIdX, 1).empty()); - EXPECT_TRUE(workers->GetAllForExtension(kIdB, 1).empty()); - EXPECT_TRUE(workers->GetAllForExtension(kIdA, 2).empty()); - EXPECT_TRUE(workers->GetAllForExtension(kIdX, 2).empty()); - EXPECT_TRUE(workers->GetAllForExtension(kIdB, 9).empty()); - EXPECT_TRUE(workers->GetAllForExtension(kIdC, 9).empty()); - EXPECT_TRUE(workers->GetAllForExtension(kIdX, 9).empty()); - EXPECT_TRUE(workers->GetAllForExtension(kIdA, 10).empty()); - EXPECT_TRUE(workers->GetAllForExtension(kIdB, 10).empty()); - EXPECT_TRUE(workers->GetAllForExtension(kIdC, 10).empty()); - EXPECT_TRUE(workers->GetAllForExtension(kIdX, 10).empty()); + EXPECT_TRUE(workers->GetAllForExtension(kIdX, Id(1)).empty()); + EXPECT_TRUE(workers->GetAllForExtension(kIdB, Id(1)).empty()); + EXPECT_TRUE(workers->GetAllForExtension(kIdA, Id(2)).empty()); + EXPECT_TRUE(workers->GetAllForExtension(kIdX, Id(2)).empty()); + EXPECT_TRUE(workers->GetAllForExtension(kIdB, Id(9)).empty()); + EXPECT_TRUE(workers->GetAllForExtension(kIdC, Id(9)).empty()); + EXPECT_TRUE(workers->GetAllForExtension(kIdX, Id(9)).empty()); + EXPECT_TRUE(workers->GetAllForExtension(kIdA, Id(10)).empty()); + EXPECT_TRUE(workers->GetAllForExtension(kIdB, Id(10)).empty()); + EXPECT_TRUE(workers->GetAllForExtension(kIdC, Id(10)).empty()); + EXPECT_TRUE(workers->GetAllForExtension(kIdX, Id(10)).empty()); } TEST_F(WorkerIdSetTest, RemoveAllForExtension) { // Some worker ids with multiple workers pointing to same extension(s). std::vector<WorkerId> workers_vector = { - {kIdA, 10, 1, 2}, {kIdA, 10, 3, 4}, {kIdA, 11, 1, 2}, {kIdA, 12, 1, 2}, - {kIdB, 10, 1, 2}, {kIdB, 14, 1, 2}, {kIdB, 15, 1, 2}, {kIdB, 16, 1, 2}, - {kIdC, 20, 7, 3}, {kIdD, 20, 7, 3}, {kIdD, 20, 8, 2}, {kIdD, 21, 7, 1}, - {kIdD, 22, 9, 9}}; + {kIdA, Id(10), 1, 2}, {kIdA, Id(10), 3, 4}, {kIdA, Id(11), 1, 2}, + {kIdA, Id(12), 1, 2}, {kIdB, Id(10), 1, 2}, {kIdB, Id(14), 1, 2}, + {kIdB, Id(15), 1, 2}, {kIdB, Id(16), 1, 2}, {kIdC, Id(20), 7, 3}, + {kIdD, Id(20), 7, 3}, {kIdD, Id(20), 8, 2}, {kIdD, Id(21), 7, 1}, + {kIdD, Id(22), 9, 9}}; std::unique_ptr<WorkerIdSet> worker_id_set = CreateWorkerIdSet(workers_vector); EXPECT_EQ(workers_vector.size(), worker_id_set->count_for_testing()); @@ -219,15 +228,16 @@ // Tests parity of WorkerIdSet methods with a std::vector implementation. TEST_F(WorkerIdSetTest, ExtensiveCases) { - std::vector<WorkerId> worker_ids_vector = - GenerateWorkerIds({kIdA, kIdC, kIdE, kIdG}, {1, 3, 5, 7, 9}, - {10, 30, 50, 70}, {100, 300, 500, 700}); + std::vector<WorkerId> worker_ids_vector = GenerateWorkerIds( + {kIdA, kIdC, kIdE, kIdG}, {Id(1), Id(3), Id(5), Id(7), Id(9)}, + {10, 30, 50, 70}, {100, 300, 500, 700}); VectorWorkerIdListImpl test_impl(worker_ids_vector); std::unique_ptr<WorkerIdSet> impl = CreateWorkerIdSet(worker_ids_vector); // Worker ids that we're going to use to test |worker_ids_vector|. std::vector<WorkerId> test_case_worker_ids_vector = GenerateWorkerIds( - {kIdA, kIdB, kIdC, kIdD, kIdE, kIdF, kIdG}, {1, 2, 3, 4, 5, 6, 7, 8, 9}, + {kIdA, kIdB, kIdC, kIdD, kIdE, kIdF, kIdG}, + {Id(1), Id(2), Id(3), Id(4), Id(5), Id(6), Id(7), Id(8), Id(9)}, {10, 20, 30, 40, 50, 60, 70, 80, 90}, {100, 200, 300, 400, 500, 600, 700, 800, 900});
diff --git a/extensions/renderer/api/messaging/message_target.cc b/extensions/renderer/api/messaging/message_target.cc index f453b0f..a85e4a2 100644 --- a/extensions/renderer/api/messaging/message_target.cc +++ b/extensions/renderer/api/messaging/message_target.cc
@@ -19,8 +19,9 @@ MessageTarget target(TAB); target.tab_id = tab_id; target.frame_id = frame_id; - if (!document_id.empty()) + if (!document_id.empty()) { target.document_id = document_id; + } return target; }
diff --git a/extensions/renderer/api/messaging/messaging_util.cc b/extensions/renderer/api/messaging/messaging_util.cc index d421cf7..161052f3 100644 --- a/extensions/renderer/api/messaging/messaging_util.cc +++ b/extensions/renderer/api/messaging/messaging_util.cc
@@ -293,8 +293,9 @@ } int ExtractIntegerId(v8::Local<v8::Value> value) { - if (value->IsInt32()) + if (value->IsInt32()) { return value.As<v8::Int32>()->Value(); + } // Account for -0, which is a valid integer, but is stored as a number in v8. DCHECK(value->IsNumber() && value.As<v8::Number>()->Value() == 0.0); @@ -408,14 +409,16 @@ v8::LocalVector<v8::Value>* arguments_out) { base::span<const v8::Local<v8::Value>> arguments = *arguments_out; size_t max_size = allow_options_argument ? 4u : 3u; - if (arguments.empty() || arguments.size() > max_size) + if (arguments.empty() || arguments.size() > max_size) { return; + } v8::Local<v8::Value> target_id = v8::Null(isolate); v8::Local<v8::Value> message = v8::Null(isolate); v8::Local<v8::Value> options; - if (allow_options_argument) + if (allow_options_argument) { options = v8::Null(isolate); + } v8::Local<v8::Value> response_callback = v8::Null(isolate); // If the last argument is a function, it is the response callback. @@ -427,8 +430,9 @@ // Re-check for too many arguments after looking for the callback. If there // are, early-out and rely on normal signature parsing to report the error. - if (arguments.size() >= max_size) + if (arguments.size() >= max_size) { return; + } switch (arguments.size()) { case 0: @@ -465,10 +469,11 @@ NOTREACHED(); } - if (allow_options_argument) + if (allow_options_argument) { *arguments_out = {target_id, message, options, response_callback}; - else + } else { *arguments_out = {target_id, message, response_callback}; + } } bool IsSendRequestDisabled(ScriptContext* script_context) {
diff --git a/extensions/renderer/api/messaging/native_renderer_messaging_service.cc b/extensions/renderer/api/messaging/native_renderer_messaging_service.cc index ff53fee..c2782df 100644 --- a/extensions/renderer/api/messaging/native_renderer_messaging_service.cc +++ b/extensions/renderer/api/messaging/native_renderer_messaging_service.cc
@@ -319,13 +319,15 @@ const MessageTarget& target, const std::string& channel_name, mojom::SerializationFormat format) { - if (!ScriptContextIsValid(script_context)) + if (!ScriptContextIsValid(script_context)) { return nullptr; + } MessagingPerContextData* data = GetPerContextData<MessagingPerContextData>( script_context->v8_context(), CreatePerContextData::kCreateIfMissing); - if (!data) + if (!data) { return nullptr; + } MessagePortScope* scope = GetMessagePortScope(script_context->GetRenderFrame()); @@ -354,8 +356,9 @@ const Message& message, binding::AsyncResponseType async_type, v8::Local<v8::Function> response_callback) { - if (!ScriptContextIsValid(script_context)) + if (!ScriptContextIsValid(script_context)) { return v8::Local<v8::Promise>(); + } MessagingPerContextData* data = GetPerContextData<MessagingPerContextData>( script_context->v8_context(), CreatePerContextData::kCreateIfMissing); @@ -392,8 +395,9 @@ std::unique_ptr<Message> message) { ScriptContext* script_context = GetScriptContextFromV8Context(context); CHECK(script_context); - if (!ScriptContextIsValid(script_context)) + if (!ScriptContextIsValid(script_context)) { return; + } auto* scope = GetMessagePortScope(script_context->GetRenderFrame()); // BFCache can disconnect the mojo pipe but leave the GinPort thinking @@ -411,14 +415,16 @@ MessagingPerContextData* data = GetPerContextData<MessagingPerContextData>( script_context->v8_context(), CreatePerContextData::kDontCreateIfMissing); - if (!data) + if (!data) { return; + } size_t erased = data->ports.erase(port_id); DCHECK_GT(erased, 0u); - if (!ScriptContextIsValid(script_context)) + if (!ScriptContextIsValid(script_context)) { return; + } CloseMessagePort(script_context, port_id, /*close_channel=*/true); } @@ -470,8 +476,9 @@ // If the channel was opened by this same context, ignore it. This should only // happen when messages are sent to an entire process (rather than a single // frame) as an optimization; otherwise the browser process filters this out. - if (script_context->context_id() == target_port_id.context_id) + if (script_context->context_id() == target_port_id.context_id) { return; + } // First, determine the event we'll use to connect. std::string target_extension_id = script_context->GetExtensionID(); @@ -580,8 +587,9 @@ const PortId& port_id, const std::string& error_message, ScriptContext* script_context) { - if (!ContextHasMessagePort(script_context, port_id)) + if (!ContextHasMessagePort(script_context, port_id)) { return; + } DispatchOnDisconnectToListeners(script_context, port_id, error_message); } @@ -589,8 +597,9 @@ bool NativeRendererMessagingService::ContextHasMessagePort( ScriptContext* script_context, const PortId& port_id) { - if (one_time_message_handler_.HasPort(script_context, port_id)) + if (one_time_message_handler_.HasPort(script_context, port_id)) { return true; + } v8::HandleScope handle_scope(script_context->isolate()); MessagingPerContextData* data = GetPerContextData<MessagingPerContextData>( script_context->v8_context(), CreatePerContextData::kDontCreateIfMissing); @@ -612,16 +621,19 @@ v8::Context::Scope context_scope(v8_context); gin::DataObjectBuilder sender_builder(isolate); - if (info.source_endpoint.extension_id) + if (info.source_endpoint.extension_id) { sender_builder.Set("id", *info.source_endpoint.extension_id); + } if (info.source_endpoint.native_app_name) { sender_builder.Set("nativeApplication", *info.source_endpoint.native_app_name); } - if (!info.source_url.is_empty()) + if (!info.source_url.is_empty()) { sender_builder.Set("url", info.source_url.spec()); - if (info.source_origin) + } + if (info.source_origin) { sender_builder.Set("origin", info.source_origin->Serialize()); + } if (source.frame_id >= 0) { sender_builder.Set("frameId", source.frame_id); } @@ -679,17 +691,19 @@ APIActivityLogger::IsLoggingEnabled()) { base::ListValue list; list.reserve(2u); - if (info.source_endpoint.extension_id) + if (info.source_endpoint.extension_id) { list.Append(*info.source_endpoint.extension_id); - else if (info.source_endpoint.native_app_name) + } else if (info.source_endpoint.native_app_name) { list.Append(*info.source_endpoint.native_app_name); - else + } else { list.Append(base::Value()); + } - if (!info.source_url.is_empty()) + if (!info.source_url.is_empty()) { list.Append(info.source_url.spec()); - else + } else { list.Append(base::Value()); + } APIActivityLogger::LogEvent(bindings_system_->GetIPCMessageSender(), script_context, event_name, std::move(list)); @@ -744,8 +758,9 @@ port->DispatchOnDisconnect(v8_context); // Note: Arbitrary JS may have run; the context may now be deleted. - if (!binding::IsContextValid(v8_context)) + if (!binding::IsContextValid(v8_context)) { return; + } if (!error_message.empty()) { bindings_system_->api_system()->request_handler()->last_error()->ClearError( @@ -772,10 +787,11 @@ // If this port is an opener, then it should have been created in this // context. Otherwise, it should have been created in another context, because // we don't support intra-context message passing. - if (port_id.is_opener) + if (port_id.is_opener) { DCHECK_EQ(port_id.context_id, script_context->context_id()); - else + } else { DCHECK_NE(port_id.context_id, script_context->context_id()); + } MessagingPerContextData* data = GetPerContextData<MessagingPerContextData>( context, CreatePerContextData::kCreateIfMissing);
diff --git a/extensions/renderer/api/messaging/one_time_message_handler.cc b/extensions/renderer/api/messaging/one_time_message_handler.cc index d586b80..f2acf83d 100644 --- a/extensions/renderer/api/messaging/one_time_message_handler.cc +++ b/extensions/renderer/api/messaging/one_time_message_handler.cc
@@ -152,8 +152,9 @@ OneTimeMessageContextData* data = GetPerContextData<OneTimeMessageContextData>( context, CreatePerContextData::kDontCreateIfMissing); - if (!data) + if (!data) { return; + } // Retrieve the CallbackID from v8 that we set when we created the callback. v8::Local<v8::String> callback_id_v8_string = info.Data().As<v8::String>(); @@ -432,8 +433,9 @@ GetPerContextData<OneTimeMessageContextData>( script_context->v8_context(), CreatePerContextData::kDontCreateIfMissing); - if (!data) + if (!data) { return false; + } return port_id.is_opener ? data->openers.contains(port_id) : data->receivers.contains(port_id); } @@ -466,8 +468,9 @@ if (wants_response) { // If this is a promise based request no callback should have been passed // in. - if (async_type == binding::AsyncResponseType::kPromise) + if (async_type == binding::AsyncResponseType::kPromise) { DCHECK(response_callback.IsEmpty()); + } APIRequestHandler::RequestDetails details = bindings_system_->api_system()->request_handler()->AddPendingRequest( @@ -654,12 +657,14 @@ OneTimeMessageContextData* data = GetPerContextData<OneTimeMessageContextData>( context, CreatePerContextData::kDontCreateIfMissing); - if (!data) + if (!data) { return handled; + } auto iter = data->receivers.find(target_port_id); - if (iter == data->receivers.end()) + if (iter == data->receivers.end()) { return handled; + } handled = true; OneTimeReceiver& port = iter->second; @@ -747,12 +752,14 @@ OneTimeMessageContextData* data = GetPerContextData<OneTimeMessageContextData>( v8_context, CreatePerContextData::kDontCreateIfMissing); - if (!data) + if (!data) { return handled; + } auto iter = data->openers.find(target_port_id); - if (iter == data->openers.end()) + if (iter == data->openers.end()) { return handled; + } handled = true; @@ -801,12 +808,14 @@ OneTimeMessageContextData* data = GetPerContextData<OneTimeMessageContextData>( context, CreatePerContextData::kDontCreateIfMissing); - if (!data) + if (!data) { return handled; + } auto iter = data->receivers.find(port_id); - if (iter == data->receivers.end()) + if (iter == data->receivers.end()) { return handled; + } handled = true; // With the channel closed, clean up the receiver port and its pending @@ -829,12 +838,14 @@ OneTimeMessageContextData* data = GetPerContextData<OneTimeMessageContextData>( v8_context, CreatePerContextData::kDontCreateIfMissing); - if (!data) + if (!data) { return handled; + } auto iter = data->openers.find(port_id); - if (iter == data->openers.end()) + if (iter == data->openers.end()) { return handled; + } handled = true; @@ -905,14 +916,16 @@ OneTimeMessageContextData* data = GetPerContextData<OneTimeMessageContextData>( context, CreatePerContextData::kDontCreateIfMissing); - if (!data) + if (!data) { return; + } auto iter = data->receivers.find(port_id); // The channel may already be closed (if a listener replied (promise rejected) // or listener threw error). - if (iter == data->receivers.end()) + if (iter == data->receivers.end()) { return; + } // The response will be sent after this point so we no longer need to track // the receiver. data->receivers.erase(port_id); @@ -920,10 +933,11 @@ v8::Local<v8::Value> value; // We allow omitting the message argument (e.g., sendMessage()). Default the // value to undefined. - if (arguments->Length() > 0) + if (arguments->Length() > 0) { CHECK(arguments->GetNext(&value)); - else + } else { value = v8::Undefined(isolate); + } ScriptContext* script_context = GetScriptContextFromV8Context(context); @@ -1046,8 +1060,9 @@ // ScriptContext invalidation and PerContextData cleanup happen "around" the // same time, but there aren't strict guarantees about ordering. It's possible // the data was collected. - if (!data) + if (!data) { return; + } // Since there is no way to call the callback anymore, we can remove it from // the pending callbacks and delete the port entry if this was the last @@ -1301,8 +1316,9 @@ OneTimeMessageContextData* data = GetPerContextData<OneTimeMessageContextData>( context, CreatePerContextData::kDontCreateIfMissing); - if (!data) + if (!data) { return; + } ScriptContext* script_context = GetScriptContextFromV8Context(context); DCHECK(script_context)
diff --git a/gin/array_buffer.cc b/gin/array_buffer.cc index 28d3814..81ac9de8 100644 --- a/gin/array_buffer.cc +++ b/gin/array_buffer.cc
@@ -91,21 +91,37 @@ // ArrayBuffer ---------------------------------------------------------------- ArrayBuffer::ArrayBuffer() = default; -ArrayBuffer::ArrayBuffer(v8::Isolate* isolate, v8::Local<v8::ArrayBuffer> array) +ArrayBuffer::ArrayBuffer(v8::Local<v8::ArrayBuffer> array) : backing_store_(array->GetBackingStore()) {} ArrayBuffer::~ArrayBuffer() = default; ArrayBuffer& ArrayBuffer::operator=(const ArrayBuffer& other) = default; +base::span<uint8_t> ArrayBuffer::span() { + if (!backing_store_) { + return {}; + } + + // SAFETY: Provided by `BackingStore`. + return UNSAFE_BUFFERS( + base::span<uint8_t>(static_cast<uint8_t*>(backing_store_->Data()), + backing_store_->ByteLength())); +} + +base::span<const uint8_t> ArrayBuffer::span() const { + return const_cast<ArrayBuffer*>(this)->span(); +} + // Converter<ArrayBuffer> ----------------------------------------------------- -bool Converter<ArrayBuffer>::FromV8(v8::Isolate* isolate, +bool Converter<ArrayBuffer>::FromV8(v8::Isolate* /*isolate*/, v8::Local<v8::Value> val, ArrayBuffer* out) { - if (!val->IsArrayBuffer()) + if (!val->IsArrayBuffer()) { return false; - *out = ArrayBuffer(isolate, v8::Local<v8::ArrayBuffer>::Cast(val)); + } + *out = ArrayBuffer(v8::Local<v8::ArrayBuffer>::Cast(val)); return true; } @@ -116,26 +132,33 @@ num_bytes_(0) { } -ArrayBufferView::ArrayBufferView(v8::Isolate* isolate, - v8::Local<v8::ArrayBufferView> view) - : array_buffer_(isolate, view->Buffer()), +ArrayBufferView::ArrayBufferView(v8::Local<v8::ArrayBufferView> view) + : array_buffer_(view->Buffer()), offset_(view->ByteOffset()), - num_bytes_(view->ByteLength()) { -} + num_bytes_(view->ByteLength()) {} ArrayBufferView::~ArrayBufferView() = default; ArrayBufferView& ArrayBufferView::operator=(const ArrayBufferView& other) = default; +base::span<uint8_t> ArrayBufferView::span() { + return array_buffer_.span().subspan(offset_, num_bytes_); +} + +base::span<const uint8_t> ArrayBufferView::span() const { + return const_cast<ArrayBufferView*>(this)->span(); +} + // Converter<ArrayBufferView> ------------------------------------------------- -bool Converter<ArrayBufferView>::FromV8(v8::Isolate* isolate, +bool Converter<ArrayBufferView>::FromV8(v8::Isolate* /*isolate*/, v8::Local<v8::Value> val, ArrayBufferView* out) { - if (!val->IsArrayBufferView()) + if (!val->IsArrayBufferView()) { return false; - *out = ArrayBufferView(isolate, v8::Local<v8::ArrayBufferView>::Cast(val)); + } + *out = ArrayBufferView(v8::Local<v8::ArrayBufferView>::Cast(val)); return true; }
diff --git a/gin/array_buffer.h b/gin/array_buffer.h index fbedcc07..2cdbc9a 100644 --- a/gin/array_buffer.h +++ b/gin/array_buffer.h
@@ -8,7 +8,7 @@ #include <stddef.h> #include <stdint.h> -#include "base/compiler_specific.h" +#include "base/containers/span.h" #include "base/memory/shared_memory_mapper.h" #include "gin/converter.h" #include "gin/gin_export.h" @@ -48,17 +48,13 @@ class GIN_EXPORT ArrayBuffer { public: ArrayBuffer(); - ArrayBuffer(v8::Isolate* isolate, v8::Local<v8::ArrayBuffer> buffer); + explicit ArrayBuffer(v8::Local<v8::ArrayBuffer> buffer); ArrayBuffer(const ArrayBuffer&) = delete; ~ArrayBuffer(); ArrayBuffer& operator=(const ArrayBuffer& other); - void* bytes() const { - return backing_store_ ? backing_store_->Data() : nullptr; - } - size_t num_bytes() const { - return backing_store_ ? backing_store_->ByteLength() : 0; - } + base::span<uint8_t> span(); + base::span<const uint8_t> span() const; private: std::shared_ptr<v8::BackingStore> backing_store_; @@ -73,15 +69,13 @@ class GIN_EXPORT ArrayBufferView { public: ArrayBufferView(); - ArrayBufferView(v8::Isolate* isolate, v8::Local<v8::ArrayBufferView> view); + explicit ArrayBufferView(v8::Local<v8::ArrayBufferView> view); ArrayBufferView(const ArrayBufferView&) = delete; ~ArrayBufferView(); ArrayBufferView& operator=(const ArrayBufferView& other); - void* bytes() const { - return UNSAFE_TODO(static_cast<uint8_t*>(array_buffer_.bytes()) + offset_); - } - size_t num_bytes() const { return num_bytes_; } + base::span<uint8_t> span(); + base::span<const uint8_t> span() const; private: ArrayBuffer array_buffer_;
diff --git a/gpu/command_buffer/service/drm_modifiers_filter_dawn.h b/gpu/command_buffer/service/drm_modifiers_filter_dawn.h index c56dc0e..9134b3a 100644 --- a/gpu/command_buffer/service/drm_modifiers_filter_dawn.h +++ b/gpu/command_buffer/service/drm_modifiers_filter_dawn.h
@@ -38,7 +38,8 @@ const std::vector<uint64_t>& modifiers) override; private: - // Map from all BufferFormats to a set of modifiers supported by that format. + // Map from all SharedImageFormats to a set of modifiers supported by that + // format. base::flat_map<viz::SharedImageFormat, std::vector<uint64_t>> modifier_map_; };
diff --git a/gpu/command_buffer/service/shared_image/dawn_ozone_image_representation.cc b/gpu/command_buffer/service/shared_image/dawn_ozone_image_representation.cc index f1a11d7..5b3d9d8 100644 --- a/gpu/command_buffer/service/shared_image/dawn_ozone_image_representation.cc +++ b/gpu/command_buffer/service/shared_image/dawn_ozone_image_representation.cc
@@ -110,7 +110,7 @@ descriptor.planeLayouts[plane].offset = pixmap_->GetDmaBufOffset(plane); descriptor.planeLayouts[plane].stride = pixmap_->GetDmaBufPitch(plane); } - descriptor.drmModifier = pixmap_->GetBufferFormatModifier(); + descriptor.drmModifier = pixmap_->GetFormatModifier(); descriptor.waitFDs = {}; for (auto& fence : fences) {
diff --git a/ios/chrome/app/resources/Settings.bundle/Experimental.plist b/ios/chrome/app/resources/Settings.bundle/Experimental.plist index 77aee5e..38895d7b 100644 --- a/ios/chrome/app/resources/Settings.bundle/Experimental.plist +++ b/ios/chrome/app/resources/Settings.bundle/Experimental.plist
@@ -97,11 +97,13 @@ <integer>0</integer> <key>Values</key> <array> + <integer>-1</integer> <integer>10</integer> <integer>3600</integer> </array> <key>Titles</key> <array> + <string>Instant Trigger</string> <string>10 Seconds</string> <string>1 Hour</string> </array>
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd index 9b1e4b23..eeccec60 100644 --- a/ios/chrome/app/strings/ios_strings.grd +++ b/ios/chrome/app/strings/ios_strings.grd
@@ -4907,6 +4907,12 @@ <message name="IDS_IOS_PASSKEY_HIDDEN_INFO" desc="Label in the popover informing the user that a passkey was deleted by a website and will be deleted from password manager soon. Includes a link to a help center article informing more about passkeys. Refer to Glossary for the meaning of 'passkey', do not translate it as 'password'. [iOS only]"> This passkey doesn't work anymore. It was deleted from <ph name="WEBSITE">$1<ex>shrine.com</ex></ph> and will get deleted from here soon. <ph name="BEGIN_LINK">BEGIN_LINK</ph>About passkeys<ph name="END_LINK">END_LINK</ph> </message> + <message name="IDS_IOS_PASSKEY_INCOGNITO_INTERSTITIAL_DIALOG_SUBTITLE" desc="Subtitle of the dialog shown when a user tries to create a passkey while in Incognito mode, explaining that passkeys are accessible even outside incognito mode."> + Passkeys get saved to your password manager. They’re accessible in Incognito and regular tabs. + </message> + <message name="IDS_IOS_PASSKEY_INCOGNITO_INTERSTITIAL_DIALOG_TITLE" desc="Title of the dialog shown when a user tries to create a passkey while in Incognito mode."> + Create a passkey outside of Incognito? + </message> <message name="IDS_IOS_PASSKEY_PARTIAL_BOOTSTRAPPING_TITLE" desc="Title of the screen shown when the user needs to create their Google Password Manager pin to set up passkeys on their device."> Set up passkeys for this device </message>
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PASSKEY_INCOGNITO_INTERSTITIAL_DIALOG_SUBTITLE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PASSKEY_INCOGNITO_INTERSTITIAL_DIALOG_SUBTITLE.png.sha1 new file mode 100644 index 0000000..e277771 --- /dev/null +++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PASSKEY_INCOGNITO_INTERSTITIAL_DIALOG_SUBTITLE.png.sha1
@@ -0,0 +1 @@ +bee52356400d478d609262381a6e3b0c15861602 \ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PASSKEY_INCOGNITO_INTERSTITIAL_DIALOG_TITLE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PASSKEY_INCOGNITO_INTERSTITIAL_DIALOG_TITLE.png.sha1 new file mode 100644 index 0000000..e277771 --- /dev/null +++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PASSKEY_INCOGNITO_INTERSTITIAL_DIALOG_TITLE.png.sha1
@@ -0,0 +1 @@ +bee52356400d478d609262381a6e3b0c15861602 \ No newline at end of file
diff --git a/ios/chrome/browser/browser_view/ui_bundled/BUILD.gn b/ios/chrome/browser/browser_view/ui_bundled/BUILD.gn index 5bfde3fc..d61992ef 100644 --- a/ios/chrome/browser/browser_view/ui_bundled/BUILD.gn +++ b/ios/chrome/browser/browser_view/ui_bundled/BUILD.gn
@@ -256,6 +256,8 @@ "//ios/chrome/browser/sharing/ui_bundled", "//ios/chrome/browser/side_swipe/ui_bundled", "//ios/chrome/browser/signin/model", + "//ios/chrome/browser/signin/model:authentication_service", + "//ios/chrome/browser/signin/model:authentication_service_factory", "//ios/chrome/browser/snapshots/model", "//ios/chrome/browser/snapshots/model:model_common", "//ios/chrome/browser/snapshots/model:model_swift", @@ -308,6 +310,7 @@ "//ios/chrome/browser/web/model/font_size", "//ios/chrome/browser/web/model/print", "//ios/chrome/browser/web_state_list/model/web_usage_enabler", + "//ios/chrome/browser/webauthn/coordinator", "//ios/chrome/browser/webui/model", "//ios/chrome/browser/webui/ui_bundled:coordinator", "//ios/chrome/browser/welcome_back/coordinator",
diff --git a/ios/chrome/browser/browser_view/ui_bundled/DEPS b/ios/chrome/browser/browser_view/ui_bundled/DEPS index 7e103745..1b9eadb 100644 --- a/ios/chrome/browser/browser_view/ui_bundled/DEPS +++ b/ios/chrome/browser/browser_view/ui_bundled/DEPS
@@ -141,6 +141,7 @@ "+ios/chrome/browser/voice/ui_bundled", "+ios/chrome/browser/web/model", "+ios/chrome/browser/web_state_list/model", + "+ios/chrome/browser/webauthn/coordinator", "+ios/chrome/browser/webui", "+ios/chrome/browser/welcome_back/coordinator", "+ios/chrome/browser/whats_new/coordinator",
diff --git a/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm b/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm index 4d4b14a..7b47e7e 100644 --- a/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm +++ b/ios/chrome/browser/browser_view/ui_bundled/browser_coordinator.mm
@@ -300,6 +300,8 @@ #import "ios/chrome/browser/side_swipe/ui_bundled/side_swipe_mediator.h" #import "ios/chrome/browser/signin/model/account_consistency_browser_agent.h" #import "ios/chrome/browser/signin/model/account_consistency_service_factory.h" +#import "ios/chrome/browser/signin/model/authentication_service.h" +#import "ios/chrome/browser/signin/model/authentication_service_factory.h" #import "ios/chrome/browser/snapshots/model/model_swift.h" #import "ios/chrome/browser/snapshots/model/snapshot_browser_agent.h" #import "ios/chrome/browser/snapshots/model/snapshot_source_tab_helper.h" @@ -346,6 +348,7 @@ #import "ios/chrome/browser/web/model/web_state_delegate_browser_agent.h" #import "ios/chrome/browser/web_state_list/model/web_usage_enabler/web_usage_enabler_browser_agent.h" #import "ios/chrome/browser/web_state_list/model/web_usage_enabler/web_usage_enabler_browser_agent_observer_bridge.h" +#import "ios/chrome/browser/webauthn/coordinator/passkey_welcome_screen_coordinator.h" #import "ios/chrome/browser/webui/model/net_export_tab_helper_delegate.h" #import "ios/chrome/browser/webui/ui_bundled/net_export_coordinator.h" #import "ios/chrome/browser/welcome_back/coordinator/welcome_back_coordinator.h" @@ -413,6 +416,7 @@ PageInfoPresentation, ParentAccessCommands, PasswordBreachCommands, + PasskeyWelcomeScreenCoordinatorDelegate, PasswordControllerDelegate, PasswordProtectionCommands, PasswordProtectionCoordinatorDelegate, @@ -775,6 +779,9 @@ // The coordinator for the passkey creation bottom sheet. PasskeyCreationBottomSheetCoordinator* _passkeyCreationBottomSheetCoordinator; + // The coordinator for the passkey welcome screen. + PasskeyWelcomeScreenCoordinator* _passkeyWelcomeScreenCoordinator; + // Block to run after the Synced Set Up UI has finished dismissing. ProceduralBlock _runAfterSyncedSetUpDismissal; } @@ -1041,6 +1048,9 @@ [_passkeyCreationBottomSheetCoordinator stop]; _passkeyCreationBottomSheetCoordinator = nil; + [_passkeyWelcomeScreenCoordinator stop]; + _passkeyWelcomeScreenCoordinator = nil; + if (IsSyncedSetUpEnabled()) { [self stopSyncedSetUpCoordinator]; } @@ -1114,6 +1124,12 @@ _trustedVaultReauthenticationCoordinator = nil; } +- (void)stopPasskeyWelcomeScreenCoordinator { + [_passkeyWelcomeScreenCoordinator stop]; + _passkeyWelcomeScreenCoordinator.delegate = nil; + _passkeyWelcomeScreenCoordinator = nil; +} + // The Lens UI takes the necessary steps before being backgrounded. - (void)updateLensUIForBackground { web::WebState* activeWebState = self.activeWebState; @@ -1658,6 +1674,9 @@ /* _passkeyCreationBottomSheetCoordinator is created and started by a * BrowserCommand */ + /* _passkeyWelcomeScreenCoordinator is created and started by a + * BrowserCommand */ + /* saveCardBottomSheetCoordinator is created and started by a * BrowserCommand */ @@ -1921,6 +1940,7 @@ [self dismissAutoDeletionActionSheet]; [self hideGoogleOne]; [self stopTrustedVaultReauthentication]; + [self stopPasskeyWelcomeScreenCoordinator]; [self dismissSearchWhatYouSeePromo]; [self dismissNotificationsOptIn]; [self hideWelcomeBackPromo]; @@ -2421,7 +2441,17 @@ completion: (webauthn::PasskeyWelcomeScreenAction) completion { - // TODO(crbug.com/460485614): Implement. + _passkeyWelcomeScreenCoordinator = [[PasskeyWelcomeScreenCoordinator alloc] + initWithBaseViewController:self.viewController + browser:self.browser + purpose:purpose + completion:completion]; + _passkeyWelcomeScreenCoordinator.delegate = self; + [_passkeyWelcomeScreenCoordinator start]; +} + +- (void)dismissPasskeyWelcomeScreen { + [self stopPasskeyWelcomeScreenCoordinator]; } #pragma mark - BrowserCoordinatorCommands @@ -3723,8 +3753,9 @@ _urlLoadingBrowserAgent->SetDelegate(self); } - AccountConsistencyBrowserAgent::CreateForBrowser(self.browser, - self.viewController); + AccountConsistencyBrowserAgent::CreateForBrowser( + self.browser, self.viewController, + AuthenticationServiceFactory::GetForProfile(self.profile)); ReaderModeBrowserAgent* readerModeBrowserAgent = ReaderModeBrowserAgent::FromBrowser(self.browser); @@ -5107,6 +5138,14 @@ [self stopTrustedVaultReauthentication]; } +#pragma mark - PasskeyWelcomeScreenCoordinatorDelegate + +- (void)passkeyWelcomeScreenCoordinatorWantsToBeDismissed: + (PasskeyWelcomeScreenCoordinator*)coordinator { + CHECK_EQ(coordinator, _passkeyWelcomeScreenCoordinator); + [self stopPasskeyWelcomeScreenCoordinator]; +} + #pragma mark - DownloadListCommands - (void)hideDownloadList {
diff --git a/ios/chrome/browser/composebox/ui/composebox_egtest.mm b/ios/chrome/browser/composebox/ui/composebox_egtest.mm index af2c3bc..d5f38cc 100644 --- a/ios/chrome/browser/composebox/ui/composebox_egtest.mm +++ b/ios/chrome/browser/composebox/ui/composebox_egtest.mm
@@ -172,6 +172,24 @@ assertWithMatcher:grey_notNil()]; } +// Matcher for the close button of an item in the carousel. +id<GREYMatcher> ComposeboxInputItemCellCloseButtonMatcher() { + return grey_allOf( + grey_accessibilityID( + kComposeboxInputItemCellCloseButtonAccessibilityIdentifier), + grey_sufficientlyVisible(), nil); +} + +// Removes an attachment with the given `title` from the composebox carousel. +void RemoveAttachmentWithTitle(NSString* title) { + id<GREYMatcher> cellMatcher = TabCellAttachmentMatcherWithTitle(title); + id<GREYMatcher> closeButtonMatcher = + grey_allOf(ComposeboxInputItemCellCloseButtonMatcher(), + grey_ancestor(cellMatcher), nil); + [[EarlGrey selectElementWithMatcher:closeButtonMatcher] + performAction:grey_tap()]; +} + } // namespace @interface ComposeboxTestCase : ChromeTestCase @@ -649,4 +667,94 @@ assertWithMatcher:grey_nil()]; } +// Tests that a tab cannot be attached when in image generation mode, and that +// image generation mode can be entered after attachments are removed. +- (void)testNoTabAttachmentsInImageGeneration { + // Composebox is not available on iPad. + if ([ChromeEarlGrey isIPadIdiom]) { + EARL_GREY_TEST_SKIPPED(@"Skipped for iPad as composebox is not available."); + } + + // Add a tab and attach it. + [ChromeEarlGrey closeAllNormalTabs]; + GURL URL = self.testServer->GetURL(base::StringPrintf(kPageURL, 1)); + [ChromeEarlGrey openNewTab]; + [ChromeEarlGrey loadURL:URL]; + [ChromeEarlGrey + waitForWebStateContainingText:base::StringPrintf(kPageContent, 1)]; + OpenTabPicker(); + NSString* pageTitle = + base::SysUTF8ToNSString(base::StringPrintf(kPageTitle, 1)); + SelectTabWithTitle(pageTitle); + [[EarlGrey + selectElementWithMatcher:chrome_test_util::NavigationBarDoneButton()] + performAction:grey_tap()]; + VerifyTabIsAttachedWithTitle(pageTitle); + + // Display the plus menu to verify that the image generation action is + // disabled. This mode only supports a single image attachment, making it + // incompatible with the currently attached tab. + [[EarlGrey + selectElementWithMatcher: + grey_accessibilityID(kComposeboxPlusButtonAccessibilityIdentifier)] + performAction:grey_tap()]; + [[EarlGrey selectElementWithMatcher: + grey_accessibilityID( + kComposeboxImageGenerationActionAccessibilityIdentifier)] + assertWithMatcher:grey_allOf(grey_notNil(), + grey_accessibilityTrait( + UIAccessibilityTraitNotEnabled), + nil)]; + + // Tap somewhere to dismiss the plus button menu. + [[EarlGrey selectElementWithMatcher:ComposeboxMatcher()] + performAction:grey_tap()]; + + // Remove the attachment and switch to image generation mode. + RemoveAttachmentWithTitle(pageTitle); + [[EarlGrey + selectElementWithMatcher:TabCellAttachmentMatcherWithTitle(pageTitle)] + assertWithMatcher:grey_notVisible()]; + [[EarlGrey + selectElementWithMatcher: + grey_accessibilityID(kComposeboxPlusButtonAccessibilityIdentifier)] + performAction:grey_tap()]; + [[EarlGrey selectElementWithMatcher: + grey_accessibilityID( + kComposeboxImageGenerationActionAccessibilityIdentifier)] + performAction:grey_tap()]; + + // Verify that the image generation button is visible in the composebox. + [ChromeEarlGrey + waitForUIElementToAppearWithMatcher: + grey_accessibilityID( + kComposeboxImageGenerationButtonAccessibilityIdentifier)]; + + // Verify that the "Select tabs" action is disabled and the "Create Image" + // action is selected. + [[EarlGrey + selectElementWithMatcher: + grey_accessibilityID(kComposeboxPlusButtonAccessibilityIdentifier)] + performAction:grey_tap()]; + [[EarlGrey + selectElementWithMatcher: + grey_allOf(grey_accessibilityID( + kComposeboxSelectTabsActionAccessibilityIdentifier), + grey_sufficientlyVisible(), nil)] + assertWithMatcher:grey_allOf(grey_notNil(), + grey_accessibilityTrait( + UIAccessibilityTraitNotEnabled), + nil)]; + [[EarlGrey + selectElementWithMatcher: + grey_allOf( + grey_accessibilityID( + kComposeboxImageGenerationActionAccessibilityIdentifier), + grey_sufficientlyVisible(), nil)] + assertWithMatcher:grey_allOf(grey_notNil(), + grey_accessibilityTrait( + UIAccessibilityTraitSelected), + nil)]; +} + @end
diff --git a/ios/chrome/browser/composebox/ui/composebox_input_item_cell.mm b/ios/chrome/browser/composebox/ui/composebox_input_item_cell.mm index 579b511..0b196de 100644 --- a/ios/chrome/browser/composebox/ui/composebox_input_item_cell.mm +++ b/ios/chrome/browser/composebox/ui/composebox_input_item_cell.mm
@@ -221,6 +221,8 @@ - (void)setupCloseButton { _closeButton = [UIButton buttonWithType:UIButtonTypeSystem]; _closeButton.translatesAutoresizingMaskIntoConstraints = NO; + _closeButton.accessibilityIdentifier = + kComposeboxInputItemCellCloseButtonAccessibilityIdentifier; _closeButton.isAccessibilityElement = NO; [_closeButton addTarget:self action:@selector(closeButtonTapped)
diff --git a/ios/chrome/browser/composebox/ui/composebox_ui_constants.h b/ios/chrome/browser/composebox/ui/composebox_ui_constants.h index 27867c0..ab5d3020 100644 --- a/ios/chrome/browser/composebox/ui/composebox_ui_constants.h +++ b/ios/chrome/browser/composebox/ui/composebox_ui_constants.h
@@ -60,5 +60,8 @@ extern NSString* const kComposeboxCarouselAccessibilityIdentifier; // Accessibility identifier for an item in the carousel. extern NSString* const kComposeboxCarouselItemAccessibilityIdentifier; +// Accessibility identifier for the close button of an item in the carousel. +extern NSString* const + kComposeboxInputItemCellCloseButtonAccessibilityIdentifier; #endif // IOS_CHROME_BROWSER_COMPOSEBOX_UI_COMPOSEBOX_UI_CONSTANTS_H_
diff --git a/ios/chrome/browser/composebox/ui/composebox_ui_constants.mm b/ios/chrome/browser/composebox/ui/composebox_ui_constants.mm index 0a83dcc6..1a33e54 100644 --- a/ios/chrome/browser/composebox/ui/composebox_ui_constants.mm +++ b/ios/chrome/browser/composebox/ui/composebox_ui_constants.mm
@@ -51,3 +51,5 @@ @"kComposeboxCarouselAccessibilityIdentifier"; NSString* const kComposeboxCarouselItemAccessibilityIdentifier = @"kComposeboxCarouselItemAccessibilityIdentifier"; +NSString* const kComposeboxInputItemCellCloseButtonAccessibilityIdentifier = + @"kComposeboxInputItemCellCloseButtonAccessibilityIdentifier";
diff --git a/ios/chrome/browser/context_menu/ui_bundled/context_menu_configuration_provider.mm b/ios/chrome/browser/context_menu/ui_bundled/context_menu_configuration_provider.mm index 67d8f24..783197e1 100644 --- a/ios/chrome/browser/context_menu/ui_bundled/context_menu_configuration_provider.mm +++ b/ios/chrome/browser/context_menu/ui_bundled/context_menu_configuration_provider.mm
@@ -531,29 +531,6 @@ __weak __typeof(self) weakSelf = self; - // Launch the Gemini experience with an image attached. - raw_ptr<BwgService> BWGService = - BwgServiceFactory::GetForProfile(self.browser->GetProfile()); - BOOL canShowGeminiElement = IsGeminiImageRemixToolEnabled() && BWGService && - BWGService->IsBwgAvailableForWebState(webState); - if (canShowGeminiElement) { - RecordImageRemixContextMenuEntryPointShown(); - - ProceduralBlock geminiElementCallback = ^{ - [weakSelf openGeminiWithImageURL:imageURL referrer:referrer]; - }; - UIMenuElement* geminiElement = [actionFactory - actionToOpenImageInGeminiWithBlock:geminiElementCallback]; - - // Wrap the Gemini element in an inline menu to create a distinct section. - UIMenu* geminiSection = [UIMenu menuWithTitle:@"" - image:nil - identifier:nil - options:UIMenuOptionsDisplayInline - children:@[ geminiElement ]]; - [imageMenuElements addObject:geminiSection]; - } - // Image saving. NSArray<UIMenuElement*>* imageSavingElements = [self imageSavingElementsWithURL:imageURL @@ -583,8 +560,39 @@ [self imageSearchingElementsWithURL:imageURL scenario:scenario referrer:referrer]; + + // Launch the Gemini experience with an image attached. + UIMenuElement* geminiElement = nil; + raw_ptr<BwgService> BWGService = + BwgServiceFactory::GetForProfile(self.browser->GetProfile()); + BOOL canShowGeminiElement = IsGeminiImageRemixToolEnabled() && BWGService && + BWGService->IsBwgAvailableForWebState(webState); + BOOL geminiAboveSearch = IsGeminiImageRemixToolShowAboveSearchImageEnabled(); + BOOL geminiBelowSearch = IsGeminiImageRemixToolShowBelowSearchImageEnabled(); + + if (canShowGeminiElement && (geminiAboveSearch || geminiBelowSearch)) { + RecordImageRemixContextMenuEntryPointShown(); + + ProceduralBlock geminiElementCallback = ^{ + [weakSelf openGeminiWithImageURL:imageURL referrer:referrer]; + }; + geminiElement = [actionFactory + actionToOpenImageInGeminiWithBlock:geminiElementCallback]; + } + + // Display the gemini element either above or below the search image + // element based on the flags. + if (geminiElement && geminiAboveSearch) { + [imageMenuElements addObject:geminiElement]; + } + [imageMenuElements addObjectsFromArray:imageSearchingElements]; + // Ensure we don't show gemini twice if both flags are enabled. + if (geminiElement && geminiBelowSearch && !geminiAboveSearch) { + [imageMenuElements addObject:geminiElement]; + } + // Share Image. // Shares the URL of the image and not the image itself. // This avoids doing in process image processing by working as the share sheet
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm index 9eb55bc..77bed33 100644 --- a/ios/chrome/browser/flags/about_flags.mm +++ b/ios/chrome/browser/flags/about_flags.mm
@@ -2793,6 +2793,10 @@ flag_descriptions::kDisableComposeboxFromAIMNTPName, flag_descriptions::kDisableComposeboxFromAIMNTPDescription, flags_ui::kOsIos, FEATURE_VALUE_TYPE(kDisableComposeboxFromAIMNTP)}, + {"ios-save-to-drive-signed-out", + flag_descriptions::kIOSSaveToDriveSignedOutName, + flag_descriptions::kIOSSaveToDriveSignedOutDescription, flags_ui::kOsIos, + FEATURE_VALUE_TYPE(kIOSSaveToDriveSignedOut)}, }); bool SkipConditionalFeatureEntry(const flags_ui::FeatureEntry& entry) {
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc index 8ff319acc..058ffaa 100644 --- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc +++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -822,6 +822,10 @@ const char kIOSSaveToDriveClientFolderDescription[] = "Enables a feature to use a client folder API for Save to Drive on iOS."; +const char kIOSSaveToDriveSignedOutName[] = "Save to Drive Signed Out"; +const char kIOSSaveToDriveSignedOutDescription[] = + "Enables the Save to Drive feature to signed out users."; + const char kIOSSoftLockName[] = "Soft Lock on iOS"; const char kIOSSoftLockDescription[] = "Enables experimental Soft Lock on iOS.";
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h index af8641c..bcedd0d 100644 --- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h +++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -504,6 +504,9 @@ extern const char kIOSSaveToDriveClientFolderName[]; extern const char kIOSSaveToDriveClientFolderDescription[]; +extern const char kIOSSaveToDriveSignedOutName[]; +extern const char kIOSSaveToDriveSignedOutDescription[]; + extern const char kIOSSoftLockName[]; extern const char kIOSSoftLockDescription[];
diff --git a/ios/chrome/browser/intelligence/bwg/model/bwg_tab_helper.mm b/ios/chrome/browser/intelligence/bwg/model/bwg_tab_helper.mm index 9a15239..d31269b 100644 --- a/ios/chrome/browser/intelligence/bwg/model/bwg_tab_helper.mm +++ b/ios/chrome/browser/intelligence/bwg/model/bwg_tab_helper.mm
@@ -278,8 +278,10 @@ GeminiPageContext* BwgTabHelper::GetPartialPageContext() { GeminiPageContext* gemini_page_context = [[GeminiPageContext alloc] init]; - gemini_page_context.BWGPageContextComputationState = - ios::provider::BWGPageContextComputationState::kPending; + // TODO(crbug.com/467341090): Remove the chain assignment after the migration. + gemini_page_context.geminiPageContextComputationState = + gemini_page_context.BWGPageContextComputationState = + ios::provider::BWGPageContextComputationState::kPending; gemini_page_context.favicon = current_favicon_; std::unique_ptr<optimization_guide::proto::PageContext> page_context =
diff --git a/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent.h b/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent.h index 4c4b1d0..841a1c5 100644 --- a/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent.h +++ b/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent.h
@@ -152,7 +152,7 @@ UIViewController* base_view_controller, std::unique_ptr<optimization_guide::proto::PageContext> page_context_proto, - ios::provider::BWGPageContextComputationState computation_state, + ios::provider::GeminiPageContextComputationState computation_state, gemini::EntryPoint entry_point, UIImage* image_attachment = nil);
diff --git a/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent.mm b/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent.mm index 2eae231b..3a872bc 100644 --- a/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent.mm +++ b/ios/chrome/browser/intelligence/bwg/model/gemini_browser_agent.mm
@@ -55,15 +55,16 @@ namespace { -// Helper to convert PageContextWrapperError to BWGPageContextComputationState. -ios::provider::BWGPageContextComputationState -BWGPageContextComputationStateFromPageContextWrapperError( +// Helper to convert PageContextWrapperError to +// GeminiPageContextComputationState. +ios::provider::GeminiPageContextComputationState +GeminiPageContextComputationStateFromPageContextWrapperError( PageContextWrapperError error) { switch (error) { case PageContextWrapperError::kForceDetachError: - return ios::provider::BWGPageContextComputationState::kProtected; + return ios::provider::GeminiPageContextComputationState::kProtected; default: - return ios::provider::BWGPageContextComputationState::kError; + return ios::provider::GeminiPageContextComputationState::kError; } } @@ -316,12 +317,13 @@ if (expected_page_context.has_value()) { PresentFloatyWithState( base_view_controller, std::move(expected_page_context.value()), - ios::provider::BWGPageContextComputationState::kSuccess, entry_point); + ios::provider::GeminiPageContextComputationState::kSuccess, + entry_point); } else { PresentFloatyWithState( base_view_controller, /*page_context_proto=*/nullptr, - BWGPageContextComputationStateFromPageContextWrapperError( + GeminiPageContextComputationStateFromPageContextWrapperError( expected_page_context.error()), entry_point); } @@ -362,16 +364,21 @@ base::expected<std::unique_ptr<optimization_guide::proto::PageContext>, PageContextWrapperError> expected_page_context) { GeminiPageContext* gemini_page_context = [[GeminiPageContext alloc] init]; - gemini_page_context.BWGPageContextComputationState = - ios::provider::BWGPageContextComputationState::kSuccess; + // TODO(crbug.com/467341090): Remove the chain assignment after the migration. + gemini_page_context.geminiPageContextComputationState = + gemini_page_context.BWGPageContextComputationState = + ios::provider::BWGPageContextComputationState::kSuccess; std::unique_ptr<optimization_guide::proto::PageContext> page_context_proto = nullptr; if (expected_page_context.has_value()) { page_context_proto = std::move(expected_page_context.value()); } else { - gemini_page_context.BWGPageContextComputationState = - BWGPageContextComputationStateFromPageContextWrapperError( - expected_page_context.error()); + // TODO(crbug.com/467341090): Remove the chain assignment after the + // migration. + gemini_page_context.geminiPageContextComputationState = + gemini_page_context.BWGPageContextComputationState = + GeminiPageContextComputationStateFromPageContextWrapperError( + expected_page_context.error()); } gemini_page_context.uniquePageContext = std::move(page_context_proto); gemini_page_context.favicon = FetchPageFavicon(); @@ -685,7 +692,7 @@ void GeminiBrowserAgent::PresentFloatyWithState( UIViewController* base_view_controller, std::unique_ptr<optimization_guide::proto::PageContext> page_context_proto, - ios::provider::BWGPageContextComputationState computation_state, + ios::provider::GeminiPageContextComputationState computation_state, gemini::EntryPoint entry_point, UIImage* image_attachment) { SetSessionCommandHandlers(); @@ -728,7 +735,9 @@ // Set the page context itself and page context computation/attachment state // for the current web state. config.pageContext = [[GeminiPageContext alloc] init]; - config.pageContext.BWGPageContextComputationState = computation_state; + // TODO(crbug.com/467341090): Remove the chain assignment after the migration. + config.pageContext.geminiPageContextComputationState = + config.pageContext.BWGPageContextComputationState = computation_state; config.pageContext.uniquePageContext = std::move(page_context_proto); config.pageContext.favicon = FetchPageFavicon(); ApplyUserPrefsToPageContext(config.pageContext); @@ -774,7 +783,7 @@ // If page context is not disabled by the user, page context is always // available and should be attached. Note page context is only partially // available (e.g. title, url, favicon) while - // `BWGPageContextComputationState` is pending. + // `GeminiPageContextComputationState` is pending. gemini_page_context.BWGPageContextAttachmentState = ios::provider::BWGPageContextAttachmentState::kAttached; } @@ -790,12 +799,12 @@ if (response.has_value()) { PresentFloatyWithState( base_view_controller, std::move(response.value()), - ios::provider::BWGPageContextComputationState::kSuccess, entry_point, + ios::provider::GeminiPageContextComputationState::kSuccess, entry_point, image_attachment); } else { PresentFloatyWithState( base_view_controller, nullptr, - BWGPageContextComputationStateFromPageContextWrapperError( + GeminiPageContextComputationStateFromPageContextWrapperError( response.error()), entry_point, image_attachment); }
diff --git a/ios/chrome/browser/intelligence/bwg/model/gemini_configuration.h b/ios/chrome/browser/intelligence/bwg/model/gemini_configuration.h index 8b8be6a..7385179 100644 --- a/ios/chrome/browser/intelligence/bwg/model/gemini_configuration.h +++ b/ios/chrome/browser/intelligence/bwg/model/gemini_configuration.h
@@ -17,7 +17,7 @@ namespace ios::provider { enum class GeminiLocationPermissionState; enum class BWGPageContextState; -enum class BWGPageContextComputationState; +enum class GeminiPageContextComputationState; enum class BWGPageContextAttachmentState; } // namespace ios::provider @@ -47,8 +47,14 @@ @property(nonatomic, assign) ios::provider::GeminiLocationPermissionState geminiLocationPermissionState; +// The state of the Gemini PageContext computation. +@property(nonatomic, assign) ios::provider::GeminiPageContextComputationState + geminiPageContextComputationState; + // The state of the BWG PageContext computation. -@property(nonatomic, assign) ios::provider::BWGPageContextComputationState +// TODO(crbug.com/467341090): Remove this property once all callers have +// migrated. +@property(nonatomic, assign) ios::provider::GeminiPageContextComputationState BWGPageContextComputationState; // The state of the BWG PageContext attachment.
diff --git a/ios/chrome/browser/intelligence/bwg/model/gemini_page_context.h b/ios/chrome/browser/intelligence/bwg/model/gemini_page_context.h index 50ce4bf..4894f80b 100644 --- a/ios/chrome/browser/intelligence/bwg/model/gemini_page_context.h +++ b/ios/chrome/browser/intelligence/bwg/model/gemini_page_context.h
@@ -11,7 +11,7 @@ #import <memory> namespace ios::provider { -enum class BWGPageContextComputationState; +enum class GeminiPageContextComputationState; enum class BWGPageContextAttachmentState; } // namespace ios::provider @@ -27,8 +27,14 @@ std::unique_ptr<optimization_guide::proto::PageContext> uniquePageContext; +// The state of the Gemini PageContext computation. +@property(nonatomic, assign) ios::provider::GeminiPageContextComputationState + geminiPageContextComputationState; + // The state of the BWG PageContext computation. -@property(nonatomic, assign) ios::provider::BWGPageContextComputationState +// TODO(crbug.com/467341090): Remove this property once all callers have +// migrated. +@property(nonatomic, assign) ios::provider::GeminiPageContextComputationState BWGPageContextComputationState; // The state of the BWG PageContext attachment.
diff --git a/ios/chrome/browser/main/model/BUILD.gn b/ios/chrome/browser/main/model/BUILD.gn index 7fd36e30..7e3371b 100644 --- a/ios/chrome/browser/main/model/BUILD.gn +++ b/ios/chrome/browser/main/model/BUILD.gn
@@ -55,6 +55,8 @@ "//ios/chrome/browser/shared/model/web_state_list", "//ios/chrome/browser/shared/public/commands", "//ios/chrome/browser/shared/public/features", + "//ios/chrome/browser/signin/model:authentication_service", + "//ios/chrome/browser/signin/model:authentication_service_factory", "//ios/chrome/browser/snapshots/model", "//ios/chrome/browser/start_surface/ui_bundled", "//ios/chrome/browser/sync/model:sync_error_browser_agent",
diff --git a/ios/chrome/browser/main/model/DEPS b/ios/chrome/browser/main/model/DEPS index 6ebbccc..8ce1abd 100644 --- a/ios/chrome/browser/main/model/DEPS +++ b/ios/chrome/browser/main/model/DEPS
@@ -40,6 +40,8 @@ "+ios/chrome/browser/reading_list/model/reading_list_browser_agent.h", "+ios/chrome/browser/send_tab_to_self/model/send_tab_to_self_browser_agent.h", "+ios/chrome/browser/sessions/model/live_tab_context_browser_agent.h", + "+ios/chrome/browser/signin/model/authentication_service.h", + "+ios/chrome/browser/signin/model/authentication_service_factory.h", "+ios/chrome/browser/snapshots/model/snapshot_browser_agent.h", "+ios/chrome/browser/start_surface/ui_bundled/start_surface_recent_tab_browser_agent.h", "+ios/chrome/browser/sync/model/sync_error_browser_agent.h",
diff --git a/ios/chrome/browser/main/model/browser_agent_util.mm b/ios/chrome/browser/main/model/browser_agent_util.mm index ade69f3..84ea15d 100644 --- a/ios/chrome/browser/main/model/browser_agent_util.mm +++ b/ios/chrome/browser/main/model/browser_agent_util.mm
@@ -41,6 +41,8 @@ #import "ios/chrome/browser/shared/public/commands/command_dispatcher.h" #import "ios/chrome/browser/shared/public/commands/reader_mode_commands.h" #import "ios/chrome/browser/shared/public/features/features.h" +#import "ios/chrome/browser/signin/model/authentication_service.h" +#import "ios/chrome/browser/signin/model/authentication_service_factory.h" #import "ios/chrome/browser/snapshots/model/snapshot_browser_agent.h" #import "ios/chrome/browser/start_surface/ui_bundled/start_surface_recent_tab_browser_agent.h" #import "ios/chrome/browser/sync/model/sync_error_browser_agent.h" @@ -209,7 +211,9 @@ } if (!browser_is_inactive && !browser_is_temporary && !browser_is_off_record) { - PrerenderBrowserAgent::CreateForBrowser(browser); + PrerenderBrowserAgent::CreateForBrowser( + browser, + AuthenticationServiceFactory::GetForProfile(browser->GetProfile())); } if (IsCleanupPersistedTabContextsEnabled() && !browser_is_off_record &&
diff --git a/ios/chrome/browser/prerender/model/BUILD.gn b/ios/chrome/browser/prerender/model/BUILD.gn index 53d5ed0..44fdb4b 100644 --- a/ios/chrome/browser/prerender/model/BUILD.gn +++ b/ios/chrome/browser/prerender/model/BUILD.gn
@@ -21,6 +21,7 @@ "//components/prefs", "//components/prefs/ios", "//components/signin/ios/browser", + "//components/signin/ios/browser:signin_enabled_datasource", "//components/supervised_user/core/browser", "//components/supervised_user/core/common", "//ios/chrome/browser/app_launcher/model", @@ -57,6 +58,8 @@ "//base", "//base/test:test_support", "//components/prefs", + "//components/signin/ios/browser:fake_signin_enabled_datasource", + "//components/signin/ios/browser:signin_enabled_datasource", "//components/signin/public/identity_manager", "//components/signin/public/identity_manager:test_support", "//components/supervised_user/core/browser",
diff --git a/ios/chrome/browser/prerender/model/prerender_browser_agent.h b/ios/chrome/browser/prerender/model/prerender_browser_agent.h index 957a897..6d9802b 100644 --- a/ios/chrome/browser/prerender/model/prerender_browser_agent.h +++ b/ios/chrome/browser/prerender/model/prerender_browser_agent.h
@@ -7,17 +7,18 @@ #import <Foundation/Foundation.h> -#include "base/memory/weak_ptr.h" -#include "base/sequence_checker.h" -#include "base/timer/timer.h" -#include "components/prefs/pref_change_registrar.h" -#include "ios/chrome/browser/prerender/model/prerender_pref.h" -#include "ios/chrome/browser/prerender/model/prerender_tab_helper_delegate.h" -#include "ios/chrome/browser/shared/model/browser/browser_user_data.h" -#include "ios/web/public/navigation/referrer.h" -#include "net/base/network_change_notifier.h" -#include "ui/base/page_transition_types.h" -#include "url/gurl.h" +#import "base/memory/weak_ptr.h" +#import "base/sequence_checker.h" +#import "base/timer/timer.h" +#import "components/prefs/pref_change_registrar.h" +#import "components/signin/ios/browser/signin_enabled_datasource.h" +#import "ios/chrome/browser/prerender/model/prerender_pref.h" +#import "ios/chrome/browser/prerender/model/prerender_tab_helper_delegate.h" +#import "ios/chrome/browser/shared/model/browser/browser_user_data.h" +#import "ios/web/public/navigation/referrer.h" +#import "net/base/network_change_notifier.h" +#import "ui/base/page_transition_types.h" +#import "url/gurl.h" class ManageAccountsDelegate; @protocol PrerenderBrowserAgentDelegate; @@ -94,7 +95,9 @@ friend class ManageAccountsDelegate; friend class BrowserUserData<PrerenderBrowserAgent>; - PrerenderBrowserAgent(Browser* browser); + PrerenderBrowserAgent( + Browser* browser, + signin::SigninEnabledDataSource* signin_enabled_data_source); // Returns whether pre-render is enabled or not. bool Enabled() const; @@ -156,6 +159,8 @@ // Is the pre-render tab being converted to a real tab? bool loading_prerender_ = false; + raw_ptr<signin::SigninEnabledDataSource> signin_enabled_data_source_; + base::WeakPtrFactory<PrerenderBrowserAgent> weak_ptr_factory_{this}; };
diff --git a/ios/chrome/browser/prerender/model/prerender_browser_agent.mm b/ios/chrome/browser/prerender/model/prerender_browser_agent.mm index cec724e..fee5ece0 100644 --- a/ios/chrome/browser/prerender/model/prerender_browser_agent.mm +++ b/ios/chrome/browser/prerender/model/prerender_browser_agent.mm
@@ -370,6 +370,9 @@ void OnGoIncognito(const GURL& url) final { agent_->ScheduleCancelPrerender(); } + bool SigninEnabled() const final { + return agent_->signin_enabled_data_source_->SigninEnabled(); + } private: const raw_ref<PrerenderBrowserAgent> agent_; @@ -385,8 +388,11 @@ kMaxValue = kNotAllowed, }; -PrerenderBrowserAgent::PrerenderBrowserAgent(Browser* browser) - : BrowserUserData(browser) { +PrerenderBrowserAgent::PrerenderBrowserAgent( + Browser* browser, + signin::SigninEnabledDataSource* signin_enabled_data_source) + : BrowserUserData(browser), + signin_enabled_data_source_(signin_enabled_data_source) { net::NetworkChangeNotifier::AddNetworkChangeObserver(this); registrar_.Init(browser_->GetProfile()->GetPrefs()); registrar_.Add(prefs::kNetworkPredictionSetting, @@ -592,9 +598,9 @@ AttachTabHelpers(web_state, TabHelperFilter::kPrerender); crash_report_helper::MonitorURLsForPreloadWebState(web_state); + ProfileIOS* profile = browser_->GetProfile(); if (AccountConsistencyService* service = - ios::AccountConsistencyServiceFactory::GetForProfile( - browser_->GetProfile())) { + ios::AccountConsistencyServiceFactory::GetForProfile(profile)) { if (!manage_accounts_delegate_) { manage_accounts_delegate_ = std::make_unique<ManageAccountsDelegate>(this);
diff --git a/ios/chrome/browser/prerender/model/prerender_browser_agent_unittest.mm b/ios/chrome/browser/prerender/model/prerender_browser_agent_unittest.mm index 35a54cb..159e3e8 100644 --- a/ios/chrome/browser/prerender/model/prerender_browser_agent_unittest.mm +++ b/ios/chrome/browser/prerender/model/prerender_browser_agent_unittest.mm
@@ -9,6 +9,7 @@ #import "base/check_deref.h" #import "base/memory/raw_ptr.h" #import "components/prefs/pref_service.h" +#import "components/signin/ios/browser/fake_signin_enabled_datasource.h" #import "components/signin/public/base/consent_level.h" #import "components/signin/public/identity_manager/account_info.h" #import "components/signin/public/identity_manager/identity_test_utils.h" @@ -151,7 +152,8 @@ profile_ = std::move(builder).Build(); browser_ = std::make_unique<TestBrowser>(profile_.get()); - PrerenderBrowserAgent::CreateForBrowser(browser_.get()); + PrerenderBrowserAgent::CreateForBrowser(browser_.get(), + &signin_enabled_data_source); // Prerendering clones the active WebState from the Browser's WebStateList // or fail if there is none. Insert a WebState to avoid this failure state. @@ -247,6 +249,7 @@ IOSChromeScopedTestingLocalState scoped_testing_local_state_; std::unique_ptr<net::test::MockNetworkChangeNotifier> network_change_notifier_; + signin::FakeSigninEnabledDataSource signin_enabled_data_source; std::unique_ptr<TestProfileIOS> profile_; std::unique_ptr<TestBrowser> browser_;
diff --git a/ios/chrome/browser/reader_mode/model/resources/reader_mode_scroll_anchor.ts b/ios/chrome/browser/reader_mode/model/resources/reader_mode_scroll_anchor.ts index 1e2e39ede..c64e485 100644 --- a/ios/chrome/browser/reader_mode/model/resources/reader_mode_scroll_anchor.ts +++ b/ios/chrome/browser/reader_mode/model/resources/reader_mode_scroll_anchor.ts
@@ -28,7 +28,7 @@ const rect = closestParagraph.getBoundingClientRect(); const pText = closestParagraph.innerText; // The hashing method must be consistent with `scrollToParagraphByHash` in - // `components/dom_distiller/core/javascript/dom_distiller_viewer.js`. + // `components/dom_distiller/core/javascript/dom_distiller_viewer_main.js`. const hashCode = (s: string) => s.split('').reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0); const progress = (viewportMiddle - rect.top) / rect.height;
diff --git a/ios/chrome/browser/reader_mode/ui/reader_mode_egtest.mm b/ios/chrome/browser/reader_mode/ui/reader_mode_egtest.mm index c24e56f..a97cc6a 100644 --- a/ios/chrome/browser/reader_mode/ui/reader_mode_egtest.mm +++ b/ios/chrome/browser/reader_mode/ui/reader_mode_egtest.mm
@@ -58,7 +58,7 @@ // Base font size for Reader Mode, in pixels. This is the font size that is // multiplied by the font scale multipliers. This value is defined in -// components/dom_distiller/core/javascript/dom_distiller_viewer.js. +// components/dom_distiller/core/javascript/font_size_slider.js. constexpr double kReaderModeBaseFontSize = 16.0; // Return the number of links on the page. @@ -147,6 +147,9 @@ chrome_test_util::GREYAssertErrorNil( [MetricsAppInterface setupHistogramTester]); + // The user should be signed out at the beginning of Reading Mode tests. + [SigninEarlGrey verifySignedOut]; + self.fakeIdentity = [FakeSystemIdentity fakeIdentity1]; [SigninEarlGrey addFakeIdentity:self.fakeIdentity withCapabilities:@{ @@ -175,12 +178,16 @@ [self isRunningTest:@selector(testReaderModeChipShowsAIHubIfAvailable)] || [self isRunningTest:@selector (FLAKY_testSampleContextualChipVisibleInReaderMode)] || - [self isRunningTest:@selector - (FLAKY_testReaderModeChipHiddenInReaderMode)]) { + [self isRunningTest:@selector(testReaderModeChipHiddenInReaderMode)]) { config.features_enabled_and_params.push_back({kPageActionMenu, {}}); config.features_enabled_and_params.push_back( {kProactiveSuggestionsFramework, {}}); } else { + // Force an app restart before any tests that require the Gemini kill + // switch. This is required to ensure that + // BWGServiceFactory::BuildBwgService is re-evaluated for the new flag + // configuration, otherwise a cached BWGService instance may be used. + config.relaunch_policy = ForceRelaunchByCleanShutdown; config.features_disabled.push_back(kPageActionMenu); config.features_enabled_and_params.push_back({kGeminiKillSwitch, {}}); } @@ -205,8 +212,7 @@ } if ([self isRunningTest:@selector (FLAKY_testSampleContextualChipVisibleInReaderMode)] || - [self isRunningTest:@selector - (FLAKY_testReaderModeChipHiddenInReaderMode)]) { + [self isRunningTest:@selector(testReaderModeChipHiddenInReaderMode)]) { config.features_enabled_and_params.push_back( {kProactiveSuggestionsFramework, {}}); config.features_enabled_and_params.push_back({kAskGeminiChip, {}}); @@ -592,8 +598,7 @@ } // Tests that font family can be changed from the options view. -// TODO(crbug.com/481633359): Deflake this test. -- (void)FLAKY_testChangeReaderModeFontFamilyFromOptionsView { +- (void)testChangeReaderModeFontFamilyFromOptionsView { [ChromeEarlGrey loadURL:self.testServer->GetURL("/article.html")]; // Open Reader Mode UI. @@ -960,8 +965,7 @@ // Tests that the Reader mode contextual chip is hidden inside Reader mode if // kAskGeminiChip is enabled. -// TODO(crbug.com/481633359): Deflake this test. -- (void)FLAKY_testReaderModeChipHiddenInReaderMode { +- (void)testReaderModeChipHiddenInReaderMode { [SigninEarlGrey signinWithFakeIdentity:self.fakeIdentity]; [self loadURLWithOptimizationGuideHints:self.testServer->GetURL( "/article.html")];
diff --git a/ios/chrome/browser/shared/public/features/features.h b/ios/chrome/browser/shared/public/features/features.h index 77eaece..5217c9c 100644 --- a/ios/chrome/browser/shared/public/features/features.h +++ b/ios/chrome/browser/shared/public/features/features.h
@@ -280,6 +280,9 @@ // Feature flag enabling the client folder implementation of Save to Drive. BASE_DECLARE_FEATURE(kIOSSaveToDriveClientFolder); +// Feature flag enabling the save to drive feature for signed out users. +BASE_DECLARE_FEATURE(kIOSSaveToDriveSignedOut); + // Feature flag to enable feed background refresh. // Use IsFeedBackgroundRefreshEnabled() instead of this constant directly. BASE_DECLARE_FEATURE(kEnableFeedBackgroundRefresh);
diff --git a/ios/chrome/browser/shared/public/features/features.mm b/ios/chrome/browser/shared/public/features/features.mm index e8bd8ee..4f506d2 100644 --- a/ios/chrome/browser/shared/public/features/features.mm +++ b/ios/chrome/browser/shared/public/features/features.mm
@@ -214,6 +214,8 @@ BASE_FEATURE(kIOSSaveToDriveClientFolder, base::FEATURE_ENABLED_BY_DEFAULT); +BASE_FEATURE(kIOSSaveToDriveSignedOut, base::FEATURE_DISABLED_BY_DEFAULT); + BASE_FEATURE(kEnableFeedBackgroundRefresh, base::FEATURE_DISABLED_BY_DEFAULT); BASE_FEATURE(kEnableFeedAblation, base::FEATURE_DISABLED_BY_DEFAULT); @@ -489,12 +491,7 @@ BASE_FEATURE(kEnableAppBackgroundRefresh, base::FEATURE_DISABLED_BY_DEFAULT); bool IsAppBackgroundRefreshEnabled() { - // To test background refresh in conjunction with the keychain access - // migration, enable app background refresh if *either* its flag or - // the keychain access flag is enabled. - return base::FeatureList::IsEnabled(kEnableAppBackgroundRefresh) || - base::FeatureList::IsEnabled( - crypto::features::kMigrateIOSKeychainAccessibility); + return base::FeatureList::IsEnabled(kEnableAppBackgroundRefresh); } BASE_FEATURE(kHomeMemoryImprovements, base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/ios/chrome/browser/shared/public/features/system_flags.mm b/ios/chrome/browser/shared/public/features/system_flags.mm index 9abc6a1e..eb4404c 100644 --- a/ios/chrome/browser/shared/public/features/system_flags.mm +++ b/ios/chrome/browser/shared/public/features/system_flags.mm
@@ -217,6 +217,10 @@ if (duration == 0) { return base::Hours(4); } + if (duration == -1) { + // if set to -1 the duration will be instant. + return base::Seconds(0); + } return base::Seconds(duration); }
diff --git a/ios/chrome/browser/signin/model/BUILD.gn b/ios/chrome/browser/signin/model/BUILD.gn index 6d0afeb5..d4e2fa7c 100644 --- a/ios/chrome/browser/signin/model/BUILD.gn +++ b/ios/chrome/browser/signin/model/BUILD.gn
@@ -70,6 +70,7 @@ "//components/signin/core/browser", "//components/signin/internal/identity_manager", "//components/signin/ios/browser", + "//components/signin/ios/browser:signin_enabled_datasource", "//components/signin/public/identity_manager", "//components/signin/public/identity_manager/ios", "//components/sync", @@ -141,7 +142,9 @@ "//components/policy/core/common", "//components/pref_registry", "//components/prefs", + "//components/signin/ios/browser", "//components/signin/ios/browser:features", + "//components/signin/ios/browser:signin_enabled_datasource", "//components/signin/public/base", "//components/signin/public/base:signin_switches", "//components/signin/public/identity_manager", @@ -179,6 +182,7 @@ ":model", "//base", "//components/browsing_data/core", + "//components/signin/ios/browser:signin_enabled_datasource", "//ios/chrome/browser/browsing_data/model", "//ios/chrome/browser/ntp/ui_bundled:feature_flags", "//ios/chrome/browser/shared/model/prefs:pref_names", @@ -450,6 +454,7 @@ "//components/prefs", "//components/prefs:test_support", "//components/signin/internal/identity_manager", + "//components/signin/ios/browser:fake_signin_enabled_datasource", "//components/signin/ios/browser:features", "//components/signin/public/base:test_support", "//components/signin/public/identity_manager",
diff --git a/ios/chrome/browser/signin/model/account_consistency_browser_agent.h b/ios/chrome/browser/signin/model/account_consistency_browser_agent.h index f1696fef..71e21ad 100644 --- a/ios/chrome/browser/signin/model/account_consistency_browser_agent.h +++ b/ios/chrome/browser/signin/model/account_consistency_browser_agent.h
@@ -7,6 +7,7 @@ #import "base/memory/raw_ptr.h" #import "components/signin/ios/browser/manage_accounts_delegate.h" +#import "components/signin/ios/browser/signin_enabled_datasource.h" #import "ios/chrome/browser/authentication/ui_bundled/signin/signin_constants.h" #import "ios/chrome/browser/shared/model/browser/browser_user_data.h" #import "ios/chrome/browser/tabs/model/tabs_dependency_installer.h" @@ -45,6 +46,7 @@ void OnShowConsistencyPromo(const GURL& url, web::WebState* webState) override; void OnGoIncognito(const GURL& url) override; + bool SigninEnabled() const override; private: friend class BrowserUserData<AccountConsistencyBrowserAgent>; @@ -62,8 +64,10 @@ // `base_view_controller` is the view controller which UI will be presented // from. - AccountConsistencyBrowserAgent(Browser* browser, - UIViewController* base_view_controller); + AccountConsistencyBrowserAgent( + Browser* browser, + UIViewController* base_view_controller, + signin::SigninEnabledDataSource* signin_enabled_data_source); // Returns whether it is is possible to show the browser's account menu. bool CanShowAccountMenu() const; @@ -77,6 +81,8 @@ id<SettingsCommands> settings_handler_; SigninCoordinator* add_account_coordinator_; + raw_ptr<signin::SigninEnabledDataSource> signin_enabled_data_source_; + // Bridge object to act as the delegate. ManageAccountsDelegateBridge* bridge_; };
diff --git a/ios/chrome/browser/signin/model/account_consistency_browser_agent.mm b/ios/chrome/browser/signin/model/account_consistency_browser_agent.mm index 51db44d..303054df0 100644 --- a/ios/chrome/browser/signin/model/account_consistency_browser_agent.mm +++ b/ios/chrome/browser/signin/model/account_consistency_browser_agent.mm
@@ -37,8 +37,11 @@ AccountConsistencyBrowserAgent::AccountConsistencyBrowserAgent( Browser* browser, - UIViewController* base_view_controller) - : BrowserUserData(browser), base_view_controller_(base_view_controller) { + UIViewController* base_view_controller, + signin::SigninEnabledDataSource* signin_enabled_data_source) + : BrowserUserData(browser), + base_view_controller_(base_view_controller), + signin_enabled_data_source_(signin_enabled_data_source) { StartObserving(browser); application_handler_ = HandlerForProtocol(browser_->GetCommandDispatcher(), SceneCommands); @@ -60,9 +63,9 @@ void AccountConsistencyBrowserAgent::OnWebStateInserted( web::WebState* web_state) { + ProfileIOS* profile = browser_->GetProfile(); if (AccountConsistencyService* accountConsistencyService = - ios::AccountConsistencyServiceFactory::GetForProfile( - browser_->GetProfile())) { + ios::AccountConsistencyServiceFactory::GetForProfile(profile)) { accountConsistencyService->SetWebStateHandler(web_state, this); } } @@ -229,6 +232,10 @@ [application_handler_ openURLInNewTab:command]; } +bool AccountConsistencyBrowserAgent::SigninEnabled() const { + return signin_enabled_data_source_->SigninEnabled(); +} + bool AccountConsistencyBrowserAgent::CanShowAccountMenu() const { if (!AreSeparateProfilesForManagedAccountsEnabled()) { return false;
diff --git a/ios/chrome/browser/signin/model/account_consistency_browser_agent_unittest.mm b/ios/chrome/browser/signin/model/account_consistency_browser_agent_unittest.mm index f543660..dde4fd6 100644 --- a/ios/chrome/browser/signin/model/account_consistency_browser_agent_unittest.mm +++ b/ios/chrome/browser/signin/model/account_consistency_browser_agent_unittest.mm
@@ -6,6 +6,8 @@ #import "base/memory/raw_ptr.h" #import "base/strings/sys_string_conversions.h" +#import "base/test/scoped_feature_list.h" +#import "components/signin/ios/browser/fake_signin_enabled_datasource.h" #import "components/test/ios/test_utils.h" #import "ios/chrome/browser/shared/model/application_context/application_context.h" #import "ios/chrome/browser/shared/model/browser/test/test_browser.h" @@ -64,7 +66,8 @@ base_view_controller_mock_ = OCMStrictClassMock([UIViewController class]); WebNavigationBrowserAgent::CreateForBrowser(browser_.get()); AccountConsistencyBrowserAgent::CreateForBrowser( - browser_.get(), base_view_controller_mock_); + browser_.get(), base_view_controller_mock_, + &signin_enabled_data_source_); agent_ = AccountConsistencyBrowserAgent::FromBrowser(browser_.get()); WebStateList* web_state_list = browser_.get()->GetWebStateList(); @@ -101,6 +104,7 @@ std::unique_ptr<Browser> browser_; raw_ptr<AccountConsistencyBrowserAgent> agent_; id<SceneCommands> mock_scene_handler_; + signin::FakeSigninEnabledDataSource signin_enabled_data_source_; id<SettingsCommands> settings_commands_mock_; id<BrowserCoordinatorCommands> browser_coordinator_commands_mock_; UIViewController* base_view_controller_mock_;
diff --git a/ios/chrome/browser/signin/model/authentication_service.h b/ios/chrome/browser/signin/model/authentication_service.h index 5b5ca33..a2195b8 100644 --- a/ios/chrome/browser/signin/model/authentication_service.h +++ b/ios/chrome/browser/signin/model/authentication_service.h
@@ -15,6 +15,8 @@ #import "base/scoped_observation.h" #import "components/keyed_service/core/keyed_service.h" #import "components/pref_registry/pref_registry_syncable.h" +#import "components/signin/ios/browser/account_consistency_service.h" +#import "components/signin/ios/browser/signin_enabled_datasource.h" #import "components/signin/public/base/consent_level.h" #import "components/signin/public/base/signin_metrics.h" #import "components/signin/public/identity_manager/identity_manager.h" @@ -37,7 +39,8 @@ // policies. class AuthenticationService : public KeyedService, public signin::IdentityManager::Observer, - public ChromeAccountManagerService::Observer { + public ChromeAccountManagerService::Observer, + public signin::SigninEnabledDataSource { public: // The service status for AuthenticationService. enum class ServiceStatus { @@ -148,8 +151,7 @@ // sync the accounts between the IdentityManager and the SSO library. void OnApplicationWillEnterForeground(); - // Whether the sign-in is not disabled. - bool SigninEnabled() const; + bool SigninEnabled() const override; private: friend class AuthenticationServiceTest;
diff --git a/ios/chrome/browser/start_surface/ui_bundled/home_surface_egtest_utils.mm b/ios/chrome/browser/start_surface/ui_bundled/home_surface_egtest_utils.mm index bbec06f9..c895516 100644 --- a/ios/chrome/browser/start_surface/ui_bundled/home_surface_egtest_utils.mm +++ b/ios/chrome/browser/start_surface/ui_bundled/home_surface_egtest_utils.mm
@@ -12,7 +12,7 @@ } // namespace void MakeHomeSurfaceOpenImmediately() { - [ChromeEarlGrey setUserDefaultsObject:@1 forKey:kHomeSurfaceDuration]; + [ChromeEarlGrey setUserDefaultsObject:@-1 forKey:kHomeSurfaceDuration]; } void ResetMakeHomeSurfaceOpenImmediately() {
diff --git a/ios/chrome/browser/start_surface/ui_bundled/start_surface_egtest.mm b/ios/chrome/browser/start_surface/ui_bundled/start_surface_egtest.mm index 0a6edfc..d799409 100644 --- a/ios/chrome/browser/start_surface/ui_bundled/start_surface_egtest.mm +++ b/ios/chrome/browser/start_surface/ui_bundled/start_surface_egtest.mm
@@ -72,7 +72,7 @@ config.additional_args.push_back("--test-ios-module-ranker=tab_resumption"); - if ([self isRunningTest:@selector(FLAKY_testShowTabGroupInGridOnStart)] || + if ([self isRunningTest:@selector(testShowTabGroupInGridOnStart)] || [self isRunningTest:@selector (testDoNotShowTabGroupInGridOnStartInIncognitoMode)]) { config.features_enabled_and_params.push_back( @@ -199,11 +199,16 @@ // Tests that the tab group in grid view is opened if Chrome is activated in the // right time interval. -// TODO(crbug.com/462071614): Re-enable flaky test. This test is flaky due -// to devices possibly running under Stage Manager, hence the app never goes -// in the background. These tests expect the app to be backgrounding, and -// fail. -- (void)FLAKY_testShowTabGroupInGridOnStart { +- (void)testShowTabGroupInGridOnStart { + if ([ChromeEarlGrey isIPadIdiom]) { + // Disabled on iPad, due to stage manager the app is not backgrounded + // properly. + EARL_GREY_TEST_DISABLED(@"Test disabled on iPad."); + } + // This test needs to be in the interval between the + // ShowTabGroupInGridInactiveDurationInSeconds and the HomeSurfaceDuration. + ResetMakeHomeSurfaceOpenImmediately(); + [ChromeEarlGreyUI openTabGrid]; // Create a tab group with an item at 0. @@ -228,6 +233,11 @@ // Tests that the tab group in grid view is not opened if Chrome is not // activated in the right time interval. - (void)testDoNotShowTabGroupInGridOnStart { + if ([ChromeEarlGrey isIPadIdiom]) { + // Disabled on iPad, due to stage manager the app is not backgrounded + // properly. + EARL_GREY_TEST_DISABLED(@"Test disabled on iPad."); + } [ChromeEarlGreyUI openTabGrid]; // Create a tab group with an item at 0. @@ -252,6 +262,15 @@ // Tests that the tab group in grid view is not opened if Chrome is activated in // the right time interval but in Incognito mode. - (void)testDoNotShowTabGroupInGridOnStartInIncognitoMode { + if ([ChromeEarlGrey isIPadIdiom]) { + // Disabled on iPad, due to stage manager the app is not backgrounded + // properly. + EARL_GREY_TEST_DISABLED(@"Test disabled on iPad."); + } + // This test needs to be in the interval between the + // ShowTabGroupInGridInactiveDurationInSeconds and the HomeSurfaceDuration. + ResetMakeHomeSurfaceOpenImmediately(); + [ChromeEarlGrey openNewIncognitoTab]; [ChromeEarlGreyUI openTabGrid]; @@ -278,6 +297,11 @@ // Tests that the created NTP is ungrouped, even if a group was active when // backgrounded. - (void)testOpenNTPOutsideTheActiveGroupAfterFourHoursInBackground { + if ([ChromeEarlGrey isIPadIdiom]) { + // Disabled on iPad, due to stage manager the app is not backgrounded + // properly. + EARL_GREY_TEST_DISABLED(@"Test disabled on iPad."); + } [self loadFirstTabURL]; [ChromeEarlGreyUI openTabGrid];
diff --git a/ios/chrome/browser/webauthn/coordinator/BUILD.gn b/ios/chrome/browser/webauthn/coordinator/BUILD.gn new file mode 100644 index 0000000..5126471c --- /dev/null +++ b/ios/chrome/browser/webauthn/coordinator/BUILD.gn
@@ -0,0 +1,27 @@ +# Copyright 2026 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +source_set("coordinator") { + sources = [ + "passkey_welcome_screen_coordinator.h", + "passkey_welcome_screen_coordinator.mm", + ] + deps = [ + "//base", + "//components/strings", + "//components/webauthn/ios:passkey_types", + "//ios/chrome/app/strings", + "//ios/chrome/browser/settings/ui_bundled/password:title_view", + "//ios/chrome/browser/shared/coordinator/chrome_coordinator", + "//ios/chrome/browser/shared/model/browser", + "//ios/chrome/browser/shared/model/profile", + "//ios/chrome/browser/signin/model", + "//ios/chrome/browser/signin/model:authentication_service", + "//ios/chrome/browser/signin/model:authentication_service_factory", + "//ios/chrome/browser/webauthn/public", + "//ios/chrome/common/credential_provider/ui", + "//ios/chrome/common/ui/elements:branded_navigation_item_title_view", + "//ui/base", + ] +}
diff --git a/ios/chrome/browser/webauthn/coordinator/DEPS b/ios/chrome/browser/webauthn/coordinator/DEPS new file mode 100644 index 0000000..ecfe0ab --- /dev/null +++ b/ios/chrome/browser/webauthn/coordinator/DEPS
@@ -0,0 +1,6 @@ +include_rules = [ + # keep-sorted start + "+ios/chrome/browser/settings/ui_bundled/password", + "+ios/chrome/browser/signin/model", + # keep-sorted end +]
diff --git a/ios/chrome/browser/webauthn/coordinator/passkey_welcome_screen_coordinator.h b/ios/chrome/browser/webauthn/coordinator/passkey_welcome_screen_coordinator.h new file mode 100644 index 0000000..3b97391 --- /dev/null +++ b/ios/chrome/browser/webauthn/coordinator/passkey_welcome_screen_coordinator.h
@@ -0,0 +1,43 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IOS_CHROME_BROWSER_WEBAUTHN_COORDINATOR_PASSKEY_WELCOME_SCREEN_COORDINATOR_H_ +#define IOS_CHROME_BROWSER_WEBAUTHN_COORDINATOR_PASSKEY_WELCOME_SCREEN_COORDINATOR_H_ + +#import "components/webauthn/ios/passkey_types.h" +#import "ios/chrome/browser/shared/coordinator/chrome_coordinator/chrome_coordinator.h" + +@class PasskeyWelcomeScreenCoordinator; + +// Delegate for the PasskeyWelcomeScreenCoordinator. +@protocol PasskeyWelcomeScreenCoordinatorDelegate <NSObject> + +// Requests the delegate to dismiss the coordinator. +- (void)passkeyWelcomeScreenCoordinatorWantsToBeDismissed: + (PasskeyWelcomeScreenCoordinator*)coordinator; + +@end + +// Coordinator for the passkey welcome screen. +@interface PasskeyWelcomeScreenCoordinator : ChromeCoordinator + +// Delegate for the coordinator. +@property(nonatomic, weak) id<PasskeyWelcomeScreenCoordinatorDelegate> delegate; + +// Designated initializer. `purpose` indicates the purpose for which the passkey +// welcome screen needs to be shown. `completion` is the block to execute when +// the screen is dismissed. +- (instancetype) + initWithBaseViewController:(UIViewController*)viewController + browser:(Browser*)browser + purpose:(webauthn::PasskeyWelcomeScreenPurpose)purpose + completion:(webauthn::PasskeyWelcomeScreenAction)completion + NS_DESIGNATED_INITIALIZER; + +- (instancetype)initWithBaseViewController:(UIViewController*)viewController + browser:(Browser*)browser NS_UNAVAILABLE; + +@end + +#endif // IOS_CHROME_BROWSER_WEBAUTHN_COORDINATOR_PASSKEY_WELCOME_SCREEN_COORDINATOR_H_
diff --git a/ios/chrome/browser/webauthn/coordinator/passkey_welcome_screen_coordinator.mm b/ios/chrome/browser/webauthn/coordinator/passkey_welcome_screen_coordinator.mm new file mode 100644 index 0000000..4827161 --- /dev/null +++ b/ios/chrome/browser/webauthn/coordinator/passkey_welcome_screen_coordinator.mm
@@ -0,0 +1,99 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "ios/chrome/browser/webauthn/coordinator/passkey_welcome_screen_coordinator.h" + +#import "base/check.h" +#import "base/strings/sys_string_conversions.h" +#import "base/strings/utf_string_conversions.h" +#import "components/strings/grit/components_strings.h" +#import "components/webauthn/ios/passkey_types.h" +#import "ios/chrome/browser/settings/ui_bundled/password/create_password_manager_title_view.h" +#import "ios/chrome/browser/shared/model/browser/browser.h" +#import "ios/chrome/browser/shared/model/profile/profile_ios.h" +#import "ios/chrome/browser/signin/model/authentication_service.h" +#import "ios/chrome/browser/signin/model/authentication_service_factory.h" +#import "ios/chrome/browser/webauthn/public/passkey_welcome_screen_util.h" +#import "ios/chrome/common/credential_provider/ui/passkey_welcome_screen_strings.h" +#import "ios/chrome/common/credential_provider/ui/passkey_welcome_screen_view_controller.h" +#import "ios/chrome/common/ui/elements/branded_navigation_item_title_view.h" +#import "ios/chrome/grit/ios_branded_strings.h" +#import "ios/chrome/grit/ios_strings.h" +#import "ui/base/l10n/l10n_util_mac.h" + +@interface PasskeyWelcomeScreenCoordinator () < + PasskeyWelcomeScreenViewControllerDelegate> +@end + +@implementation PasskeyWelcomeScreenCoordinator { + // Purpose of the welcome screen. + webauthn::PasskeyWelcomeScreenPurpose _purpose; + + // Completion block. + webauthn::PasskeyWelcomeScreenAction _completion; + + // Navigation controller to present the welcome screen. + UINavigationController* _navigationController; + + // The welcome screen view controller. + PasskeyWelcomeScreenViewController* _viewController; +} + +- (instancetype) + initWithBaseViewController:(UIViewController*)viewController + browser:(Browser*)browser + purpose:(webauthn::PasskeyWelcomeScreenPurpose)purpose + completion: + (webauthn::PasskeyWelcomeScreenAction)completion { + self = [super initWithBaseViewController:viewController browser:browser]; + if (self) { + _purpose = purpose; + _completion = completion; + } + return self; +} + +- (void)start { + ProfileIOS* profile = self.browser->GetProfile(); + AuthenticationService* authService = + AuthenticationServiceFactory::GetForProfile(profile); + id<SystemIdentity> identity = + authService->GetPrimaryIdentity(signin::ConsentLevel::kSignin); + std::string userEmail = base::SysNSStringToUTF8(identity.userEmail); + UIView* passkeyNavigationTitleView = + password_manager::CreatePasswordManagerTitleView( + l10n_util::GetNSString(IDS_IOS_PASSWORD_MANAGER)); + PasskeyWelcomeScreenStrings* strings = + GetPasskeyWelcomeScreenStrings(_purpose, std::move(userEmail)); + + _viewController = [[PasskeyWelcomeScreenViewController alloc] + initForPurpose:_purpose + navigationItemTitleView:passkeyNavigationTitleView + delegate:self + primaryButtonAction:_completion + strings:strings]; + _navigationController = [[UINavigationController alloc] + initWithRootViewController:_viewController]; + [self.baseViewController presentViewController:_navigationController + animated:YES + completion:nil]; +} + +- (void)stop { + [_navigationController.presentingViewController + dismissViewControllerAnimated:YES + completion:nil]; + _viewController = nil; + _navigationController = nil; + _completion = nil; +} + +#pragma mark - PasskeyWelcomeScreenViewControllerDelegate + +- (void)passkeyWelcomeScreenViewControllerShouldBeDismissed: + (PasskeyWelcomeScreenViewController*)passkeyWelcomeScreenViewController { + [self.delegate passkeyWelcomeScreenCoordinatorWantsToBeDismissed:self]; +} + +@end
diff --git a/ios/chrome/browser/webauthn/model/ios_chrome_passkey_client.mm b/ios/chrome/browser/webauthn/model/ios_chrome_passkey_client.mm index 3108176..94fc7967 100644 --- a/ios/chrome/browser/webauthn/model/ios_chrome_passkey_client.mm +++ b/ios/chrome/browser/webauthn/model/ios_chrome_passkey_client.mm
@@ -145,12 +145,14 @@ signin::ConsentLevel::kSignin); auto completion_block = base::CallbackToBlock(base::BindOnce( - [](webauthn::KeysFetchedCallback inner_callback, + [](id<IOSPasskeyClientCommands> handler, + webauthn::KeysFetchedCallback inner_callback, NSArray<NSData*>* trusted_vault_keys) { std::move(inner_callback) .Run(SharedKeyListFromTrustedVaultKeys(trusted_vault_keys)); + [handler dismissPasskeyWelcomeScreen]; }, - std::move(callback))); + command_handler_, std::move(callback))); [GetPasskeyKeychainProviderBridge() fetchTrustedVaultKeysForGaia:account.gaia.ToNSString() credential:nil
diff --git a/ios/chrome/browser/webauthn/public/passkey_welcome_screen_util.h b/ios/chrome/browser/webauthn/public/passkey_welcome_screen_util.h index 77dafe9..8a82ef3 100644 --- a/ios/chrome/browser/webauthn/public/passkey_welcome_screen_util.h +++ b/ios/chrome/browser/webauthn/public/passkey_welcome_screen_util.h
@@ -12,6 +12,13 @@ #import "components/webauthn/ios/passkey_types.h" @protocol PasskeyWelcomeScreenViewControllerDelegate; +@class PasskeyWelcomeScreenStrings; + +// Returns strings needed in the welcome string for `purpose`. `userEmail` is +// needed for `PasskeyWelcomeScreenPurpose::kEnroll`, otherwise can be nil. +PasskeyWelcomeScreenStrings* GetPasskeyWelcomeScreenStrings( + webauthn::PasskeyWelcomeScreenPurpose purpose, + std::string userEmail); // Creates a passkey welcome screen for `purpose` and pushes it on the provided // `navigationController`. `primaryButtonAction` is invoked on the primary
diff --git a/ios/chrome/browser/webauthn/public/passkey_welcome_screen_util.mm b/ios/chrome/browser/webauthn/public/passkey_welcome_screen_util.mm index f7be98e..eca58640 100644 --- a/ios/chrome/browser/webauthn/public/passkey_welcome_screen_util.mm +++ b/ios/chrome/browser/webauthn/public/passkey_welcome_screen_util.mm
@@ -69,8 +69,8 @@ ]; } -// Returns strings needed in the welcome string for `purpose`. `userEmail` is -// needed for `PasskeyWelcomeScreenPurpose::kEnroll`, otherwise can be nil. +} // namespace + PasskeyWelcomeScreenStrings* GetPasskeyWelcomeScreenStrings( PasskeyWelcomeScreenPurpose purpose, std::string userEmail) { @@ -91,8 +91,6 @@ instructions:GetInstructions(purpose)]; } -} // namespace - void CreateAndPresentPasskeyWelcomeScreen( PasskeyWelcomeScreenPurpose purpose, UINavigationController* navigationController,
diff --git a/ios/public/provider/chrome/browser/bwg/bwg_api.h b/ios/public/provider/chrome/browser/bwg/bwg_api.h index e3ece81..4f6743e 100644 --- a/ios/public/provider/chrome/browser/bwg/bwg_api.h +++ b/ios/public/provider/chrome/browser/bwg/bwg_api.h
@@ -43,10 +43,10 @@ kEnterpriseDisabled, }; -// Enum representing the page context computation state of the BWG experience. +// Enum representing a page context computation state for the Gemini experience. // This needs to stay in sync with GCRGeminiPageContextComputationState (and its // SDK counterpart). -enum class BWGPageContextComputationState { +enum class GeminiPageContextComputationState { // The state of the page context is unknown; this likely means that it was not // set. kUnknown, @@ -65,6 +65,9 @@ kPending, }; +// TODO(crbug.com/467341090): Remove this alias once all callers have migrated. +using BWGPageContextComputationState = GeminiPageContextComputationState; + // Enum representing the page context attachment state of the BWG experience. // This needs to stay in sync with GCRGeminiPageContextAttachmentState (and its // SDK counterpart).
diff --git a/ios_internal b/ios_internal index fd85621..85213da 160000 --- a/ios_internal +++ b/ios_internal
@@ -1 +1 @@ -Subproject commit fd8562181f848f91602e15903c2087549ebfc693 +Subproject commit 85213da2ba5de03bda5a52539b276c288b872b46
diff --git a/media/gpu/chromeos/native_pixmap_frame_resource.cc b/media/gpu/chromeos/native_pixmap_frame_resource.cc index ecc6536..7ead2a7 100644 --- a/media/gpu/chromeos/native_pixmap_frame_resource.cc +++ b/media/gpu/chromeos/native_pixmap_frame_resource.cc
@@ -183,7 +183,7 @@ auto layout = media::VideoFrameLayout::CreateWithPlanes( *pixel_format, pixmap->GetBufferSize(), std::move(planes), media::VideoFrameLayout::kBufferAddressAlignment, - pixmap->GetBufferFormatModifier()); + pixmap->GetFormatModifier()); if (!layout) { DLOGF(ERROR) << " Invalid layout"; return nullptr;
diff --git a/media/gpu/chromeos/platform_video_frame_utils_unittest.cc b/media/gpu/chromeos/platform_video_frame_utils_unittest.cc index eb3ef2c..d7fea23 100644 --- a/media/gpu/chromeos/platform_video_frame_utils_unittest.cc +++ b/media/gpu/chromeos/platform_video_frame_utils_unittest.cc
@@ -112,7 +112,7 @@ CreateNativePixmapDmaBuf(video_frame.get()); ASSERT_TRUE(native_pixmap); EXPECT_EQ(native_pixmap->GetSharedImageFormat(), *si_format); - EXPECT_EQ(native_pixmap->GetBufferFormatModifier(), + EXPECT_EQ(native_pixmap->GetFormatModifier(), video_frame->layout().modifier()); // Verify the DMA Buf layouts are the same.
diff --git a/media/gpu/vaapi/vaapi_wrapper.cc b/media/gpu/vaapi/vaapi_wrapper.cc index 780bbdc..1a95e9b 100644 --- a/media/gpu/vaapi/vaapi_wrapper.cc +++ b/media/gpu/vaapi/vaapi_wrapper.cc
@@ -475,7 +475,7 @@ UNSAFE_TODO(descriptor.objects[i]).size = base::checked_cast<uint32_t>(data_size); UNSAFE_TODO(descriptor.objects[i]).drm_format_modifier = - pixmap.GetBufferFormatModifier(); + pixmap.GetFormatModifier(); UNSAFE_TODO(descriptor.layers[0].object_index[i]) = base::checked_cast<uint32_t>(i); @@ -2440,7 +2440,7 @@ GetImplementationType() == VAImplementation::kChromiumFakeDriver || GetImplementationType() == VAImplementation::kMesaGallium) && !protected_content && - pixmap->GetBufferFormatModifier() != gfx::NativePixmapHandle::kNoModifier; + pixmap->GetFormatModifier() != gfx::NativePixmapHandle::kNoModifier; union { VADRMPRIMESurfaceDescriptor descriptor;
diff --git a/mojo/core/core_ipcz.cc b/mojo/core/core_ipcz.cc index 6a2db1f..384bb86 100644 --- a/mojo/core/core_ipcz.cc +++ b/mojo/core/core_ipcz.cc
@@ -685,6 +685,11 @@ const base::UnguessableToken guid = buffer->region().GetGUID(); const uint32_t size = static_cast<uint32_t>(buffer->region().GetSize()); + // SAFETY: The caller is a C-API which cannot be spanified further + // up the stack. The caller guarantees that `platform_handles` points to + // `*num_platform_handles` elements. + auto platform_handles_span = + UNSAFE_BUFFERS(base::span(platform_handles, *num_platform_handles)); uint32_t capacity = *num_platform_handles; uint32_t required_handles = 1; #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_ANDROID) @@ -698,7 +703,7 @@ return MOJO_RESULT_RESOURCE_EXHAUSTED; } - PlatformHandle handles[2]; + std::array<PlatformHandle, 2> handles = {}; base::subtle::ScopedPlatformSharedMemoryHandle region_handle = buffer->region().PassPlatformHandle(); #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_ANDROID) @@ -709,8 +714,8 @@ #endif for (size_t i = 0; i < required_handles; ++i) { - PlatformHandle::ToMojoPlatformHandle(std::move(UNSAFE_TODO(handles[i])), - UNSAFE_TODO(&platform_handles[i])); + PlatformHandle::ToMojoPlatformHandle(std::move(handles[i]), + &platform_handles_span[i]); } *num_bytes = size;
diff --git a/mojo/public/rust/system/BUILD.gn b/mojo/public/rust/system/BUILD.gn index 1b732b0..98d86b7 100644 --- a/mojo/public/rust/system/BUILD.gn +++ b/mojo/public/rust/system/BUILD.gn
@@ -48,6 +48,7 @@ ":mojo_c_system_bindings", "//third_party/rust/bitflags/v2:lib", "//third_party/rust/static_assertions/v1:lib", + "//third_party/rust/strum/v0_27:lib", ] allow_unsafe = true }
diff --git a/mojo/public/rust/system/ffi/result.rs b/mojo/public/rust/system/ffi/result.rs index 9731d50..417ba6e 100644 --- a/mojo/public/rust/system/ffi/result.rs +++ b/mojo/public/rust/system/ffi/result.rs
@@ -16,12 +16,9 @@ use raw_ffi::MojoResult as MojoResultCode; // TODO(https://crbug.com/457917334): See if `bindgen` can generate -// bindings for `MOJO_RESULT_CANCELLED` and use that here instead of +// bindings for the code definitions and use those here instead of // manually hardcoding constants for each variant. -// FOR_RELEASE: See if we can get approval for the `strum` crate and use it here -// instead of manually writing string/int conversion functions. - /// MojoError represents all failures that can happen as a result of performing /// some operation in Mojo. /// @@ -29,7 +26,7 @@ /// (//mojo/public/c/system/types.h) so this enum can be used across the FFI /// boundary simply by using "as u32" (but do not that the "OK" code is not a /// valid value, since this represents errors only). -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, strum::FromRepr, strum::IntoStaticStr)] #[repr(u32)] pub enum MojoError { Cancelled = 1, @@ -59,49 +56,13 @@ pub fn result_from_code(code: MojoResultCode) -> MojoResult<()> { match code { 0 => Ok(()), - 1 => Err(MojoError::Cancelled), - 2 => Err(MojoError::Unknown), - 3 => Err(MojoError::InvalidArgument), - 4 => Err(MojoError::DeadlineExceeded), - 5 => Err(MojoError::NotFound), - 6 => Err(MojoError::AlreadyExists), - 7 => Err(MojoError::PermissionDenied), - 8 => Err(MojoError::ResourceExhausted), - 9 => Err(MojoError::FailedPrecondition), - 10 => Err(MojoError::Aborted), - 11 => Err(MojoError::OutOfRange), - 12 => Err(MojoError::Unimplemented), - 13 => Err(MojoError::Internal), - 14 => Err(MojoError::Unavailable), - 15 => Err(MojoError::DataLoss), - 16 => Err(MojoError::Busy), - 17 => Err(MojoError::ShouldWait), - _ => unreachable!("Received unexpected return code from mojo function: {}", code), + _ => Err(MojoError::from_repr(code) + .expect("Received unexpected return code from mojo function")), } } pub fn as_str(&self) -> &'static str { - // TODO(https://crbug.com/456535277): Deduplicate MojoResult string - // definitions across different language APIs. - match *self { - MojoError::Cancelled => "Cancelled", - MojoError::Unknown => "Unknown", - MojoError::InvalidArgument => "Invalid Argument", - MojoError::DeadlineExceeded => "Deadline Exceeded", - MojoError::NotFound => "Not Found", - MojoError::AlreadyExists => "Already Exists", - MojoError::PermissionDenied => "Permission Denied", - MojoError::ResourceExhausted => "Resource Exhausted", - MojoError::FailedPrecondition => "Failed Precondition", - MojoError::Aborted => "Aborted", - MojoError::OutOfRange => "Out Of Range", - MojoError::Unimplemented => "Unimplemented", - MojoError::Internal => "Internal", - MojoError::Unavailable => "Unavailable", - MojoError::DataLoss => "Data Loss", - MojoError::Busy => "Busy", - MojoError::ShouldWait => "Should Wait", - } + self.into() } }
diff --git a/net/disk_cache/backend_unittest.cc b/net/disk_cache/backend_unittest.cc index 0c3f688..83cfcda 100644 --- a/net/disk_cache/backend_unittest.cc +++ b/net/disk_cache/backend_unittest.cc
@@ -4315,7 +4315,7 @@ } // TODO(crbug.com/475586889): Test has flaked regularly. -#if BUILDFLAG(IS_ANDROID) +#if BUILDFLAG(IS_ANDROID) || !TARGET_IPHONE_SIMULATOR #define MAYBE_SimpleCacheNegMaxSize DISABLED_SimpleCacheNegMaxSize #else #define MAYBE_SimpleCacheNegMaxSize SimpleCacheNegMaxSize
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index 25043a35..23d2262 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json
@@ -5642,21 +5642,6 @@ ] } ], - "ChromeOSEnterpriseEventBasedLogUpload": [ - { - "platforms": [ - "chromeos" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "EventBasedLogUpload" - ] - } - ] - } - ], "ChromeOSGrowthFramework": [ { "platforms": [ @@ -10462,6 +10447,21 @@ ] } ], + "EnableEncryptedMediaOcclusionTracking": [ + { + "platforms": [ + "windows" + ], + "experiments": [ + { + "name": "Enabled", + "enable_features": [ + "EncryptedMediaOcclusionTracking" + ] + } + ] + } + ], "EnableExclusiveAccessManager": [ { "platforms": [
diff --git a/third_party/blink/public/devtools_protocol/domains/Overlay.pdl b/third_party/blink/public/devtools_protocol/domains/Overlay.pdl index 689e42d..8ef51bbb 100644 --- a/third_party/blink/public/devtools_protocol/domains/Overlay.pdl +++ b/third_party/blink/public/devtools_protocol/domains/Overlay.pdl
@@ -255,6 +255,13 @@ captureAreaScreenshot none + type InspectedElementAnchorConfig extends object + properties + # Identifier of the node to highlight. + optional DOM.NodeId nodeId + # Identifier of the backend node to highlight. + optional DOM.BackendNodeId backendNodeId + # Disables domain notifications. command disable @@ -423,6 +430,11 @@ # An array of node identifiers and descriptors for the highlight appearance. array of ContainerQueryHighlightConfig containerQueryHighlightConfigs + command setShowInspectedElementAnchor + parameters + # Node identifier for which to show an anchor for. + InspectedElementAnchorConfig inspectedElementAnchorConfig + # Requests that backend shows paint rectangles command setShowPaintRects parameters @@ -494,5 +506,17 @@ # Viewport to capture, in device independent pixels (dip). Page.Viewport viewport + # Fired when user asks to show the Inspect panel. + event inspectPanelShowRequested + parameters + # Id of the node to show in the panel. + DOM.BackendNodeId backendNodeId + + # Fired when user asks to restore the Inspected Element floating window. + event inspectedElementWindowRestored + parameters + # Id of the node to restore the floating window for. + DOM.BackendNodeId backendNodeId + # Fired when user cancels the inspect mode. event inspectModeCanceled
diff --git a/third_party/blink/renderer/build/scripts/core/css/make_cssom_types.py b/third_party/blink/renderer/build/scripts/core/css/make_cssom_types.py index c32ac97..10948a7 100755 --- a/third_party/blink/renderer/build/scripts/core/css/make_cssom_types.py +++ b/third_party/blink/renderer/build/scripts/core/css/make_cssom_types.py
@@ -30,9 +30,14 @@ types.append(single_type) property_.typedom_types = types + if property_.typedom_keywords: + keywords = property_.typedom_keywords + else: + keywords = property_.keywords + # Generate CSSValueID values from keywords. - property_.keywordIDs = list( - map(enum_key_for_css_keyword, property_.keywords)) + property_.keywordIDs = list(map(enum_key_for_css_keyword, + keywords)) self._outputs = { 'cssom_types.cc': self.generate_types,
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5 index 73ada945..3a3973a 100644 --- a/third_party/blink/renderer/core/css/css_properties.json5 +++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -454,6 +454,12 @@ "Image" ], }, + // If typedom_types include "Keyword", these keywords are the ones reified + // and accepted as CSSKeywordValue objects when set on StylePropertyMap. + // Defaults to the same as 'keywords'. + typedom_keywords: { + }, + separator: { valid_values: [",", " ", "/"], }, @@ -5023,7 +5029,8 @@ style_builder_custom_functions: ["value"], field_group: "*", field_template: "external", - keywords: ["none", "flip-block", "flip-inline", "flip-start"], + keywords: ["none", "flip-block", "flip-inline", "flip-start", "flip-x", "flip-y"], + typedom_keywords: ["none"], typedom_types: ["Keyword"], include_paths: ["third_party/blink/renderer/core/style/position_try_fallbacks.h"], wrapper_pointer_name: "Member", @@ -8493,6 +8500,7 @@ "none", "top", "bottom", "center", "left", "right", "x-start", "x-end", "y-start", "y-end", "start", "end", "self-start", "self-end", "all" ], + typedom_keywords: ["none"], typedom_types: ["Keyword"], valid_for_position_try: true, valid_for_permission_element: true,
diff --git a/third_party/blink/renderer/core/css/cssom/style_property_map_test.cc b/third_party/blink/renderer/core/css/cssom/style_property_map_test.cc index bc0840e..eae2f3a 100644 --- a/third_party/blink/renderer/core/css/cssom/style_property_map_test.cc +++ b/third_party/blink/renderer/core/css/cssom/style_property_map_test.cc
@@ -149,8 +149,6 @@ case CSSPropertyID::kGridAutoRows: case CSSPropertyID::kGridLanesDirection: case CSSPropertyID::kOffsetRotate: - case CSSPropertyID::kPositionArea: - case CSSPropertyID::kPositionTryFallbacks: case CSSPropertyID::kScrollSnapType: case CSSPropertyID::kScrollbarGutter: // scrollbar-gutter:both-edges DCHECK fails for other reasons. Needs
diff --git a/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc b/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc index 74aa9d51b..9eec9c5 100644 --- a/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc +++ b/third_party/blink/renderer/core/inspector/inspector_overlay_agent.cc
@@ -948,6 +948,13 @@ return protocol::Response::Success(); } +protocol::Response InspectorOverlayAgent::setShowInspectedElementAnchor( + std::unique_ptr<protocol::Overlay::InspectedElementAnchorConfig> + inspected_element_anchor_config) { + LOG(ERROR) << "Not implemented yet"; + return protocol::Response::Success(); +} + protocol::Response InspectorOverlayAgent::highlightSourceOrder( std::unique_ptr<protocol::Overlay::SourceOrderConfig> source_order_inspector_object,
diff --git a/third_party/blink/renderer/core/inspector/inspector_overlay_agent.h b/third_party/blink/renderer/core/inspector/inspector_overlay_agent.h index 70b6279..1fb53d4d 100644 --- a/third_party/blink/renderer/core/inspector/inspector_overlay_agent.h +++ b/third_party/blink/renderer/core/inspector/inspector_overlay_agent.h
@@ -261,6 +261,9 @@ std::unique_ptr< protocol::Array<protocol::Overlay::IsolatedElementHighlightConfig>> isolated_element_highlight_configs) override; + protocol::Response setShowInspectedElementAnchor( + std::unique_ptr<protocol::Overlay::InspectedElementAnchorConfig> + inspected_element_anchor_config) override; // InspectorBaseAgent overrides. void Restore() override;
diff --git a/third_party/blink/renderer/modules/direct_sockets/BUILD.gn b/third_party/blink/renderer/modules/direct_sockets/BUILD.gn index 8e8261a..4ea158e 100644 --- a/third_party/blink/renderer/modules/direct_sockets/BUILD.gn +++ b/third_party/blink/renderer/modules/direct_sockets/BUILD.gn
@@ -6,6 +6,8 @@ blink_modules_sources("direct_sockets") { sources = [ + "direct_sockets_features.cc", + "direct_sockets_features.h", "multicast_controller.cc", "multicast_controller.h", "socket.cc",
diff --git a/third_party/blink/renderer/modules/direct_sockets/direct_sockets_features.cc b/third_party/blink/renderer/modules/direct_sockets/direct_sockets_features.cc new file mode 100644 index 0000000..af55aa6 --- /dev/null +++ b/third_party/blink/renderer/modules/direct_sockets/direct_sockets_features.cc
@@ -0,0 +1,12 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/modules/direct_sockets/direct_sockets_features.h" + +namespace blink { + +BASE_FEATURE(kDirectSocketsAllowRecoverableErrors, + base::FEATURE_ENABLED_BY_DEFAULT); + +} // namespace blink
diff --git a/third_party/blink/renderer/modules/direct_sockets/direct_sockets_features.h b/third_party/blink/renderer/modules/direct_sockets/direct_sockets_features.h new file mode 100644 index 0000000..9921ae3 --- /dev/null +++ b/third_party/blink/renderer/modules/direct_sockets/direct_sockets_features.h
@@ -0,0 +1,17 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_DIRECT_SOCKETS_DIRECT_SOCKETS_FEATURES_H_ +#define THIRD_PARTY_BLINK_RENDERER_MODULES_DIRECT_SOCKETS_DIRECT_SOCKETS_FEATURES_H_ + +#include "base/feature_list.h" +#include "third_party/blink/renderer/modules/modules_export.h" + +namespace blink { + +MODULES_EXPORT BASE_DECLARE_FEATURE(kDirectSocketsAllowRecoverableErrors); + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_DIRECT_SOCKETS_DIRECT_SOCKETS_FEATURES_H_
diff --git a/third_party/blink/renderer/modules/direct_sockets/udp_readable_stream_wrapper.cc b/third_party/blink/renderer/modules/direct_sockets/udp_readable_stream_wrapper.cc index 0f5b8ec..b20c9456 100644 --- a/third_party/blink/renderer/modules/direct_sockets/udp_readable_stream_wrapper.cc +++ b/third_party/blink/renderer/modules/direct_sockets/udp_readable_stream_wrapper.cc
@@ -20,6 +20,7 @@ #include "third_party/blink/renderer/core/streams/underlying_source_base.h" #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h" #include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h" +#include "third_party/blink/renderer/modules/direct_sockets/direct_sockets_features.h" #include "third_party/blink/renderer/modules/direct_sockets/stream_wrapper.h" #include "third_party/blink/renderer/modules/direct_sockets/udp_writable_stream_wrapper.h" #include "third_party/blink/renderer/platform/bindings/script_state.h" @@ -146,9 +147,12 @@ const std::optional<::net::IPEndPoint>& src_addr, std::optional<::base::span<const ::uint8_t>> data) { if (result != net::OK) { - if (result == net::ERR_MSG_TOO_BIG) { - // TODO(crbug.com/362145407): Figure out the root cause. - // Error codes are negative. + if (base::FeatureList::IsEnabled(kDirectSocketsAllowRecoverableErrors) && + (result == net::ERR_MSG_TOO_BIG || + result == net::ERR_CONNECTION_REFUSED || + result == net::ERR_ADDRESS_UNREACHABLE)) { + // These errors are typically transient or asynchronous notifications + // of past failures and should not close the stream. base::UmaHistogramSparse("DirectSockets.UDPReadableStreamError", -result); DCHECK_GT(pending_receive_requests_, 0);
diff --git a/third_party/blink/renderer/modules/direct_sockets/udp_writable_stream_wrapper.cc b/third_party/blink/renderer/modules/direct_sockets/udp_writable_stream_wrapper.cc index a968b435..4b23761 100644 --- a/third_party/blink/renderer/modules/direct_sockets/udp_writable_stream_wrapper.cc +++ b/third_party/blink/renderer/modules/direct_sockets/udp_writable_stream_wrapper.cc
@@ -25,6 +25,7 @@ #include "third_party/blink/renderer/core/streams/writable_stream.h" #include "third_party/blink/renderer/core/streams/writable_stream_default_controller.h" #include "third_party/blink/renderer/core/typed_arrays/dom_array_piece.h" +#include "third_party/blink/renderer/modules/direct_sockets/direct_sockets_features.h" #include "third_party/blink/renderer/modules/direct_sockets/stream_wrapper.h" #include "third_party/blink/renderer/modules/direct_sockets/udp_socket_mojo_remote.h" #include "third_party/blink/renderer/platform/bindings/exception_code.h" @@ -174,7 +175,13 @@ void UDPWritableStreamWrapper::OnSend(int32_t result) { if (write_promise_resolver_) { - if (result == net::Error::OK) { + if (result == net::ERR_ADDRESS_UNREACHABLE && + base::FeatureList::IsEnabled(kDirectSocketsAllowRecoverableErrors)) { + // Log recoverable errors and resolve to keep the WritableStream alive. + base::UmaHistogramSparse("DirectSockets.UDPWritableStreamError", -result); + write_promise_resolver_->Resolve(); + write_promise_resolver_ = nullptr; + } else if (result >= net::OK) { write_promise_resolver_->Resolve(); write_promise_resolver_ = nullptr; } else {
diff --git a/third_party/blink/renderer/modules/xr/xr_layer_init.idl b/third_party/blink/renderer/modules/xr/xr_layer_init.idl index 622cc6f..c0fe329 100644 --- a/third_party/blink/renderer/modules/xr/xr_layer_init.idl +++ b/third_party/blink/renderer/modules/xr/xr_layer_init.idl
@@ -8,6 +8,7 @@ GLenum colorFormat = 0x1908; // RGBA GLenum depthFormat = 0x1902; // DEPTH_COMPONENT double scaleFactor = 1.0; + boolean clearOnAccess = true; }; // https://www.w3.org/TR/webxrlayers-1/#xrlayerinittype
diff --git a/third_party/blink/renderer/modules/xr/xr_swap_chain.h b/third_party/blink/renderer/modules/xr/xr_swap_chain.h index 953da37..314d364 100644 --- a/third_party/blink/renderer/modules/xr/xr_swap_chain.h +++ b/third_party/blink/renderer/modules/xr/xr_swap_chain.h
@@ -28,10 +28,13 @@ virtual ~XRSwapChain() = default; Texture* GetCurrentTexture() { - texture_queried_ = true; if (!current_texture_) { current_texture_ = ProduceTexture(); } + if (!texture_queried_) { + texture_queried_ = true; + OnTextureQueried(); + } return current_texture_; } virtual void OnFrameStart() { texture_queried_ = false; } @@ -53,6 +56,7 @@ // Produces a new Texture for the swap chain. Will not be called again unless // the current texture is reset. virtual Texture* ProduceTexture() = 0; + virtual void OnTextureQueried() {} // Resets the cached texture so that next GetCurrentTexture call will trigger // a ProduceTexture call.
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_binding.cc b/third_party/blink/renderer/modules/xr/xr_webgl_binding.cc index b925a40..a8aa6d2 100644 --- a/third_party/blink/renderer/modules/xr/xr_webgl_binding.cc +++ b/third_party/blink/renderer/modules/xr/xr_webgl_binding.cc
@@ -117,7 +117,8 @@ GLenum layer_format, gfx::Size texture_size, V8XRTextureType texture_type, - V8XRLayerLayout::Enum final_layout) { + V8XRLayerLayout::Enum final_layout, + bool clear_on_access) { XRWebGLSwapChain::Descriptor color_desc = {}; color_desc.format = FormatForLayerFormat(layer_format); color_desc.internal_format = InternalFormatForLayerFormat(layer_format); @@ -127,6 +128,13 @@ color_desc.height = static_cast<uint32_t>(texture_size.height()); color_desc.layers = 1; color_desc.is_texture_array = false; + // If we use XRWebGLTextureArraySwapChain as a wrapper, we don't need to + // clear the old buffer of the wrapped swapchain because the wrapper + // will always overwrite the entire texture. The value of "clear_on_access" + // will be passed and used by the wrapper. + color_desc.clear_on_access = + clear_on_access && + texture_type.AsEnum() != V8XRTextureType::Enum::kTextureArray; XRWebGLSwapChain* color_swap_chain; if (session()->xr()->frameProvider()->DrawingIntoSharedBuffer()) { @@ -145,7 +153,7 @@ ? session()->array_texture_layers() : 1; color_swap_chain = MakeGarbageCollected<XRWebGLTextureArraySwapChain>( - color_swap_chain, layers); + color_swap_chain, layers, clear_on_access); } return color_swap_chain; @@ -213,7 +221,8 @@ gfx::Size texture_size = gfx::ToFlooredSize(scaled_size); XRWebGLSwapChain* color_swap_chain = CreateColorSwapchain( - init->colorFormat(), texture_size, init->textureType(), final_layout); + init->colorFormat(), texture_size, init->textureType(), final_layout, + init->clearOnAccess()); CHECK_EQ(color_swap_chain->descriptor().is_texture_array, is_texture_array); CHECK_EQ(color_swap_chain->descriptor().layers, layers); @@ -227,6 +236,7 @@ depth_stencil_desc.type = TypeForLayerFormat(init->depthFormat()); depth_stencil_desc.attachment_target = GL_DEPTH_ATTACHMENT; depth_stencil_desc.is_texture_array = is_texture_array; + depth_stencil_desc.clear_on_access = init->clearOnAccess(); if (is_texture_array) { texture_size.set_width(texture_size.width() / layers); @@ -283,7 +293,7 @@ XRWebGLSwapChain* color_swap_chain = CreateColorSwapchain( init->colorFormat(), GetTextureSizeForLayer(init, final_layout), - init->textureType(), final_layout); + init->textureType(), final_layout, init->clearOnAccess()); auto* drawing_context = MakeGarbageCollected<XRWebGLDrawingContext>(this, color_swap_chain); @@ -335,7 +345,7 @@ XRWebGLSwapChain* color_swap_chain = CreateColorSwapchain( init->colorFormat(), GetTextureSizeForLayer(init, final_layout), - init->textureType(), final_layout); + init->textureType(), final_layout, init->clearOnAccess()); auto* drawing_context = MakeGarbageCollected<XRWebGLDrawingContext>(this, color_swap_chain); @@ -379,7 +389,7 @@ XRWebGLSwapChain* color_swap_chain = CreateColorSwapchain( init->colorFormat(), GetTextureSizeForLayer(init, final_layout), - init->textureType(), final_layout); + init->textureType(), final_layout, init->clearOnAccess()); auto* drawing_context = MakeGarbageCollected<XRWebGLDrawingContext>(this, color_swap_chain); @@ -426,14 +436,17 @@ return nullptr; } + // We don't need to clear the buffer anyway because the wrapper + // XRWebGLCubemapSwapChain will do it. XRWebGLSwapChain* texture_2d_swapchain = CreateColorSwapchain( init->colorFormat(), gfx::Size(init->viewPixelWidth(), init->viewPixelHeight()), V8XRTextureType(V8XRTextureType::Enum::kTexture), - V8XRLayerLayout::Enum::kMono); + V8XRLayerLayout::Enum::kMono, false /*clear_on_access*/); XRWebGLSwapChain* cubemap_swap_chain = - MakeGarbageCollected<XRWebGLCubemapSwapChain>(texture_2d_swapchain); + MakeGarbageCollected<XRWebGLCubemapSwapChain>(texture_2d_swapchain, + init->clearOnAccess()); auto* drawing_context = MakeGarbageCollected<XRWebGLDrawingContext>(this, cubemap_swap_chain);
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_binding.h b/third_party/blink/renderer/modules/xr/xr_webgl_binding.h index e3f91b06..eafaadaf 100644 --- a/third_party/blink/renderer/modules/xr/xr_webgl_binding.h +++ b/third_party/blink/renderer/modules/xr/xr_webgl_binding.h
@@ -119,7 +119,8 @@ XRWebGLSwapChain* CreateColorSwapchain(GLenum layer_format, gfx::Size layer_size, V8XRTextureType texture_type, - V8XRLayerLayout::Enum final_layout); + V8XRLayerLayout::Enum final_layout, + bool clear_on_access); XRWebGLSwapChain* GetSwapchainForLayer(XRCompositionLayer* layer); Member<WebGLRenderingContextBase> webgl_context_;
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.cc b/third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.cc index bb08a2b6..ed409409 100644 --- a/third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.cc +++ b/third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.cc
@@ -111,12 +111,21 @@ gl->DeleteShader(shader); } +XRWebGLSwapChain::Descriptor MakeLayerDescriptor( + XRWebGLSwapChain* wrapped_swapchain, + bool clear_on_access) { + XRWebGLSwapChain::Descriptor descriptor = wrapped_swapchain->descriptor(); + descriptor.clear_on_access = clear_on_access; + return descriptor; +} + } // namespace XRWebGLCubemapSwapChain::XRWebGLCubemapSwapChain( - XRWebGLSwapChain* wrapped_swapchain) + XRWebGLSwapChain* wrapped_swapchain, + bool clear_on_access) : XRWebGLSwapChain(wrapped_swapchain->context(), - wrapped_swapchain->descriptor(), + MakeLayerDescriptor(wrapped_swapchain, clear_on_access), wrapped_swapchain->webgl2()), wrapped_swapchain_(wrapped_swapchain) {} @@ -281,10 +290,6 @@ gl->BindBuffer(GL_ARRAY_BUFFER, 0); gl->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - // ClearCurrentTexture resets the framebuffer binding and mask/clear values - // prior to returning. - ClearCurrentTexture(); - // Restore the saved old state gl->Viewport(curr_viewport[0], curr_viewport[1], curr_viewport[2], curr_viewport[3]); @@ -310,6 +315,8 @@ static_cast<DrawingBuffer::Client*>(context()); client->DrawingBufferClientRestoreTextureCubeMapBinding(); client->DrawingBufferClientRestoreScissorTest(); + client->DrawingBufferClientRestoreMaskAndClearValues(); + client->DrawingBufferClientRestoreFramebufferBinding(); context()->RestoreVertexArrayObjectBinding(); context()->RestoreProgram();
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.h b/third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.h index 3f3b787..eac9987 100644 --- a/third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.h +++ b/third_party/blink/renderer/modules/xr/xr_webgl_cubemap_swap_chain.h
@@ -18,7 +18,8 @@ // crbug.com/459811463. class XRWebGLCubemapSwapChain final : public XRWebGLSwapChain { public: - explicit XRWebGLCubemapSwapChain(XRWebGLSwapChain* wrapped_swapchain); + explicit XRWebGLCubemapSwapChain(XRWebGLSwapChain* wrapped_swapchain, + bool clear_on_access); ~XRWebGLCubemapSwapChain() override; bool IsCube() const override { return true; }
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.cc b/third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.cc index 31a7c7c..45032ae 100644 --- a/third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.cc +++ b/third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.cc
@@ -20,6 +20,12 @@ CHECK(context); } +void XRWebGLSwapChain::OnTextureQueried() { + if (descriptor().clear_on_access) { + ClearCurrentTexture(); + } +} + // Clears the contents of the current texture to transparent black or 0 (for // depth/stencil textures). void XRWebGLSwapChain::ClearCurrentTexture() { @@ -153,8 +159,6 @@ } void XRWebGLStaticSwapChain::OnFrameEnd() { - ClearCurrentTexture(); - // Intentionally not calling ResetCurrentTexture() here to keep the previously // produced texture for the next frame. }
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.h b/third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.h index 965cd50..bff7026 100644 --- a/third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.h +++ b/third_party/blink/renderer/modules/xr/xr_webgl_swap_chain.h
@@ -29,6 +29,7 @@ uint16_t height; uint16_t layers; bool is_texture_array; + bool clear_on_access; }; XRWebGLSwapChain(WebGLRenderingContextBase*, @@ -50,6 +51,7 @@ virtual bool IsCube() const { return false; } protected: + void OnTextureQueried() override; WebGLFramebuffer* GetFramebuffer(); private:
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.cc b/third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.cc index 99c02d4..ac29efd 100644 --- a/third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.cc +++ b/third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.cc
@@ -14,7 +14,8 @@ XRWebGLSwapChain::Descriptor MakeLayerDescriptor( XRWebGLSwapChain* wrapped_swap_chain, - uint32_t layers) { + uint32_t layers, + bool clear_on_access) { // Copy the wrapped swap chain's descriptor and divide its width by the // number of requested layers. XRWebGLSwapChain::Descriptor descriptor = wrapped_swap_chain->descriptor(); @@ -23,6 +24,7 @@ descriptor.width /= layers; descriptor.layers = layers; descriptor.is_texture_array = true; + descriptor.clear_on_access = clear_on_access; return descriptor; } @@ -30,10 +32,12 @@ XRWebGLTextureArraySwapChain::XRWebGLTextureArraySwapChain( XRWebGLSwapChain* wrapped_swap_chain, - uint32_t layers) - : XRWebGLSwapChain(wrapped_swap_chain->context(), - MakeLayerDescriptor(wrapped_swap_chain, layers), - wrapped_swap_chain->webgl2()), + uint32_t layers, + bool clear_on_access) + : XRWebGLSwapChain( + wrapped_swap_chain->context(), + MakeLayerDescriptor(wrapped_swap_chain, layers, clear_on_access), + wrapped_swap_chain->webgl2()), wrapped_swap_chain_(wrapped_swap_chain) { CHECK(wrapped_swap_chain_); CHECK(webgl2()); // Texture arrays are only available in WebGL 2 @@ -144,10 +148,6 @@ // Draw one quad for each layer. gl->DrawArraysInstancedANGLE(GL_TRIANGLES, 0, 6, descriptor().layers); - // ClearCurrentTexture resets the framebuffer binding and mask/clear values - // prior to returning. - ClearCurrentTexture(); - // Restore manually tracked state gl->Viewport(curr_viewport[0], curr_viewport[1], curr_viewport[2], curr_viewport[3]); @@ -173,6 +173,8 @@ static_cast<DrawingBuffer::Client*>(context()); client->DrawingBufferClientRestoreTexture2DArrayBinding(); client->DrawingBufferClientRestoreScissorTest(); + client->DrawingBufferClientRestoreMaskAndClearValues(); + client->DrawingBufferClientRestoreFramebufferBinding(); context()->RestoreVertexArrayObjectBinding(); context()->RestoreProgram();
diff --git a/third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.h b/third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.h index 9ea308a..f31a239fb 100644 --- a/third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.h +++ b/third_party/blink/renderer/modules/xr/xr_webgl_texture_array_swap_chain.h
@@ -19,7 +19,8 @@ class XRWebGLTextureArraySwapChain final : public XRWebGLSwapChain { public: XRWebGLTextureArraySwapChain(XRWebGLSwapChain* wrapped_swap_chain, - uint32_t layers); + uint32_t layers, + bool clear_on_access); ~XRWebGLTextureArraySwapChain() override; WebGLUnownedTexture* ProduceTexture() override;
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations index 44641fb..8a445cf8 100644 --- a/third_party/blink/web_tests/TestExpectations +++ b/third_party/blink/web_tests/TestExpectations
@@ -1179,11 +1179,6 @@ crbug.com/1076027 external/wpt/css/css-grid/grid-lanes/tentative/baseline/grid-lanes-grid-item-self-baseline-002b.html [ Failure ] crbug.com/1076027 external/wpt/css/css-grid/grid-lanes/tentative/fragmentation/* [ Failure Skip ] crbug.com/1076027 external/wpt/css/css-grid/grid-lanes/tentative/subgrid/* [ Failure ] -# Grid Lanes sizing test failures dependent on https://github.com/w3c/csswg-drafts/issues/12843#issuecomment-3482172845 -crbug.com/1076027 external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-mix1.html [ Failure ] -crbug.com/1076027 external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-mix2.html [ Failure ] -crbug.com/1076027 external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-mix1.html [ Failure ] -crbug.com/1076027 external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-mix2.html [ Failure ] # Grid Lanes sizing test failures dependent on https://github.com/w3c/csswg-drafts/issues/12964 crbug.com/1076027 external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-003.html [ Failure ] crbug.com/1076027 external/wpt/css/css-grid/grid-lanes/tentative/track-sizing/auto-repeat/row-auto-repeat-006.html [ Failure ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites index f692185..c2414e4a 100644 --- a/third_party/blink/web_tests/VirtualTestSuites +++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -2582,7 +2582,7 @@ "--enable-blink-features=PreferDefaultScrollbarStyles", "--blink-settings=prefersDefaultScrollbarStyles=true" ], - "expires": "Feb 1, 2026", + "expires": "Feb 1, 2027", "owners": [ "evelynn.kaplan@microsoft.com", "almaher@microsoft.com"
diff --git a/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_modulepreload_allow.sub.window.js b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_modulepreload_allow.sub.window.js new file mode 100644 index 0000000..8c99313 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_modulepreload_allow.sub.window.js
@@ -0,0 +1,13 @@ +// META: script=/common/utils.js +// META: script=resources/utils.js +// +// The test assumes the policy `Connection-Allowlist: (response-origin)` has +// been set in the response. The response also contains a link header that +// triggers a modulepreload to the same-origin KV server at +// http://{{hosts[][]}}:{{ports[http][0]}}. + +promise_test(async () => { + const result = + await nextValueFromServer('bb906791-f686-45f9-9bb7-4de2352ce382'); + assert_equals(result, 'hello'); +}, 'Link header modulepreload to an allow-listed url succeeds.');
diff --git a/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_modulepreload_allow.sub.window.js.sub.headers b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_modulepreload_allow.sub.window.js.sub.headers new file mode 100644 index 0000000..7cfe3690 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_modulepreload_allow.sub.window.js.sub.headers
@@ -0,0 +1,2 @@ +Connection-Allowlist: (response-origin "*://*:*/*\\?*&ignore-allowlist=true*") +Link: <http://{{hosts[][]}}:{{ports[http][0]}}/connection-allowlist/tentative/resources/key-value-store.py?key=bb906791-f686-45f9-9bb7-4de2352ce382&value=hello>; rel=modulepreload \ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_modulepreload_deny.sub.window.js b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_modulepreload_deny.sub.window.js new file mode 100644 index 0000000..b2a01ec --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_modulepreload_deny.sub.window.js
@@ -0,0 +1,16 @@ +// META: script=/common/utils.js +// META: script=resources/utils.js +// META: timeout=long +// +// The test assumes the policy `Connection-Allowlist: (response-origin)` has +// been set in the response. The response also contains a link header that +// triggers a modulepreload to the cross-origin KV server at +// http://{{hosts[][www]}}:{{ports[http][0]}}. + +promise_test(async (t) => { + const result = await Promise.race([ + new Promise(r => t.step_timeout(r, 1000)), + nextValueFromServer('cabcb250-9769-4d6a-976a-8e8aa6a95f6a') + ]); + assert_true(typeof result === 'undefined'); +}, 'Link header modulepreload to a not allow-listed url fails.');
diff --git a/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_modulepreload_deny.sub.window.js.sub.headers b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_modulepreload_deny.sub.window.js.sub.headers new file mode 100644 index 0000000..be08760 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_modulepreload_deny.sub.window.js.sub.headers
@@ -0,0 +1,2 @@ +Connection-Allowlist: (response-origin "*://*:*/*\\?*&ignore-allowlist=true*") +Link: <http://{{hosts[][www]}}:{{ports[http][0]}}/connection-allowlist/tentative/resources/key-value-store.py?key=cabcb250-9769-4d6a-976a-8e8aa6a95f6a&value=hello>; rel=modulepreload \ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_prefetch_allow.sub.window.js b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_prefetch_allow.sub.window.js new file mode 100644 index 0000000..891a1d3 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_prefetch_allow.sub.window.js
@@ -0,0 +1,13 @@ +// META: script=/common/utils.js +// META: script=resources/utils.js +// +// The test assumes the policy `Connection-Allowlist: (response-origin)` has +// been set in the response. The response also contains a link header that +// triggers a prefetch to the same-origin KV server at +// http://{{hosts[][]}}:{{ports[http][0]}}. + +promise_test(async () => { + const result = + await nextValueFromServer('641530d4-9e7d-4760-91da-2a57191363c1'); + assert_equals(result, 'hello'); +}, 'Link header prefetch to an allow-listed url succeeds.');
diff --git a/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_prefetch_allow.sub.window.js.sub.headers b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_prefetch_allow.sub.window.js.sub.headers new file mode 100644 index 0000000..e732664 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_prefetch_allow.sub.window.js.sub.headers
@@ -0,0 +1,2 @@ +Connection-Allowlist: (response-origin "*://*:*/*\\?*&ignore-allowlist=true*") +Link: <http://{{hosts[][]}}:{{ports[http][0]}}/connection-allowlist/tentative/resources/key-value-store.py?key=641530d4-9e7d-4760-91da-2a57191363c1&value=hello>; rel=prefetch \ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_prefetch_deny.sub.window.js b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_prefetch_deny.sub.window.js new file mode 100644 index 0000000..b464981 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_prefetch_deny.sub.window.js
@@ -0,0 +1,16 @@ +// META: script=/common/utils.js +// META: script=resources/utils.js +// META: timeout=long +// +// The test assumes the policy `Connection-Allowlist: (response-origin)` has +// been set in the response. The response also contains a link header that +// triggers a prefetch to the cross-origin KV server at +// http://{{hosts[][www]}}:{{ports[http][0]}}. + +promise_test(async (t) => { + const result = await Promise.race([ + new Promise(r => t.step_timeout(r, 1000)), + nextValueFromServer('639d1a56-16f6-4f1d-8739-87b1278441e6') + ]); + assert_true(typeof result === 'undefined'); +}, 'Link header prefetch to a not allow-listed url fails.');
diff --git a/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_prefetch_deny.sub.window.js.sub.headers b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_prefetch_deny.sub.window.js.sub.headers new file mode 100644 index 0000000..c087a6b5f --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_prefetch_deny.sub.window.js.sub.headers
@@ -0,0 +1,2 @@ +Connection-Allowlist: (response-origin "*://*:*/*\\?*&ignore-allowlist=true*") +Link: <http://{{hosts[][www]}}:{{ports[http][0]}}/connection-allowlist/tentative/resources/key-value-store.py?key=641530d4-9e7d-4760-91da-2a57191363c1&value=hello>; rel=prefetch \ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_preload_allow.sub.window.js b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_preload_allow.sub.window.js new file mode 100644 index 0000000..51501856 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_preload_allow.sub.window.js
@@ -0,0 +1,13 @@ +// META: script=/common/utils.js +// META: script=resources/utils.js +// +// The test assumes the policy `Connection-Allowlist: (response-origin)` has +// been set in the response. The response also contains a link header that +// triggers a preload to the same-origin KV server at +// http://{{hosts[][]}}:{{ports[http][0]}}. + +promise_test(async () => { + const result = + await nextValueFromServer('6cd381d3-85b5-40ce-8c63-1ffb06154b8b'); + assert_equals(result, 'hello'); +}, 'Link header preload to an allow-listed url succeeds.');
diff --git a/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_preload_allow.sub.window.js.sub.headers b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_preload_allow.sub.window.js.sub.headers new file mode 100644 index 0000000..4312212 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_preload_allow.sub.window.js.sub.headers
@@ -0,0 +1,2 @@ +Connection-Allowlist: (response-origin "*://*:*/*\\?*&ignore-allowlist=true*") +Link: <http://{{hosts[][]}}:{{ports[http][0]}}/connection-allowlist/tentative/resources/key-value-store.py?key=6cd381d3-85b5-40ce-8c63-1ffb06154b8b&value=hello>; rel=preload; as=script \ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_preload_deny.sub.window.js b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_preload_deny.sub.window.js new file mode 100644 index 0000000..46a7aec74 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_preload_deny.sub.window.js
@@ -0,0 +1,16 @@ +// META: script=/common/utils.js +// META: script=resources/utils.js +// META: timeout=long +// +// The test assumes the policy `Connection-Allowlist: (response-origin)` has +// been set in the response. The response also contains a link header that +// triggers a preload to the cross-origin KV server at +// http://{{hosts[][www]}}:{{ports[http][0]}}. + +promise_test(async (t) => { + const result = await Promise.race([ + new Promise(r => t.step_timeout(r, 1000)), + nextValueFromServer('277a5831-dd75-4d34-bf27-975a8ede398e') + ]); + assert_true(typeof result === 'undefined'); +}, 'Link header preload to a not allow-listed url fails.');
diff --git a/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_preload_deny.sub.window.js.sub.headers b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_preload_deny.sub.window.js.sub.headers new file mode 100644 index 0000000..47d6cf0 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/connection-allowlist/tentative/link_header_preload_deny.sub.window.js.sub.headers
@@ -0,0 +1,2 @@ +Connection-Allowlist: (response-origin "*://*:*/*\\?*&ignore-allowlist=true*") +Link: <http://{{hosts[][www]}}:{{ports[http][0]}}/connection-allowlist/tentative/resources/key-value-store.py?key=277a5831-dd75-4d34-bf27-975a8ede398e&value=hello>; rel=preload; as=script \ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-mix1-ref.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-mix1-ref.html index 4489957..67a4005 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-mix1-ref.html +++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-mix1-ref.html
@@ -96,7 +96,7 @@ <item class="hidden" style="grid-row: 4; grid-column: 2;">0 0</item> </grid> -<grid style="grid-template-columns: 30px auto"> +<grid style="grid-template-columns: 30px auto; width: calc(2em + 2px);"> <item>1</item> <item>2 2</item> <item>3 3</item> @@ -107,7 +107,7 @@ <item class="hidden" style="grid-row: 4; grid-column: 2;">0 0</item> </grid> -<grid style="grid-template-columns: 30px auto auto"> +<grid style="grid-template-columns: 30px auto auto; width: calc(3em + 4px);"> <item>1</item> <item>2 2</item> <item>3 3</item> @@ -119,7 +119,7 @@ <item class="hidden" style="grid-row: 4; grid-column: 2;">0 0</item> </grid> -<grid style="grid-template-columns: 30px auto auto"> +<grid style="grid-template-columns: 30px auto auto; width: calc(3em + 4px);"> <item>1</item> <item>2 2</item> <item>3 3</item> @@ -131,7 +131,7 @@ <item class="hidden" style="grid-row: 4; grid-column: 2;">0 0</item> </grid> -<grid style="grid-template-columns: 30px auto"> +<grid style="grid-template-columns: 30px auto; width: calc(2em + 2px);"> <item style="height: 3ch;">1</item> <item>2 2</item> <item>3 3</item> @@ -139,7 +139,7 @@ <item style="grid-column: 2; grid-row: span 4;">5 5</item> </grid> -<grid style="grid-template-columns: 30px auto"> +<grid style="grid-template-columns: 30px auto; width: calc(2em + 2px);"> <item style="height: 3ch;">1</item> <item>2 2</item> <item>3 3</item> @@ -147,7 +147,7 @@ <item style="height:6ch; grid-row:span 4;">5 5</item> </grid> -<grid style="grid-template-columns: 30px auto"> +<grid style="grid-template-columns: 30px auto; width: calc(2em + 2px);"> <item style="height: 3ch;">1</item> <item>2 2</item> <item>3 3</item>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-mix2-ref.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-mix2-ref.html index 9d9587a..46f7fe6 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-mix2-ref.html +++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-003-mix2-ref.html
@@ -38,7 +38,7 @@ <body> <section> -<grid> +<grid style="width: calc(2em + 2px)"> <item style="height:2ch">1</item> <item style="grid-column: span 2">2 2</item> <item style="grid-column: span 2">3 3</item> @@ -68,7 +68,7 @@ <item class="hidden" style="grid-area: 4/2">0 0</item> </grid> -<grid> +<grid style="width: calc(2em + 2px)"> <item>1</item> <item>2 2</item> <item>3 3</item> @@ -78,7 +78,7 @@ <item class="hidden" style="grid-area: 4/2">0 0</item> </grid> -<grid> +<grid style="width: calc(3em + 4px)"> <item>1</item> <item>2 2</item> <item>3 3</item> @@ -89,7 +89,7 @@ <item class="hidden" style="grid-area: 4/2">0 0</item> </grid> -<grid> +<grid style="width: calc(3em + 4px)"> <item>1</item> <item>2 2</item> <item>3 3</item> @@ -100,7 +100,7 @@ <item class="hidden" style="grid-area: 4/2">0 0</item> </grid> -<grid> +<grid style="width: calc(2em + 2px)"> <item>1</item> <item style="grid-column: span 2">2 2</item> <item style="grid-column: span 2">3 3</item> @@ -110,7 +110,7 @@ <item class="hidden" style="grid-area: 4/3">0 0</item> </grid> -<grid> +<grid style="width: calc(2em + 2px)"> <item>1</item> <item style="grid-column: span 2">2 2</item> <item style="grid-column: span 2">3 3</item> @@ -120,7 +120,7 @@ <item class="hidden" style="grid-area: 4/3">0 0</item> </grid> -<grid> +<grid style="width: calc(2em + 2px)"> <item>1</item> <item style="grid-column: span 2">2 2</item> <item style="grid-column: span 2">3 3</item>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-mix1-ref.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-mix1-ref.html index 9d28c277..5447c2e 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-mix1-ref.html +++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-mix1-ref.html
@@ -95,7 +95,7 @@ <item class="hidden" style="grid-area: 4/2">0 0</item> </grid> -<grid style="grid-template-columns: 30px auto;"> +<grid style="grid-template-columns: 30px auto; width: calc(2em + 2px);"> <item>1</item> <item>2 2</item> <item>3 3</item> @@ -106,7 +106,7 @@ <item class="hidden" style="grid-area: 4/2">0 0</item> </grid> -<grid style="grid-template-columns: 30px auto;"> +<grid style="grid-template-columns: 30px auto; width: calc(3em + 4px);"> <item>1</item> <item>2 2</item> <item>3 3</item> @@ -118,7 +118,7 @@ <item class="hidden" style="grid-area: 4/2">0 0</item> </grid> -<grid style="grid-template-columns: 30px auto;"> +<grid style="grid-template-columns: 30px auto; width: calc(3em + 4px);"> <item>1</item> <item>2 2</item> <item>3 3</item> @@ -130,7 +130,7 @@ <item class="hidden" style="grid-area: 4/2">0 0</item> </grid> -<grid style="grid-template-columns: 30px auto;"> +<grid style="grid-template-columns: 30px auto; width: calc(2em + 2px);"> <item style="height: 3ch">1</item> <item>2 2</item> <item>3 3</item> @@ -138,7 +138,7 @@ <item style="grid-row:span 4">5 5</item> </grid> -<grid style="grid-template-columns: 30px auto;"> +<grid style="grid-template-columns: 30px auto; width: calc(2em + 2px);"> <item style="height: 3ch">1</item> <item>2 2</item> <item>3 3</item> @@ -146,7 +146,7 @@ <item style="height: 6ch; grid-row:span 4">5 5</item> </grid> -<grid style="grid-template-columns: 30px auto;"> +<grid style="grid-template-columns: 30px auto; width: calc(2em + 2px);"> <item style="height: 3ch">1</item> <item>2 2</item> <item>3 3</item>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-mix2-ref.html b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-mix2-ref.html index a096142..6796ef8 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-mix2-ref.html +++ b/third_party/blink/web_tests/external/wpt/css/css-grid/grid-lanes/tentative/intrinsic-sizing/grid-lanes-intrinsic-sizing-rows-004-mix2-ref.html
@@ -39,7 +39,7 @@ <body> <section> -<grid> +<grid style="width: calc(2em + 2px)"> <item style="height:2ch">1</item> <item>2 2</item> <item style="grid-column: span 2">3 3</item> @@ -69,7 +69,7 @@ <item class="hidden" style="grid-area: 4/2">0 0</item> </grid> -<grid> +<grid style="width: calc(2em + 2px)"> <item>1</item> <item>2 2</item> <item>3 3</item> @@ -79,7 +79,7 @@ <item class="hidden" style="grid-area: 4/2">0 0</item> </grid> -<grid> +<grid style="width: calc(3em + 4px)"> <item>1</item> <item>2 2</item> <item>3 3</item> @@ -90,7 +90,7 @@ <item class="hidden" style="grid-area: 4/2">0 0</item> </grid> -<grid> +<grid style="width: calc(3em + 4px)"> <item>1</item> <item>2 2</item> <item>3 3</item> @@ -101,7 +101,7 @@ <item class="hidden" style="grid-area: 4/2">0 0</item> </grid> -<grid> +<grid style="width: calc(2em + 2px)"> <item>1</item> <item style="grid-column: span 2">2 2</item> <item style="grid-column: span 2">3 3</item> @@ -111,7 +111,7 @@ <item class="hidden" style="grid-area: 4/3">0 0</item> </grid> -<grid> +<grid style="width: calc(2em + 2px)"> <item>1</item> <item>2 2</item> <item style="grid-column: span 2">3 3</item> @@ -121,7 +121,7 @@ <item class="hidden" style="grid-area: 4/3">0 0</item> </grid> -<grid> +<grid style="width: calc(2em + 2px)"> <item>1</item> <item>2 2</item> <item style="grid-column: span 2">3 3</item>
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src index 16d736b..dea9116 160000 --- a/third_party/devtools-frontend/src +++ b/third_party/devtools-frontend/src
@@ -1 +1 @@ -Subproject commit 16d736bf9361bd7a4e26bfec357a2f106468c467 +Subproject commit dea9116ea444a915d659b78f160d909db789319b
diff --git a/third_party/jni_zero/parse.py b/third_party/jni_zero/parse.py index 7646c63..9fe3b66e 100644 --- a/third_party/jni_zero/parse.py +++ b/third_party/jni_zero/parse.py
@@ -97,16 +97,15 @@ return _COMMENT_REMOVER_REGEX.sub(replacer, contents) -# Remove everything between and including <> except at the end of a string, e.g. -# @JniType("std::vector<int>") -# This will also break lines with comparison operators, but we don't care. -_GENERICS_REGEX = re.compile(r'<[^<>\n]*>(?!>*")') +# Remove <...> while maintaining "...". +_GENERICS_REGEX = re.compile(r'("(?:\\.|[^\\"\n])*")|(<[^<>\n]*>)') def _remove_generics(value): """Strips Java generics from a string.""" while True: - ret = _GENERICS_REGEX.sub(' ', value) + # Replace "..." with itself, and <...> with " ". + ret = _GENERICS_REGEX.sub(lambda m: m.group(1) or ' ', value) if len(ret) == len(value): return ret value = ret @@ -156,21 +155,23 @@ return outer_class, sorted(nested_classes), null_marked - +# Complicated example: +# @JniType("std::optional<void(*)(const std::vector<bool>&)>") Callback<Boolean> funcType, +# Eager search for quotes to skip over )s within quotes. _ANNOTATION_REGEX = re.compile( - r'@(?P<annotation_name>[\w.]+)(?P<annotation_args>\([^)]+\))?\s*') + r'@(?P<name>[\w.]+)(?P<args>\((?:\".*?\")*[^)]*\))?\s*') # Only supports ("foo") -_ANNOTATION_ARGS_REGEX = re.compile( - r'\(\s*"(?P<annotation_value>[^"]*?)"\s*\)\s*') +_ANNOTATION_ARGS_REGEX = re.compile(r'\(\s*"(?P<value>[^"]*?)"\s*\)\s*', + flags=re.DOTALL) def _parse_annotations(value): annotations = {} for m in _ANNOTATION_REGEX.finditer(value): string_value = '' - if match_args := m.group('annotation_args'): + if match_args := m.group('args'): if match_arg_value := _ANNOTATION_ARGS_REGEX.match(match_args): - string_value = match_arg_value.group('annotation_value') - annotations[m.group('annotation_name')] = string_value + string_value = match_arg_value.group('value') + annotations[m.group('name')] = string_value # Use replace rather than tracking end index to handle: # "OuterClass.@Nullable InnerClass"
diff --git a/third_party/jni_zero/test/golden/testEndToEndManualRegistration-Final-GEN_JNI.java.golden b/third_party/jni_zero/test/golden/testEndToEndManualRegistration-Final-GEN_JNI.java.golden index f58d6f43..544ab3c 100644 --- a/third_party/jni_zero/test/golden/testEndToEndManualRegistration-Final-GEN_JNI.java.golden +++ b/third_party/jni_zero/test/golden/testEndToEndManualRegistration-Final-GEN_JNI.java.golden
@@ -80,6 +80,7 @@ Object convertedString, Object convertedStrings, Object optionalString, + Object funcType, Object tStruct, Object structs, Object obj,
diff --git a/third_party/jni_zero/test/golden/testEndToEndManualRegistration-Registration.h.golden b/third_party/jni_zero/test/golden/testEndToEndManualRegistration-Registration.h.golden index 07ead54..47d7003 100644 --- a/third_party/jni_zero/test/golden/testEndToEndManualRegistration-Registration.h.golden +++ b/third_party/jni_zero/test/golden/testEndToEndManualRegistration-Registration.h.golden
@@ -108,6 +108,7 @@ jobject convertedString, jobject convertedStrings, jobject optionalString, + jobject funcType, jobject tStruct, jobject structs, jobject obj, @@ -187,7 +188,7 @@ "(ILjava/lang/Object;JLjava/lang/Object;SLjava/lang/Object;CLjava/lang/Object;BLjava/lang/Object;DLjava/lang/Object;FLjava/lang/Object;ZLjava/lang/Object;)Ljava/lang/Object;", reinterpret_cast<void*>(Java_org_jni_1zero_GEN_1JNI_org_1jni_11zero_1SampleForAnnotationProcessor_1testAllPrimitives)}, {"org_jni_1zero_SampleForAnnotationProcessor_testSpecialTypes", - "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V", reinterpret_cast<void*>(Java_org_jni_1zero_GEN_1JNI_org_1jni_11zero_1SampleForAnnotationProcessor_1testSpecialTypes)}, };
diff --git a/third_party/jni_zero/test/golden/testEndToEndProxyHashed-Final-GEN_JNI.java.golden b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-Final-GEN_JNI.java.golden index 2c9ce63e..48439d82 100644 --- a/third_party/jni_zero/test/golden/testEndToEndProxyHashed-Final-GEN_JNI.java.golden +++ b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-Final-GEN_JNI.java.golden
@@ -115,6 +115,7 @@ Object convertedString, Object convertedStrings, Object optionalString, + Object funcType, Object tStruct, Object structs, Object obj, @@ -134,6 +135,7 @@ convertedString, convertedStrings, optionalString, + funcType, tStruct, structs, obj,
diff --git a/third_party/jni_zero/test/golden/testEndToEndProxyHashed-Final-N.java.golden b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-Final-N.java.golden index 61eb592a..3d26bafd 100644 --- a/third_party/jni_zero/test/golden/testEndToEndProxyHashed-Final-N.java.golden +++ b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-Final-N.java.golden
@@ -77,6 +77,7 @@ Object convertedString, Object convertedStrings, Object optionalString, + Object funcType, Object tStruct, Object structs, Object obj,
diff --git a/third_party/jni_zero/test/golden/testEndToEndProxyHashed-Placeholder-GEN_JNI.java.golden b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-Placeholder-GEN_JNI.java.golden index c74dbdb..940439e63 100644 --- a/third_party/jni_zero/test/golden/testEndToEndProxyHashed-Placeholder-GEN_JNI.java.golden +++ b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-Placeholder-GEN_JNI.java.golden
@@ -34,5 +34,5 @@ public static native Object org_jni_1zero_SampleForAnnotationProcessor_sendSamplesToNative(Object strs); public static native Object org_jni_1zero_SampleForAnnotationProcessor_sendToNative(Object strs); public static native Object org_jni_1zero_SampleForAnnotationProcessor_testAllPrimitives(int zint, Object ints, long zlong, Object longs, short zshort, Object shorts, char zchar, Object chars, byte zbyte, Object bytes, double zdouble, Object doubles, float zfloat, Object floats, boolean zbool, Object bools); - public static native void org_jni_1zero_SampleForAnnotationProcessor_testSpecialTypes(Object clazz, Object classes, Object throwable, Object throwables, Object string, Object strings, Object convertedString, Object convertedStrings, Object optionalString, Object tStruct, Object structs, Object obj, Object convertedObj, Object objects, Object nestedInterface, Object view, Object context, Object convertedObjects); + public static native void org_jni_1zero_SampleForAnnotationProcessor_testSpecialTypes(Object clazz, Object classes, Object throwable, Object throwables, Object string, Object strings, Object convertedString, Object convertedStrings, Object optionalString, Object funcType, Object tStruct, Object structs, Object obj, Object convertedObj, Object objects, Object nestedInterface, Object view, Object context, Object convertedObjects); }
diff --git a/third_party/jni_zero/test/golden/testEndToEndProxyHashed-Registration.h.golden b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-Registration.h.golden index 9156d0d1..bf9a449 100644 --- a/third_party/jni_zero/test/golden/testEndToEndProxyHashed-Registration.h.golden +++ b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-Registration.h.golden
@@ -72,6 +72,7 @@ jobject convertedString, jobject convertedStrings, jobject optionalString, + jobject funcType, jobject tStruct, jobject structs, jobject obj,
diff --git a/third_party/jni_zero/test/golden/testEndToEndProxyHashed-SampleForAnnotationProcessorJni.java.golden b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-SampleForAnnotationProcessorJni.java.golden index 93eaecd..f5c1ee2 100644 --- a/third_party/jni_zero/test/golden/testEndToEndProxyHashed-SampleForAnnotationProcessorJni.java.golden +++ b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-SampleForAnnotationProcessorJni.java.golden
@@ -5,6 +5,7 @@ import android.content.Context; import android.view.View; +import java.util.function.Consumer; import javax.annotation.processing.Generated; import org.jni_zero.CheckDiscard; import org.jni_zero.GEN_JNI; @@ -161,7 +162,7 @@ } @Override - public void testSpecialTypes(Class clazz, Class[] classes, Throwable throwable, Throwable[] throwables, String string, String[] strings, String convertedString, String[] convertedStrings, String optionalString, SampleForAnnotationProcessor.TestStruct tStruct, SampleForAnnotationProcessor.TestStruct[] structs, Object obj, Object convertedObj, Object[] objects, MyClass.SecondNestedInterface nestedInterface, View view, Context context, Object[] convertedObjects) { + public void testSpecialTypes(Class clazz, Class[] classes, Throwable throwable, Throwable[] throwables, String string, String[] strings, String convertedString, String[] convertedStrings, String optionalString, Consumer funcType, SampleForAnnotationProcessor.TestStruct tStruct, SampleForAnnotationProcessor.TestStruct[] structs, Object obj, Object convertedObj, Object[] objects, MyClass.SecondNestedInterface nestedInterface, View view, Context context, Object[] convertedObjects) { GEN_JNI.org_jni_1zero_SampleForAnnotationProcessor_testSpecialTypes( clazz, classes, @@ -172,6 +173,7 @@ convertedString, convertedStrings, optionalString, + funcType, tStruct, structs, obj,
diff --git a/third_party/jni_zero/test/golden/testEndToEndProxyHashed-SampleForAnnotationProcessor_jni.h.golden b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-SampleForAnnotationProcessor_jni.h.golden index 8f66de1..5ab1565 100644 --- a/third_party/jni_zero/test/golden/testEndToEndProxyHashed-SampleForAnnotationProcessor_jni.h.golden +++ b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-SampleForAnnotationProcessor_jni.h.golden
@@ -102,6 +102,7 @@ // const std::string& convertedString, // const std::vector<std::string>& convertedStrings, // const std::optional<std::string>& optionalString, +// const std::optional<void(*)(const std::vector<bool>&)>& funcType, // const jni_zero::JavaRef<jobject>& tStruct, // const jni_zero::JavaRef<jobjectArray>& structs, // const jni_zero::JavaRef<jobject>& obj, @@ -708,6 +709,7 @@ jobject convertedString, \ jobject convertedStrings, \ jobject optionalString, \ + jobject funcType, \ jobject tStruct, \ jobject structs, \ jobject obj, \ @@ -732,6 +734,9 @@ std::optional<std::string> optionalString_converted = jni_zero::FromJniType<std::optional<std::string>>( \ env, \ jni_zero::JavaRef<jobject>::CreateLeaky(env, optionalString)); \ + std::optional<void(*)(const std::vector<bool>&)> funcType_converted = jni_zero::FromJniType<std::optional<void(*)(const std::vector<bool>&)>>( \ + env, \ + jni_zero::JavaRef<jobject>::CreateLeaky(env, funcType)); \ jni_zero::JavaRef<jobject> tStruct_ref = jni_zero::JavaRef<jobject>::CreateLeaky(env, tStruct); \ jni_zero::JavaRef<jobjectArray> structs_ref = jni_zero::JavaRef<jobjectArray>::CreateLeaky(env, static_cast<jobjectArray>(structs)); \ jni_zero::JavaRef<jobject> obj_ref = jni_zero::JavaRef<jobject>::CreateLeaky(env, obj); \ @@ -763,6 +768,7 @@ convertedString_converted, \ convertedStrings_converted, \ optionalString_converted, \ + funcType_converted, \ tStruct_ref, \ structs_ref, \ obj_ref, \ @@ -783,6 +789,7 @@ convertedString_converted, \ convertedStrings_converted, \ optionalString_converted, \ + funcType_converted, \ tStruct_ref, \ structs_ref, \ obj_ref, \ @@ -802,6 +809,7 @@ convertedString_converted, \ convertedStrings_converted, \ optionalString_converted, \ + funcType_converted, \ tStruct_ref, \ structs_ref, \ obj_ref, \ @@ -821,6 +829,7 @@ convertedString_converted, \ convertedStrings_converted, \ optionalString_converted, \ + funcType_converted, \ tStruct_ref, \ structs_ref, \ obj_ref, \ @@ -831,7 +840,7 @@ context_ref, \ convertedObjects_converted); \ } else { \ - static_assert(false, "JNI_SampleForAnnotationProcessor_TestSpecialTypes() has incorrect signature. It should accept an optional JNIEnv* parameter, plus: (const jni_zero::JavaRef<jclass>&, const jni_zero::JavaRef<jobjectArray>&, const jni_zero::JavaRef<jthrowable>&, const jni_zero::JavaRef<jobjectArray>&, const jni_zero::JavaRef<jstring>&, const jni_zero::JavaRef<jobjectArray>&, std::string, std::vector<std::string>, std::optional<std::string>, const jni_zero::JavaRef<jobject>&, const jni_zero::JavaRef<jobjectArray>&, const jni_zero::JavaRef<jobject>&, jni_zero::tests::CPPClass, const jni_zero::JavaRef<jobjectArray>&, const jni_zero::JavaRef<jobject>&, const jni_zero::JavaRef<jobject>&, const jni_zero::JavaRef<jobject>&, std::vector<jni_zero::tests::CPPClass>)"); \ + static_assert(false, "JNI_SampleForAnnotationProcessor_TestSpecialTypes() has incorrect signature. It should accept an optional JNIEnv* parameter, plus: (const jni_zero::JavaRef<jclass>&, const jni_zero::JavaRef<jobjectArray>&, const jni_zero::JavaRef<jthrowable>&, const jni_zero::JavaRef<jobjectArray>&, const jni_zero::JavaRef<jstring>&, const jni_zero::JavaRef<jobjectArray>&, std::string, std::vector<std::string>, std::optional<std::string>, std::optional<void(*)(const std::vector<bool>&)>, const jni_zero::JavaRef<jobject>&, const jni_zero::JavaRef<jobjectArray>&, const jni_zero::JavaRef<jobject>&, jni_zero::tests::CPPClass, const jni_zero::JavaRef<jobjectArray>&, const jni_zero::JavaRef<jobject>&, const jni_zero::JavaRef<jobject>&, const jni_zero::JavaRef<jobject>&, std::vector<jni_zero::tests::CPPClass>)"); \ } \ }; \ dependent_context(0); \
diff --git a/third_party/jni_zero/test/golden/testEndToEndProxyHashed-placeholder.srcjar.golden b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-placeholder.srcjar.golden index 8201357..c650f6d3 100644 --- a/third_party/jni_zero/test/golden/testEndToEndProxyHashed-placeholder.srcjar.golden +++ b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-placeholder.srcjar.golden
@@ -34,7 +34,7 @@ public org.jni_zero.SampleForAnnotationProcessor[] sendSamplesToNative(org.jni_zero.SampleForAnnotationProcessor[] strs); public String[] sendToNative(String[] strs); public int[] testAllPrimitives(int zint, int[] ints, long zlong, long[] longs, short zshort, short[] shorts, char zchar, char[] chars, byte zbyte, byte[] bytes, double zdouble, double[] doubles, float zfloat, float[] floats, boolean zbool, boolean[] bools); - public void testSpecialTypes(Class clazz, Class[] classes, Throwable throwable, Throwable[] throwables, String string, String[] strings, String convertedString, String[] convertedStrings, String optionalString, org.jni_zero.SampleForAnnotationProcessor.TestStruct tStruct, org.jni_zero.SampleForAnnotationProcessor.TestStruct[] structs, Object obj, Object convertedObj, Object[] objects, org.stubs.MyClass.SecondNestedInterface nestedInterface, android.view.View view, android.content.Context context, Object[] convertedObjects); + public void testSpecialTypes(Class clazz, Class[] classes, Throwable throwable, Throwable[] throwables, String string, String[] strings, String convertedString, String[] convertedStrings, String optionalString, java.util.function.Consumer funcType, org.jni_zero.SampleForAnnotationProcessor.TestStruct tStruct, org.jni_zero.SampleForAnnotationProcessor.TestStruct[] structs, Object obj, Object convertedObj, Object[] objects, org.stubs.MyClass.SecondNestedInterface nestedInterface, android.view.View view, android.content.Context context, Object[] convertedObjects); } public interface TestStruct {} }
diff --git a/third_party/jni_zero/test/golden/testMultiplexing-Final-GEN_JNI.java.golden b/third_party/jni_zero/test/golden/testMultiplexing-Final-GEN_JNI.java.golden index 0c8896d..7d1ce1ba 100644 --- a/third_party/jni_zero/test/golden/testMultiplexing-Final-GEN_JNI.java.golden +++ b/third_party/jni_zero/test/golden/testMultiplexing-Final-GEN_JNI.java.golden
@@ -115,6 +115,7 @@ Object convertedString, Object convertedStrings, Object optionalString, + Object funcType, Object tStruct, Object structs, Object obj, @@ -124,7 +125,7 @@ Object view, Object context, Object convertedObjects) { - J.N.VOOOOOOOOOOOOOOOOOO( + J.N.VOOOOOOOOOOOOOOOOOOO( clazz, classes, throwable, @@ -134,6 +135,7 @@ convertedString, convertedStrings, optionalString, + funcType, tStruct, structs, obj,
diff --git a/third_party/jni_zero/test/golden/testMultiplexing-Final-N.java.golden b/third_party/jni_zero/test/golden/testMultiplexing-Final-N.java.golden index ef9df6b..e543286 100644 --- a/third_party/jni_zero/test/golden/testMultiplexing-Final-N.java.golden +++ b/third_party/jni_zero/test/golden/testMultiplexing-Final-N.java.golden
@@ -32,7 +32,7 @@ Object p13, short p14, boolean p15); - public static native void VOOOOOOOOOOOOOOOOOO( + public static native void VOOOOOOOOOOOOOOOOOOO( Object p0, Object p1, Object p2, @@ -50,5 +50,6 @@ Object p14, Object p15, Object p16, - Object p17); + Object p17, + Object p18); }
diff --git a/third_party/jni_zero/test/golden/testMultiplexing-Registration.h.golden b/third_party/jni_zero/test/golden/testMultiplexing-Registration.h.golden index 74d5314..9457428a 100644 --- a/third_party/jni_zero/test/golden/testMultiplexing-Registration.h.golden +++ b/third_party/jni_zero/test/golden/testMultiplexing-Registration.h.golden
@@ -88,6 +88,7 @@ jobject convertedString, jobject convertedStrings, jobject optionalString, + jobject funcType, jobject tStruct, jobject structs, jobject obj, @@ -180,7 +181,7 @@ bool p15) { return Muxed_org_jni_1zero_SampleForAnnotationProcessor_testAllPrimitives(env, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15); } -JNI_ZERO_BOUNDARY_EXPORT void Java_J_N_VOOOOOOOOOOOOOOOOOO( +JNI_ZERO_BOUNDARY_EXPORT void Java_J_N_VOOOOOOOOOOOOOOOOOOO( JNIEnv* env, jclass jcaller, jobject p0, @@ -200,8 +201,9 @@ jobject p14, jobject p15, jobject p16, - jobject p17) { - return Muxed_org_jni_1zero_SampleForAnnotationProcessor_testSpecialTypes(env, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17); + jobject p17, + jobject p18) { + return Muxed_org_jni_1zero_SampleForAnnotationProcessor_testSpecialTypes(env, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18); } // Helper Methods. @@ -224,9 +226,9 @@ {"OBCDFIJOOOOOOOOSZ", "(BCDFIJLjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;SZ)Ljava/lang/Object;", reinterpret_cast<void*>(Java_J_N_OBCDFIJOOOOOOOOSZ)}, - {"VOOOOOOOOOOOOOOOOOO", - "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V", - reinterpret_cast<void*>(Java_J_N_VOOOOOOOOOOOOOOOOOO)}, + {"VOOOOOOOOOOOOOOOOOOO", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V", + reinterpret_cast<void*>(Java_J_N_VOOOOOOOOOOOOOOOOOOO)}, }; jni_zero::ScopedJavaLocalRef<jclass> native_clazz =
diff --git a/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistration-Final-GEN_JNI.java.golden b/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistration-Final-GEN_JNI.java.golden index a75d642..cae42f17 100644 --- a/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistration-Final-GEN_JNI.java.golden +++ b/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistration-Final-GEN_JNI.java.golden
@@ -80,6 +80,7 @@ Object convertedString, Object convertedStrings, Object optionalString, + Object funcType, Object tStruct, Object structs, Object obj,
diff --git a/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistration-Registration.h.golden b/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistration-Registration.h.golden index 6f78b2d..6d2e0c61 100644 --- a/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistration-Registration.h.golden +++ b/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistration-Registration.h.golden
@@ -108,6 +108,7 @@ jobject convertedString, jobject convertedStrings, jobject optionalString, + jobject funcType, jobject tStruct, jobject structs, jobject obj, @@ -187,7 +188,7 @@ "(ILjava/lang/Object;JLjava/lang/Object;SLjava/lang/Object;CLjava/lang/Object;BLjava/lang/Object;DLjava/lang/Object;FLjava/lang/Object;ZLjava/lang/Object;)Ljava/lang/Object;", reinterpret_cast<void*>(Java_this_is_a_package_prefix_org_jni_1zero_GEN_1JNI_org_1jni_11zero_1SampleForAnnotationProcessor_1testAllPrimitives)}, {"org_jni_1zero_SampleForAnnotationProcessor_testSpecialTypes", - "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V", reinterpret_cast<void*>(Java_this_is_a_package_prefix_org_jni_1zero_GEN_1JNI_org_1jni_11zero_1SampleForAnnotationProcessor_1testSpecialTypes)}, };
diff --git a/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistrationWithMultiplexing-Final-GEN_JNI.java.golden b/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistrationWithMultiplexing-Final-GEN_JNI.java.golden index 1cfaf38..1606c28 100644 --- a/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistrationWithMultiplexing-Final-GEN_JNI.java.golden +++ b/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistrationWithMultiplexing-Final-GEN_JNI.java.golden
@@ -115,6 +115,7 @@ Object convertedString, Object convertedStrings, Object optionalString, + Object funcType, Object tStruct, Object structs, Object obj, @@ -124,7 +125,7 @@ Object view, Object context, Object convertedObjects) { - this.is.a.package.prefix.J.N.VOOOOOOOOOOOOOOOOOO( + this.is.a.package.prefix.J.N.VOOOOOOOOOOOOOOOOOOO( clazz, classes, throwable, @@ -134,6 +135,7 @@ convertedString, convertedStrings, optionalString, + funcType, tStruct, structs, obj,
diff --git a/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistrationWithMultiplexing-Final-N.java.golden b/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistrationWithMultiplexing-Final-N.java.golden index 58bb38c..583b014 100644 --- a/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistrationWithMultiplexing-Final-N.java.golden +++ b/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistrationWithMultiplexing-Final-N.java.golden
@@ -32,7 +32,7 @@ Object p13, short p14, boolean p15); - public static native void VOOOOOOOOOOOOOOOOOO( + public static native void VOOOOOOOOOOOOOOOOOOO( Object p0, Object p1, Object p2, @@ -50,5 +50,6 @@ Object p14, Object p15, Object p16, - Object p17); + Object p17, + Object p18); }
diff --git a/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistrationWithMultiplexing-Registration.h.golden b/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistrationWithMultiplexing-Registration.h.golden index 11209d5..e8e9457c 100644 --- a/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistrationWithMultiplexing-Registration.h.golden +++ b/third_party/jni_zero/test/golden/testPackagePrefixWithManualRegistrationWithMultiplexing-Registration.h.golden
@@ -88,6 +88,7 @@ jobject convertedString, jobject convertedStrings, jobject optionalString, + jobject funcType, jobject tStruct, jobject structs, jobject obj, @@ -183,7 +184,7 @@ bool p15) { return Muxed_org_jni_1zero_SampleForAnnotationProcessor_testAllPrimitives(env, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15); } -JNI_ZERO_BOUNDARY_EXPORT void Java_this_is_a_package_prefix_J_N_VOOOOOOOOOOOOOOOOOO( +JNI_ZERO_BOUNDARY_EXPORT void Java_this_is_a_package_prefix_J_N_VOOOOOOOOOOOOOOOOOOO( JNIEnv* env, jclass jcaller, jobject p0, @@ -203,8 +204,9 @@ jobject p14, jobject p15, jobject p16, - jobject p17) { - return Muxed_org_jni_1zero_SampleForAnnotationProcessor_testSpecialTypes(env, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17); + jobject p17, + jobject p18) { + return Muxed_org_jni_1zero_SampleForAnnotationProcessor_testSpecialTypes(env, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18); } // Helper Methods. @@ -227,9 +229,9 @@ {"OBCDFIJOOOOOOOOSZ", "(BCDFIJLjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;SZ)Ljava/lang/Object;", reinterpret_cast<void*>(Java_this_is_a_package_prefix_J_N_OBCDFIJOOOOOOOOSZ)}, - {"VOOOOOOOOOOOOOOOOOO", - "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V", - reinterpret_cast<void*>(Java_this_is_a_package_prefix_J_N_VOOOOOOOOOOOOOOOOOO)}, + {"VOOOOOOOOOOOOOOOOOOO", + "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V", + reinterpret_cast<void*>(Java_this_is_a_package_prefix_J_N_VOOOOOOOOOOOOOOOOOOO)}, }; jni_zero::ScopedJavaLocalRef<jclass> native_clazz =
diff --git a/third_party/jni_zero/test/golden/testPackagePrefixWithMultiplexing-Final-GEN_JNI.java.golden b/third_party/jni_zero/test/golden/testPackagePrefixWithMultiplexing-Final-GEN_JNI.java.golden index 1cfaf38..1606c28 100644 --- a/third_party/jni_zero/test/golden/testPackagePrefixWithMultiplexing-Final-GEN_JNI.java.golden +++ b/third_party/jni_zero/test/golden/testPackagePrefixWithMultiplexing-Final-GEN_JNI.java.golden
@@ -115,6 +115,7 @@ Object convertedString, Object convertedStrings, Object optionalString, + Object funcType, Object tStruct, Object structs, Object obj, @@ -124,7 +125,7 @@ Object view, Object context, Object convertedObjects) { - this.is.a.package.prefix.J.N.VOOOOOOOOOOOOOOOOOO( + this.is.a.package.prefix.J.N.VOOOOOOOOOOOOOOOOOOO( clazz, classes, throwable, @@ -134,6 +135,7 @@ convertedString, convertedStrings, optionalString, + funcType, tStruct, structs, obj,
diff --git a/third_party/jni_zero/test/golden/testPackagePrefixWithMultiplexing-Final-N.java.golden b/third_party/jni_zero/test/golden/testPackagePrefixWithMultiplexing-Final-N.java.golden index 58bb38c..583b014 100644 --- a/third_party/jni_zero/test/golden/testPackagePrefixWithMultiplexing-Final-N.java.golden +++ b/third_party/jni_zero/test/golden/testPackagePrefixWithMultiplexing-Final-N.java.golden
@@ -32,7 +32,7 @@ Object p13, short p14, boolean p15); - public static native void VOOOOOOOOOOOOOOOOOO( + public static native void VOOOOOOOOOOOOOOOOOOO( Object p0, Object p1, Object p2, @@ -50,5 +50,6 @@ Object p14, Object p15, Object p16, - Object p17); + Object p17, + Object p18); }
diff --git a/third_party/jni_zero/test/golden/testPackagePrefixWithMultiplexing-Registration.h.golden b/third_party/jni_zero/test/golden/testPackagePrefixWithMultiplexing-Registration.h.golden index 074f3557..00895c8c 100644 --- a/third_party/jni_zero/test/golden/testPackagePrefixWithMultiplexing-Registration.h.golden +++ b/third_party/jni_zero/test/golden/testPackagePrefixWithMultiplexing-Registration.h.golden
@@ -88,6 +88,7 @@ jobject convertedString, jobject convertedStrings, jobject optionalString, + jobject funcType, jobject tStruct, jobject structs, jobject obj, @@ -183,7 +184,7 @@ bool p15) { return Muxed_org_jni_1zero_SampleForAnnotationProcessor_testAllPrimitives(env, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15); } -JNI_ZERO_BOUNDARY_EXPORT void Java_this_is_a_package_prefix_J_N_VOOOOOOOOOOOOOOOOOO( +JNI_ZERO_BOUNDARY_EXPORT void Java_this_is_a_package_prefix_J_N_VOOOOOOOOOOOOOOOOOOO( JNIEnv* env, jclass jcaller, jobject p0, @@ -203,8 +204,9 @@ jobject p14, jobject p15, jobject p16, - jobject p17) { - return Muxed_org_jni_1zero_SampleForAnnotationProcessor_testSpecialTypes(env, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17); + jobject p17, + jobject p18) { + return Muxed_org_jni_1zero_SampleForAnnotationProcessor_testSpecialTypes(env, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18); }
diff --git a/third_party/jni_zero/test/golden/testPerFileNatives-SampleForAnnotationProcessorJni.java.golden b/third_party/jni_zero/test/golden/testPerFileNatives-SampleForAnnotationProcessorJni.java.golden index b5322ae..cdbe973 100644 --- a/third_party/jni_zero/test/golden/testPerFileNatives-SampleForAnnotationProcessorJni.java.golden +++ b/third_party/jni_zero/test/golden/testPerFileNatives-SampleForAnnotationProcessorJni.java.golden
@@ -5,6 +5,7 @@ import android.content.Context; import android.view.View; +import java.util.function.Consumer; import javax.annotation.processing.Generated; import org.jni_zero.CheckDiscard; import org.jni_zero.JniTestInstanceHolder; @@ -176,10 +177,10 @@ zbool, bools); } - private static native void nativeTestSpecialTypes(Object clazz, Object classes, Object throwable, Object throwables, Object string, Object strings, Object convertedString, Object convertedStrings, Object optionalString, Object tStruct, Object structs, Object obj, Object convertedObj, Object objects, Object nestedInterface, Object view, Object context, Object convertedObjects); + private static native void nativeTestSpecialTypes(Object clazz, Object classes, Object throwable, Object throwables, Object string, Object strings, Object convertedString, Object convertedStrings, Object optionalString, Object funcType, Object tStruct, Object structs, Object obj, Object convertedObj, Object objects, Object nestedInterface, Object view, Object context, Object convertedObjects); @Override - public void testSpecialTypes(Class clazz, Class[] classes, Throwable throwable, Throwable[] throwables, String string, String[] strings, String convertedString, String[] convertedStrings, String optionalString, SampleForAnnotationProcessor.TestStruct tStruct, SampleForAnnotationProcessor.TestStruct[] structs, Object obj, Object convertedObj, Object[] objects, MyClass.SecondNestedInterface nestedInterface, View view, Context context, Object[] convertedObjects) { + public void testSpecialTypes(Class clazz, Class[] classes, Throwable throwable, Throwable[] throwables, String string, String[] strings, String convertedString, String[] convertedStrings, String optionalString, Consumer funcType, SampleForAnnotationProcessor.TestStruct tStruct, SampleForAnnotationProcessor.TestStruct[] structs, Object obj, Object convertedObj, Object[] objects, MyClass.SecondNestedInterface nestedInterface, View view, Context context, Object[] convertedObjects) { nativeTestSpecialTypes( clazz, classes, @@ -190,6 +191,7 @@ convertedString, convertedStrings, optionalString, + funcType, tStruct, structs, obj,
diff --git a/third_party/jni_zero/test/golden/testPerFileNatives-SampleForAnnotationProcessor_jni.h.golden b/third_party/jni_zero/test/golden/testPerFileNatives-SampleForAnnotationProcessor_jni.h.golden index 2db0c6c..a1627aaa 100644 --- a/third_party/jni_zero/test/golden/testPerFileNatives-SampleForAnnotationProcessor_jni.h.golden +++ b/third_party/jni_zero/test/golden/testPerFileNatives-SampleForAnnotationProcessor_jni.h.golden
@@ -102,6 +102,7 @@ // const std::string& convertedString, // const std::vector<std::string>& convertedStrings, // const std::optional<std::string>& optionalString, +// const std::optional<void(*)(const std::vector<bool>&)>& funcType, // const jni_zero::JavaRef<jobject>& tStruct, // const jni_zero::JavaRef<jobjectArray>& structs, // const jni_zero::JavaRef<jobject>& obj, @@ -708,6 +709,7 @@ jobject convertedString, \ jobject convertedStrings, \ jobject optionalString, \ + jobject funcType, \ jobject tStruct, \ jobject structs, \ jobject obj, \ @@ -732,6 +734,9 @@ std::optional<std::string> optionalString_converted = jni_zero::FromJniType<std::optional<std::string>>( \ env, \ jni_zero::JavaRef<jobject>::CreateLeaky(env, optionalString)); \ + std::optional<void(*)(const std::vector<bool>&)> funcType_converted = jni_zero::FromJniType<std::optional<void(*)(const std::vector<bool>&)>>( \ + env, \ + jni_zero::JavaRef<jobject>::CreateLeaky(env, funcType)); \ jni_zero::JavaRef<jobject> tStruct_ref = jni_zero::JavaRef<jobject>::CreateLeaky(env, tStruct); \ jni_zero::JavaRef<jobjectArray> structs_ref = jni_zero::JavaRef<jobjectArray>::CreateLeaky(env, static_cast<jobjectArray>(structs)); \ jni_zero::JavaRef<jobject> obj_ref = jni_zero::JavaRef<jobject>::CreateLeaky(env, obj); \ @@ -763,6 +768,7 @@ convertedString_converted, \ convertedStrings_converted, \ optionalString_converted, \ + funcType_converted, \ tStruct_ref, \ structs_ref, \ obj_ref, \ @@ -783,6 +789,7 @@ convertedString_converted, \ convertedStrings_converted, \ optionalString_converted, \ + funcType_converted, \ tStruct_ref, \ structs_ref, \ obj_ref, \ @@ -802,6 +809,7 @@ convertedString_converted, \ convertedStrings_converted, \ optionalString_converted, \ + funcType_converted, \ tStruct_ref, \ structs_ref, \ obj_ref, \ @@ -821,6 +829,7 @@ convertedString_converted, \ convertedStrings_converted, \ optionalString_converted, \ + funcType_converted, \ tStruct_ref, \ structs_ref, \ obj_ref, \ @@ -831,7 +840,7 @@ context_ref, \ convertedObjects_converted); \ } else { \ - static_assert(false, "JNI_SampleForAnnotationProcessor_TestSpecialTypes() has incorrect signature. It should accept an optional JNIEnv* parameter, plus: (const jni_zero::JavaRef<jclass>&, const jni_zero::JavaRef<jobjectArray>&, const jni_zero::JavaRef<jthrowable>&, const jni_zero::JavaRef<jobjectArray>&, const jni_zero::JavaRef<jstring>&, const jni_zero::JavaRef<jobjectArray>&, std::string, std::vector<std::string>, std::optional<std::string>, const jni_zero::JavaRef<jobject>&, const jni_zero::JavaRef<jobjectArray>&, const jni_zero::JavaRef<jobject>&, jni_zero::tests::CPPClass, const jni_zero::JavaRef<jobjectArray>&, const jni_zero::JavaRef<jobject>&, const jni_zero::JavaRef<jobject>&, const jni_zero::JavaRef<jobject>&, std::vector<jni_zero::tests::CPPClass>)"); \ + static_assert(false, "JNI_SampleForAnnotationProcessor_TestSpecialTypes() has incorrect signature. It should accept an optional JNIEnv* parameter, plus: (const jni_zero::JavaRef<jclass>&, const jni_zero::JavaRef<jobjectArray>&, const jni_zero::JavaRef<jthrowable>&, const jni_zero::JavaRef<jobjectArray>&, const jni_zero::JavaRef<jstring>&, const jni_zero::JavaRef<jobjectArray>&, std::string, std::vector<std::string>, std::optional<std::string>, std::optional<void(*)(const std::vector<bool>&)>, const jni_zero::JavaRef<jobject>&, const jni_zero::JavaRef<jobjectArray>&, const jni_zero::JavaRef<jobject>&, jni_zero::tests::CPPClass, const jni_zero::JavaRef<jobjectArray>&, const jni_zero::JavaRef<jobject>&, const jni_zero::JavaRef<jobject>&, const jni_zero::JavaRef<jobject>&, std::vector<jni_zero::tests::CPPClass>)"); \ } \ }; \ dependent_context(0); \
diff --git a/third_party/jni_zero/test/golden/testStubRegistration-Final-GEN_JNI.java.golden b/third_party/jni_zero/test/golden/testStubRegistration-Final-GEN_JNI.java.golden index 044dd0db..426651f1 100644 --- a/third_party/jni_zero/test/golden/testStubRegistration-Final-GEN_JNI.java.golden +++ b/third_party/jni_zero/test/golden/testStubRegistration-Final-GEN_JNI.java.golden
@@ -80,6 +80,7 @@ Object convertedString, Object convertedStrings, Object optionalString, + Object funcType, Object tStruct, Object structs, Object obj,
diff --git a/third_party/jni_zero/test/java/src/org/jni_zero/SampleForAnnotationProcessor.java b/third_party/jni_zero/test/java/src/org/jni_zero/SampleForAnnotationProcessor.java index 9d95e66..5243ff0 100644 --- a/third_party/jni_zero/test/java/src/org/jni_zero/SampleForAnnotationProcessor.java +++ b/third_party/jni_zero/test/java/src/org/jni_zero/SampleForAnnotationProcessor.java
@@ -12,6 +12,7 @@ import org.stubs.MyInterface; import java.util.ArrayList; +import java.util.function.Consumer; /** * Sample class that uses the JNI annotation processor for static methods. @@ -67,6 +68,10 @@ @JniType("std::string") String convertedString, @JniType("std::vector<std::string>") String[] convertedStrings, @JniType("std::optional<std::string>") String optionalString, + + // Test ()s within the type. + @JniType("std::optional<void(*)(const std::vector<bool>&)>") + Consumer<Boolean> funcType, TestStruct tStruct, TestStruct[] structs, Object obj,
diff --git a/third_party/jni_zero/test/sample_for_tests.cc b/third_party/jni_zero/test/sample_for_tests.cc index 08cbdb7..addfa28c 100644 --- a/third_party/jni_zero/test/sample_for_tests.cc +++ b/third_party/jni_zero/test/sample_for_tests.cc
@@ -29,6 +29,15 @@ using jni_zero::AttachCurrentThread; using jni_zero::JavaRef; using jni_zero::ScopedJavaLocalRef; +using FuncType = void (*)(const std::vector<bool>&); + +namespace jni_zero { + +template <> +FuncType FromJniType<FuncType>(JNIEnv* env, const JavaRef<jobject>& j_object) { + return nullptr; +} +} // namespace jni_zero namespace jni_zero::tests { @@ -235,6 +244,7 @@ std::string& convertedString, std::vector<std::string>& convertedStrings, std::optional<std::string>& optionalString, + std::optional<FuncType> optionalFunc, const JavaRef<jobject>& tStruct, const JavaRef<jobjectArray>& structs, const JavaRef<jobject>& obj,
diff --git a/third_party/lit/v3_0/BUILD.gn b/third_party/lit/v3_0/BUILD.gn index 56973ac..7b86ff03 100644 --- a/third_party/lit/v3_0/BUILD.gn +++ b/third_party/lit/v3_0/BUILD.gn
@@ -48,6 +48,7 @@ "//chrome/browser/resources/family_link_user_internals:build_ts", "//chrome/browser/resources/feedback:build_ts", "//chrome/browser/resources/glic:build_ts", + "//chrome/browser/resources/guest_view_shared:build_ts", "//chrome/browser/resources/history:build_ts", "//chrome/browser/resources/infobar_internals:build_ts", "//chrome/browser/resources/intro:build_ts",
diff --git a/third_party/litert/README.chromium b/third_party/litert/README.chromium index 031a1bbc..c1ae919 100644 --- a/third_party/litert/README.chromium +++ b/third_party/litert/README.chromium
@@ -2,7 +2,7 @@ Short Name: litert URL: https://github.com/google-ai-edge/LiteRT Version: N/A -Revision: 9eef334461f15249eb7c7bb16e8a57dacaa63124 +Revision: 320c13c17b995e7ccd7b8d6560db255d2f994199 Date: 2025-11-24 Update Mechanism: Manual License: Apache-2.0
diff --git a/third_party/litert/src b/third_party/litert/src index ba80d53c..320c13c 160000 --- a/third_party/litert/src +++ b/third_party/litert/src
@@ -1 +1 @@ -Subproject commit ba80d53cf2e97763b48ebbd03120871b57820f99 +Subproject commit 320c13c17b995e7ccd7b8d6560db255d2f994199
diff --git a/third_party/rust/chromium_crates_io/Cargo.lock b/third_party/rust/chromium_crates_io/Cargo.lock index e23a04c..7413567 100644 --- a/third_party/rust/chromium_crates_io/Cargo.lock +++ b/third_party/rust/chromium_crates_io/Cargo.lock
@@ -187,6 +187,7 @@ "skrifa", "small_ctor", "static_assertions", + "strum", "subtle", "symphonia", "syn",
diff --git a/third_party/rust/chromium_crates_io/Cargo.toml b/third_party/rust/chromium_crates_io/Cargo.toml index bceeeb7c..c86b83c6 100644 --- a/third_party/rust/chromium_crates_io/Cargo.toml +++ b/third_party/rust/chromium_crates_io/Cargo.toml
@@ -43,6 +43,7 @@ skrifa = "0.40.0" small_ctor = "0.1" static_assertions = "1" +strum = "0.27.2" xml = "1.2.0" [dependencies.bytemuck]
diff --git a/third_party/rust/strum/v0_27/BUILD.gn b/third_party/rust/strum/v0_27/BUILD.gn index d8751f2d..caee5ab 100644 --- a/third_party/rust/strum/v0_27/BUILD.gn +++ b/third_party/rust/strum/v0_27/BUILD.gn
@@ -38,11 +38,6 @@ "strum_macros", ] - # Only for usage from third-party crates. Add the crate to - # //third_party/rust/chromium_crates_io/Cargo.toml to use - # it from first-party code. - visibility = [ "//third_party/rust/*" ] - ##################################################################### # Tweaking which GN `config`s apply to this target.
diff --git a/third_party/skia b/third_party/skia index 8296b1d..8810cff9 160000 --- a/third_party/skia +++ b/third_party/skia
@@ -1 +1 @@ -Subproject commit 8296b1df7e1fa34e3378d597f9d285f9c4f6f9d8 +Subproject commit 8810cff96d09cd1aa4386527561fc21d0036b091
diff --git a/third_party/webrtc b/third_party/webrtc index a4ea3c2..33387a2 160000 --- a/third_party/webrtc +++ b/third_party/webrtc
@@ -1 +1 @@ -Subproject commit a4ea3c2b9c7708d08b20efb4167a0c20ecf3e3a7 +Subproject commit 33387a2045e49f8b458ce9631fb74613461f8c3d
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec index ee30752f..9978d08 100644 --- a/tools/gritsettings/resource_ids.spec +++ b/tools/gritsettings/resource_ids.spec
@@ -457,6 +457,10 @@ "META": {"sizes": {"includes": [10]}}, "includes": [4030], }, + "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/guest_view_shared/resources.grd": { + "META": {"sizes": {"includes": [10]}}, + "includes": [4035], + }, "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/history/resources.grd": { "META": {"sizes": {"includes": [53]}}, "includes": [4040],
diff --git a/tools/metrics/histograms/metadata/password/histograms.xml b/tools/metrics/histograms/metadata/password/histograms.xml index 6f8df94..76842ff9 100644 --- a/tools/metrics/histograms/metadata/password/histograms.xml +++ b/tools/metrics/histograms/metadata/password/histograms.xml
@@ -236,7 +236,7 @@ </histogram> <histogram name="KeyboardAccessory.DisabledSavingAccessoryImpressions" - enum="BooleanShown" expires_after="2026-01-18"> + enum="BooleanShown" expires_after="2026-07-18"> <owner>ioanap@chromium.org</owner> <owner>friedrichh@chromium.org</owner> <summary>
diff --git a/tools/metrics/histograms/metadata/uma/histograms.xml b/tools/metrics/histograms/metadata/uma/histograms.xml index 9321abc..d6c655b 100644 --- a/tools/metrics/histograms/metadata/uma/histograms.xml +++ b/tools/metrics/histograms/metadata/uma/histograms.xml
@@ -1147,7 +1147,7 @@ </histogram> <histogram name="UMA.UserDemographics.Status" enum="UserDemographicsStatus" - expires_after="2026-03-08"> + expires_after="2026-09-30"> <owner>rogerm@google.com</owner> <owner>src/base/metrics/OWNERS</owner> <summary>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml index c5439b4..4af3d2f 100644 --- a/tools/metrics/ukm/ukm.xml +++ b/tools/metrics/ukm/ukm.xml
@@ -15085,7 +15085,8 @@ a percentage from 0 to 100. 0 means that the element is not visible due to factors like being off-screen, clipped, occluded by other elements, or styled as invisible (e.g., opacity:0). 100 means that the element is fully - visible. This value can be unset if nothing is reported. + visible. This value is currently set for only encrypted media and if the + kEncryptedMediaOcclusionTracking flag is enabled. </summary> </metric> </event>
diff --git a/tools/typescript/definitions/BUILD.gn b/tools/typescript/definitions/BUILD.gn index c45b7bb..4d547e442 100644 --- a/tools/typescript/definitions/BUILD.gn +++ b/tools/typescript/definitions/BUILD.gn
@@ -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("//components/guest_view/buildflags/buildflags.gni") +import("//extensions/buildflags/buildflags.gni") import("//tools/typescript/ts_library.gni") # This target is the only target that actually checks the correctness of the @@ -69,4 +71,9 @@ "//tools/typescript/definitions/quick_unlock_private.d.ts", ] } + + if (enable_guest_view && !enable_extensions_core) { + definitions += + [ "//tools/typescript/definitions/chrome_slim_web_view_private.d.ts" ] + } }
diff --git a/tools/typescript/definitions/chrome_slim_web_view_private.d.ts b/tools/typescript/definitions/chrome_slim_web_view_private.d.ts new file mode 100644 index 0000000..defa8060 --- /dev/null +++ b/tools/typescript/definitions/chrome_slim_web_view_private.d.ts
@@ -0,0 +1,13 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This namespace is populated by bindings in +// components/guest_view/renderer/slim_web_view/slim_web_view_bindings.cc +// and is used to implement SlimWebViewElement. +declare namespace chrome { + export namespace slimWebViewPrivate { + export function getNextId(): number; + export function registerView(viewInstanceId: number, view: object): void; + } +}
diff --git a/ui/gfx/linux/native_pixmap_dmabuf.cc b/ui/gfx/linux/native_pixmap_dmabuf.cc index bc41c2ef..e5d52a44 100644 --- a/ui/gfx/linux/native_pixmap_dmabuf.cc +++ b/ui/gfx/linux/native_pixmap_dmabuf.cc
@@ -48,7 +48,7 @@ return base::checked_cast<size_t>(handle_.planes[plane].size); } -uint64_t NativePixmapDmaBuf::GetBufferFormatModifier() const { +uint64_t NativePixmapDmaBuf::GetFormatModifier() const { return handle_.modifier; }
diff --git a/ui/gfx/linux/native_pixmap_dmabuf.h b/ui/gfx/linux/native_pixmap_dmabuf.h index cc909eb8..28924d4b 100644 --- a/ui/gfx/linux/native_pixmap_dmabuf.h +++ b/ui/gfx/linux/native_pixmap_dmabuf.h
@@ -32,7 +32,7 @@ uint32_t GetDmaBufPitch(size_t plane) const override; size_t GetDmaBufOffset(size_t plane) const override; size_t GetDmaBufPlaneSize(size_t plane) const override; - uint64_t GetBufferFormatModifier() const override; + uint64_t GetFormatModifier() const override; viz::SharedImageFormat GetSharedImageFormat() const override; size_t GetNumberOfPlanes() const override; bool SupportsZeroCopyWebGPUImport() const override;
diff --git a/ui/gfx/linux/native_pixmap_dmabuf_unittest.cc b/ui/gfx/linux/native_pixmap_dmabuf_unittest.cc index 853b189..cf2a608 100644 --- a/ui/gfx/linux/native_pixmap_dmabuf_unittest.cc +++ b/ui/gfx/linux/native_pixmap_dmabuf_unittest.cc
@@ -60,8 +60,7 @@ scoped_refptr<gfx::NativePixmap> native_pixmap_dmabuf( new gfx::NativePixmapDmaBuf(image_size, format, std::move(handle))); EXPECT_TRUE(native_pixmap_dmabuf->AreDmaBufFdsValid()); - EXPECT_EQ(native_pixmap_dmabuf->GetBufferFormatModifier(), - handle_clone.modifier); + EXPECT_EQ(native_pixmap_dmabuf->GetFormatModifier(), handle_clone.modifier); // NativePixmap to NativePixmapHandle. const int num_planes = format.NumberOfPlanes(); for (int i = 0; i < num_planes; ++i) {
diff --git a/ui/gfx/native_pixmap.h b/ui/gfx/native_pixmap.h index dc611ca..68522bee 100644 --- a/ui/gfx/native_pixmap.h +++ b/ui/gfx/native_pixmap.h
@@ -40,7 +40,7 @@ // The following methods return format, modifier and size of the buffer, // respectively. virtual viz::SharedImageFormat GetSharedImageFormat() const = 0; - virtual uint64_t GetBufferFormatModifier() const = 0; + virtual uint64_t GetFormatModifier() const = 0; virtual gfx::Size GetBufferSize() const = 0; // Return an id that is guaranteed to be unique and equal for all instances
diff --git a/ui/ozone/common/native_pixmap_egl_binding.cc b/ui/ozone/common/native_pixmap_egl_binding.cc index 705b7941..e2c9afef 100644 --- a/ui/ozone/common/native_pixmap_egl_binding.cc +++ b/ui/ozone/common/native_pixmap_egl_binding.cc
@@ -189,7 +189,7 @@ attrs.push_back(kPlanePitchAttrs[attrs_plane]); attrs.push_back(pixmap->GetDmaBufPitch(attrs_plane)); - uint64_t modifier = pixmap->GetBufferFormatModifier(); + uint64_t modifier = pixmap->GetFormatModifier(); if (has_dma_buf_import_modifier && modifier != gfx::NativePixmapHandle::kNoModifier) { DCHECK(attrs_plane < std::size(kPlaneLoModifierAttrs));
diff --git a/ui/ozone/platform/cast/surface_factory_cast.cc b/ui/ozone/platform/cast/surface_factory_cast.cc index 8f3e765..560a728 100644 --- a/ui/ozone/platform/cast/surface_factory_cast.cc +++ b/ui/ozone/platform/cast/surface_factory_cast.cc
@@ -57,7 +57,7 @@ uint32_t GetDmaBufPitch(size_t plane) const override { return 0; } size_t GetDmaBufOffset(size_t plane) const override { return 0; } size_t GetDmaBufPlaneSize(size_t plane) const override { return 0; } - uint64_t GetBufferFormatModifier() const override { return 0; } + uint64_t GetFormatModifier() const override { return 0; } viz::SharedImageFormat GetSharedImageFormat() const override { return viz::SinglePlaneFormat::kBGRA_8888; }
diff --git a/ui/ozone/platform/drm/gpu/gbm_pixmap.cc b/ui/ozone/platform/drm/gpu/gbm_pixmap.cc index 371b7e69..60124e1 100644 --- a/ui/ozone/platform/drm/gpu/gbm_pixmap.cc +++ b/ui/ozone/platform/drm/gpu/gbm_pixmap.cc
@@ -56,7 +56,7 @@ return buffer_->SupportsZeroCopyWebGPUImport(); } -uint64_t GbmPixmap::GetBufferFormatModifier() const { +uint64_t GbmPixmap::GetFormatModifier() const { return buffer_->GetFormatModifier(); }
diff --git a/ui/ozone/platform/drm/gpu/gbm_pixmap.h b/ui/ozone/platform/drm/gpu/gbm_pixmap.h index 6659e69a..70be5ce 100644 --- a/ui/ozone/platform/drm/gpu/gbm_pixmap.h +++ b/ui/ozone/platform/drm/gpu/gbm_pixmap.h
@@ -36,7 +36,7 @@ size_t GetDmaBufPlaneSize(size_t plane) const override; size_t GetNumberOfPlanes() const override; bool SupportsZeroCopyWebGPUImport() const override; - uint64_t GetBufferFormatModifier() const override; + uint64_t GetFormatModifier() const override; viz::SharedImageFormat GetSharedImageFormat() const override; gfx::Size GetBufferSize() const override; uint32_t GetUniqueId() const override;
diff --git a/ui/ozone/platform/flatland/flatland_sysmem_native_pixmap.cc b/ui/ozone/platform/flatland/flatland_sysmem_native_pixmap.cc index 21c7136..5b162e0 100644 --- a/ui/ozone/platform/flatland/flatland_sysmem_native_pixmap.cc +++ b/ui/ozone/platform/flatland/flatland_sysmem_native_pixmap.cc
@@ -48,7 +48,7 @@ NOTREACHED(); } -uint64_t FlatlandSysmemNativePixmap::GetBufferFormatModifier() const { +uint64_t FlatlandSysmemNativePixmap::GetFormatModifier() const { NOTREACHED(); }
diff --git a/ui/ozone/platform/flatland/flatland_sysmem_native_pixmap.h b/ui/ozone/platform/flatland/flatland_sysmem_native_pixmap.h index cc0877b..400e2cb 100644 --- a/ui/ozone/platform/flatland/flatland_sysmem_native_pixmap.h +++ b/ui/ozone/platform/flatland/flatland_sysmem_native_pixmap.h
@@ -29,7 +29,7 @@ size_t GetDmaBufPlaneSize(size_t plane) const override; size_t GetNumberOfPlanes() const override; bool SupportsZeroCopyWebGPUImport() const override; - uint64_t GetBufferFormatModifier() const override; + uint64_t GetFormatModifier() const override; viz::SharedImageFormat GetSharedImageFormat() const override; gfx::Size GetBufferSize() const override; uint32_t GetUniqueId() const override;
diff --git a/ui/ozone/platform/headless/headless_surface_factory.cc b/ui/ozone/platform/headless/headless_surface_factory.cc index 7202827..cf1911b 100644 --- a/ui/ozone/platform/headless/headless_surface_factory.cc +++ b/ui/ozone/platform/headless/headless_surface_factory.cc
@@ -154,7 +154,7 @@ uint32_t GetDmaBufPitch(size_t plane) const override { return 0; } size_t GetDmaBufOffset(size_t plane) const override { return 0; } size_t GetDmaBufPlaneSize(size_t plane) const override { return 0; } - uint64_t GetBufferFormatModifier() const override { return 0; } + uint64_t GetFormatModifier() const override { return 0; } viz::SharedImageFormat GetSharedImageFormat() const override { return format_; }
diff --git a/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.cc b/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.cc index 70708c4..82dacd5 100644 --- a/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.cc +++ b/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.cc
@@ -158,7 +158,7 @@ return false; } -uint64_t GbmPixmapWayland::GetBufferFormatModifier() const { +uint64_t GbmPixmapWayland::GetFormatModifier() const { return gbm_bo_->GetFormatModifier(); } @@ -222,7 +222,7 @@ gbm_bo_->GetPlaneSize(i), std::move(scoped_fds[i])); } - handle.modifier = GetBufferFormatModifier(); + handle.modifier = GetFormatModifier(); return handle; }
diff --git a/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.h b/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.h index 822b290..0c4eff7 100644 --- a/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.h +++ b/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.h
@@ -61,7 +61,7 @@ size_t GetDmaBufPlaneSize(size_t plane) const override; size_t GetNumberOfPlanes() const override; bool SupportsZeroCopyWebGPUImport() const override; - uint64_t GetBufferFormatModifier() const override; + uint64_t GetFormatModifier() const override; viz::SharedImageFormat GetSharedImageFormat() const override; gfx::Size GetBufferSize() const override; uint32_t GetUniqueId() const override;
diff --git a/ui/web_dialogs/web_dialog_ui_unittest.cc b/ui/web_dialogs/web_dialog_ui_unittest.cc index 4e845955..94178666 100644 --- a/ui/web_dialogs/web_dialog_ui_unittest.cc +++ b/ui/web_dialogs/web_dialog_ui_unittest.cc
@@ -25,7 +25,10 @@ MojoWebDialogUI web_dialog_ui(&test_web_ui); // MojoWebDialogUIs rely on both Mojo and chrome.send(). - EXPECT_EQ(content::kWebUIBindingsPolicySet, test_web_ui.GetBindings()); + EXPECT_EQ(content::BindingsPolicySet::FromRange( + content::BindingsPolicyValue::kWebUi, + content::BindingsPolicyValue::kWebUiHistograms), + test_web_ui.GetBindings()); } } // namespace ui
diff --git a/ui/webui/resources/cr_components/searchbox/searchbox.css b/ui/webui/resources/cr_components/searchbox/searchbox.css index 2c80785..fadf38df 100644 --- a/ui/webui/resources/cr_components/searchbox/searchbox.css +++ b/ui/webui/resources/cr_components/searchbox/searchbox.css
@@ -73,19 +73,6 @@ box-shadow: none; } -:host([match-searchbox]) { - box-shadow: none; -} - -:host([match-searchbox]:not([dropdown-is-visible]):hover) { - border: 1px solid transparent; - box-shadow: var(--cr-searchbox-shadow); -} - -:host([match-searchbox]:not([is-dark]):not([dropdown-is-visible]):not(:hover)) { - border: 1px solid var(--color-searchbox-border); -} - #inputWrapper { background-color: var(--color-searchbox-background); border-radius: var(--cr-searchbox-border-radius);
diff --git a/ui/webui/resources/cr_components/searchbox/searchbox.ts b/ui/webui/resources/cr_components/searchbox/searchbox.ts index 22299ff..7da7e85 100644 --- a/ui/webui/resources/cr_components/searchbox/searchbox.ts +++ b/ui/webui/resources/cr_components/searchbox/searchbox.ts
@@ -256,12 +256,6 @@ reflect: true, }, - /** Whether the searchbox should match the searchbox. */ - matchSearchbox: { - type: Boolean, - reflect: true, - }, - multiLineEnabled: { type: Boolean, reflect: true, @@ -427,8 +421,6 @@ accessor hadSecondarySide: boolean = false; accessor hasSecondarySide: boolean = false; accessor isDark: boolean = false; - accessor matchSearchbox: boolean = - loadTimeData.getBoolean('searchboxMatchSearchboxTheme'); accessor multiLineEnabled: boolean = false; accessor searchboxAriaDescription: string = ''; accessor searchboxLensSearchEnabled: boolean =
diff --git a/v8 b/v8 index 8d2f516..419b5ea 160000 --- a/v8 +++ b/v8
@@ -1 +1 @@ -Subproject commit 8d2f5169115a949eea2d3a1a8675885655b1114f +Subproject commit 419b5ea8df9fb8dc551075135473c24adb82937c